/[thuban]/trunk/thuban/Thuban/UI/classifier.py
ViewVC logotype

Contents of /trunk/thuban/Thuban/UI/classifier.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 549 - (show annotations)
Thu Mar 20 09:45:07 2003 UTC (21 years, 11 months ago) by jonathan
File MIME type: text/x-python
File size: 36517 byte(s)
 Remember if any changes have actually
        been applied so that if the dialog is cancelled without an application
        of changes we don't have to set a new classification.
(ClassDataPreviewer): Pulled out the window specific code and put it
        ClassDataPreviewWindow. ClassDataPreviewer can then be used to draw
        symbols on any DC.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26