/[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 509 - (hide annotations)
Tue Mar 11 09:45:59 2003 UTC (21 years, 11 months ago) by jonathan
File MIME type: text/x-python
File size: 35928 byte(s)
Working to get wxPanel to behave correctly.

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     elif newRows < curRows:
300     msg = wxGridTableMessage(self,
301     wxGRIDTABLE_NOTIFY_ROWS_DELETED,
302     curRows - newRows, # position
303     curRows - newRows) # how many
304     self.GetView().ProcessTableMessage(msg)
305    
306 jonathan 441 def __SetRow(self, row, group):
307 jonathan 460 """Set a row's data to that of the group.
308 jonathan 441
309 jonathan 485 The table is considered modified after this operation.
310    
311 jonathan 460 row -- if row is -1 or greater than the current number of rows
312     then group is appended to the end.
313     """
314 jonathan 441
315 jonathan 460 # either append or replace
316     if row == -1 or row >= self.GetNumberRows():
317     self.tdata.append(group)
318 jonathan 441 else:
319 jonathan 460 self.tdata[row] = group
320 jonathan 441
321 jonathan 460 self.__Modified()
322    
323 jonathan 415 def GetColLabelValue(self, col):
324 jonathan 460 """Return the label for the given column."""
325 jonathan 415 return self.__col_labels[col]
326    
327     def GetRowLabelValue(self, row):
328 jonathan 460 """Return the label for the given row."""
329 jonathan 415
330 jonathan 460 group = self.tdata[row]
331     if isinstance(group, ClassGroupDefault): return _("Default")
332     if isinstance(group, ClassGroupSingleton): return _("Singleton")
333     if isinstance(group, ClassGroupRange): return _("Range")
334     if isinstance(group, ClassGroupMap): return _("Map")
335    
336     assert(False) # shouldn't get here
337     return _("")
338    
339 jonathan 376 def GetNumberRows(self):
340 jonathan 460 """Return the number of rows."""
341 jonathan 376 return len(self.tdata)
342    
343     def GetNumberCols(self):
344 jonathan 460 """Return the number of columns."""
345 jonathan 415 return self.NUM_COLS
346 jonathan 376
347     def IsEmptyCell(self, row, col):
348 jonathan 460 """Determine if a cell is empty. This is always false."""
349     return False
350 jonathan 376
351     def GetValue(self, row, col):
352 jonathan 460 """Return the object that is used to represent the given
353     cell coordinates. This may not be a string."""
354     return self.GetValueAsCustom(row, col, None)
355 jonathan 376
356     def SetValue(self, row, col, value):
357 jonathan 460 """Assign 'value' to the cell specified by 'row' and 'col'.
358    
359     The table is considered modified after this operation.
360     """
361    
362     self.SetValueAsCustom(row, col, None, value)
363 jonathan 415 self.__Modified()
364    
365 jonathan 392 def GetValueAsCustom(self, row, col, typeName):
366 jonathan 460 """Return the object that is used to represent the given
367     cell coordinates. This may not be a string.
368    
369     typeName -- unused, but needed to overload wxPyGridTableBase
370     """
371 jonathan 376
372 jonathan 460 group = self.tdata[row]
373    
374     if col == COL_SYMBOL:
375 jonathan 485 return group.GetProperties()
376 jonathan 460
377     if col == COL_LABEL:
378     return group.GetLabel()
379    
380     # col must be COL_VALUE
381     assert(col == COL_VALUE)
382    
383     if isinstance(group, ClassGroupDefault):
384     return _("DEFAULT")
385     elif isinstance(group, ClassGroupSingleton):
386     return group.GetValue()
387     elif isinstance(group, ClassGroupRange):
388     return _("%s - %s") % (group.GetMin(), group.GetMax())
389    
390     assert(False) # shouldn't get here
391     return None
392    
393 jonathan 415 def __ParseInput(self, value):
394     """Try to determine what kind of input value is
395 jonathan 460 (string, number, or range)
396    
397     Returns a tuple of length one if there is a single
398     value, or of length two if it is a range.
399 jonathan 415 """
400 jonathan 392
401 jonathan 485 type = self.fieldType
402 jonathan 415
403 jonathan 460 if type == FIELDTYPE_STRING:
404 jonathan 451 return (value,)
405 jonathan 460 elif type == FIELDTYPE_INT or type == FIELDTYPE_DOUBLE:
406 jonathan 451
407 jonathan 460 if type == FIELDTYPE_INT:
408     conv = lambda p: int(float(p))
409     else:
410     conv = lambda p: p
411    
412 jonathan 451 #
413     # first try to take the input as a single number
414     # if there's an exception try to break it into
415     # a range seperated by a '-'. take care to ignore
416     # a leading '-' as that could be for a negative number.
417     # then try to parse the individual parts. if there
418     # is an exception here, let it pass up to the calling
419     # function.
420     #
421     try:
422 jonathan 460 return (conv(Str2Num(value)),)
423     except ValueError:
424 jonathan 451 i = value.find('-')
425     if i == 0:
426     i = value.find('-', 1)
427    
428 jonathan 460 return (conv(Str2Num(value[:i])), conv(Str2Num(value[i+1:])))
429    
430     assert(False) # shouldn't get here
431 jonathan 485 return (0,)
432 jonathan 415
433    
434     def SetValueAsCustom(self, row, col, typeName, value):
435 jonathan 460 """Set the cell specified by 'row' and 'col' to 'value'.
436 jonathan 415
437 jonathan 460 If column represents the value column, the input is parsed
438     to determine if a string, number, or range was entered.
439     A new ClassGroup may be created if the type of data changes.
440    
441     The table is considered modified after this operation.
442    
443     typeName -- unused, but needed to overload wxPyGridTableBase
444     """
445    
446     assert(col >= 0 and col < self.GetNumberCols())
447     assert(row >= 0 and row < self.GetNumberRows())
448    
449     group = self.tdata[row]
450    
451 jonathan 485 mod = True # assume the data will change
452 jonathan 460
453     if col == COL_SYMBOL:
454 jonathan 485 group.SetProperties(value)
455     elif col == COL_LABEL:
456     group.SetLabel(value)
457 jonathan 415 elif col == COL_VALUE:
458 jonathan 451 if isinstance(group, ClassGroupDefault):
459     # not allowed to modify the default value
460     pass
461     elif isinstance(group, ClassGroupMap):
462     # something special
463     pass
464     else: # SINGLETON, RANGE
465     try:
466     dataInfo = self.__ParseInput(value)
467 jonathan 460 except ValueError:
468 jonathan 451 # bad input, ignore the request
469 jonathan 485 mod = False
470 jonathan 451 else:
471 jonathan 415
472 jonathan 485 changed = False
473 jonathan 451 ngroup = group
474     props = group.GetProperties()
475 jonathan 460
476     #
477     # try to update the values, which may include
478     # changing the underlying group type if the
479     # group was a singleton and a range was entered
480     #
481 jonathan 451 if len(dataInfo) == 1:
482     if not isinstance(group, ClassGroupSingleton):
483     ngroup = ClassGroupSingleton(prop = props)
484 jonathan 485 changed = True
485 jonathan 451 ngroup.SetValue(dataInfo[0])
486     elif len(dataInfo) == 2:
487     if not isinstance(group, ClassGroupRange):
488     ngroup = ClassGroupRange(prop = props)
489 jonathan 485 changed = True
490 jonathan 451 ngroup.SetRange(dataInfo[0], dataInfo[1])
491 jonathan 415 else:
492 jonathan 451 assert(False)
493 jonathan 485 pass
494 jonathan 415
495 jonathan 485 if changed:
496     ngroup.SetLabel(group.GetLabel())
497     self.SetClassGroup(row, ngroup)
498     else:
499     assert(False) # shouldn't be here
500     pass
501 jonathan 460
502 jonathan 485 if mod:
503 jonathan 460 self.__Modified()
504     self.GetView().Refresh()
505 jonathan 415
506     def GetAttr(self, row, col, someExtraParameter):
507 jonathan 460 """Returns the cell attributes"""
508    
509 jonathan 415 attr = wxGridCellAttr()
510     #attr = wxPyGridTableBase.GetAttr(self, row, col, someExtraParameter)
511    
512 jonathan 460 if col == COL_SYMBOL:
513 jonathan 485 # we need to create a new renderer each time, because
514     # SetRenderer takes control of the parameter
515 jonathan 415 attr.SetRenderer(ClassRenderer(self.shapeType))
516     attr.SetReadOnly()
517    
518     return attr
519    
520 jonathan 441 def GetClassGroup(self, row):
521 jonathan 460 """Return the ClassGroup object representing row 'row'."""
522 jonathan 415
523 jonathan 485 return self.tdata[row] # self.GetValueAsCustom(row, COL_SYMBOL, None)
524 jonathan 415
525 jonathan 460 def SetClassGroup(self, row, group):
526 jonathan 485 self.__SetRow(row, group)
527     self.GetView().Refresh()
528 jonathan 460
529     def __Modified(self, mod = True):
530 jonathan 485 """Adjust the modified flag.
531 jonathan 460
532 jonathan 485 mod -- if -1 set the modified flag to False, otherwise perform
533     an 'or' operation with the current value of the flag and
534     'mod'
535     """
536    
537     if mod == -1:
538     self.modified = False
539     else:
540     self.modified = mod or self.modified
541    
542 jonathan 415 def IsModified(self):
543 jonathan 460 """True if this table is considered modified."""
544 jonathan 415 return self.modified
545    
546 jonathan 451 def DeleteRows(self, pos, numRows = 1):
547 jonathan 485 """Deletes 'numRows' beginning at row 'pos'.
548 jonathan 460
549 jonathan 485 The row representing the default group is not removed.
550    
551     The table is considered modified if any rows are removed.
552 jonathan 460 """
553    
554 jonathan 451 assert(pos >= 0)
555     old_len = len(self.tdata)
556     for row in range(pos, pos - numRows, -1):
557 jonathan 485 group = self.GetClassGroup(row)
558 jonathan 451 if not isinstance(group, ClassGroupDefault):
559     self.tdata.pop(row)
560     self.__Modified()
561    
562     if self.IsModified():
563     self.__NotifyRowChanges(old_len, len(self.tdata))
564 jonathan 415
565 jonathan 451 def AppendRows(self, numRows = 1):
566 jonathan 485 """Append 'numRows' empty rows to the end of the table.
567 jonathan 460
568 jonathan 485 The table is considered modified if any rows are appended.
569     """
570    
571 jonathan 451 old_len = len(self.tdata)
572     for i in range(numRows):
573     np = ClassGroupSingleton()
574 jonathan 460 self.__SetRow(-1, np)
575 jonathan 451
576     if self.IsModified():
577     self.__NotifyRowChanges(old_len, len(self.tdata))
578    
579    
580 jonathan 485 class Classifier(NonModalDialog):
581 jonathan 372
582 jonathan 485 def __init__(self, parent, interactor, name, layer):
583     NonModalDialog.__init__(self, parent, interactor, name,
584     _("Classifier: %s") % layer.Title())
585 jonathan 372
586 jonathan 509 panel = wxPanel(self, -1)
587    
588 jonathan 415 self.layer = layer
589    
590 jonathan 485 self.originalClass = self.layer.GetClassification()
591     field = self.originalClass.GetField()
592     fieldType = self.originalClass.GetFieldType()
593    
594 jonathan 372 topBox = wxBoxSizer(wxVERTICAL)
595    
596 jonathan 509 #topBox.Add(wxStaticText(panel, -1, _("Layer: %s") % layer.Title()),
597 jonathan 485 #0, wxALIGN_LEFT | wxALL, 4)
598 jonathan 509 topBox.Add(wxStaticText(panel, -1,
599 jonathan 485 _("Layer Type: %s") % layer.ShapeType()),
600 jonathan 451 0, wxALIGN_LEFT | wxALL, 4)
601 jonathan 415
602 jonathan 372
603 jonathan 485 #
604     # make field combo box
605     #
606 jonathan 509 self.fields = wxComboBox(panel, ID_PROPERTY_SELECT, "",
607 jonathan 372 style = wxCB_READONLY)
608    
609     self.num_cols = layer.table.field_count()
610 jonathan 441 # just assume the first field in case one hasn't been
611     # specified in the file.
612 jonathan 451 self.__cur_field = 0
613 jonathan 460
614     self.fields.Append("<None>")
615     self.fields.SetClientData(0, None)
616    
617 jonathan 372 for i in range(self.num_cols):
618     type, name, len, decc = layer.table.field_info(i)
619 jonathan 451 self.fields.Append(name)
620    
621 jonathan 415 if name == field:
622 jonathan 460 self.__cur_field = i + 1
623 jonathan 485 self.fields.SetClientData(i + 1, self.originalClass)
624 jonathan 451 else:
625 jonathan 460 self.fields.SetClientData(i + 1, None)
626 jonathan 372
627    
628 jonathan 509 ###########
629 jonathan 485
630 jonathan 509 self.fieldTypeText = wxStaticText(panel, -1, "")
631 jonathan 506 topBox.Add(self.fieldTypeText, 0, wxGROW | wxALIGN_LEFT | wxALL, 4)
632 jonathan 485
633     propertyBox = wxBoxSizer(wxHORIZONTAL)
634 jonathan 509 propertyBox.Add(wxStaticText(panel, -1, _("Field: ")),
635 jonathan 506 0, wxALIGN_LEFT | wxALL, 4)
636 jonathan 451 propertyBox.Add(self.fields, 1, wxGROW|wxALL, 4)
637 jonathan 460 EVT_COMBOBOX(self, ID_PROPERTY_SELECT, self._OnFieldSelect)
638 jonathan 451
639 jonathan 376 topBox.Add(propertyBox, 0, wxGROW, 4)
640 jonathan 372
641 jonathan 509 ###########
642 jonathan 372 #
643     # Classification data table
644     #
645    
646 jonathan 415 controlBox = wxBoxSizer(wxHORIZONTAL)
647 jonathan 485
648 jonathan 460 self.classGrid = ClassGrid(self)
649     self.__SetGridTable(self.__cur_field)
650    
651 jonathan 415 controlBox.Add(self.classGrid, 1, wxGROW, 0)
652 jonathan 376
653 jonathan 509 ###########
654 jonathan 485 #
655     # Control buttons:
656     #
657     self.controlButtons = []
658 jonathan 460
659 jonathan 509 controlButtonBox = wxBoxSizer(wxVERTICAL)
660    
661     button = wxButton(panel, ID_CLASSIFY_ADD, _("Add"))
662 jonathan 485 controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
663     self.controlButtons.append(button)
664 jonathan 451
665 jonathan 509 #button = wxButton(panel, ID_CLASSIFY_GENRANGE, _("Generate Ranges"))
666 jonathan 485 #controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
667     #self.controlButtons.append(button)
668    
669 jonathan 509 button = wxButton(panel, ID_CLASSIFY_MOVEUP, _("Move Up"))
670 jonathan 485 controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
671     self.controlButtons.append(button)
672    
673 jonathan 509 button = wxButton(panel, ID_CLASSIFY_MOVEDOWN, _("Move Down"))
674 jonathan 485 controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
675     self.controlButtons.append(button)
676    
677     controlButtonBox.Add(60, 20, 0, wxGROW | wxALL | wxALIGN_BOTTOM, 4)
678    
679 jonathan 509 button = wxButton(panel, ID_CLASSIFY_REMOVE, _("Remove"))
680 jonathan 485 controlButtonBox.Add(button, 0, wxGROW | wxALL | wxALIGN_BOTTOM, 4)
681     self.controlButtons.append(button)
682    
683 jonathan 415 controlBox.Add(controlButtonBox, 0, wxGROW, 10)
684 jonathan 509 topBox.Add(controlBox, 0, wxGROW, 10)
685 jonathan 415
686 jonathan 460 EVT_BUTTON(self, ID_CLASSIFY_ADD, self._OnAdd)
687     EVT_BUTTON(self, ID_CLASSIFY_REMOVE, self._OnRemove)
688     EVT_BUTTON(self, ID_CLASSIFY_GENRANGE, self._OnGenRange)
689     EVT_BUTTON(self, ID_CLASSIFY_MOVEUP, self._OnMoveUp)
690     EVT_BUTTON(self, ID_CLASSIFY_MOVEDOWN, self._OnMoveDown)
691 jonathan 415
692 jonathan 509 ###########
693    
694 jonathan 372 buttonBox = wxBoxSizer(wxHORIZONTAL)
695 jonathan 509 buttonBox.Add(wxButton(panel, ID_CLASSIFY_OK, _("OK")),
696 jonathan 372 0, wxALL, 4)
697 jonathan 485 buttonBox.Add(60, 20, 0, wxALL, 4)
698 jonathan 509 buttonBox.Add(wxButton(panel, ID_CLASSIFY_APPLY, _("Apply")),
699 jonathan 485 0, wxALL, 4)
700     buttonBox.Add(60, 20, 0, wxALL, 4)
701 jonathan 509 buttonBox.Add(wxButton(panel, ID_CLASSIFY_CANCEL, _("Cancel")),
702 jonathan 372 0, wxALL, 4)
703 jonathan 509 topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 0)
704 jonathan 372
705 jonathan 460 EVT_BUTTON(self, ID_CLASSIFY_OK, self._OnOK)
706 jonathan 485 EVT_BUTTON(self, ID_CLASSIFY_APPLY, self._OnApply)
707 jonathan 460 EVT_BUTTON(self, ID_CLASSIFY_CANCEL, self._OnCancel)
708 jonathan 372
709 jonathan 509 ###########
710    
711 jonathan 496 self.fields.SetSelection(self.__cur_field)
712     self.__SelectField(self.__cur_field)
713    
714 jonathan 509 panel.SetAutoLayout(True)
715     panel.SetSizer(topBox)
716 jonathan 372
717 jonathan 509 topBox.SetSizeHints(panel)
718     topBox.Fit(panel)
719 jonathan 485
720 jonathan 509 self.SetAutoLayout(True)
721     self.Fit()
722    
723    
724 jonathan 460 def __BuildClassification(self, fieldIndex):
725 jonathan 415
726 jonathan 496 numRows = self.classGrid.GetNumberRows()
727     assert(numRows > 0) # there should always be a default row
728    
729 jonathan 415 clazz = Classification()
730 jonathan 496 if fieldIndex == 0:
731     fieldName = None
732     fieldType = None
733     else:
734     fieldName = self.fields.GetString(fieldIndex)
735     fieldType = self.layer.GetFieldType(fieldName)
736 jonathan 415
737 jonathan 460 clazz.SetField(fieldName)
738     clazz.SetFieldType(fieldType)
739    
740 jonathan 415
741 jonathan 460 table = self.classGrid.GetTable()
742     clazz.SetDefaultGroup(table.GetClassGroup(0))
743 jonathan 415
744 jonathan 460 for i in range(1, numRows):
745     clazz.AddGroup(table.GetClassGroup(i))
746    
747 jonathan 415 return clazz
748    
749 jonathan 460 def __SetGridTable(self, fieldIndex):
750 jonathan 415
751 jonathan 460 clazz = self.fields.GetClientData(fieldIndex)
752 jonathan 415
753 jonathan 460 if clazz is None:
754     clazz = Classification()
755     clazz.SetDefaultGroup(
756     ClassGroupDefault(
757 jonathan 485 self.layer.GetClassification().
758     GetDefaultGroup().GetProperties()))
759 jonathan 460
760     fieldName = self.fields.GetString(fieldIndex)
761     fieldType = self.layer.GetFieldType(fieldName)
762     clazz.SetFieldType(fieldType)
763    
764     self.classGrid.CreateTable(clazz, self.layer.ShapeType())
765    
766 jonathan 496
767    
768 jonathan 500 type2string = {None: _("None"),
769     FIELDTYPE_STRING: _("Text"),
770     FIELDTYPE_INT: _("Integer"),
771     FIELDTYPE_DOUBLE: _("Decimal")}
772 jonathan 496
773 jonathan 485 def __SetFieldTypeText(self, fieldIndex):
774     fieldName = self.fields.GetString(fieldIndex)
775     fieldType = self.layer.GetFieldType(fieldName)
776    
777 jonathan 496 assert(Classifier.type2string.has_key(fieldType))
778 jonathan 485
779 jonathan 496 text = Classifier.type2string[fieldType]
780    
781 jonathan 485 self.fieldTypeText.SetLabel(_("Field Type: %s") % text)
782    
783 jonathan 498 def __SelectField(self, newIndex, oldIndex = -1):
784 jonathan 460
785 jonathan 498 assert(oldIndex >= -1)
786 jonathan 415
787 jonathan 498 if oldIndex != -1:
788     clazz = self.__BuildClassification(oldIndex)
789     self.fields.SetClientData(oldIndex, clazz)
790 jonathan 485
791 jonathan 498 self.__SetGridTable(newIndex)
792    
793     enabled = newIndex != 0
794    
795 jonathan 485 for b in self.controlButtons:
796     b.Enable(enabled)
797    
798 jonathan 498 self.__SetFieldTypeText(newIndex)
799 jonathan 485
800 jonathan 496
801     def _OnFieldSelect(self, event):
802 jonathan 498 index = self.fields.GetSelection()
803     self.__SelectField(index, self.__cur_field)
804     self.__cur_field = index
805 jonathan 485
806     def _OnApply(self, event):
807 jonathan 415 """Put the data from the table into a new Classification and hand
808     it to the layer.
809     """
810    
811 jonathan 460 clazz = self.fields.GetClientData(self.__cur_field)
812 jonathan 415
813     #
814     # only build the classification if there wasn't one to
815     # to begin with or it has been modified
816     #
817     if clazz is None or self.classGrid.GetTable().IsModified():
818 jonathan 451 clazz = self.__BuildClassification(self.__cur_field)
819 jonathan 415
820     self.layer.SetClassification(clazz)
821    
822 jonathan 485 def _OnOK(self, event):
823     self._OnApply(event)
824     self.OnClose(event)
825 jonathan 415
826 jonathan 460 def _OnCancel(self, event):
827 jonathan 485 """The layer's current classification stays the same."""
828     self.layer.SetClassification(self.originalClass)
829     self.OnClose(event)
830 jonathan 415
831 jonathan 460 def _OnAdd(self, event):
832 jonathan 451 self.classGrid.AppendRows()
833 jonathan 415
834 jonathan 460 def _OnRemove(self, event):
835 jonathan 451 self.classGrid.DeleteSelectedRows()
836    
837 jonathan 460 def _OnGenRange(self, event):
838     print "Classifier._OnGenRange()"
839 jonathan 415
840 jonathan 460 def _OnMoveUp(self, event):
841     sel = self.classGrid.GetCurrentSelection()
842 jonathan 415
843 jonathan 460 if len(sel) == 1:
844     i = sel[0]
845     if i > 1:
846     table = self.classGrid.GetTable()
847     x = table.GetClassGroup(i - 1)
848     y = table.GetClassGroup(i)
849     table.SetClassGroup(i - 1, y)
850     table.SetClassGroup(i, x)
851     self.classGrid.ClearSelection()
852     self.classGrid.SelectRow(i - 1)
853    
854     def _OnMoveDown(self, event):
855     sel = self.classGrid.GetCurrentSelection()
856    
857     if len(sel) == 1:
858     i = sel[0]
859     table = self.classGrid.GetTable()
860     if 0 < i < table.GetNumberRows() - 1:
861     x = table.GetClassGroup(i)
862     y = table.GetClassGroup(i + 1)
863     table.SetClassGroup(i, y)
864     table.SetClassGroup(i + 1, x)
865     self.classGrid.ClearSelection()
866     self.classGrid.SelectRow(i + 1)
867    
868    
869 jonathan 415 ID_SELPROP_OK = 4001
870     ID_SELPROP_CANCEL = 4002
871     ID_SELPROP_SPINCTRL = 4002
872 jonathan 430 ID_SELPROP_PREVIEW = 4003
873     ID_SELPROP_STROKECLR = 4004
874     ID_SELPROP_FILLCLR = 4005
875 jonathan 485 ID_SELPROP_STROKECLRTRANS = 4006
876     ID_SELPROP_FILLCLRTRANS = 4007
877 jonathan 415
878     class SelectPropertiesDialog(wxDialog):
879    
880     def __init__(self, parent, prop, shapeType):
881     wxDialog.__init__(self, parent, -1, _("Select Properties"),
882 jonathan 507 style = wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
883 jonathan 415
884 jonathan 441 self.prop = ClassGroupProperties(prop)
885 jonathan 415
886 jonathan 430 topBox = wxBoxSizer(wxVERTICAL)
887 jonathan 415
888 jonathan 430 itemBox = wxBoxSizer(wxHORIZONTAL)
889    
890     # preview box
891     previewBox = wxBoxSizer(wxVERTICAL)
892     previewBox.Add(wxStaticText(self, -1, _("Preview:")),
893     0, wxALIGN_LEFT | wxALL, 4)
894     self.previewer = ClassDataPreviewer(None, self.prop, shapeType,
895     self, ID_SELPROP_PREVIEW, (40, 40))
896     previewBox.Add(self.previewer, 1, wxGROW, 15)
897    
898     itemBox.Add(previewBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
899    
900     # control box
901     ctrlBox = wxBoxSizer(wxVERTICAL)
902 jonathan 485
903     lineColorBox = wxBoxSizer(wxHORIZONTAL)
904     lineColorBox.Add(
905 jonathan 500 wxButton(self, ID_SELPROP_STROKECLR, _("Change Line Color")),
906 jonathan 485 1, wxALL | wxGROW, 4)
907 jonathan 460 EVT_BUTTON(self, ID_SELPROP_STROKECLR, self._OnChangeLineColor)
908 jonathan 430
909 jonathan 485 lineColorBox.Add(
910 jonathan 500 wxButton(self, ID_SELPROP_STROKECLRTRANS, _("Transparent")),
911 jonathan 485 1, wxALL | wxGROW, 4)
912     EVT_BUTTON(self, ID_SELPROP_STROKECLRTRANS,
913     self._OnChangeLineColorTrans)
914    
915     ctrlBox.Add(lineColorBox, 0,
916     wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
917    
918 jonathan 430 if shapeType != SHAPETYPE_ARC:
919 jonathan 485 fillColorBox = wxBoxSizer(wxHORIZONTAL)
920     fillColorBox.Add(
921 jonathan 500 wxButton(self, ID_SELPROP_FILLCLR, _("Change Fill Color")),
922 jonathan 485 1, wxALL | wxGROW, 4)
923 jonathan 460 EVT_BUTTON(self, ID_SELPROP_FILLCLR, self._OnChangeFillColor)
924 jonathan 485 fillColorBox.Add(
925 jonathan 500 wxButton(self, ID_SELPROP_FILLCLRTRANS, _("Transparent")),
926 jonathan 485 1, wxALL | wxGROW, 4)
927     EVT_BUTTON(self, ID_SELPROP_FILLCLRTRANS,
928     self._OnChangeFillColorTrans)
929     ctrlBox.Add(fillColorBox, 0,
930     wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
931 jonathan 430
932 jonathan 415 spinBox = wxBoxSizer(wxHORIZONTAL)
933 jonathan 460 spinBox.Add(wxStaticText(self, -1, _("Line Width: ")),
934 jonathan 430 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
935 jonathan 415 self.spinCtrl = wxSpinCtrl(self, ID_SELPROP_SPINCTRL,
936     min=1, max=10,
937 jonathan 460 value=str(prop.GetLineWidth()),
938     initial=prop.GetLineWidth())
939 jonathan 415
940 jonathan 460 EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL, self._OnSpin)
941 jonathan 415
942     spinBox.Add(self.spinCtrl, 0, wxALIGN_LEFT | wxALL, 4)
943    
944 jonathan 430 ctrlBox.Add(spinBox, 0, wxALIGN_RIGHT | wxALL, 0)
945     itemBox.Add(ctrlBox, 0, wxALIGN_RIGHT | wxALL | wxGROW, 0)
946     topBox.Add(itemBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
947 jonathan 415
948     #
949     # Control buttons:
950     #
951     buttonBox = wxBoxSizer(wxHORIZONTAL)
952 jonathan 485 buttonBox.Add(wxButton(self, ID_SELPROP_OK, _("OK")),
953 jonathan 415 0, wxALL, 4)
954 jonathan 485 buttonBox.Add(wxButton(self, ID_SELPROP_CANCEL, _("Cancel")),
955 jonathan 415 0, wxALL, 4)
956     topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)
957    
958 jonathan 460 EVT_BUTTON(self, ID_SELPROP_OK, self._OnOK)
959     EVT_BUTTON(self, ID_SELPROP_CANCEL, self._OnCancel)
960 jonathan 415
961 jonathan 509 self.SetAutoLayout(True)
962 jonathan 415 self.SetSizer(topBox)
963     topBox.Fit(self)
964     topBox.SetSizeHints(self)
965    
966 jonathan 460 def _OnOK(self, event):
967 jonathan 372 self.EndModal(wxID_OK)
968    
969 jonathan 460 def _OnCancel(self, event):
970 jonathan 372 self.EndModal(wxID_CANCEL)
971    
972 jonathan 460 def _OnSpin(self, event):
973     self.prop.SetLineWidth(self.spinCtrl.GetValue())
974 jonathan 430 self.previewer.Refresh()
975 jonathan 392
976 jonathan 430 def __GetColor(self, cur):
977     dialog = wxColourDialog(self)
978     dialog.GetColourData().SetColour(Color2wxColour(cur))
979     ret = None
980     if dialog.ShowModal() == wxID_OK:
981     ret = wxColour2Color(dialog.GetColourData().GetColour())
982    
983     dialog.Destroy()
984    
985     return ret
986    
987 jonathan 460 def _OnChangeLineColor(self, event):
988     clr = self.__GetColor(self.prop.GetLineColor())
989 jonathan 430 if clr is not None:
990 jonathan 460 self.prop.SetLineColor(clr)
991 jonathan 430 self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
992    
993 jonathan 485 def _OnChangeLineColorTrans(self, event):
994     self.prop.SetLineColor(Color.None)
995     self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
996    
997 jonathan 460 def _OnChangeFillColor(self, event):
998 jonathan 430 clr = self.__GetColor(self.prop.GetFill())
999     if clr is not None:
1000     self.prop.SetFill(clr)
1001     self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
1002    
1003 jonathan 485 def _OnChangeFillColorTrans(self, event):
1004     self.prop.SetFill(Color.None)
1005     self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
1006    
1007 jonathan 441 def GetClassGroupProperties(self):
1008 jonathan 415 return self.prop
1009 jonathan 392
1010    
1011 jonathan 430 class ClassDataPreviewer(wxWindow):
1012 jonathan 415
1013 jonathan 441 def __init__(self, rect, prop, shapeType,
1014 jonathan 430 parent = None, id = -1, size = wxDefaultSize):
1015     if parent is not None:
1016     wxWindow.__init__(self, parent, id, size=size)
1017 jonathan 460 EVT_PAINT(self, self._OnPaint)
1018 jonathan 415
1019 jonathan 430 self.rect = rect
1020 jonathan 441 self.prop = prop
1021 jonathan 430 self.shapeType = shapeType
1022    
1023 jonathan 460 def _OnPaint(self, event):
1024 jonathan 430 dc = wxPaintDC(self)
1025    
1026     # XXX: this doesn't seem to be having an effect:
1027     dc.DestroyClippingRegion()
1028    
1029     self.Draw(dc, None)
1030    
1031 jonathan 441 def Draw(self, dc, rect, prop = None, shapeType = None):
1032 jonathan 430
1033 jonathan 441 if prop is None: prop = self.prop
1034 jonathan 430 if shapeType is None: shapeType = self.shapeType
1035    
1036     if rect is None:
1037     x = y = 0
1038     w, h = self.GetClientSizeTuple()
1039     else:
1040     x = rect.GetX()
1041     y = rect.GetY()
1042     w = rect.GetWidth()
1043     h = rect.GetHeight()
1044    
1045 jonathan 460 stroke = prop.GetLineColor()
1046 jonathan 415 if stroke is Color.None:
1047 jonathan 392 pen = wxTRANSPARENT_PEN
1048     else:
1049 jonathan 430 pen = wxPen(Color2wxColour(stroke),
1050 jonathan 460 prop.GetLineWidth(),
1051 jonathan 392 wxSOLID)
1052    
1053 jonathan 441 stroke = prop.GetFill()
1054 jonathan 415 if stroke is Color.None:
1055 jonathan 392 brush = wxTRANSPARENT_BRUSH
1056     else:
1057 jonathan 430 brush = wxBrush(Color2wxColour(stroke), wxSOLID)
1058 jonathan 392
1059     dc.SetPen(pen)
1060     dc.SetBrush(brush)
1061    
1062 jonathan 415 if shapeType == SHAPETYPE_ARC:
1063 jonathan 430 dc.DrawSpline([wxPoint(x, y + h),
1064     wxPoint(x + w/2, y + h/4),
1065     wxPoint(x + w/2, y + h/4*3),
1066     wxPoint(x + w, y)])
1067 jonathan 392
1068 jonathan 415 elif shapeType == SHAPETYPE_POINT or \
1069     shapeType == SHAPETYPE_POLYGON:
1070    
1071 jonathan 430 dc.DrawCircle(x + w/2, y + h/2,
1072 jonathan 460 (min(w, h) - prop.GetLineWidth())/2)
1073 jonathan 392
1074 jonathan 415 class ClassRenderer(wxPyGridCellRenderer):
1075    
1076     def __init__(self, shapeType):
1077     wxPyGridCellRenderer.__init__(self)
1078 jonathan 430 self.previewer = ClassDataPreviewer(None, None, shapeType)
1079 jonathan 415
1080     def Draw(self, grid, attr, dc, rect, row, col, isSelected):
1081 jonathan 485 data = grid.GetTable().GetClassGroup(row)
1082 jonathan 415
1083     dc.SetClippingRegion(rect.GetX(), rect.GetY(),
1084     rect.GetWidth(), rect.GetHeight())
1085     dc.SetPen(wxPen(wxLIGHT_GREY))
1086     dc.SetBrush(wxBrush(wxLIGHT_GREY, wxSOLID))
1087     dc.DrawRectangle(rect.GetX(), rect.GetY(),
1088     rect.GetWidth(), rect.GetHeight())
1089    
1090 jonathan 441 if not isinstance(data, ClassGroupMap):
1091     self.previewer.Draw(dc, rect, data.GetProperties())
1092 jonathan 415
1093     if isSelected:
1094     dc.SetPen(wxPen(wxColour(0 * 255, 0 * 255, 0 * 255),
1095     4, wxSOLID))
1096     dc.SetBrush(wxTRANSPARENT_BRUSH)
1097     dc.DrawRectangle(rect.GetX(), rect.GetY(),
1098     rect.GetWidth(), rect.GetHeight())
1099    
1100 jonathan 392 dc.DestroyClippingRegion()
1101    

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26