/[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 500 - (hide annotations)
Mon Mar 10 15:11:24 2003 UTC (21 years, 11 months ago) by jonathan
File MIME type: text/x-python
File size: 35820 byte(s)
Wrap text with _().
(ClassGrid.CreateTable): Set dimensions and size hints here,
        instead of in Reset, so we only set the size once.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26