/[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 485 - (show 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 # 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 self.SetTable(ClassTable(clazz, self.shapeType, self), true)
89 else:
90 table.Reset(clazz, self.shapeType)
91
92 self.SetSelectionMode(wxGrid.wxGridSelectRows)
93 self.ClearSelection()
94
95 def GetCurrentSelection(self):
96 """Return the currently highlighted rows as an increasing list
97 of row numbers."""
98 sel = copy.copy(self.currentSelection)
99 sel.sort()
100 return sel
101
102 def SetCellRenderer(self, row, col):
103 raise ValueError(_("Must not allow setting of renderer in ClassGrid!"))
104
105 #
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 def SetTable(self, object, *attributes):
112 self.tableRef = weakref.ref(object)
113 return wxGrid.SetTable(self, object, *attributes)
114
115 def GetTable(self):
116 try:
117 return self.tableRef()
118 except:
119 return None
120
121 def DeleteSelectedRows(self):
122 """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 sel = self.GetCurrentSelection()
128
129 # 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 if len(sel) == 1:
135 #group = self.GetTable().GetValueAsCustom(sel[0], COL_SYMBOL, None)
136 group = self.GetTable().GetClassGroup(sel[0])
137 if isinstance(group, ClassGroupDefault):
138 wxMessageDialog(self,
139 "The Default group cannot be removed.",
140 style = wxOK | wxICON_EXCLAMATION).ShowModal()
141 return
142
143
144 self.ClearSelection()
145
146 # 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 sel.reverse()
149
150 #
151 # actually remove the rows
152 #
153 table = self.GetTable()
154 for row in sel:
155 table.DeleteRows(row)
156
157 #
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 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 def _OnCellDClick(self, event):
180 """Handle a double on a cell."""
181
182 r = event.GetRow()
183 c = event.GetCol()
184 if c == COL_SYMBOL:
185 prop = self.GetTable().GetValueAsCustom(r, c, None)
186 #prop = group.GetProperties()
187
188 # get a new ClassGroupProperties object and copy the
189 # values over to our current object
190 propDlg = SelectPropertiesDialog(NULL, prop, self.shapeType)
191 if propDlg.ShowModal() == wxID_OK:
192 new_prop = propDlg.GetClassGroupProperties()
193 #prop.SetProperties(new_prop)
194 self.GetTable().SetValueAsCustom(r, c, None, new_prop)
195 propDlg.Destroy()
196
197 #
198 # _OnSelectedRange() and _OnSelectedCell() were borrowed
199 # from http://wiki.wxpython.org to keep track of which
200 # cells are currently highlighted
201 #
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 class ClassTable(wxPyGridTableBase):
223 """Represents the underlying data structure for the grid."""
224
225 NUM_COLS = 3
226
227 __col_labels = [_("Symbol"), _("Value"), _("Label")]
228
229 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 wxPyGridTableBase.__init__(self)
238
239 self.SetView(view)
240 self.tdata = []
241
242 self.Reset(clazz, shapeType)
243
244 def Reset(self, clazz, shapeType):
245 """Reset the table with the given data.
246
247 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 self.GetView().BeginBatch()
261
262 self.fieldType = clazz.GetFieldType()
263 self.shapeType = shapeType
264
265 old_len = len(self.tdata)
266
267 self.tdata = []
268
269 #
270 # copy the data out of the classification and into our
271 # array
272 #
273 for p in clazz:
274 np = copy.deepcopy(p)
275 self.__SetRow(-1, np)
276
277
278 self.__Modified(-1)
279
280 self.__NotifyRowChanges(old_len, len(self.tdata))
281
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 self.GetView().EndBatch()
289
290 def __NotifyRowChanges(self, curRows, newRows):
291 #
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 def __SetRow(self, row, group):
308 """Set a row's data to that of the group.
309
310 The table is considered modified after this operation.
311
312 row -- if row is -1 or greater than the current number of rows
313 then group is appended to the end.
314 """
315
316 # either append or replace
317 if row == -1 or row >= self.GetNumberRows():
318 self.tdata.append(group)
319 else:
320 self.tdata[row] = group
321
322 self.__Modified()
323
324 def GetColLabelValue(self, col):
325 """Return the label for the given column."""
326 return self.__col_labels[col]
327
328 def GetRowLabelValue(self, row):
329 """Return the label for the given row."""
330
331 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 def GetNumberRows(self):
341 """Return the number of rows."""
342 return len(self.tdata)
343
344 def GetNumberCols(self):
345 """Return the number of columns."""
346 return self.NUM_COLS
347
348 def IsEmptyCell(self, row, col):
349 """Determine if a cell is empty. This is always false."""
350 return False
351
352 def GetValue(self, row, col):
353 """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
357 def SetValue(self, row, col, value):
358 """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 self.__Modified()
365
366 def GetValueAsCustom(self, row, col, typeName):
367 """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
373 group = self.tdata[row]
374
375 if col == COL_SYMBOL:
376 return group.GetProperties()
377
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 def __ParseInput(self, value):
395 """Try to determine what kind of input value is
396 (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 """
401
402 type = self.fieldType
403
404 if type == FIELDTYPE_STRING:
405 return (value,)
406 elif type == FIELDTYPE_INT or type == FIELDTYPE_DOUBLE:
407
408 if type == FIELDTYPE_INT:
409 conv = lambda p: int(float(p))
410 else:
411 conv = lambda p: p
412
413 #
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 return (conv(Str2Num(value)),)
424 except ValueError:
425 i = value.find('-')
426 if i == 0:
427 i = value.find('-', 1)
428
429 return (conv(Str2Num(value[:i])), conv(Str2Num(value[i+1:])))
430
431 assert(False) # shouldn't get here
432 return (0,)
433
434
435 def SetValueAsCustom(self, row, col, typeName, value):
436 """Set the cell specified by 'row' and 'col' to 'value'.
437
438 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 mod = True # assume the data will change
453
454 if col == COL_SYMBOL:
455 group.SetProperties(value)
456 elif col == COL_LABEL:
457 group.SetLabel(value)
458 elif col == COL_VALUE:
459 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 except ValueError:
469 # bad input, ignore the request
470 mod = False
471 else:
472
473 changed = False
474 ngroup = group
475 props = group.GetProperties()
476
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 if len(dataInfo) == 1:
483 if not isinstance(group, ClassGroupSingleton):
484 ngroup = ClassGroupSingleton(prop = props)
485 changed = True
486 ngroup.SetValue(dataInfo[0])
487 elif len(dataInfo) == 2:
488 if not isinstance(group, ClassGroupRange):
489 ngroup = ClassGroupRange(prop = props)
490 changed = True
491 ngroup.SetRange(dataInfo[0], dataInfo[1])
492 else:
493 assert(False)
494 pass
495
496 if changed:
497 ngroup.SetLabel(group.GetLabel())
498 self.SetClassGroup(row, ngroup)
499 else:
500 assert(False) # shouldn't be here
501 pass
502
503 if mod:
504 self.__Modified()
505 self.GetView().Refresh()
506
507 def GetAttr(self, row, col, someExtraParameter):
508 """Returns the cell attributes"""
509
510 attr = wxGridCellAttr()
511 #attr = wxPyGridTableBase.GetAttr(self, row, col, someExtraParameter)
512
513 if col == COL_SYMBOL:
514 # we need to create a new renderer each time, because
515 # SetRenderer takes control of the parameter
516 attr.SetRenderer(ClassRenderer(self.shapeType))
517 attr.SetReadOnly()
518
519 return attr
520
521 def GetClassGroup(self, row):
522 """Return the ClassGroup object representing row 'row'."""
523
524 return self.tdata[row] # self.GetValueAsCustom(row, COL_SYMBOL, None)
525
526 def SetClassGroup(self, row, group):
527 self.__SetRow(row, group)
528 self.GetView().Refresh()
529
530 def __Modified(self, mod = True):
531 """Adjust the modified flag.
532
533 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 def IsModified(self):
544 """True if this table is considered modified."""
545 return self.modified
546
547 def DeleteRows(self, pos, numRows = 1):
548 """Deletes 'numRows' beginning at row 'pos'.
549
550 The row representing the default group is not removed.
551
552 The table is considered modified if any rows are removed.
553 """
554
555 assert(pos >= 0)
556 old_len = len(self.tdata)
557 for row in range(pos, pos - numRows, -1):
558 group = self.GetClassGroup(row)
559 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
566 def AppendRows(self, numRows = 1):
567 """Append 'numRows' empty rows to the end of the table.
568
569 The table is considered modified if any rows are appended.
570 """
571
572 old_len = len(self.tdata)
573 for i in range(numRows):
574 np = ClassGroupSingleton()
575 self.__SetRow(-1, np)
576
577 if self.IsModified():
578 self.__NotifyRowChanges(old_len, len(self.tdata))
579
580
581 class Classifier(NonModalDialog):
582
583 def __init__(self, parent, interactor, name, layer):
584 NonModalDialog.__init__(self, parent, interactor, name,
585 _("Classifier: %s") % layer.Title())
586
587 self.layer = layer
588
589 self.originalClass = self.layer.GetClassification()
590 field = self.originalClass.GetField()
591 fieldType = self.originalClass.GetFieldType()
592
593 topBox = wxBoxSizer(wxVERTICAL)
594
595 #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 0, wxALIGN_LEFT | wxALL, 4)
600
601
602 #
603 # make field combo box
604 #
605 self.fields = wxComboBox(self, ID_PROPERTY_SELECT, "",
606 style = wxCB_READONLY)
607
608 self.num_cols = layer.table.field_count()
609 # just assume the first field in case one hasn't been
610 # specified in the file.
611 self.__cur_field = 0
612
613 self.fields.Append("<None>")
614 self.fields.SetClientData(0, None)
615
616 for i in range(self.num_cols):
617 type, name, len, decc = layer.table.field_info(i)
618 self.fields.Append(name)
619
620 if name == field:
621 self.__cur_field = i + 1
622 self.fields.SetClientData(i + 1, self.originalClass)
623 else:
624 self.fields.SetClientData(i + 1, None)
625
626 self.fields.SetSelection(self.__cur_field)
627
628 #
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 propertyBox.Add(self.fields, 1, wxGROW|wxALL, 4)
642 EVT_COMBOBOX(self, ID_PROPERTY_SELECT, self._OnFieldSelect)
643
644 topBox.Add(propertyBox, 0, wxGROW, 4)
645
646 #
647 # Classification data table
648 #
649
650 controlBox = wxBoxSizer(wxHORIZONTAL)
651
652 self.classGrid = ClassGrid(self)
653 self.__SetGridTable(self.__cur_field)
654 print self.classGrid.GetSizeTuple()
655
656 controlBox.Add(self.classGrid, 1, wxGROW, 0)
657
658 controlButtonBox = wxBoxSizer(wxVERTICAL)
659
660 #
661 # Control buttons:
662 #
663 self.controlButtons = []
664
665 button = wxButton(self, ID_CLASSIFY_ADD, _("Add"))
666 controlButtonBox.Add(button, 0, wxGROW | wxALL, 4)
667 self.controlButtons.append(button)
668
669 #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 controlBox.Add(controlButtonBox, 0, wxGROW, 10)
688 topBox.Add(controlBox, 1, wxGROW, 10)
689
690 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
696 buttonBox = wxBoxSizer(wxHORIZONTAL)
697 buttonBox.Add(wxButton(self, ID_CLASSIFY_OK, _("OK")),
698 0, wxALL, 4)
699 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 buttonBox.Add(wxButton(self, ID_CLASSIFY_CANCEL, _("Cancel")),
704 0, wxALL, 4)
705 topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)
706
707 EVT_BUTTON(self, ID_CLASSIFY_OK, self._OnOK)
708 EVT_BUTTON(self, ID_CLASSIFY_APPLY, self._OnApply)
709 EVT_BUTTON(self, ID_CLASSIFY_CANCEL, self._OnCancel)
710
711 self.SetAutoLayout(true)
712 self.SetSizer(topBox)
713 topBox.Fit(self)
714 topBox.SetSizeHints(self)
715
716
717 def __BuildClassification(self, fieldIndex):
718
719 clazz = Classification()
720 fieldName = self.fields.GetString(fieldIndex)
721 fieldType = self.layer.GetFieldType(fieldName)
722
723 clazz.SetField(fieldName)
724 clazz.SetFieldType(fieldType)
725
726 numRows = self.classGrid.GetNumberRows()
727
728 assert(numRows > 0) # there should always be a default row
729
730 table = self.classGrid.GetTable()
731 clazz.SetDefaultGroup(table.GetClassGroup(0))
732
733 for i in range(1, numRows):
734 clazz.AddGroup(table.GetClassGroup(i))
735
736 return clazz
737
738 def __SetGridTable(self, fieldIndex):
739
740 clazz = self.fields.GetClientData(fieldIndex)
741
742 if clazz is None:
743 clazz = Classification()
744 clazz.SetDefaultGroup(
745 ClassGroupDefault(
746 self.layer.GetClassification().
747 GetDefaultGroup().GetProperties()))
748
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 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 def _OnFieldSelect(self, event):
774 clazz = self.__BuildClassification(self.__cur_field)
775 self.fields.SetClientData(self.__cur_field, clazz)
776
777 self.__cur_field = self.fields.GetSelection()
778 self.__SetGridTable(self.__cur_field)
779
780 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 """Put the data from the table into a new Classification and hand
790 it to the layer.
791 """
792
793 clazz = self.fields.GetClientData(self.__cur_field)
794
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 clazz = self.__BuildClassification(self.__cur_field)
801
802 self.layer.SetClassification(clazz)
803
804 def _OnOK(self, event):
805 self._OnApply(event)
806 self.OnClose(event)
807
808 def _OnCancel(self, event):
809 """The layer's current classification stays the same."""
810 self.layer.SetClassification(self.originalClass)
811 self.OnClose(event)
812
813 def _OnAdd(self, event):
814 self.classGrid.AppendRows()
815
816 def _OnRemove(self, event):
817 self.classGrid.DeleteSelectedRows()
818
819 def _OnGenRange(self, event):
820 print "Classifier._OnGenRange()"
821
822 def _OnMoveUp(self, event):
823 sel = self.classGrid.GetCurrentSelection()
824
825 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 ID_SELPROP_OK = 4001
852 ID_SELPROP_CANCEL = 4002
853 ID_SELPROP_SPINCTRL = 4002
854 ID_SELPROP_PREVIEW = 4003
855 ID_SELPROP_STROKECLR = 4004
856 ID_SELPROP_FILLCLR = 4005
857 ID_SELPROP_STROKECLRTRANS = 4006
858 ID_SELPROP_FILLCLRTRANS = 4007
859
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 self.prop = ClassGroupProperties(prop)
867
868 topBox = wxBoxSizer(wxVERTICAL)
869
870 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
885 lineColorBox = wxBoxSizer(wxHORIZONTAL)
886 lineColorBox.Add(
887 wxButton(self, ID_SELPROP_STROKECLR, "Change Line Color"),
888 1, wxALL | wxGROW, 4)
889 EVT_BUTTON(self, ID_SELPROP_STROKECLR, self._OnChangeLineColor)
890
891 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 if shapeType != SHAPETYPE_ARC:
901 fillColorBox = wxBoxSizer(wxHORIZONTAL)
902 fillColorBox.Add(
903 wxButton(self, ID_SELPROP_FILLCLR, "Change Fill Color"),
904 1, wxALL | wxGROW, 4)
905 EVT_BUTTON(self, ID_SELPROP_FILLCLR, self._OnChangeFillColor)
906 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
914 spinBox = wxBoxSizer(wxHORIZONTAL)
915 spinBox.Add(wxStaticText(self, -1, _("Line Width: ")),
916 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
917 self.spinCtrl = wxSpinCtrl(self, ID_SELPROP_SPINCTRL,
918 min=1, max=10,
919 value=str(prop.GetLineWidth()),
920 initial=prop.GetLineWidth())
921
922 EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL, self._OnSpin)
923
924 spinBox.Add(self.spinCtrl, 0, wxALIGN_LEFT | wxALL, 4)
925
926 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
930 #
931 # Control buttons:
932 #
933 buttonBox = wxBoxSizer(wxHORIZONTAL)
934 buttonBox.Add(wxButton(self, ID_SELPROP_OK, _("OK")),
935 0, wxALL, 4)
936 buttonBox.Add(wxButton(self, ID_SELPROP_CANCEL, _("Cancel")),
937 0, wxALL, 4)
938 topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)
939
940 EVT_BUTTON(self, ID_SELPROP_OK, self._OnOK)
941 EVT_BUTTON(self, ID_SELPROP_CANCEL, self._OnCancel)
942
943 self.SetAutoLayout(true)
944 self.SetSizer(topBox)
945 topBox.Fit(self)
946 topBox.SetSizeHints(self)
947
948 def _OnOK(self, event):
949 self.EndModal(wxID_OK)
950
951 def _OnCancel(self, event):
952 self.EndModal(wxID_CANCEL)
953
954 def _OnSpin(self, event):
955 self.prop.SetLineWidth(self.spinCtrl.GetValue())
956 self.previewer.Refresh()
957
958 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 def _OnChangeLineColor(self, event):
970 clr = self.__GetColor(self.prop.GetLineColor())
971 if clr is not None:
972 self.prop.SetLineColor(clr)
973 self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
974
975 def _OnChangeLineColorTrans(self, event):
976 self.prop.SetLineColor(Color.None)
977 self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
978
979 def _OnChangeFillColor(self, event):
980 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 def _OnChangeFillColorTrans(self, event):
986 self.prop.SetFill(Color.None)
987 self.previewer.Refresh() # XXX: work around, see ClassDataPreviewer
988
989 def GetClassGroupProperties(self):
990 return self.prop
991
992
993 class ClassDataPreviewer(wxWindow):
994
995 def __init__(self, rect, prop, shapeType,
996 parent = None, id = -1, size = wxDefaultSize):
997 if parent is not None:
998 wxWindow.__init__(self, parent, id, size=size)
999 EVT_PAINT(self, self._OnPaint)
1000
1001 self.rect = rect
1002 self.prop = prop
1003 self.shapeType = shapeType
1004
1005 def _OnPaint(self, event):
1006 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 def Draw(self, dc, rect, prop = None, shapeType = None):
1014
1015 if prop is None: prop = self.prop
1016 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 stroke = prop.GetLineColor()
1028 if stroke is Color.None:
1029 pen = wxTRANSPARENT_PEN
1030 else:
1031 pen = wxPen(Color2wxColour(stroke),
1032 prop.GetLineWidth(),
1033 wxSOLID)
1034
1035 stroke = prop.GetFill()
1036 if stroke is Color.None:
1037 brush = wxTRANSPARENT_BRUSH
1038 else:
1039 brush = wxBrush(Color2wxColour(stroke), wxSOLID)
1040
1041 dc.SetPen(pen)
1042 dc.SetBrush(brush)
1043
1044 if shapeType == SHAPETYPE_ARC:
1045 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
1050 elif shapeType == SHAPETYPE_POINT or \
1051 shapeType == SHAPETYPE_POLYGON:
1052
1053 dc.DrawCircle(x + w/2, y + h/2,
1054 (min(w, h) - prop.GetLineWidth())/2)
1055
1056 class ClassRenderer(wxPyGridCellRenderer):
1057
1058 def __init__(self, shapeType):
1059 wxPyGridCellRenderer.__init__(self)
1060 self.previewer = ClassDataPreviewer(None, None, shapeType)
1061
1062 def Draw(self, grid, attr, dc, rect, row, col, isSelected):
1063 data = grid.GetTable().GetClassGroup(row)
1064
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 if not isinstance(data, ClassGroupMap):
1073 self.previewer.Draw(dc, rect, data.GetProperties())
1074
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 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