/[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 560 - (hide annotations)
Wed Mar 26 11:05:47 2003 UTC (21 years, 11 months ago) by jonathan
File MIME type: text/x-python
File size: 36558 byte(s)
(Classifier.__init__): Use wxADJUST_MINSIZE
        as one of the style properties for the fieldTypeText item to
        be sure that its size is correct when the text changes.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26