/[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 489 - (hide annotations)
Fri Mar 7 18:22:47 2003 UTC (22 years ago) by jonathan
File MIME type: text/x-python
File size: 35552 byte(s)
removed print statement

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26