/[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 460 - (show annotations)
Wed Mar 5 18:16:28 2003 UTC (22 years ago) by jonathan
File MIME type: text/x-python
File size: 31165 byte(s)
Added class documentation.
        Fixed RTbug #1713, #1714. Added Move[Up|Down] buttons.
        Store just the groups in the table and generate the other
        column information when it is requested. Add "None" field
        to pull-down to select no classification.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26