/[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 485 - (hide annotations)
Fri Mar 7 18:20:31 2003 UTC (22 years ago) by jonathan
File MIME type: text/x-python
File size: 35596 byte(s)
Setting and Getting table values now
        uses a consistent set of functions.
(Classifier): Now non-modal. Has field type label which changes
        as the field changes. Keep track of buttons in a list so that
        we can enable/disable the buttons when the None field is selected.
(SelectPropertiesDialog): Add buttons to make the colors transparent.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26