/[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 473 - (show annotations)
Wed Mar 5 18:39:45 2003 UTC (22 years ago) by jonathan
File MIME type: text/x-python
File size: 31255 byte(s)
import FIELDTYPE constants from table

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26