/[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 496 - (hide annotations)
Mon Mar 10 10:54:50 2003 UTC (22 years ago) by jonathan
File MIME type: text/x-python
File size: 35710 byte(s)
(Classifier): Use __SelectField()
        to correctly set the table information and call this from
        __init__ and from _OnFieldSelect so that all the information
        is up to date when the dialog opens and when a field is changed.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26