/[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 519 - (hide annotations)
Tue Mar 11 22:27:35 2003 UTC (21 years, 11 months ago) by jonathan
File MIME type: text/x-python
File size: 36134 byte(s)
Undo last changes so that we now need wxWindows2.4

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 jonathan 372
584 jonathan 485 def __init__(self, parent, interactor, name, layer):
585     NonModalDialog.__init__(self, parent, interactor, name,
586     _("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 460 def __BuildClassification(self, fieldIndex):
727 jonathan 415
728 jonathan 496 numRows = self.classGrid.GetNumberRows()
729     assert(numRows > 0) # there should always be a default row
730    
731 jonathan 415 clazz = Classification()
732 jonathan 496 if fieldIndex == 0:
733     fieldName = None
734     fieldType = None
735     else:
736     fieldName = self.fields.GetString(fieldIndex)
737     fieldType = self.layer.GetFieldType(fieldName)
738 jonathan 415
739 jonathan 460 clazz.SetField(fieldName)
740     clazz.SetFieldType(fieldType)
741    
742 jonathan 415
743 jonathan 460 table = self.classGrid.GetTable()
744     clazz.SetDefaultGroup(table.GetClassGroup(0))
745 jonathan 415
746 jonathan 460 for i in range(1, numRows):
747     clazz.AddGroup(table.GetClassGroup(i))
748    
749 jonathan 415 return clazz
750    
751 jonathan 460 def __SetGridTable(self, fieldIndex):
752 jonathan 415
753 jonathan 460 clazz = self.fields.GetClientData(fieldIndex)
754 jonathan 415
755 jonathan 460 if clazz is None:
756     clazz = Classification()
757     clazz.SetDefaultGroup(
758     ClassGroupDefault(
759 jonathan 485 self.layer.GetClassification().
760     GetDefaultGroup().GetProperties()))
761 jonathan 460
762     fieldName = self.fields.GetString(fieldIndex)
763     fieldType = self.layer.GetFieldType(fieldName)
764     clazz.SetFieldType(fieldType)
765    
766     self.classGrid.CreateTable(clazz, self.layer.ShapeType())
767    
768 jonathan 496
769    
770 jonathan 500 type2string = {None: _("None"),
771     FIELDTYPE_STRING: _("Text"),
772     FIELDTYPE_INT: _("Integer"),
773     FIELDTYPE_DOUBLE: _("Decimal")}
774 jonathan 496
775 jonathan 485 def __SetFieldTypeText(self, fieldIndex):
776     fieldName = self.fields.GetString(fieldIndex)
777     fieldType = self.layer.GetFieldType(fieldName)
778    
779 jonathan 496 assert(Classifier.type2string.has_key(fieldType))
780 jonathan 485
781 jonathan 496 text = Classifier.type2string[fieldType]
782    
783 jonathan 485 self.fieldTypeText.SetLabel(_("Field Type: %s") % text)
784    
785 jonathan 498 def __SelectField(self, newIndex, oldIndex = -1):
786 jonathan 460
787 jonathan 498 assert(oldIndex >= -1)
788 jonathan 415
789 jonathan 498 if oldIndex != -1:
790     clazz = self.__BuildClassification(oldIndex)
791     self.fields.SetClientData(oldIndex, clazz)
792 jonathan 485
793 jonathan 498 self.__SetGridTable(newIndex)
794    
795     enabled = newIndex != 0
796    
797 jonathan 485 for b in self.controlButtons:
798     b.Enable(enabled)
799    
800 jonathan 498 self.__SetFieldTypeText(newIndex)
801 jonathan 485
802 jonathan 496
803     def _OnFieldSelect(self, event):
804 jonathan 498 index = self.fields.GetSelection()
805     self.__SelectField(index, self.__cur_field)
806     self.__cur_field = index
807 jonathan 485
808     def _OnApply(self, event):
809 jonathan 415 """Put the data from the table into a new Classification and hand
810     it to the layer.
811     """
812    
813 jonathan 460 clazz = self.fields.GetClientData(self.__cur_field)
814 jonathan 415
815     #
816     # only build the classification if there wasn't one to
817     # to begin with or it has been modified
818     #
819     if clazz is None or self.classGrid.GetTable().IsModified():
820 jonathan 451 clazz = self.__BuildClassification(self.__cur_field)
821 jonathan 415
822     self.layer.SetClassification(clazz)
823    
824 jonathan 485 def _OnOK(self, event):
825     self._OnApply(event)
826     self.OnClose(event)
827 jonathan 415
828 jonathan 460 def _OnCancel(self, event):
829 jonathan 485 """The layer's current classification stays the same."""
830     self.layer.SetClassification(self.originalClass)
831     self.OnClose(event)
832 jonathan 415
833 jonathan 460 def _OnAdd(self, event):
834 jonathan 451 self.classGrid.AppendRows()
835 jonathan 415
836 jonathan 460 def _OnRemove(self, event):
837 jonathan 451 self.classGrid.DeleteSelectedRows()
838    
839 jonathan 460 def _OnGenRange(self, event):
840     print "Classifier._OnGenRange()"
841 jonathan 415
842 jonathan 460 def _OnMoveUp(self, event):
843     sel = self.classGrid.GetCurrentSelection()
844 jonathan 415
845 jonathan 460 if len(sel) == 1:
846     i = sel[0]
847     if i > 1:
848     table = self.classGrid.GetTable()
849     x = table.GetClassGroup(i - 1)
850     y = table.GetClassGroup(i)
851     table.SetClassGroup(i - 1, y)
852     table.SetClassGroup(i, x)
853     self.classGrid.ClearSelection()
854     self.classGrid.SelectRow(i - 1)
855    
856     def _OnMoveDown(self, event):
857     sel = self.classGrid.GetCurrentSelection()
858    
859     if len(sel) == 1:
860     i = sel[0]
861     table = self.classGrid.GetTable()
862     if 0 < i < table.GetNumberRows() - 1:
863     x = table.GetClassGroup(i)
864     y = table.GetClassGroup(i + 1)
865     table.SetClassGroup(i, y)
866     table.SetClassGroup(i + 1, x)
867     self.classGrid.ClearSelection()
868     self.classGrid.SelectRow(i + 1)
869    
870    
871 jonathan 415 ID_SELPROP_OK = 4001
872     ID_SELPROP_CANCEL = 4002
873     ID_SELPROP_SPINCTRL = 4002
874 jonathan 430 ID_SELPROP_PREVIEW = 4003
875     ID_SELPROP_STROKECLR = 4004
876     ID_SELPROP_FILLCLR = 4005
877 jonathan 485 ID_SELPROP_STROKECLRTRANS = 4006
878     ID_SELPROP_FILLCLRTRANS = 4007
879 jonathan 415
880     class SelectPropertiesDialog(wxDialog):
881    
882     def __init__(self, parent, prop, shapeType):
883     wxDialog.__init__(self, parent, -1, _("Select Properties"),
884 jonathan 507 style = wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
885 jonathan 415
886 jonathan 441 self.prop = ClassGroupProperties(prop)
887 jonathan 415
888 jonathan 430 topBox = wxBoxSizer(wxVERTICAL)
889 jonathan 415
890 jonathan 430 itemBox = wxBoxSizer(wxHORIZONTAL)
891    
892     # preview box
893     previewBox = wxBoxSizer(wxVERTICAL)
894     previewBox.Add(wxStaticText(self, -1, _("Preview:")),
895     0, wxALIGN_LEFT | wxALL, 4)
896     self.previewer = ClassDataPreviewer(None, self.prop, shapeType,
897     self, ID_SELPROP_PREVIEW, (40, 40))
898     previewBox.Add(self.previewer, 1, wxGROW, 15)
899    
900     itemBox.Add(previewBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
901    
902     # control box
903     ctrlBox = wxBoxSizer(wxVERTICAL)
904 jonathan 485
905     lineColorBox = wxBoxSizer(wxHORIZONTAL)
906     lineColorBox.Add(
907 jonathan 500 wxButton(self, ID_SELPROP_STROKECLR, _("Change Line Color")),
908 jonathan 485 1, wxALL | wxGROW, 4)
909 jonathan 460 EVT_BUTTON(self, ID_SELPROP_STROKECLR, self._OnChangeLineColor)
910 jonathan 430
911 jonathan 485 lineColorBox.Add(
912 jonathan 500 wxButton(self, ID_SELPROP_STROKECLRTRANS, _("Transparent")),
913 jonathan 485 1, wxALL | wxGROW, 4)
914     EVT_BUTTON(self, ID_SELPROP_STROKECLRTRANS,
915     self._OnChangeLineColorTrans)
916    
917     ctrlBox.Add(lineColorBox, 0,
918     wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
919    
920 jonathan 430 if shapeType != SHAPETYPE_ARC:
921 jonathan 485 fillColorBox = wxBoxSizer(wxHORIZONTAL)
922     fillColorBox.Add(
923 jonathan 500 wxButton(self, ID_SELPROP_FILLCLR, _("Change Fill Color")),
924 jonathan 485 1, wxALL | wxGROW, 4)
925 jonathan 460 EVT_BUTTON(self, ID_SELPROP_FILLCLR, self._OnChangeFillColor)
926 jonathan 485 fillColorBox.Add(
927 jonathan 500 wxButton(self, ID_SELPROP_FILLCLRTRANS, _("Transparent")),
928 jonathan 485 1, wxALL | wxGROW, 4)
929     EVT_BUTTON(self, ID_SELPROP_FILLCLRTRANS,
930     self._OnChangeFillColorTrans)
931     ctrlBox.Add(fillColorBox, 0,
932     wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
933 jonathan 430
934 jonathan 415 spinBox = wxBoxSizer(wxHORIZONTAL)
935 jonathan 460 spinBox.Add(wxStaticText(self, -1, _("Line Width: ")),
936 jonathan 430 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
937 jonathan 415 self.spinCtrl = wxSpinCtrl(self, ID_SELPROP_SPINCTRL,
938     min=1, max=10,
939 jonathan 460 value=str(prop.GetLineWidth()),
940     initial=prop.GetLineWidth())
941 jonathan 415
942 jonathan 460 EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL, self._OnSpin)
943 jonathan 415
944     spinBox.Add(self.spinCtrl, 0, wxALIGN_LEFT | wxALL, 4)
945    
946 jonathan 430 ctrlBox.Add(spinBox, 0, wxALIGN_RIGHT | wxALL, 0)
947     itemBox.Add(ctrlBox, 0, wxALIGN_RIGHT | wxALL | wxGROW, 0)
948     topBox.Add(itemBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
949 jonathan 415
950     #
951     # Control buttons:
952     #
953     buttonBox = wxBoxSizer(wxHORIZONTAL)
954 jonathan 485 buttonBox.Add(wxButton(self, ID_SELPROP_OK, _("OK")),
955 jonathan 415 0, wxALL, 4)
956 jonathan 485 buttonBox.Add(wxButton(self, ID_SELPROP_CANCEL, _("Cancel")),
957 jonathan 415 0, wxALL, 4)
958     topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)
959    
960 jonathan 460 EVT_BUTTON(self, ID_SELPROP_OK, self._OnOK)
961     EVT_BUTTON(self, ID_SELPROP_CANCEL, self._OnCancel)
962 jonathan 415
963 jonathan 509 self.SetAutoLayout(True)
964 jonathan 415 self.SetSizer(topBox)
965     topBox.Fit(self)
966     topBox.SetSizeHints(self)
967    
968 jonathan 460 def _OnOK(self, event):
969 jonathan 372 self.EndModal(wxID_OK)
970    
971 jonathan 460 def _OnCancel(self, event):
972 jonathan 372 self.EndModal(wxID_CANCEL)
973    
974 jonathan 460 def _OnSpin(self, event):
975     self.prop.SetLineWidth(self.spinCtrl.GetValue())
976 jonathan 430 self.previewer.Refresh()
977 jonathan 392
978 jonathan 430 def __GetColor(self, cur):
979     dialog = wxColourDialog(self)
980     dialog.GetColourData().SetColour(Color2wxColour(cur))
981     ret = None
982     if dialog.ShowModal() == wxID_OK:
983     ret = wxColour2Color(dialog.GetColourData().GetColour())
984    
985     dialog.Destroy()
986    
987     return ret
988    
989 jonathan 460 def _OnChangeLineColor(self, event):
990     clr = self.__GetColor(self.prop.GetLineColor())
991 jonathan 430 if clr is not None:
992 jonathan 460 self.prop.SetLineColor(clr)
993 jonathan 430 self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
994    
995 jonathan 485 def _OnChangeLineColorTrans(self, event):
996     self.prop.SetLineColor(Color.None)
997     self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
998    
999 jonathan 460 def _OnChangeFillColor(self, event):
1000 jonathan 430 clr = self.__GetColor(self.prop.GetFill())
1001     if clr is not None:
1002     self.prop.SetFill(clr)
1003     self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
1004    
1005 jonathan 485 def _OnChangeFillColorTrans(self, event):
1006     self.prop.SetFill(Color.None)
1007     self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
1008    
1009 jonathan 441 def GetClassGroupProperties(self):
1010 jonathan 415 return self.prop
1011 jonathan 392
1012    
1013 jonathan 430 class ClassDataPreviewer(wxWindow):
1014 jonathan 415
1015 jonathan 441 def __init__(self, rect, prop, shapeType,
1016 jonathan 430 parent = None, id = -1, size = wxDefaultSize):
1017     if parent is not None:
1018     wxWindow.__init__(self, parent, id, size=size)
1019 jonathan 460 EVT_PAINT(self, self._OnPaint)
1020 jonathan 415
1021 jonathan 430 self.rect = rect
1022 jonathan 441 self.prop = prop
1023 jonathan 430 self.shapeType = shapeType
1024    
1025 jonathan 460 def _OnPaint(self, event):
1026 jonathan 430 dc = wxPaintDC(self)
1027    
1028     # XXX: this doesn't seem to be having an effect:
1029     dc.DestroyClippingRegion()
1030    
1031     self.Draw(dc, None)
1032    
1033 jonathan 441 def Draw(self, dc, rect, prop = None, shapeType = None):
1034 jonathan 430
1035 jonathan 441 if prop is None: prop = self.prop
1036 jonathan 430 if shapeType is None: shapeType = self.shapeType
1037    
1038     if rect is None:
1039     x = y = 0
1040     w, h = self.GetClientSizeTuple()
1041     else:
1042     x = rect.GetX()
1043     y = rect.GetY()
1044     w = rect.GetWidth()
1045     h = rect.GetHeight()
1046    
1047 jonathan 460 stroke = prop.GetLineColor()
1048 jonathan 415 if stroke is Color.None:
1049 jonathan 392 pen = wxTRANSPARENT_PEN
1050     else:
1051 jonathan 430 pen = wxPen(Color2wxColour(stroke),
1052 jonathan 460 prop.GetLineWidth(),
1053 jonathan 392 wxSOLID)
1054    
1055 jonathan 441 stroke = prop.GetFill()
1056 jonathan 415 if stroke is Color.None:
1057 jonathan 392 brush = wxTRANSPARENT_BRUSH
1058     else:
1059 jonathan 430 brush = wxBrush(Color2wxColour(stroke), wxSOLID)
1060 jonathan 392
1061     dc.SetPen(pen)
1062     dc.SetBrush(brush)
1063    
1064 jonathan 415 if shapeType == SHAPETYPE_ARC:
1065 jonathan 430 dc.DrawSpline([wxPoint(x, y + h),
1066     wxPoint(x + w/2, y + h/4),
1067     wxPoint(x + w/2, y + h/4*3),
1068     wxPoint(x + w, y)])
1069 jonathan 392
1070 jonathan 415 elif shapeType == SHAPETYPE_POINT or \
1071     shapeType == SHAPETYPE_POLYGON:
1072    
1073 jonathan 430 dc.DrawCircle(x + w/2, y + h/2,
1074 jonathan 460 (min(w, h) - prop.GetLineWidth())/2)
1075 jonathan 392
1076 jonathan 415 class ClassRenderer(wxPyGridCellRenderer):
1077    
1078     def __init__(self, shapeType):
1079     wxPyGridCellRenderer.__init__(self)
1080 jonathan 430 self.previewer = ClassDataPreviewer(None, None, shapeType)
1081 jonathan 415
1082     def Draw(self, grid, attr, dc, rect, row, col, isSelected):
1083 jonathan 485 data = grid.GetTable().GetClassGroup(row)
1084 jonathan 415
1085     dc.SetClippingRegion(rect.GetX(), rect.GetY(),
1086     rect.GetWidth(), rect.GetHeight())
1087     dc.SetPen(wxPen(wxLIGHT_GREY))
1088     dc.SetBrush(wxBrush(wxLIGHT_GREY, wxSOLID))
1089     dc.DrawRectangle(rect.GetX(), rect.GetY(),
1090     rect.GetWidth(), rect.GetHeight())
1091    
1092 jonathan 441 if not isinstance(data, ClassGroupMap):
1093     self.previewer.Draw(dc, rect, data.GetProperties())
1094 jonathan 415
1095     if isSelected:
1096     dc.SetPen(wxPen(wxColour(0 * 255, 0 * 255, 0 * 255),
1097     4, wxSOLID))
1098     dc.SetBrush(wxTRANSPARENT_BRUSH)
1099     dc.DrawRectangle(rect.GetX(), rect.GetY(),
1100     rect.GetWidth(), rect.GetHeight())
1101    
1102 jonathan 392 dc.DestroyClippingRegion()
1103    

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26