/[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 1066 - (hide annotations)
Tue May 27 14:18:05 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: 15576 byte(s)
(QueryTableFrame.__init__): The "_S_election" display has some unwanted side
effects. Removed again.

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 535 from messages import SHAPES_SELECTED
23 bh 6
24     wx_value_type_map = {FIELDTYPE_INT: wxGRID_VALUE_NUMBER,
25     FIELDTYPE_DOUBLE: wxGRID_VALUE_FLOAT,
26     FIELDTYPE_STRING: wxGRID_VALUE_STRING}
27    
28 bh 34 ROW_SELECTED = "ROW_SELECTED"
29    
30 frank 979 QUERY_KEY = 'S'
31 bh 34
32 bh 6 class DataTable(wxPyGridTableBase):
33    
34 bh 34 """Wrapper around a Thuban table object suitable for a wxGrid"""
35    
36 bh 6 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 bh 838 self.num_cols = table.NumColumns()
47     self.num_rows = table.NumRows()
48 bh 6
49     self.columns = []
50     for i in range(self.num_cols):
51 bh 838 col = table.Column(i)
52     self.columns.append((col.name, wx_value_type_map[col.type]))
53 bh 6
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 bh 838 record = self.table.ReadRowAsDict(row)
73 bh 6 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 bh 34 class TableGrid(wxGrid, Publisher):
104 bh 6
105 bh 808 """A grid view for a Thuban table
106 bh 6
107 bh 808 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 bh 6 def __init__(self, parent, table = None):
116     wxGrid.__init__(self, parent, -1)
117    
118 bh 808 self.allow_messages_count = 0
119    
120 bh 6 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 bh 77 # would need to keep a reference to it and call its Destroy
125 bh 6 # method later.
126 jan 1035 self.SetTable(self.table, True)
127 bh 6
128     #self.SetMargins(0,0)
129    
130 bh 173 # 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 jan 1035 #self.AutoSizeColumns(False)
135 bh 173
136 bh 6 self.SetSelectionMode(wxGrid.wxGridSelectRows)
137 bh 278
138 jonathan 966 #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 bh 6
143 jonathan 966 self.ToggleEventListeners(True)
144    
145 bh 6 def SetTableObject(self, table):
146     self.table.SetTable(table)
147    
148     def OnRangeSelect(self, event):
149 jonathan 881 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 bh 6 if event.Selecting():
155 jonathan 881 for i in range(event.GetTopRow(), event.GetBottomRow() + 1):
156     rows[i] = 0
157     rows[event.GetTopLeftCoords().GetRow()] = 0
158 bh 6
159 jonathan 881 self.issue(ROW_SELECTED, rows.keys())
160     event.Skip()
161    
162 bh 6 def OnSelectCell(self, event):
163 jonathan 881 self.issue(ROW_SELECTED, self.GetSelectedRows())
164     event.Skip()
165 bh 6
166 jonathan 966 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 bh 808 def disallow_messages(self):
175     """Disallow messages to be send.
176 bh 278
177 bh 808 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 bh 278 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 jonathan 881 def select_shapes(self, layer, shapes):
210 bh 278 """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 jonathan 881 if layer is not None \
216     and layer.table is self.table.table:
217    
218 bh 808 self.disallow_messages()
219     try:
220 jonathan 881 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 bh 808 finally:
236     self.allow_messages()
237 bh 6
238    
239 frank 1058 class TableFrame(NonModalNonParentDialog):
240 bh 6
241 bh 34 """Frame that displays a Thuban table in a grid view"""
242    
243 bh 535 def __init__(self, parent, name, title, table):
244 frank 1058 NonModalNonParentDialog.__init__(self, parent, name, title)
245 bh 278 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 jonathan 881 ID_QUERY = 4001
258 jan 1013 ID_EXPORT = 4002
259 jonathan 881
260 jan 1013 class QueryTableFrame(TableFrame):
261 bh 278
262 jan 1013 """Frame that displays a table in a grid view and offers user actions
263     selection and export
264 bh 278
265     A LayerTableFrame is TableFrame whose selection is connected to the
266     selected object in a map.
267     """
268    
269 jan 1013 def __init__(self, parent, name, title, table):
270 bh 535 TableFrame.__init__(self, parent, name, title, table)
271 bh 30
272 jonathan 881 self.combo_fields = wxComboBox(self, -1, style=wxCB_READONLY)
273     self.choice_comp = wxChoice(self, -1,
274 jonathan 939 choices=["<", "<=", "==", "!=", ">=", ">"])
275 jonathan 881 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 jan 1013 button_saveas = wxButton(self, ID_EXPORT, _("Export"))
283 jonathan 881
284     self.grid.SetSize((400, 200))
285    
286     self.combo_value.Append("")
287 jonathan 939 for i in range(table.NumColumns()):
288     name = table.Column(i).name
289 jonathan 881 self.combo_fields.Append(name)
290     self.combo_value.Append(name)
291 jonathan 939
292 jonathan 881 # assume at least one field?
293     self.combo_fields.SetSelection(0)
294     self.combo_value.SetSelection(0)
295    
296     topBox = wxBoxSizer(wxVERTICAL)
297    
298 frank 1045 sizer = wxStaticBoxSizer(wxStaticBox(self, -1,
299 frank 1066 _("Selection")),
300 jonathan 881 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 frank 979 sizer.Add(button_query, 0, wxALL | wxALIGN_CENTER_VERTICAL, 4)
306 jonathan 881 sizer.Add(40, 20, 0, wxALL, 4)
307 frank 979 sizer.Add(button_saveas, 0, wxALL | wxALIGN_CENTER_VERTICAL, 4)
308 jonathan 881
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 frank 979 self.grid.SetFocus()
318 jonathan 881 EVT_BUTTON(self, ID_QUERY, self.OnQuery)
319 jan 1013 EVT_BUTTON(self, ID_EXPORT, self.OnSaveAs)
320 frank 979 EVT_KEY_DOWN(self.grid, self.OnKeyDown)
321 jonathan 881
322 frank 979 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 jonathan 881 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 jonathan 939 ids = self.table.SimpleQuery(
340     self.table.Column(self.combo_fields.GetStringSelection()),
341     self.choice_comp.GetStringSelection(),
342     value)
343 jonathan 881
344     choice = self.choice_action.GetSelection()
345    
346 jonathan 966 #
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 jonathan 881 if choice == 0:
360     # Replace Selection
361     self.grid.ClearSelection()
362     elif choice == 1:
363     # Refine Selection
364 jan 1013 sel = self.get_selected()
365 jonathan 881 self.grid.ClearSelection()
366 jonathan 966 ids = filter(sel.has_key, ids)
367 jonathan 881 elif choice == 2:
368     # Add to Selection
369 jonathan 966 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 jonathan 881 self.grid.SelectRow(id, True)
380    
381 jonathan 966 self.grid.ToggleEventListeners(True)
382    
383     #
384     # select the first row
385     #
386     if ids:
387     self.grid.SelectRow(ids[0], True)
388    
389 jonathan 881 wxEndBusyCursor()
390    
391     def OnSaveAs(self, event):
392 jan 1013 dlg = wxFileDialog(self, _("Export Table To"), ".", "",
393     _("DBF Files (*.dbf)|*.dbf|") +
394     _("CSV Files (*.csv)|*.csv|") +
395     _("All Files (*.*)|*.*"),
396 jonathan 881 wxSAVE|wxOVERWRITE_PROMPT)
397     if dlg.ShowModal() == wxID_OK:
398 frank 1027 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 jonathan 881
413 jan 1013 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 jan 1032 # 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 bh 278 def make_grid(self, table):
443     """Override the derived method to return a LayerTableGrid.
444     """
445     return LayerTableGrid(self, table)
446    
447 jan 1013 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 bh 30 def OnClose(self, event):
454 jan 1013 """Override the derived method to first unsubscribed."""
455 jonathan 881 self.parent.Unsubscribe(SHAPES_SELECTED, self.select_shapes)
456 jan 1013 QueryTableFrame.OnClose(self, event)
457 bh 30
458 jonathan 881 def select_shapes(self, layer, shapes):
459 bh 535 """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 jonathan 881 self.grid.select_shapes(layer, shapes)
465 bh 34
466 jonathan 881 def rows_selected(self, rows):
467 jan 1013 """Return the selected rows of the layer as they are returned
468     by Layer.SelectShapes().
469     """
470 bh 34 if self.layer is not None:
471 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