/[thuban]/trunk/thuban/Thuban/UI/classifier.py
ViewVC logotype

Contents of /trunk/thuban/Thuban/UI/classifier.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 576 - (show annotations)
Mon Mar 31 18:31:17 2003 UTC (21 years, 11 months ago) by jonathan
File MIME type: text/x-python
File size: 39193 byte(s)
Commented out some debugging statements.
(ClassDataPreviewer.Draw): Draw rectangles for polygon layers per
        RTbug #1769.

1 # Copyright (c) 2001, 2003 by Intevation GmbH
2 # Authors:
3 # Jonathan Coles <[email protected]>
4 #
5 # This program is free software under the GPL (>=v2)
6 # Read the file COPYING coming with Thuban for details.
7
8 """Dialog for classifying how layers are displayed"""
9
10 __version__ = "$Revision$"
11
12 import copy
13
14 from Thuban.Model.table import FIELDTYPE_INT, FIELDTYPE_DOUBLE, \
15 FIELDTYPE_STRING
16
17 from wxPython.wx import *
18 from wxPython.grid import *
19
20 from Thuban import _
21 from Thuban.common import *
22 from Thuban.UI.common import *
23
24 from Thuban.Model.classification import *
25
26 from Thuban.Model.color import Color
27
28 from Thuban.Model.layer import Layer, SHAPETYPE_ARC, SHAPETYPE_POLYGON, SHAPETYPE_POINT
29
30 from dialogs import NonModalDialog
31
32 # widget id's
33 ID_PROPERTY_SELECT = 4010
34 ID_CLASS_TABLE = 40011
35
36 ID_CLASSIFY_OK = 4001
37 ID_CLASSIFY_CANCEL = 4002
38 ID_CLASSIFY_ADD = 4003
39 ID_CLASSIFY_GENRANGE = 4004
40 ID_CLASSIFY_REMOVE = 4005
41 ID_CLASSIFY_MOVEUP = 4006
42 ID_CLASSIFY_MOVEDOWN = 4007
43 ID_CLASSIFY_APPLY = 4008
44
45 # table columns
46 COL_SYMBOL = 0
47 COL_VALUE = 1
48 COL_LABEL = 2
49
50 # indices into the client data lists in Classifier.fields
51 FIELD_CLASS = 0
52 FIELD_TYPE = 1
53 FIELD_NAME = 2
54
55 #
56 # this is a silly work around to ensure that the table that is
57 # passed into SetTable is the same that is returned by GetTable
58 #
59 import weakref
60 class ClassGrid(wxGrid):
61
62
63 def __init__(self, parent):
64 """Constructor.
65
66 parent -- the parent window
67
68 clazz -- the working classification that this grid should
69 use for display.
70 """
71
72 #wxGrid.__init__(self, parent, ID_CLASS_TABLE, size = (340, 160))
73 wxGrid.__init__(self, parent, ID_CLASS_TABLE)
74 #self.SetTable(ClassTable(fieldData, layer.ShapeType(), self), True)
75
76 self.currentSelection = []
77
78 EVT_GRID_CELL_LEFT_DCLICK(self, self._OnCellDClick)
79 EVT_GRID_RANGE_SELECT(self, self._OnSelectedRange)
80 EVT_GRID_SELECT_CELL(self, self._OnSelectedCell)
81
82 #print "123123123: ", ('Show' in dir(self))
83
84 #def Show(self):
85 #print "SHOW!"
86
87 #def Refresh(self):
88 #self.Show()
89 #def Update(self):
90 #self.Show()
91
92 def CreateTable(self, clazz, shapeType, group = None):
93
94 assert(isinstance(clazz, Classification))
95
96 self.shapeType = shapeType
97 table = self.GetTable()
98 if table is None:
99 w = self.GetDefaultColSize() * 3 + self.GetDefaultRowLabelSize()
100 h = self.GetDefaultRowSize() * 4 + self.GetDefaultColLabelSize()
101 self.SetDimensions(-1, -1, w, h)
102 self.SetSizeHints(w, h, -1, -1)
103 table = ClassTable(self)
104 self.SetTable(table, True)
105
106
107 self.SetSelectionMode(wxGrid.wxGridSelectRows)
108 self.ClearSelection()
109
110 #print "8------------------"
111 table.Reset(clazz, self.shapeType, group)
112 #print "9------------------"
113
114 # def Show(self, show = True):
115 # print "SHOW!"
116 # wxGrid.Show(self, show)
117
118 # sel = self.GetCurrentSelection()
119
120 # print "( 1"
121 # if len(sel) == 1:
122 # print "( 2"
123 # self.MakeCellVisible(sel[0], 0)
124
125 def GetCurrentSelection(self):
126 """Return the currently highlighted rows as an increasing list
127 of row numbers."""
128 #print "@@ ", self.currentSelection
129 sel = copy.copy(self.currentSelection)
130 sel.sort()
131 return sel
132
133 def GetSelectedRows(self):
134 return self.GetCurrentSelection()
135
136 def SetCellRenderer(self, row, col):
137 raise ValueError(_("Must not allow setting of renderer in ClassGrid!"))
138
139 #
140 # [Set|Get]Table is taken from http://wiki.wxpython.org
141 # they are needed as a work around to ensure that the table
142 # that is passed to SetTable is the one that is returned
143 # by GetTable.
144 #
145 def SetTable(self, object, *attributes):
146 self.tableRef = weakref.ref(object)
147 return wxGrid.SetTable(self, object, *attributes)
148
149 def GetTable(self):
150 try:
151 return self.tableRef()
152 except:
153 return None
154
155 def DeleteSelectedRows(self):
156 """Deletes all highlighted rows.
157
158 If only one row is highlighted then after it is deleted the
159 row that was below the deleted row is highlighted."""
160
161 sel = self.GetCurrentSelection()
162
163 # nothing to do
164 if len(sel) == 0: return
165
166 # if only one thing is selected check if it is the default
167 # data row, because we can't remove that
168 if len(sel) == 1:
169 #group = self.GetTable().GetValueAsCustom(sel[0], COL_SYMBOL, None)
170 group = self.GetTable().GetClassGroup(sel[0])
171 if isinstance(group, ClassGroupDefault):
172 wxMessageDialog(self,
173 "The Default group cannot be removed.",
174 style = wxOK | wxICON_EXCLAMATION).ShowModal()
175 return
176
177
178 self.ClearSelection()
179
180 # we need to remove things from the bottom up so we don't
181 # change the indexes of rows that will be deleted next
182 sel.reverse()
183
184 #
185 # actually remove the rows
186 #
187 table = self.GetTable()
188 for row in sel:
189 table.DeleteRows(row)
190
191 #
192 # if there was only one row selected highlight the row
193 # that was directly below it, or move up one if the
194 # deleted row was the last row.
195 #
196 if len(sel) == 1:
197 r = sel[0]
198 if r > self.GetNumberRows() - 1:
199 r = self.GetNumberRows() - 1
200 self.SelectRow(r)
201
202
203 def SelectGroup(self, group, makeVisible = True):
204 if group is None: return
205
206 assert(isinstance(group, ClassGroup))
207
208 table = self.GetTable()
209
210 assert(table is not None)
211
212
213 #print "-- ", group
214 for i in range(table.GetNumberRows()):
215 g = table.GetClassGroup(i)
216 #print "1", g
217 if g is group:
218 #print "2"
219 self.SelectRow(i)
220 if makeVisible:
221 #print "3"
222 self.MakeCellVisible(i, 0)
223 break
224
225
226
227 #
228 # XXX: This isn't working, and there is no way to deselect rows wxPython!
229 #
230 # def DeselectRow(self, row):
231 # self.ProcessEvent(
232 # wxGridRangeSelectEvent(-1,
233 # wxEVT_GRID_RANGE_SELECT,
234 # self,
235 # (row, row), (row, row),
236 # sel = False))
237
238 def _OnCellDClick(self, event):
239 """Handle a double on a cell."""
240
241 r = event.GetRow()
242 c = event.GetCol()
243 if c == COL_SYMBOL:
244 prop = self.GetTable().GetValueAsCustom(r, c, None)
245 #prop = group.GetProperties()
246
247 # get a new ClassGroupProperties object and copy the
248 # values over to our current object
249 propDlg = SelectPropertiesDialog(NULL, prop, self.shapeType)
250 if propDlg.ShowModal() == wxID_OK:
251 new_prop = propDlg.GetClassGroupProperties()
252 #prop.SetProperties(new_prop)
253 self.GetTable().SetValueAsCustom(r, c, None, new_prop)
254 propDlg.Destroy()
255
256 #
257 # _OnSelectedRange() and _OnSelectedCell() were borrowed
258 # from http://wiki.wxpython.org to keep track of which
259 # cells are currently highlighted
260 #
261 def _OnSelectedRange(self, event):
262 """Internal update to the selection tracking list"""
263 if event.Selecting():
264 for index in range( event.GetTopRow(), event.GetBottomRow()+1):
265 if index not in self.currentSelection:
266 #print " ", index
267 self.currentSelection.append( index )
268 else:
269 for index in range( event.GetTopRow(), event.GetBottomRow()+1):
270 while index in self.currentSelection:
271 #print " ", index
272 self.currentSelection.remove( index )
273 #self.ConfigureForSelection()
274
275 #print self.GetCurrentSelection()
276 event.Skip()
277
278 def _OnSelectedCell( self, event ):
279 """Internal update to the selection tracking list"""
280 #print "selecting cell: ", event.GetRow()
281 self.currentSelection = [ event.GetRow() ]
282 #self.ConfigureForSelection()
283 event.Skip()
284
285 class ClassTable(wxPyGridTableBase):
286 """Represents the underlying data structure for the grid."""
287
288 NUM_COLS = 3
289
290 __col_labels = [_("Symbol"), _("Value"), _("Label")]
291
292
293 def __init__(self, view = None):
294 #def __init__(self, clazz, shapeType, view = None):
295 """Constructor.
296
297 shapeType -- the type of shape that the layer uses
298
299 view -- a wxGrid object that uses this class for its table
300 """
301
302 wxPyGridTableBase.__init__(self)
303
304 self.SetView(view)
305 self.tdata = []
306
307 #self.Reset(clazz, shapeType)
308
309 def Reset(self, clazz, shapeType, group = None):
310 """Reset the table with the given data.
311
312 This is necessary because wxWindows does not allow a grid's
313 table to change once it has been intially set and so we
314 need a way of modifying the data.
315
316 clazz -- the working classification that this table should
317 use for display. This may be different from the
318 classification in the layer.
319
320 shapeType -- the type of shape that the layer uses
321 """
322
323 assert(isinstance(clazz, Classification))
324
325 self.GetView().BeginBatch()
326
327 self.fieldType = clazz.GetFieldType()
328 self.shapeType = shapeType
329
330 old_len = len(self.tdata)
331
332 self.tdata = []
333
334 #print "9------------------"
335 #
336 # copy the data out of the classification and into our
337 # array
338 #
339 row = -1
340 for g in clazz:
341 ng = copy.deepcopy(g)
342 self.__SetRow(-1, ng)
343 if g is group:
344 row = self.GetNumberRows() - 1
345 #print "selecting row..."
346
347 #print "10------------------"
348
349 self.__Modified(-1)
350
351 self.__NotifyRowChanges(old_len, len(self.tdata))
352 #print "11------------------"
353
354 if row > -1:
355 self.GetView().ClearSelection()
356 self.GetView().SelectRow(row)
357 self.GetView().MakeCellVisible(row, 0)
358
359 self.GetView().EndBatch()
360
361 def __NotifyRowChanges(self, curRows, newRows):
362 #
363 # silly message processing for updates to the number of
364 # rows and columns
365 #
366 if newRows > curRows:
367 msg = wxGridTableMessage(self,
368 wxGRIDTABLE_NOTIFY_ROWS_APPENDED,
369 newRows - curRows) # how many
370 self.GetView().ProcessTableMessage(msg)
371 self.GetView().FitInside()
372 elif newRows < curRows:
373 msg = wxGridTableMessage(self,
374 wxGRIDTABLE_NOTIFY_ROWS_DELETED,
375 curRows - newRows, # position
376 curRows - newRows) # how many
377 self.GetView().ProcessTableMessage(msg)
378 self.GetView().FitInside()
379
380 def __SetRow(self, row, group):
381 """Set a row's data to that of the group.
382
383 The table is considered modified after this operation.
384
385 row -- if row is -1 or greater than the current number of rows
386 then group is appended to the end.
387 """
388
389 # either append or replace
390 if row == -1 or row >= self.GetNumberRows():
391 self.tdata.append(group)
392 else:
393 self.tdata[row] = group
394
395 self.__Modified()
396
397 def GetColLabelValue(self, col):
398 """Return the label for the given column."""
399 return self.__col_labels[col]
400
401 def GetRowLabelValue(self, row):
402 """Return the label for the given row."""
403
404 group = self.tdata[row]
405 if isinstance(group, ClassGroupDefault): return _("Default")
406 if isinstance(group, ClassGroupSingleton): return _("Singleton")
407 if isinstance(group, ClassGroupRange): return _("Range")
408 if isinstance(group, ClassGroupMap): return _("Map")
409
410 assert(False) # shouldn't get here
411 return _("")
412
413 def GetNumberRows(self):
414 """Return the number of rows."""
415 return len(self.tdata)
416
417 def GetNumberCols(self):
418 """Return the number of columns."""
419 return self.NUM_COLS
420
421 def IsEmptyCell(self, row, col):
422 """Determine if a cell is empty. This is always false."""
423 return False
424
425 def GetValue(self, row, col):
426 """Return the object that is used to represent the given
427 cell coordinates. This may not be a string."""
428 return self.GetValueAsCustom(row, col, None)
429
430 def SetValue(self, row, col, value):
431 """Assign 'value' to the cell specified by 'row' and 'col'.
432
433 The table is considered modified after this operation.
434 """
435
436 self.SetValueAsCustom(row, col, None, value)
437 self.__Modified()
438
439 def GetValueAsCustom(self, row, col, typeName):
440 """Return the object that is used to represent the given
441 cell coordinates. This may not be a string.
442
443 typeName -- unused, but needed to overload wxPyGridTableBase
444 """
445
446 group = self.tdata[row]
447
448 if col == COL_SYMBOL:
449 return group.GetProperties()
450
451 if col == COL_LABEL:
452 return group.GetLabel()
453
454 # col must be COL_VALUE
455 assert(col == COL_VALUE)
456
457 if isinstance(group, ClassGroupDefault):
458 return _("DEFAULT")
459 elif isinstance(group, ClassGroupSingleton):
460 return group.GetValue()
461 elif isinstance(group, ClassGroupRange):
462 return _("%s - %s") % (group.GetMin(), group.GetMax())
463
464 assert(False) # shouldn't get here
465 return None
466
467 def __ParseInput(self, value):
468 """Try to determine what kind of input value is
469 (string, number, or range)
470
471 Returns a tuple of length one if there is a single
472 value, or of length two if it is a range.
473 """
474
475 type = self.fieldType
476
477 if type == FIELDTYPE_STRING:
478 return (value,)
479 elif type == FIELDTYPE_INT or type == FIELDTYPE_DOUBLE:
480
481 if type == FIELDTYPE_INT:
482 conv = lambda p: int(float(p))
483 else:
484 conv = lambda p: p
485
486 #
487 # first try to take the input as a single number
488 # if there's an exception try to break it into
489 # a range seperated by a '-'. take care to ignore
490 # a leading '-' as that could be for a negative number.
491 # then try to parse the individual parts. if there
492 # is an exception here, let it pass up to the calling
493 # function.
494 #
495 try:
496 return (conv(Str2Num(value)),)
497 except ValueError:
498 i = value.find('-')
499 if i == 0:
500 i = value.find('-', 1)
501
502 return (conv(Str2Num(value[:i])), conv(Str2Num(value[i+1:])))
503
504 assert(False) # shouldn't get here
505 return (0,)
506
507
508 def SetValueAsCustom(self, row, col, typeName, value):
509 """Set the cell specified by 'row' and 'col' to 'value'.
510
511 If column represents the value column, the input is parsed
512 to determine if a string, number, or range was entered.
513 A new ClassGroup may be created if the type of data changes.
514
515 The table is considered modified after this operation.
516
517 typeName -- unused, but needed to overload wxPyGridTableBase
518 """
519
520 assert(col >= 0 and col < self.GetNumberCols())
521 assert(row >= 0 and row < self.GetNumberRows())
522
523 group = self.tdata[row]
524
525 mod = True # assume the data will change
526
527 if col == COL_SYMBOL:
528 group.SetProperties(value)
529 elif col == COL_LABEL:
530 group.SetLabel(value)
531 elif col == COL_VALUE:
532 if isinstance(group, ClassGroupDefault):
533 # not allowed to modify the default value
534 pass
535 elif isinstance(group, ClassGroupMap):
536 # something special
537 pass
538 else: # SINGLETON, RANGE
539 try:
540 dataInfo = self.__ParseInput(value)
541 except ValueError:
542 # bad input, ignore the request
543 mod = False
544 else:
545
546 changed = False
547 ngroup = group
548 props = group.GetProperties()
549
550 #
551 # try to update the values, which may include
552 # changing the underlying group type if the
553 # group was a singleton and a range was entered
554 #
555 if len(dataInfo) == 1:
556 if not isinstance(group, ClassGroupSingleton):
557 ngroup = ClassGroupSingleton(prop = props)
558 changed = True
559 ngroup.SetValue(dataInfo[0])
560 elif len(dataInfo) == 2:
561 if not isinstance(group, ClassGroupRange):
562 ngroup = ClassGroupRange(prop = props)
563 changed = True
564 ngroup.SetRange(dataInfo[0], dataInfo[1])
565 else:
566 assert(False)
567 pass
568
569 if changed:
570 ngroup.SetLabel(group.GetLabel())
571 self.SetClassGroup(row, ngroup)
572 else:
573 assert(False) # shouldn't be here
574 pass
575
576 if mod:
577 self.__Modified()
578 self.GetView().Refresh()
579
580 def GetAttr(self, row, col, someExtraParameter):
581 """Returns the cell attributes"""
582
583 attr = wxGridCellAttr()
584 #attr = wxPyGridTableBase.GetAttr(self, row, col, someExtraParameter)
585
586 if col == COL_SYMBOL:
587 # we need to create a new renderer each time, because
588 # SetRenderer takes control of the parameter
589 attr.SetRenderer(ClassRenderer(self.shapeType))
590 attr.SetReadOnly()
591
592 return attr
593
594 def GetClassGroup(self, row):
595 """Return the ClassGroup object representing row 'row'."""
596
597 return self.tdata[row] # self.GetValueAsCustom(row, COL_SYMBOL, None)
598
599 def SetClassGroup(self, row, group):
600 self.__SetRow(row, group)
601 self.GetView().Refresh()
602
603 def __Modified(self, mod = True):
604 """Adjust the modified flag.
605
606 mod -- if -1 set the modified flag to False, otherwise perform
607 an 'or' operation with the current value of the flag and
608 'mod'
609 """
610
611 if mod == -1:
612 self.modified = False
613 else:
614 self.modified = mod or self.modified
615
616 def IsModified(self):
617 """True if this table is considered modified."""
618 return self.modified
619
620 def DeleteRows(self, pos, numRows = 1):
621 """Deletes 'numRows' beginning at row 'pos'.
622
623 The row representing the default group is not removed.
624
625 The table is considered modified if any rows are removed.
626 """
627
628 assert(pos >= 0)
629 old_len = len(self.tdata)
630 for row in range(pos, pos - numRows, -1):
631 group = self.GetClassGroup(row)
632 if not isinstance(group, ClassGroupDefault):
633 self.tdata.pop(row)
634 self.__Modified()
635
636 if self.IsModified():
637 self.__NotifyRowChanges(old_len, len(self.tdata))
638
639 def AppendRows(self, numRows = 1):
640 """Append 'numRows' empty rows to the end of the table.
641
642 The table is considered modified if any rows are appended.
643 """
644
645 old_len = len(self.tdata)
646 for i in range(numRows):
647 np = ClassGroupSingleton()
648 self.__SetRow(-1, np)
649
650 if self.IsModified():
651 self.__NotifyRowChanges(old_len, len(self.tdata))
652
653
654 class Classifier(NonModalDialog):
655
656 def __init__(self, parent, name, layer, group = None):
657 NonModalDialog.__init__(self, parent, name,
658 _("Classifier: %s") % layer.Title())
659
660 panel = wxPanel(self, -1, size=(100, 100))
661
662 self.layer = layer
663
664 self.originalClass = self.layer.GetClassification()
665 field = self.originalClass.GetField()
666 fieldType = self.originalClass.GetFieldType()
667
668 topBox = wxBoxSizer(wxVERTICAL)
669 panelBox = wxBoxSizer(wxVERTICAL)
670
671 #panelBox.Add(wxStaticText(panel, -1, _("Layer: %s") % layer.Title()),
672 #0, wxALIGN_LEFT | wxALL, 4)
673 panelBox.Add(wxStaticText(panel, -1,
674 _("Layer Type: %s") % layer.ShapeType()),
675 0, wxALIGN_LEFT | wxALL, 4)
676
677
678 #
679 # make field combo box
680 #
681 self.fields = wxComboBox(panel, ID_PROPERTY_SELECT, "",
682 style = wxCB_READONLY)
683
684 self.num_cols = layer.table.field_count()
685 # just assume the first field in case one hasn't been
686 # specified in the file.
687 self.__cur_field = 0
688
689 self.fields.Append("<None>")
690 self.fields.SetClientData(0, None)
691
692 for i in range(self.num_cols):
693 type, name, len, decc = layer.table.field_info(i)
694 self.fields.Append(name)
695
696 if name == field:
697 self.__cur_field = i + 1
698 self.fields.SetClientData(i + 1, self.originalClass)
699 else:
700 self.fields.SetClientData(i + 1, None)
701
702
703 ###########
704
705 self.fieldTypeText = wxStaticText(panel, -1, "")
706 panelBox.Add(self.fieldTypeText, 0,
707 wxGROW | wxALIGN_LEFT | wxALL | wxADJUST_MINSIZE, 4)
708
709 propertyBox = wxBoxSizer(wxHORIZONTAL)
710 propertyBox.Add(wxStaticText(panel, -1, _("Field: ")),
711 0, wxALIGN_LEFT | wxALL, 4)
712 propertyBox.Add(self.fields, 1, wxGROW|wxALL, 4)
713 EVT_COMBOBOX(self, ID_PROPERTY_SELECT, self._OnFieldSelect)
714
715 panelBox.Add(propertyBox, 0, wxGROW, 4)
716
717
718 #
719 # Control Box
720 #
721 controlBox = wxBoxSizer(wxHORIZONTAL)
722
723
724 ###########
725 #
726 # Control buttons:
727 #
728 self.controlButtons = []
729
730 controlButtonBox = wxBoxSizer(wxVERTICAL)
731
732 button = wxButton(panel, ID_CLASSIFY_ADD, _("Add"))
733 controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
734 self.controlButtons.append(button)
735
736 button = wxButton(panel, ID_CLASSIFY_GENRANGE, _("Generate Ranges"))
737 controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
738 self.controlButtons.append(button)
739
740 button = wxButton(panel, ID_CLASSIFY_MOVEUP, _("Move Up"))
741 controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
742 self.controlButtons.append(button)
743
744 button = wxButton(panel, ID_CLASSIFY_MOVEDOWN, _("Move Down"))
745 controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
746 self.controlButtons.append(button)
747
748 controlButtonBox.Add(60, 20, 0, wxGROW | wxALL | wxALIGN_BOTTOM, 4)
749
750 button = wxButton(panel, ID_CLASSIFY_REMOVE, _("Remove"))
751 controlButtonBox.Add(button, 0, wxGROW | wxALL | wxALIGN_BOTTOM, 4)
752 self.controlButtons.append(button)
753
754
755 ###########
756 #
757 # Classification data table
758 #
759
760 self.classGrid = ClassGrid(panel)
761 #self.__SetGridTable(self.__cur_field, group)
762 #self.fields.SetSelection(self.__cur_field)
763
764 # calling __SelectField after creating the classGrid fills in the
765 # grid with the correct information
766 #print "2------------------"
767 self.__SelectField(self.__cur_field, group = group)
768
769 #self.classGrid.SelectGroup(group)
770
771 controlBox.Add(self.classGrid, 1, wxGROW, 0)
772
773
774
775 controlBox.Add(controlButtonBox, 0, wxGROW, 10)
776 panelBox.Add(controlBox, 1, wxGROW, 10)
777
778 EVT_BUTTON(self, ID_CLASSIFY_ADD, self._OnAdd)
779 EVT_BUTTON(self, ID_CLASSIFY_REMOVE, self._OnRemove)
780 EVT_BUTTON(self, ID_CLASSIFY_GENRANGE, self._OnGenRange)
781 EVT_BUTTON(self, ID_CLASSIFY_MOVEUP, self._OnMoveUp)
782 EVT_BUTTON(self, ID_CLASSIFY_MOVEDOWN, self._OnMoveDown)
783
784 ###########
785
786 buttonBox = wxBoxSizer(wxHORIZONTAL)
787 buttonBox.Add(wxButton(panel, ID_CLASSIFY_OK, _("OK")),
788 0, wxALL, 4)
789 buttonBox.Add(60, 20, 0, wxALL, 4)
790 buttonBox.Add(wxButton(panel, ID_CLASSIFY_APPLY, _("Apply")),
791 0, wxALL, 4)
792 buttonBox.Add(60, 20, 0, wxALL, 4)
793 buttonBox.Add(wxButton(panel, ID_CLASSIFY_CANCEL, _("Cancel")),
794 0, wxALL, 4)
795 panelBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 0)
796
797 EVT_BUTTON(self, ID_CLASSIFY_OK, self._OnOK)
798 EVT_BUTTON(self, ID_CLASSIFY_APPLY, self._OnApply)
799 EVT_BUTTON(self, ID_CLASSIFY_CANCEL, self._OnCancel)
800
801 ###########
802
803
804 panel.SetAutoLayout(True)
805 panel.SetSizer(panelBox)
806 panelBox.SetSizeHints(panel)
807
808 topBox.Add(panel, 1, wxGROW, 0)
809 panelBox.SetSizeHints(self)
810 self.SetAutoLayout(True)
811 self.SetSizer(topBox)
812
813 #print "1------------------"
814 #self.Fit()
815 ######################
816
817 self.haveApplied = False
818
819 def __BuildClassification(self, fieldIndex):
820
821 numRows = self.classGrid.GetNumberRows()
822 assert(numRows > 0) # there should always be a default row
823
824 clazz = Classification()
825 if fieldIndex == 0:
826 fieldName = None
827 fieldType = None
828 else:
829 fieldName = self.fields.GetString(fieldIndex)
830 fieldType = self.layer.GetFieldType(fieldName)
831
832 clazz.SetField(fieldName)
833 clazz.SetFieldType(fieldType)
834
835
836 table = self.classGrid.GetTable()
837 clazz.SetDefaultGroup(table.GetClassGroup(0))
838
839 for i in range(1, numRows):
840 clazz.AddGroup(table.GetClassGroup(i))
841
842 return clazz
843
844 def __SetGridTable(self, fieldIndex, group = None):
845
846 clazz = self.fields.GetClientData(fieldIndex)
847
848 if clazz is None:
849 clazz = Classification()
850 clazz.SetDefaultGroup(
851 ClassGroupDefault(
852 self.layer.GetClassification().
853 GetDefaultGroup().GetProperties()))
854
855 fieldName = self.fields.GetString(fieldIndex)
856 fieldType = self.layer.GetFieldType(fieldName)
857 clazz.SetFieldType(fieldType)
858
859 #print "6------------------"
860 self.classGrid.CreateTable(clazz, self.layer.ShapeType(), group)
861 #print "7------------------"
862
863
864
865 type2string = {None: _("None"),
866 FIELDTYPE_STRING: _("Text"),
867 FIELDTYPE_INT: _("Integer"),
868 FIELDTYPE_DOUBLE: _("Decimal")}
869
870 def __SetFieldTypeText(self, fieldIndex):
871 fieldName = self.fields.GetString(fieldIndex)
872 fieldType = self.layer.GetFieldType(fieldName)
873
874 assert(Classifier.type2string.has_key(fieldType))
875
876 text = Classifier.type2string[fieldType]
877
878 self.fieldTypeText.SetLabel(_("Field Type: %s") % text)
879
880 def __SelectField(self, newIndex, oldIndex = -1, group = None):
881
882 #print "3------------------"
883
884 assert(oldIndex >= -1)
885
886 self.fields.SetSelection(newIndex)
887
888 if oldIndex != -1:
889 clazz = self.__BuildClassification(oldIndex)
890 self.fields.SetClientData(oldIndex, clazz)
891
892 #print "4------------------"
893 self.__SetGridTable(newIndex, group)
894 #print "5------------------"
895
896 enabled = newIndex != 0
897
898 for b in self.controlButtons:
899 b.Enable(enabled)
900
901 self.__SetFieldTypeText(newIndex)
902
903
904 def _OnFieldSelect(self, event):
905 index = self.fields.GetSelection()
906 self.__SelectField(index, self.__cur_field)
907 self.__cur_field = index
908
909 def _OnApply(self, event):
910 """Put the data from the table into a new Classification and hand
911 it to the layer.
912 """
913
914 clazz = self.fields.GetClientData(self.__cur_field)
915
916 #
917 # only build the classification if there wasn't one to
918 # to begin with or it has been modified
919 #
920 if clazz is None or self.classGrid.GetTable().IsModified():
921 clazz = self.__BuildClassification(self.__cur_field)
922
923 self.layer.SetClassification(clazz)
924
925 self.haveApplied = True
926
927 def _OnOK(self, event):
928 self._OnApply(event)
929 self.OnClose(event)
930
931 def _OnCancel(self, event):
932 """The layer's current classification stays the same."""
933 if self.haveApplied:
934 self.layer.SetClassification(self.originalClass)
935
936 self.OnClose(event)
937
938 def _OnAdd(self, event):
939 self.classGrid.AppendRows()
940
941 def _OnRemove(self, event):
942 self.classGrid.DeleteSelectedRows()
943
944 def _OnGenRange(self, event):
945 print "Classifier._OnGenRange()"
946
947 def _OnMoveUp(self, event):
948 sel = self.classGrid.GetCurrentSelection()
949
950 #print "sel: ", sel
951
952 if len(sel) == 1:
953 i = sel[0]
954 if i > 1:
955 table = self.classGrid.GetTable()
956 x = table.GetClassGroup(i - 1)
957 y = table.GetClassGroup(i)
958 table.SetClassGroup(i - 1, y)
959 table.SetClassGroup(i, x)
960 self.classGrid.ClearSelection()
961 self.classGrid.SelectRow(i - 1)
962 self.classGrid.MakeCellVisible(i - 1, 0)
963
964 def _OnMoveDown(self, event):
965 sel = self.classGrid.GetCurrentSelection()
966
967 if len(sel) == 1:
968 i = sel[0]
969 table = self.classGrid.GetTable()
970 if 0 < i < table.GetNumberRows() - 1:
971 x = table.GetClassGroup(i)
972 y = table.GetClassGroup(i + 1)
973 table.SetClassGroup(i, y)
974 table.SetClassGroup(i + 1, x)
975 self.classGrid.ClearSelection()
976 self.classGrid.SelectRow(i + 1)
977 self.classGrid.MakeCellVisible(i + 1, 0)
978
979
980 ID_SELPROP_OK = 4001
981 ID_SELPROP_CANCEL = 4002
982 ID_SELPROP_SPINCTRL = 4002
983 ID_SELPROP_PREVIEW = 4003
984 ID_SELPROP_STROKECLR = 4004
985 ID_SELPROP_FILLCLR = 4005
986 ID_SELPROP_STROKECLRTRANS = 4006
987 ID_SELPROP_FILLCLRTRANS = 4007
988
989 class SelectPropertiesDialog(wxDialog):
990
991 def __init__(self, parent, prop, shapeType):
992 wxDialog.__init__(self, parent, -1, _("Select Properties"),
993 style = wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
994
995 self.prop = ClassGroupProperties(prop)
996
997 topBox = wxBoxSizer(wxVERTICAL)
998
999 itemBox = wxBoxSizer(wxHORIZONTAL)
1000
1001 # preview box
1002 previewBox = wxBoxSizer(wxVERTICAL)
1003 previewBox.Add(wxStaticText(self, -1, _("Preview:")),
1004 0, wxALIGN_LEFT | wxALL, 4)
1005 self.previewWin = ClassDataPreviewWindow(None, self.prop, shapeType,
1006 self, ID_SELPROP_PREVIEW, (40, 40))
1007 previewBox.Add(self.previewWin, 1, wxGROW, 15)
1008
1009 itemBox.Add(previewBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
1010
1011 # control box
1012 ctrlBox = wxBoxSizer(wxVERTICAL)
1013
1014 lineColorBox = wxBoxSizer(wxHORIZONTAL)
1015 lineColorBox.Add(
1016 wxButton(self, ID_SELPROP_STROKECLR, _("Change Line Color")),
1017 1, wxALL | wxGROW, 4)
1018 EVT_BUTTON(self, ID_SELPROP_STROKECLR, self._OnChangeLineColor)
1019
1020 lineColorBox.Add(
1021 wxButton(self, ID_SELPROP_STROKECLRTRANS, _("Transparent")),
1022 1, wxALL | wxGROW, 4)
1023 EVT_BUTTON(self, ID_SELPROP_STROKECLRTRANS,
1024 self._OnChangeLineColorTrans)
1025
1026 ctrlBox.Add(lineColorBox, 0,
1027 wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
1028
1029 if shapeType != SHAPETYPE_ARC:
1030 fillColorBox = wxBoxSizer(wxHORIZONTAL)
1031 fillColorBox.Add(
1032 wxButton(self, ID_SELPROP_FILLCLR, _("Change Fill Color")),
1033 1, wxALL | wxGROW, 4)
1034 EVT_BUTTON(self, ID_SELPROP_FILLCLR, self._OnChangeFillColor)
1035 fillColorBox.Add(
1036 wxButton(self, ID_SELPROP_FILLCLRTRANS, _("Transparent")),
1037 1, wxALL | wxGROW, 4)
1038 EVT_BUTTON(self, ID_SELPROP_FILLCLRTRANS,
1039 self._OnChangeFillColorTrans)
1040 ctrlBox.Add(fillColorBox, 0,
1041 wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
1042
1043 spinBox = wxBoxSizer(wxHORIZONTAL)
1044 spinBox.Add(wxStaticText(self, -1, _("Line Width: ")),
1045 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
1046 self.spinCtrl = wxSpinCtrl(self, ID_SELPROP_SPINCTRL,
1047 min=1, max=10,
1048 value=str(prop.GetLineWidth()),
1049 initial=prop.GetLineWidth())
1050
1051 EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL, self._OnSpin)
1052
1053 spinBox.Add(self.spinCtrl, 0, wxALIGN_LEFT | wxALL, 4)
1054
1055 ctrlBox.Add(spinBox, 0, wxALIGN_RIGHT | wxALL, 0)
1056 itemBox.Add(ctrlBox, 0, wxALIGN_RIGHT | wxALL | wxGROW, 0)
1057 topBox.Add(itemBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
1058
1059 #
1060 # Control buttons:
1061 #
1062 buttonBox = wxBoxSizer(wxHORIZONTAL)
1063 buttonBox.Add(wxButton(self, ID_SELPROP_OK, _("OK")),
1064 0, wxALL, 4)
1065 buttonBox.Add(wxButton(self, ID_SELPROP_CANCEL, _("Cancel")),
1066 0, wxALL, 4)
1067 topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)
1068
1069 EVT_BUTTON(self, ID_SELPROP_OK, self._OnOK)
1070 EVT_BUTTON(self, ID_SELPROP_CANCEL, self._OnCancel)
1071
1072 self.SetAutoLayout(True)
1073 self.SetSizer(topBox)
1074 topBox.Fit(self)
1075 topBox.SetSizeHints(self)
1076
1077 def _OnOK(self, event):
1078 self.EndModal(wxID_OK)
1079
1080 def _OnCancel(self, event):
1081 self.EndModal(wxID_CANCEL)
1082
1083 def _OnSpin(self, event):
1084 self.prop.SetLineWidth(self.spinCtrl.GetValue())
1085 self.previewWin.Refresh()
1086
1087 def __GetColor(self, cur):
1088 dialog = wxColourDialog(self)
1089 dialog.GetColourData().SetColour(Color2wxColour(cur))
1090 ret = None
1091 if dialog.ShowModal() == wxID_OK:
1092 ret = wxColour2Color(dialog.GetColourData().GetColour())
1093
1094 dialog.Destroy()
1095
1096 return ret
1097
1098 def _OnChangeLineColor(self, event):
1099 clr = self.__GetColor(self.prop.GetLineColor())
1100 if clr is not None:
1101 self.prop.SetLineColor(clr)
1102 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1103
1104 def _OnChangeLineColorTrans(self, event):
1105 self.prop.SetLineColor(Color.None)
1106 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1107
1108 def _OnChangeFillColor(self, event):
1109 clr = self.__GetColor(self.prop.GetFill())
1110 if clr is not None:
1111 self.prop.SetFill(clr)
1112 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1113
1114 def _OnChangeFillColorTrans(self, event):
1115 self.prop.SetFill(Color.None)
1116 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1117
1118 def GetClassGroupProperties(self):
1119 return self.prop
1120
1121
1122 class ClassDataPreviewWindow(wxWindow):
1123
1124 def __init__(self, rect, prop, shapeType,
1125 parent = None, id = -1, size = wxDefaultSize):
1126 if parent is not None:
1127 wxWindow.__init__(self, parent, id, (0, 0), size)
1128 EVT_PAINT(self, self._OnPaint)
1129
1130 self.rect = rect
1131
1132 self.prop = prop
1133 self.shapeType = shapeType
1134 self.previewer = ClassDataPreviewer()
1135
1136 def _OnPaint(self, event):
1137 dc = wxPaintDC(self)
1138
1139 # XXX: this doesn't seem to be having an effect:
1140 dc.DestroyClippingRegion()
1141
1142 if self.rect is None:
1143 w, h = self.GetSize()
1144 rect = wxRect(0, 0, w, h)
1145 else:
1146 rect = self.rect
1147
1148 self.previewer.Draw(dc, rect, self.prop, self.shapeType)
1149
1150 class ClassDataPreviewer:
1151
1152 def Draw(self, dc, rect, prop, shapeType):
1153
1154 assert(dc is not None)
1155 assert(isinstance(prop, ClassGroupProperties))
1156
1157 if rect is None:
1158 x = 0
1159 y = 0
1160 w, h = dc.GetSize()
1161 else:
1162 x = rect.GetX()
1163 y = rect.GetY()
1164 w = rect.GetWidth()
1165 h = rect.GetHeight()
1166
1167 stroke = prop.GetLineColor()
1168 if stroke is Color.None:
1169 pen = wxTRANSPARENT_PEN
1170 else:
1171 pen = wxPen(Color2wxColour(stroke),
1172 prop.GetLineWidth(),
1173 wxSOLID)
1174
1175 stroke = prop.GetFill()
1176 if stroke is Color.None:
1177 brush = wxTRANSPARENT_BRUSH
1178 else:
1179 brush = wxBrush(Color2wxColour(stroke), wxSOLID)
1180
1181 dc.SetPen(pen)
1182 dc.SetBrush(brush)
1183
1184 if shapeType == SHAPETYPE_ARC:
1185 dc.DrawSpline([wxPoint(x, y + h),
1186 wxPoint(x + w/2, y + h/4),
1187 wxPoint(x + w/2, y + h/4*3),
1188 wxPoint(x + w, y)])
1189
1190 elif shapeType == SHAPETYPE_POINT:
1191
1192 dc.DrawCircle(x + w/2, y + h/2,
1193 (min(w, h) - prop.GetLineWidth())/2)
1194
1195 elif shapeType == SHAPETYPE_POLYGON:
1196 dc.DrawRectangle(x, y, w, h)
1197
1198 class ClassRenderer(wxPyGridCellRenderer):
1199
1200 def __init__(self, shapeType):
1201 wxPyGridCellRenderer.__init__(self)
1202 self.shapeType = shapeType
1203 self.previewer = ClassDataPreviewer()
1204
1205 def Draw(self, grid, attr, dc, rect, row, col, isSelected):
1206 data = grid.GetTable().GetClassGroup(row)
1207
1208 dc.SetClippingRegion(rect.GetX(), rect.GetY(),
1209 rect.GetWidth(), rect.GetHeight())
1210 dc.SetPen(wxPen(wxLIGHT_GREY))
1211 dc.SetBrush(wxBrush(wxLIGHT_GREY, wxSOLID))
1212 dc.DrawRectangle(rect.GetX(), rect.GetY(),
1213 rect.GetWidth(), rect.GetHeight())
1214
1215 if not isinstance(data, ClassGroupMap):
1216 self.previewer.Draw(dc, rect, data.GetProperties(), self.shapeType)
1217
1218 if isSelected:
1219 dc.SetPen(wxPen(wxColour(0 * 255, 0 * 255, 0 * 255),
1220 4, wxSOLID))
1221 dc.SetBrush(wxTRANSPARENT_BRUSH)
1222 dc.DrawRectangle(rect.GetX(), rect.GetY(),
1223 rect.GetWidth(), rect.GetHeight())
1224
1225 dc.DestroyClippingRegion()
1226

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26