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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1104 - (hide annotations)
Fri May 30 06:29:54 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: 18268 byte(s)
(QueryTableFrame.OnQuery): Wrap code with try/finally. Fixes RTBug #1904.

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