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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 549 - (hide annotations)
Thu Mar 20 09:45:07 2003 UTC (21 years, 11 months ago) by jonathan
File MIME type: text/x-python
File size: 36517 byte(s)
 Remember if any changes have actually
        been applied so that if the dialog is cancelled without an application
        of changes we don't have to set a new classification.
(ClassDataPreviewer): Pulled out the window specific code and put it
        ClassDataPreviewWindow. ClassDataPreviewer can then be used to draw
        symbols on any DC.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26