/[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 1013 - (show annotations)
Fri May 23 09:18:28 2003 UTC (21 years, 9 months ago) by jan
Original Path: trunk/thuban/Thuban/UI/tableview.py
File MIME type: text/x-python
File size: 14816 byte(s)
(LayerTableFrame, QueryTableFrame): Split the class LayerTableFrame into
two classes, LayerTableFrame and QueryTableFrame.
The latter implements the selection GUI without dependency on a layer.
LayerTableFrame now is derived from QueryTableFrame and connects
to a layer.

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