/[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 615 - (show annotations)
Mon Apr 7 08:57:20 2003 UTC (21 years, 11 months ago) by jonathan
File MIME type: text/x-python
File size: 42348 byte(s)
Now that we can depend on the order in
        a Classification and have methods to manipulate that order we don't
        need to use our own data structures in the grid. We can simply make
        the grid/table access the information they need from a copy of
        the classification object.
(Classifier._OnCloseBtn): Event handler for when the user clicks
        'Close'. This is needed so if the user applies changes and then
        continues to change the table the user has the option of discarding
        the most recent changes and keeping what they applied.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26