/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/UI/tableview.py
ViewVC logotype

Contents of /branches/WIP-pyshapelib-bramz/Thuban/UI/tableview.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 966 - (show annotations)
Wed May 21 17:24:40 2003 UTC (21 years, 9 months ago) by jonathan
Original Path: trunk/thuban/Thuban/UI/tableview.py
File MIME type: text/x-python
File size: 13384 byte(s)
(TableGrid.__init__): Call
        ToggleEventListeners to turn on listening.
(TableGrid.ToggleEventListeners): New. Turns event listening on
        and off so as to prevent excessive messages.
(LayerTableFrame.OnQuery): Use TableGrid.ToggleEventListeners
        to suppress excessive messages when selecting many rows.
        Fixes RTBug #1880.

1 # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2 # Authors:
3 # Bernhard Herzog <[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 __version__ = "$Revision$"
9
10 from Thuban import _
11
12 from wxPython.wx import *
13 from wxPython.grid import *
14
15 from Thuban.Lib.connector import Publisher
16 from Thuban.Model.table import FIELDTYPE_INT, FIELDTYPE_DOUBLE, \
17 FIELDTYPE_STRING
18 import view
19 from dialogs import NonModalDialog
20 from messages import SHAPES_SELECTED
21
22 wx_value_type_map = {FIELDTYPE_INT: wxGRID_VALUE_NUMBER,
23 FIELDTYPE_DOUBLE: wxGRID_VALUE_FLOAT,
24 FIELDTYPE_STRING: wxGRID_VALUE_STRING}
25
26 ROW_SELECTED = "ROW_SELECTED"
27
28
29 class DataTable(wxPyGridTableBase):
30
31 """Wrapper around a Thuban table object suitable for a wxGrid"""
32
33 def __init__(self, table = None):
34 wxPyGridTableBase.__init__(self)
35 self.num_cols = 0
36 self.num_rows = 0
37 self.columns = []
38 self.table = None
39 self.SetTable(table)
40
41 def SetTable(self, table):
42 self.table = table
43 self.num_cols = table.NumColumns()
44 self.num_rows = table.NumRows()
45
46 self.columns = []
47 for i in range(self.num_cols):
48 col = table.Column(i)
49 self.columns.append((col.name, wx_value_type_map[col.type]))
50
51 #
52 # required methods for the wxPyGridTableBase interface
53 #
54
55 def GetNumberRows(self):
56 return self.num_rows
57
58 def GetNumberCols(self):
59 return self.num_cols
60
61 def IsEmptyCell(self, row, col):
62 return 0
63
64 # Get/Set values in the table. The Python version of these
65 # methods can handle any data-type, (as long as the Editor and
66 # Renderer understands the type too,) not just strings as in the
67 # C++ version.
68 def GetValue(self, row, col):
69 record = self.table.ReadRowAsDict(row)
70 return record[self.columns[col][0]]
71
72 def SetValue(self, row, col, value):
73 pass
74
75 #
76 # Some optional methods
77 #
78
79 # Called when the grid needs to display labels
80 def GetColLabelValue(self, col):
81 return self.columns[col][0]
82
83 # Called to determine the kind of editor/renderer to use by
84 # default, doesn't necessarily have to be the same type used
85 # nativly by the editor/renderer if they know how to convert.
86 def GetTypeName(self, row, col):
87 return self.columns[col][1]
88
89 # Called to determine how the data can be fetched and stored by the
90 # editor and renderer. This allows you to enforce some type-safety
91 # in the grid.
92 def CanGetValueAs(self, row, col, typeName):
93 # perhaps we should allow conversion int->double?
94 return self.GetTypeName(row, col) == typeName
95
96 def CanSetValueAs(self, row, col, typeName):
97 return self.CanGetValueAs(row, col, typeName)
98
99
100 class TableGrid(wxGrid, Publisher):
101
102 """A grid view for a Thuban table
103
104 When rows are selected by the user the table issues ROW_SELECTED
105 messages. wx sends selection events even when the selection is
106 manipulated by code (instead of by the user) which usually lead to
107 ROW_SELECTED messages being sent in turn. Therefore sending messages
108 can be switched on and off with the allow_messages and
109 disallow_messages methods.
110 """
111
112 def __init__(self, parent, table = None):
113 wxGrid.__init__(self, parent, -1)
114
115 self.allow_messages_count = 0
116
117 self.table = DataTable(table)
118
119 # The second parameter means that the grid is to take ownership
120 # of the table and will destroy it when done. Otherwise you
121 # would need to keep a reference to it and call its Destroy
122 # method later.
123 self.SetTable(self.table, true)
124
125 #self.SetMargins(0,0)
126
127 # AutoSizeColumns would allow us to make the grid have optimal
128 # column widths automatically but it would cause a traversal of
129 # the entire table which for large .dbf files can take a very
130 # long time.
131 #self.AutoSizeColumns(false)
132
133 self.SetSelectionMode(wxGrid.wxGridSelectRows)
134
135 #EVT_GRID_RANGE_SELECT(self, None)
136 #EVT_GRID_SELECT_CELL(self, None)
137 #EVT_GRID_RANGE_SELECT(self, self.OnRangeSelect)
138 #EVT_GRID_SELECT_CELL(self, self.OnSelectCell)
139
140 self.ToggleEventListeners(True)
141
142 def SetTableObject(self, table):
143 self.table.SetTable(table)
144
145 def OnRangeSelect(self, event):
146 rows = dict([(i, 0) for i in self.GetSelectedRows()])
147
148 # if we're selecting we need to include the selected range and
149 # make sure that the current row is also included, which may
150 # not be the case if you just click on a single row!
151 if event.Selecting():
152 for i in range(event.GetTopRow(), event.GetBottomRow() + 1):
153 rows[i] = 0
154 rows[event.GetTopLeftCoords().GetRow()] = 0
155
156 self.issue(ROW_SELECTED, rows.keys())
157 event.Skip()
158
159 def OnSelectCell(self, event):
160 self.issue(ROW_SELECTED, self.GetSelectedRows())
161 event.Skip()
162
163 def ToggleEventListeners(self, on):
164 if on:
165 EVT_GRID_RANGE_SELECT(self, self.OnRangeSelect)
166 EVT_GRID_SELECT_CELL(self, self.OnSelectCell)
167 else:
168 EVT_GRID_RANGE_SELECT(self, None)
169 EVT_GRID_SELECT_CELL(self, None)
170
171 def disallow_messages(self):
172 """Disallow messages to be send.
173
174 This method only increases a counter so that calls to
175 disallow_messages and allow_messages can be nested. Only the
176 outermost calls will actually switch message sending on and off.
177 """
178 self.allow_messages_count += 1
179
180 def allow_messages(self):
181 """Allow messages to be send.
182
183 This method only decreases a counter so that calls to
184 disallow_messages and allow_messages can be nested. Only the
185 outermost calls will actually switch message sending on and off.
186 """
187 self.allow_messages_count -= 1
188
189 def issue(self, *args):
190 """Issue a message unless disallowed.
191
192 See the allow_messages and disallow_messages methods.
193 """
194 if self.allow_messages_count == 0:
195 Publisher.issue(self, *args)
196
197
198 class LayerTableGrid(TableGrid):
199
200 """Table grid for the layer tables.
201
202 The LayerTableGrid is basically the same as a TableGrid but it's
203 selection is usually coupled to the selected object in the map.
204 """
205
206 def select_shapes(self, layer, shapes):
207 """Select the row corresponding to the specified shape and layer
208
209 If layer is not the layer the table is associated with do
210 nothing. If shape or layer is None also do nothing.
211 """
212 if layer is not None \
213 and layer.table is self.table.table:
214
215 self.disallow_messages()
216 try:
217 self.ClearSelection()
218 if len(shapes) > 0:
219 #
220 # keep track of the lowest id so we can make it
221 # the first visible item
222 #
223 first = shapes[0]
224
225 for shape in shapes:
226 self.SelectRow(shape, True)
227 if shape < first:
228 first = shape
229
230 self.SetGridCursor(first, 0)
231 self.MakeCellVisible(first, 0)
232 finally:
233 self.allow_messages()
234
235
236 class TableFrame(NonModalDialog):
237
238 """Frame that displays a Thuban table in a grid view"""
239
240 def __init__(self, parent, name, title, table):
241 NonModalDialog.__init__(self, parent, name, title)
242 self.table = table
243 self.grid = self.make_grid(self.table)
244
245 def make_grid(self, table):
246 """Return the table grid to use in the frame.
247
248 The default implementation returns a TableGrid instance.
249 Override in derived classes to use different grid classes.
250 """
251 return TableGrid(self, table)
252
253
254 ID_QUERY = 4001
255 ID_SAVEAS = 4002
256
257 class LayerTableFrame(TableFrame):
258
259 """Frame that displays a layer table in a grid view
260
261 A LayerTableFrame is TableFrame whose selection is connected to the
262 selected object in a map.
263 """
264
265 def __init__(self, parent, name, title, layer, table):
266 TableFrame.__init__(self, parent, name, title, table)
267 self.layer = layer
268 self.grid.Subscribe(ROW_SELECTED, self.rows_selected)
269 self.parent.Subscribe(SHAPES_SELECTED, self.select_shapes)
270
271 self.combo_fields = wxComboBox(self, -1, style=wxCB_READONLY)
272 self.choice_comp = wxChoice(self, -1,
273 choices=["<", "<=", "==", "!=", ">=", ">"])
274 self.combo_value = wxComboBox(self, -1)
275 self.choice_action = wxChoice(self, -1,
276 choices=[_("Replace Selection"),
277 _("Refine Selection"),
278 _("Add to Selection")])
279
280 button_query = wxButton(self, ID_QUERY, _("Query"))
281 button_saveas = wxButton(self, ID_SAVEAS, _("Save As..."))
282
283 self.grid.SetSize((400, 200))
284
285 self.combo_value.Append("")
286 for i in range(table.NumColumns()):
287 name = table.Column(i).name
288 self.combo_fields.Append(name)
289 self.combo_value.Append(name)
290
291 # assume at least one field?
292 self.combo_fields.SetSelection(0)
293 self.combo_value.SetSelection(0)
294
295 topBox = wxBoxSizer(wxVERTICAL)
296
297 sizer = wxStaticBoxSizer(wxStaticBox(self, -1, _("Selections")),
298 wxHORIZONTAL)
299 sizer.Add(self.combo_fields, 1, wxEXPAND|wxALL, 4)
300 sizer.Add(self.choice_comp, 0, wxALL, 4)
301 sizer.Add(self.combo_value, 1, wxEXPAND|wxALL, 4)
302 sizer.Add(self.choice_action, 0, wxALL, 4)
303 sizer.Add(button_query, 0, wxALL, 4)
304 sizer.Add(40, 20, 0, wxALL, 4)
305 sizer.Add(button_saveas, 0, wxALL, 4)
306
307 topBox.Add(sizer, 0, wxEXPAND|wxALL, 4)
308 topBox.Add(self.grid, 1, wxEXPAND|wxALL, 0)
309
310 self.SetAutoLayout(True)
311 self.SetSizer(topBox)
312 topBox.Fit(self)
313 topBox.SetSizeHints(self)
314
315 EVT_BUTTON(self, ID_QUERY, self.OnQuery)
316 EVT_BUTTON(self, ID_SAVEAS, self.OnSaveAs)
317
318 def OnQuery(self, event):
319 wxBeginBusyCursor()
320
321 if self.combo_value.GetSelection() < 1:
322 value = self.combo_value.GetValue()
323 else:
324 value = self.table.Column(self.combo_value.GetValue())
325
326 ids = self.table.SimpleQuery(
327 self.table.Column(self.combo_fields.GetStringSelection()),
328 self.choice_comp.GetStringSelection(),
329 value)
330
331 choice = self.choice_action.GetSelection()
332
333 #
334 # what used to be nice code got became a bit ugly because
335 # each time we select a row a message is sent to the grid
336 # which we are listening for and then we send further
337 # messages.
338 #
339 # now, we disable those listeners select everything but
340 # the first item, reenable the listeners, and select
341 # the first element, which causes everything to be
342 # updated properly.
343 #
344 self.grid.ToggleEventListeners(False)
345
346 if choice == 0:
347 # Replace Selection
348 self.grid.ClearSelection()
349 elif choice == 1:
350 # Refine Selection
351 sel = dict([(i, 0) for i in self.parent.SelectedShapes()])
352 self.grid.ClearSelection()
353 ids = filter(sel.has_key, ids)
354 elif choice == 2:
355 # Add to Selection
356 pass
357
358 #
359 # select the rows (all but the first)
360 #
361 firsttime = True
362 for id in ids:
363 if firsttime:
364 firsttime = False
365 else:
366 self.grid.SelectRow(id, True)
367
368 self.grid.ToggleEventListeners(True)
369
370 #
371 # select the first row
372 #
373 if ids:
374 self.grid.SelectRow(ids[0], True)
375
376 wxEndBusyCursor()
377
378 def OnSaveAs(self, event):
379 dlg = wxFileDialog(self, _("Save Table As"), ".", "",
380 "DBF Files (*.dbf)|*.dbf|" +
381 "CSV Files (*.csv)|*.csv|" +
382 "All Files (*.*)|*.*",
383 wxSAVE|wxOVERWRITE_PROMPT)
384 if dlg.ShowModal() == wxID_OK:
385 pass
386
387 dlg.Destroy()
388
389 def make_grid(self, table):
390 """Override the derived method to return a LayerTableGrid.
391 """
392 return LayerTableGrid(self, table)
393
394 def OnClose(self, event):
395 self.parent.Unsubscribe(SHAPES_SELECTED, self.select_shapes)
396 TableFrame.OnClose(self, event)
397
398 def select_shapes(self, layer, shapes):
399 """Subscribed to the SHAPES_SELECTED message.
400
401 If shapes contains exactly one shape id, select that shape in
402 the grid. Otherwise deselect all.
403 """
404 self.grid.select_shapes(layer, shapes)
405
406 def rows_selected(self, rows):
407 if self.layer is not None:
408 self.parent.SelectShapes(self.layer, rows)

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26