/[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 498 - (hide annotations)
Mon Mar 10 11:03:22 2003 UTC (21 years, 11 months ago) by jonathan
File MIME type: text/x-python
File size: 35810 byte(s)
(Classifier): SelectField() needed
        to know the old field index as well as the new one.

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 498 def __SelectField(self, newIndex, oldIndex = -1):
778 jonathan 460
779 jonathan 498 assert(oldIndex >= -1)
780 jonathan 415
781 jonathan 498 if oldIndex != -1:
782     clazz = self.__BuildClassification(oldIndex)
783     self.fields.SetClientData(oldIndex, clazz)
784 jonathan 485
785 jonathan 498 self.__SetGridTable(newIndex)
786    
787     enabled = newIndex != 0
788    
789 jonathan 485 for b in self.controlButtons:
790     b.Enable(enabled)
791    
792 jonathan 498 self.__SetFieldTypeText(newIndex)
793 jonathan 485
794 jonathan 496
795     def _OnFieldSelect(self, event):
796 jonathan 498 index = self.fields.GetSelection()
797     self.__SelectField(index, self.__cur_field)
798     self.__cur_field = index
799 jonathan 485
800     def _OnApply(self, event):
801 jonathan 415 """Put the data from the table into a new Classification and hand
802     it to the layer.
803     """
804    
805 jonathan 460 clazz = self.fields.GetClientData(self.__cur_field)
806 jonathan 415
807     #
808     # only build the classification if there wasn't one to
809     # to begin with or it has been modified
810     #
811     if clazz is None or self.classGrid.GetTable().IsModified():
812 jonathan 451 clazz = self.__BuildClassification(self.__cur_field)
813 jonathan 415
814     self.layer.SetClassification(clazz)
815    
816 jonathan 485 def _OnOK(self, event):
817     self._OnApply(event)
818     self.OnClose(event)
819 jonathan 415
820 jonathan 460 def _OnCancel(self, event):
821 jonathan 485 """The layer's current classification stays the same."""
822     self.layer.SetClassification(self.originalClass)
823     self.OnClose(event)
824 jonathan 415
825 jonathan 460 def _OnAdd(self, event):
826 jonathan 451 self.classGrid.AppendRows()
827 jonathan 415
828 jonathan 460 def _OnRemove(self, event):
829 jonathan 451 self.classGrid.DeleteSelectedRows()
830    
831 jonathan 460 def _OnGenRange(self, event):
832     print "Classifier._OnGenRange()"
833 jonathan 415
834 jonathan 460 def _OnMoveUp(self, event):
835     sel = self.classGrid.GetCurrentSelection()
836 jonathan 415
837 jonathan 460 if len(sel) == 1:
838     i = sel[0]
839     if i > 1:
840     table = self.classGrid.GetTable()
841     x = table.GetClassGroup(i - 1)
842     y = table.GetClassGroup(i)
843     table.SetClassGroup(i - 1, y)
844     table.SetClassGroup(i, x)
845     self.classGrid.ClearSelection()
846     self.classGrid.SelectRow(i - 1)
847    
848     def _OnMoveDown(self, event):
849     sel = self.classGrid.GetCurrentSelection()
850    
851     if len(sel) == 1:
852     i = sel[0]
853     table = self.classGrid.GetTable()
854     if 0 < i < table.GetNumberRows() - 1:
855     x = table.GetClassGroup(i)
856     y = table.GetClassGroup(i + 1)
857     table.SetClassGroup(i, y)
858     table.SetClassGroup(i + 1, x)
859     self.classGrid.ClearSelection()
860     self.classGrid.SelectRow(i + 1)
861    
862    
863 jonathan 415 ID_SELPROP_OK = 4001
864     ID_SELPROP_CANCEL = 4002
865     ID_SELPROP_SPINCTRL = 4002
866 jonathan 430 ID_SELPROP_PREVIEW = 4003
867     ID_SELPROP_STROKECLR = 4004
868     ID_SELPROP_FILLCLR = 4005
869 jonathan 485 ID_SELPROP_STROKECLRTRANS = 4006
870     ID_SELPROP_FILLCLRTRANS = 4007
871 jonathan 415
872     class SelectPropertiesDialog(wxDialog):
873    
874     def __init__(self, parent, prop, shapeType):
875     wxDialog.__init__(self, parent, -1, _("Select Properties"),
876     style = wxRESIZE_BORDER)
877    
878 jonathan 441 self.prop = ClassGroupProperties(prop)
879 jonathan 415
880 jonathan 430 topBox = wxBoxSizer(wxVERTICAL)
881 jonathan 415
882 jonathan 430 itemBox = wxBoxSizer(wxHORIZONTAL)
883    
884     # preview box
885     previewBox = wxBoxSizer(wxVERTICAL)
886     previewBox.Add(wxStaticText(self, -1, _("Preview:")),
887     0, wxALIGN_LEFT | wxALL, 4)
888     self.previewer = ClassDataPreviewer(None, self.prop, shapeType,
889     self, ID_SELPROP_PREVIEW, (40, 40))
890     previewBox.Add(self.previewer, 1, wxGROW, 15)
891    
892     itemBox.Add(previewBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
893    
894     # control box
895     ctrlBox = wxBoxSizer(wxVERTICAL)
896 jonathan 485
897     lineColorBox = wxBoxSizer(wxHORIZONTAL)
898     lineColorBox.Add(
899 jonathan 460 wxButton(self, ID_SELPROP_STROKECLR, "Change Line Color"),
900 jonathan 485 1, wxALL | wxGROW, 4)
901 jonathan 460 EVT_BUTTON(self, ID_SELPROP_STROKECLR, self._OnChangeLineColor)
902 jonathan 430
903 jonathan 485 lineColorBox.Add(
904     wxButton(self, ID_SELPROP_STROKECLRTRANS, "Transparent"),
905     1, wxALL | wxGROW, 4)
906     EVT_BUTTON(self, ID_SELPROP_STROKECLRTRANS,
907     self._OnChangeLineColorTrans)
908    
909     ctrlBox.Add(lineColorBox, 0,
910     wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
911    
912 jonathan 430 if shapeType != SHAPETYPE_ARC:
913 jonathan 485 fillColorBox = wxBoxSizer(wxHORIZONTAL)
914     fillColorBox.Add(
915 jonathan 430 wxButton(self, ID_SELPROP_FILLCLR, "Change Fill Color"),
916 jonathan 485 1, wxALL | wxGROW, 4)
917 jonathan 460 EVT_BUTTON(self, ID_SELPROP_FILLCLR, self._OnChangeFillColor)
918 jonathan 485 fillColorBox.Add(
919     wxButton(self, ID_SELPROP_FILLCLRTRANS, "Transparent"),
920     1, wxALL | wxGROW, 4)
921     EVT_BUTTON(self, ID_SELPROP_FILLCLRTRANS,
922     self._OnChangeFillColorTrans)
923     ctrlBox.Add(fillColorBox, 0,
924     wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
925 jonathan 430
926 jonathan 415 spinBox = wxBoxSizer(wxHORIZONTAL)
927 jonathan 460 spinBox.Add(wxStaticText(self, -1, _("Line Width: ")),
928 jonathan 430 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
929 jonathan 415 self.spinCtrl = wxSpinCtrl(self, ID_SELPROP_SPINCTRL,
930     min=1, max=10,
931 jonathan 460 value=str(prop.GetLineWidth()),
932     initial=prop.GetLineWidth())
933 jonathan 415
934 jonathan 460 EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL, self._OnSpin)
935 jonathan 415
936     spinBox.Add(self.spinCtrl, 0, wxALIGN_LEFT | wxALL, 4)
937    
938 jonathan 430 ctrlBox.Add(spinBox, 0, wxALIGN_RIGHT | wxALL, 0)
939     itemBox.Add(ctrlBox, 0, wxALIGN_RIGHT | wxALL | wxGROW, 0)
940     topBox.Add(itemBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
941 jonathan 415
942     #
943     # Control buttons:
944     #
945     buttonBox = wxBoxSizer(wxHORIZONTAL)
946 jonathan 485 buttonBox.Add(wxButton(self, ID_SELPROP_OK, _("OK")),
947 jonathan 415 0, wxALL, 4)
948 jonathan 485 buttonBox.Add(wxButton(self, ID_SELPROP_CANCEL, _("Cancel")),
949 jonathan 415 0, wxALL, 4)
950     topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)
951    
952 jonathan 460 EVT_BUTTON(self, ID_SELPROP_OK, self._OnOK)
953     EVT_BUTTON(self, ID_SELPROP_CANCEL, self._OnCancel)
954 jonathan 415
955     self.SetAutoLayout(true)
956     self.SetSizer(topBox)
957     topBox.Fit(self)
958     topBox.SetSizeHints(self)
959    
960 jonathan 460 def _OnOK(self, event):
961 jonathan 372 self.EndModal(wxID_OK)
962    
963 jonathan 460 def _OnCancel(self, event):
964 jonathan 372 self.EndModal(wxID_CANCEL)
965    
966 jonathan 460 def _OnSpin(self, event):
967     self.prop.SetLineWidth(self.spinCtrl.GetValue())
968 jonathan 430 self.previewer.Refresh()
969 jonathan 392
970 jonathan 430 def __GetColor(self, cur):
971     dialog = wxColourDialog(self)
972     dialog.GetColourData().SetColour(Color2wxColour(cur))
973     ret = None
974     if dialog.ShowModal() == wxID_OK:
975     ret = wxColour2Color(dialog.GetColourData().GetColour())
976    
977     dialog.Destroy()
978    
979     return ret
980    
981 jonathan 460 def _OnChangeLineColor(self, event):
982     clr = self.__GetColor(self.prop.GetLineColor())
983 jonathan 430 if clr is not None:
984 jonathan 460 self.prop.SetLineColor(clr)
985 jonathan 430 self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
986    
987 jonathan 485 def _OnChangeLineColorTrans(self, event):
988     self.prop.SetLineColor(Color.None)
989     self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
990    
991 jonathan 460 def _OnChangeFillColor(self, event):
992 jonathan 430 clr = self.__GetColor(self.prop.GetFill())
993     if clr is not None:
994     self.prop.SetFill(clr)
995     self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
996    
997 jonathan 485 def _OnChangeFillColorTrans(self, event):
998     self.prop.SetFill(Color.None)
999     self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
1000    
1001 jonathan 441 def GetClassGroupProperties(self):
1002 jonathan 415 return self.prop
1003 jonathan 392
1004    
1005 jonathan 430 class ClassDataPreviewer(wxWindow):
1006 jonathan 415
1007 jonathan 441 def __init__(self, rect, prop, shapeType,
1008 jonathan 430 parent = None, id = -1, size = wxDefaultSize):
1009     if parent is not None:
1010     wxWindow.__init__(self, parent, id, size=size)
1011 jonathan 460 EVT_PAINT(self, self._OnPaint)
1012 jonathan 415
1013 jonathan 430 self.rect = rect
1014 jonathan 441 self.prop = prop
1015 jonathan 430 self.shapeType = shapeType
1016    
1017 jonathan 460 def _OnPaint(self, event):
1018 jonathan 430 dc = wxPaintDC(self)
1019    
1020     # XXX: this doesn't seem to be having an effect:
1021     dc.DestroyClippingRegion()
1022    
1023     self.Draw(dc, None)
1024    
1025 jonathan 441 def Draw(self, dc, rect, prop = None, shapeType = None):
1026 jonathan 430
1027 jonathan 441 if prop is None: prop = self.prop
1028 jonathan 430 if shapeType is None: shapeType = self.shapeType
1029    
1030     if rect is None:
1031     x = y = 0
1032     w, h = self.GetClientSizeTuple()
1033     else:
1034     x = rect.GetX()
1035     y = rect.GetY()
1036     w = rect.GetWidth()
1037     h = rect.GetHeight()
1038    
1039 jonathan 460 stroke = prop.GetLineColor()
1040 jonathan 415 if stroke is Color.None:
1041 jonathan 392 pen = wxTRANSPARENT_PEN
1042     else:
1043 jonathan 430 pen = wxPen(Color2wxColour(stroke),
1044 jonathan 460 prop.GetLineWidth(),
1045 jonathan 392 wxSOLID)
1046    
1047 jonathan 441 stroke = prop.GetFill()
1048 jonathan 415 if stroke is Color.None:
1049 jonathan 392 brush = wxTRANSPARENT_BRUSH
1050     else:
1051 jonathan 430 brush = wxBrush(Color2wxColour(stroke), wxSOLID)
1052 jonathan 392
1053     dc.SetPen(pen)
1054     dc.SetBrush(brush)
1055    
1056 jonathan 415 if shapeType == SHAPETYPE_ARC:
1057 jonathan 430 dc.DrawSpline([wxPoint(x, y + h),
1058     wxPoint(x + w/2, y + h/4),
1059     wxPoint(x + w/2, y + h/4*3),
1060     wxPoint(x + w, y)])
1061 jonathan 392
1062 jonathan 415 elif shapeType == SHAPETYPE_POINT or \
1063     shapeType == SHAPETYPE_POLYGON:
1064    
1065 jonathan 430 dc.DrawCircle(x + w/2, y + h/2,
1066 jonathan 460 (min(w, h) - prop.GetLineWidth())/2)
1067 jonathan 392
1068 jonathan 415 class ClassRenderer(wxPyGridCellRenderer):
1069    
1070     def __init__(self, shapeType):
1071     wxPyGridCellRenderer.__init__(self)
1072 jonathan 430 self.previewer = ClassDataPreviewer(None, None, shapeType)
1073 jonathan 415
1074     def Draw(self, grid, attr, dc, rect, row, col, isSelected):
1075 jonathan 485 data = grid.GetTable().GetClassGroup(row)
1076 jonathan 415
1077     dc.SetClippingRegion(rect.GetX(), rect.GetY(),
1078     rect.GetWidth(), rect.GetHeight())
1079     dc.SetPen(wxPen(wxLIGHT_GREY))
1080     dc.SetBrush(wxBrush(wxLIGHT_GREY, wxSOLID))
1081     dc.DrawRectangle(rect.GetX(), rect.GetY(),
1082     rect.GetWidth(), rect.GetHeight())
1083    
1084 jonathan 441 if not isinstance(data, ClassGroupMap):
1085     self.previewer.Draw(dc, rect, data.GetProperties())
1086 jonathan 415
1087     if isSelected:
1088     dc.SetPen(wxPen(wxColour(0 * 255, 0 * 255, 0 * 255),
1089     4, wxSOLID))
1090     dc.SetBrush(wxTRANSPARENT_BRUSH)
1091     dc.DrawRectangle(rect.GetX(), rect.GetY(),
1092     rect.GetWidth(), rect.GetHeight())
1093    
1094 jonathan 392 dc.DestroyClippingRegion()
1095    

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26