/[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 1058 - (show annotations)
Tue May 27 11:30:29 2003 UTC (21 years, 9 months ago) by frank
Original Path: trunk/thuban/Thuban/UI/tableview.py
File MIME type: text/x-python
File size: 15577 byte(s)
Dialog derived from NonModalNonParentDialog

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