/[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 606 - (show annotations)
Fri Apr 4 12:16:39 2003 UTC (21 years, 11 months ago) by jonathan
File MIME type: text/x-python
File size: 40757 byte(s)
Fix assert calls.
(ClassGrid._OnCellDClick): Call up to the classifier to open the
        dialog to edit the groups properties.
(ClassGrid._OnCellResize): Make sure that the scollbars are drawn
        correctly if a cell is resized.
(ClassTable.SetClassification): New. Changes the classification
        that is in the table.
(ClassTable.__SetRow): Allow groups to be prepended.
(Classifier): New code for opening the EditProperties and
        GenerateRanges dialogs.
(SelectPropertiesDialog.__GetColor): Only set the color in the
        color dialog if the current color is not None.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26