/[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 611 - (show annotations)
Fri Apr 4 16:34:46 2003 UTC (21 years, 11 months ago) by jonathan
File MIME type: text/x-python
File size: 40941 byte(s)
(Classifier.__SelectField): Move the
    call to SetSelection out of the method and before the call
    to __SelectField in __init__. This prevents a recursion of events
    when _OnFieldSelect is triggered by the user.

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.fields.SetSelection(self.__cur_field)
784 self.__SelectField(self.__cur_field, group = group)
785
786 #self.classGrid.SelectGroup(group)
787
788 controlBox.Add(self.classGrid, 1, wxGROW, 0)
789
790
791
792 controlBox.Add(controlButtonBox, 0, wxGROW, 10)
793 panelBox.Add(controlBox, 1, wxGROW, 10)
794
795 EVT_BUTTON(self, ID_CLASSIFY_ADD, self._OnAdd)
796 EVT_BUTTON(self, ID_CLASSIFY_EDITPROPS, self._OnEditGroupProperties)
797 EVT_BUTTON(self, ID_CLASSIFY_REMOVE, self._OnRemove)
798 EVT_BUTTON(self, ID_CLASSIFY_GENCLASS, self._OnGenClass)
799 EVT_BUTTON(self, ID_CLASSIFY_MOVEUP, self._OnMoveUp)
800 EVT_BUTTON(self, ID_CLASSIFY_MOVEDOWN, self._OnMoveDown)
801
802 ###########
803
804 buttonBox = wxBoxSizer(wxHORIZONTAL)
805 buttonBox.Add(wxButton(panel, ID_CLASSIFY_OK, _("OK")),
806 0, wxALL, 4)
807 buttonBox.Add(60, 20, 0, wxALL, 4)
808 buttonBox.Add(wxButton(panel, ID_CLASSIFY_APPLY, _("Apply")),
809 0, wxALL, 4)
810 buttonBox.Add(60, 20, 0, wxALL, 4)
811 buttonBox.Add(wxButton(panel, ID_CLASSIFY_CANCEL, _("Cancel")),
812 0, wxALL, 4)
813 panelBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 0)
814
815 EVT_BUTTON(self, ID_CLASSIFY_OK, self._OnOK)
816 EVT_BUTTON(self, ID_CLASSIFY_APPLY, self._OnApply)
817 EVT_BUTTON(self, ID_CLASSIFY_CANCEL, self._OnCancel)
818
819 ###########
820
821
822 panel.SetAutoLayout(True)
823 panel.SetSizer(panelBox)
824 panelBox.SetSizeHints(panel)
825
826 topBox.Add(panel, 1, wxGROW, 0)
827 panelBox.SetSizeHints(self)
828 self.SetAutoLayout(True)
829 self.SetSizer(topBox)
830
831 #print "1------------------"
832 #self.Fit()
833 ######################
834
835 self.haveApplied = False
836
837 def EditGroupProperties(self, row):
838 table = self.classGrid.GetTable()
839 prop = table.GetValueAsCustom(row, COL_SYMBOL, None)
840
841 # get a new ClassGroupProperties object and copy the
842 # values over to our current object
843 propDlg = SelectPropertiesDialog(NULL, prop, self.layer.ShapeType())
844 if propDlg.ShowModal() == wxID_OK:
845 new_prop = propDlg.GetClassGroupProperties()
846 table.SetValueAsCustom(row, COL_SYMBOL, None, new_prop)
847 propDlg.Destroy()
848
849
850 def __BuildClassification(self, fieldIndex):
851
852 numRows = self.classGrid.GetNumberRows()
853 assert numRows > 0 # there should always be a default row
854
855 clazz = Classification()
856 if fieldIndex == 0:
857 fieldName = None
858 fieldType = None
859 else:
860 fieldName = self.fields.GetString(fieldIndex)
861 fieldType = self.layer.GetFieldType(fieldName)
862
863 clazz.SetField(fieldName)
864 clazz.SetFieldType(fieldType)
865
866
867 table = self.classGrid.GetTable()
868 clazz.SetDefaultGroup(table.GetClassGroup(0))
869
870 for i in range(1, numRows):
871 clazz.AddGroup(table.GetClassGroup(i))
872
873 return clazz
874
875 def __SetGridTable(self, fieldIndex, group = None):
876
877 clazz = self.fields.GetClientData(fieldIndex)
878
879 if clazz is None:
880 clazz = Classification()
881 clazz.SetDefaultGroup(
882 ClassGroupDefault(
883 self.layer.GetClassification().
884 GetDefaultGroup().GetProperties()))
885
886 fieldName = self.fields.GetString(fieldIndex)
887 fieldType = self.layer.GetFieldType(fieldName)
888 clazz.SetFieldType(fieldType)
889
890 #print "6------------------"
891 self.classGrid.CreateTable(clazz, self.layer.ShapeType(), group)
892 #print "7------------------"
893
894
895
896 type2string = {None: _("None"),
897 FIELDTYPE_STRING: _("Text"),
898 FIELDTYPE_INT: _("Integer"),
899 FIELDTYPE_DOUBLE: _("Decimal")}
900
901 def __SetFieldTypeText(self, fieldIndex):
902 fieldName = self.fields.GetString(fieldIndex)
903 fieldType = self.layer.GetFieldType(fieldName)
904
905 assert Classifier.type2string.has_key(fieldType)
906
907 text = Classifier.type2string[fieldType]
908
909 self.fieldTypeText.SetLabel(_("Field Type: %s") % text)
910
911 def __SelectField(self, newIndex, oldIndex = -1, group = None):
912 """This method assumes that the current selection for the
913 combo has already been set by a call to SetSelection().
914 """
915
916 #print "3------------------"
917
918 assert oldIndex >= -1
919
920 if oldIndex != -1:
921 clazz = self.__BuildClassification(oldIndex)
922 self.fields.SetClientData(oldIndex, clazz)
923
924 #print "4------------------"
925 self.__SetGridTable(newIndex, group)
926 #print "5------------------"
927
928 enabled = newIndex != 0
929
930 for b in self.controlButtons:
931 b.Enable(enabled)
932
933 self.__SetFieldTypeText(newIndex)
934
935
936 def _OnEditGroupProperties(self, event):
937 sel = self.classGrid.GetCurrentSelection()
938
939 if len(sel) == 1:
940 self.EditGroupProperties(sel[0])
941
942 def _OnFieldSelect(self, event):
943 index = self.fields.GetSelection()
944 self.__SelectField(index, self.__cur_field)
945 self.__cur_field = index
946
947 def _OnApply(self, event):
948 """Put the data from the table into a new Classification and hand
949 it to the layer.
950 """
951
952 clazz = self.fields.GetClientData(self.__cur_field)
953
954 #
955 # only build the classification if there wasn't one to
956 # to begin with or it has been modified
957 #
958 if clazz is None or self.classGrid.GetTable().IsModified():
959 clazz = self.__BuildClassification(self.__cur_field)
960
961 self.layer.SetClassification(clazz)
962
963 self.haveApplied = True
964
965 def _OnOK(self, event):
966 self._OnApply(event)
967 self.OnClose(event)
968
969 def _OnCancel(self, event):
970 """The layer's current classification stays the same."""
971 if self.haveApplied:
972 self.layer.SetClassification(self.originalClass)
973
974 self.OnClose(event)
975
976 def _OnAdd(self, event):
977 self.classGrid.AppendRows()
978
979 def _OnRemove(self, event):
980 self.classGrid.DeleteSelectedRows()
981
982 def _OnGenClass(self, event):
983
984 genDlg = ClassGenDialog(self,
985 self.layer.table,
986 self.fields.GetString(self.__cur_field))
987
988 if genDlg.ShowModal() == wxID_OK:
989 clazz = genDlg.GetClassification()
990 self.fields.SetClientData(self.__cur_field, clazz)
991 self.classGrid.GetTable().SetClassification(clazz)
992 genDlg.Destroy()
993
994 def _OnMoveUp(self, event):
995 sel = self.classGrid.GetCurrentSelection()
996
997 #print "sel: ", sel
998
999 if len(sel) == 1:
1000 i = sel[0]
1001 if i > 1:
1002 table = self.classGrid.GetTable()
1003 x = table.GetClassGroup(i - 1)
1004 y = table.GetClassGroup(i)
1005 table.SetClassGroup(i - 1, y)
1006 table.SetClassGroup(i, x)
1007 self.classGrid.ClearSelection()
1008 self.classGrid.SelectRow(i - 1)
1009 self.classGrid.MakeCellVisible(i - 1, 0)
1010
1011 def _OnMoveDown(self, event):
1012 sel = self.classGrid.GetCurrentSelection()
1013
1014 if len(sel) == 1:
1015 i = sel[0]
1016 table = self.classGrid.GetTable()
1017 if 0 < i < table.GetNumberRows() - 1:
1018 x = table.GetClassGroup(i)
1019 y = table.GetClassGroup(i + 1)
1020 table.SetClassGroup(i, y)
1021 table.SetClassGroup(i + 1, x)
1022 self.classGrid.ClearSelection()
1023 self.classGrid.SelectRow(i + 1)
1024 self.classGrid.MakeCellVisible(i + 1, 0)
1025
1026
1027 ID_SELPROP_OK = 4001
1028 ID_SELPROP_CANCEL = 4002
1029 ID_SELPROP_SPINCTRL = 4002
1030 ID_SELPROP_PREVIEW = 4003
1031 ID_SELPROP_STROKECLR = 4004
1032 ID_SELPROP_FILLCLR = 4005
1033 ID_SELPROP_STROKECLRTRANS = 4006
1034 ID_SELPROP_FILLCLRTRANS = 4007
1035
1036 class SelectPropertiesDialog(wxDialog):
1037
1038 def __init__(self, parent, prop, shapeType):
1039 wxDialog.__init__(self, parent, -1, _("Select Properties"),
1040 style = wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
1041
1042 self.prop = ClassGroupProperties(prop)
1043
1044 topBox = wxBoxSizer(wxVERTICAL)
1045
1046 itemBox = wxBoxSizer(wxHORIZONTAL)
1047
1048 # preview box
1049 previewBox = wxBoxSizer(wxVERTICAL)
1050 previewBox.Add(wxStaticText(self, -1, _("Preview:")),
1051 0, wxALIGN_LEFT | wxALL, 4)
1052 self.previewWin = ClassDataPreviewWindow(None, self.prop, shapeType,
1053 self, ID_SELPROP_PREVIEW, (40, 40))
1054 previewBox.Add(self.previewWin, 1, wxGROW, 15)
1055
1056 itemBox.Add(previewBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
1057
1058 # control box
1059 ctrlBox = wxBoxSizer(wxVERTICAL)
1060
1061 lineColorBox = wxBoxSizer(wxHORIZONTAL)
1062 lineColorBox.Add(
1063 wxButton(self, ID_SELPROP_STROKECLR, _("Change Line Color")),
1064 1, wxALL | wxGROW, 4)
1065 EVT_BUTTON(self, ID_SELPROP_STROKECLR, self._OnChangeLineColor)
1066
1067 lineColorBox.Add(
1068 wxButton(self, ID_SELPROP_STROKECLRTRANS, _("Transparent")),
1069 1, wxALL | wxGROW, 4)
1070 EVT_BUTTON(self, ID_SELPROP_STROKECLRTRANS,
1071 self._OnChangeLineColorTrans)
1072
1073 ctrlBox.Add(lineColorBox, 0,
1074 wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
1075
1076 if shapeType != SHAPETYPE_ARC:
1077 fillColorBox = wxBoxSizer(wxHORIZONTAL)
1078 fillColorBox.Add(
1079 wxButton(self, ID_SELPROP_FILLCLR, _("Change Fill Color")),
1080 1, wxALL | wxGROW, 4)
1081 EVT_BUTTON(self, ID_SELPROP_FILLCLR, self._OnChangeFillColor)
1082 fillColorBox.Add(
1083 wxButton(self, ID_SELPROP_FILLCLRTRANS, _("Transparent")),
1084 1, wxALL | wxGROW, 4)
1085 EVT_BUTTON(self, ID_SELPROP_FILLCLRTRANS,
1086 self._OnChangeFillColorTrans)
1087 ctrlBox.Add(fillColorBox, 0,
1088 wxALIGN_CENTER_HORIZONTAL | wxALL | wxGROW, 4)
1089
1090 spinBox = wxBoxSizer(wxHORIZONTAL)
1091 spinBox.Add(wxStaticText(self, -1, _("Line Width: ")),
1092 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxALL, 4)
1093 self.spinCtrl = wxSpinCtrl(self, ID_SELPROP_SPINCTRL,
1094 min=1, max=10,
1095 value=str(prop.GetLineWidth()),
1096 initial=prop.GetLineWidth())
1097
1098 EVT_SPINCTRL(self, ID_SELPROP_SPINCTRL, self._OnSpin)
1099
1100 spinBox.Add(self.spinCtrl, 0, wxALIGN_LEFT | wxALL, 4)
1101
1102 ctrlBox.Add(spinBox, 0, wxALIGN_RIGHT | wxALL, 0)
1103 itemBox.Add(ctrlBox, 0, wxALIGN_RIGHT | wxALL | wxGROW, 0)
1104 topBox.Add(itemBox, 1, wxALIGN_LEFT | wxALL | wxGROW, 0)
1105
1106 #
1107 # Control buttons:
1108 #
1109 buttonBox = wxBoxSizer(wxHORIZONTAL)
1110 buttonBox.Add(wxButton(self, ID_SELPROP_OK, _("OK")),
1111 0, wxALL, 4)
1112 buttonBox.Add(wxButton(self, ID_SELPROP_CANCEL, _("Cancel")),
1113 0, wxALL, 4)
1114 topBox.Add(buttonBox, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM, 10)
1115
1116 EVT_BUTTON(self, ID_SELPROP_OK, self._OnOK)
1117 EVT_BUTTON(self, ID_SELPROP_CANCEL, self._OnCancel)
1118
1119 self.SetAutoLayout(True)
1120 self.SetSizer(topBox)
1121 topBox.Fit(self)
1122 topBox.SetSizeHints(self)
1123
1124 def _OnOK(self, event):
1125 self.EndModal(wxID_OK)
1126
1127 def _OnCancel(self, event):
1128 self.EndModal(wxID_CANCEL)
1129
1130 def _OnSpin(self, event):
1131 self.prop.SetLineWidth(self.spinCtrl.GetValue())
1132 self.previewWin.Refresh()
1133
1134 def __GetColor(self, cur):
1135 dialog = wxColourDialog(self)
1136 if cur is not Color.Transparent:
1137 dialog.GetColourData().SetColour(Color2wxColour(cur))
1138
1139 ret = None
1140 if dialog.ShowModal() == wxID_OK:
1141 ret = wxColour2Color(dialog.GetColourData().GetColour())
1142
1143 dialog.Destroy()
1144
1145 return ret
1146
1147 def _OnChangeLineColor(self, event):
1148 clr = self.__GetColor(self.prop.GetLineColor())
1149 if clr is not None:
1150 self.prop.SetLineColor(clr)
1151 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1152
1153 def _OnChangeLineColorTrans(self, event):
1154 self.prop.SetLineColor(Color.Transparent)
1155 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1156
1157 def _OnChangeFillColor(self, event):
1158 clr = self.__GetColor(self.prop.GetFill())
1159 if clr is not None:
1160 self.prop.SetFill(clr)
1161 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1162
1163 def _OnChangeFillColorTrans(self, event):
1164 self.prop.SetFill(Color.Transparent)
1165 self.previewWin.Refresh() # XXX: work around, see ClassDataPreviewer
1166
1167 def GetClassGroupProperties(self):
1168 return self.prop
1169
1170
1171 class ClassDataPreviewWindow(wxWindow):
1172
1173 def __init__(self, rect, prop, shapeType,
1174 parent = None, id = -1, size = wxDefaultSize):
1175 if parent is not None:
1176 wxWindow.__init__(self, parent, id, (0, 0), size)
1177 EVT_PAINT(self, self._OnPaint)
1178
1179 self.rect = rect
1180
1181 self.prop = prop
1182 self.shapeType = shapeType
1183 self.previewer = ClassDataPreviewer()
1184
1185 def _OnPaint(self, event):
1186 dc = wxPaintDC(self)
1187
1188 # XXX: this doesn't seem to be having an effect:
1189 dc.DestroyClippingRegion()
1190
1191 if self.rect is None:
1192 w, h = self.GetSize()
1193 rect = wxRect(0, 0, w, h)
1194 else:
1195 rect = self.rect
1196
1197 self.previewer.Draw(dc, rect, self.prop, self.shapeType)
1198
1199 class ClassDataPreviewer:
1200
1201 def Draw(self, dc, rect, prop, shapeType):
1202
1203 assert dc is not None
1204 assert isinstance(prop, ClassGroupProperties)
1205
1206 if rect is None:
1207 x = 0
1208 y = 0
1209 w, h = dc.GetSize()
1210 else:
1211 x = rect.GetX()
1212 y = rect.GetY()
1213 w = rect.GetWidth()
1214 h = rect.GetHeight()
1215
1216 stroke = prop.GetLineColor()
1217 if stroke is Color.Transparent:
1218 pen = wxTRANSPARENT_PEN
1219 else:
1220 pen = wxPen(Color2wxColour(stroke),
1221 prop.GetLineWidth(),
1222 wxSOLID)
1223
1224 stroke = prop.GetFill()
1225 if stroke is Color.Transparent:
1226 brush = wxTRANSPARENT_BRUSH
1227 else:
1228 brush = wxBrush(Color2wxColour(stroke), wxSOLID)
1229
1230 dc.SetPen(pen)
1231 dc.SetBrush(brush)
1232
1233 if shapeType == SHAPETYPE_ARC:
1234 dc.DrawSpline([wxPoint(x, y + h),
1235 wxPoint(x + w/2, y + h/4),
1236 wxPoint(x + w/2, y + h/4*3),
1237 wxPoint(x + w, y)])
1238
1239 elif shapeType == SHAPETYPE_POINT:
1240
1241 dc.DrawCircle(x + w/2, y + h/2,
1242 (min(w, h) - prop.GetLineWidth())/2)
1243
1244 elif shapeType == SHAPETYPE_POLYGON:
1245 dc.DrawRectangle(x, y, w, h)
1246
1247 class ClassRenderer(wxPyGridCellRenderer):
1248
1249 def __init__(self, shapeType):
1250 wxPyGridCellRenderer.__init__(self)
1251 self.shapeType = shapeType
1252 self.previewer = ClassDataPreviewer()
1253
1254 def Draw(self, grid, attr, dc, rect, row, col, isSelected):
1255 data = grid.GetTable().GetClassGroup(row)
1256
1257 dc.SetClippingRegion(rect.GetX(), rect.GetY(),
1258 rect.GetWidth(), rect.GetHeight())
1259 dc.SetPen(wxPen(wxLIGHT_GREY))
1260 dc.SetBrush(wxBrush(wxLIGHT_GREY, wxSOLID))
1261 dc.DrawRectangle(rect.GetX(), rect.GetY(),
1262 rect.GetWidth(), rect.GetHeight())
1263
1264 if not isinstance(data, ClassGroupMap):
1265 self.previewer.Draw(dc, rect, data.GetProperties(), self.shapeType)
1266
1267 if isSelected:
1268 dc.SetPen(wxPen(wxColour(0 * 255, 0 * 255, 0 * 255),
1269 4, wxSOLID))
1270 dc.SetBrush(wxTRANSPARENT_BRUSH)
1271 dc.DrawRectangle(rect.GetX(), rect.GetY(),
1272 rect.GetWidth(), rect.GetHeight())
1273
1274 dc.DestroyClippingRegion()
1275

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26