/[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 1068 - (hide annotations)
Tue May 27 15:02:37 2003 UTC (21 years, 9 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/tableview.py
File MIME type: text/x-python
File size: 16638 byte(s)
* Thuban/Model/messages.py (TABLE_REMOVED): New message.

* Thuban/Model/session.py (Session.UnreferencedTables): New method
to return tables that are not referenced by other tables or shape
stores and can be removed.
(Session.RemoveTable): Issue a TABLE_REMOVED message after
removing the table

* Thuban/UI/mainwindow.py: Remove unused imports
(MainWindow.TableClose): Implement.

* Thuban/UI/tableview.py (TableFrame.__init__): Subscribe to some
messages so that the frame will be automatically closed when a new
session is opened or the table is removed.
(TableFrame.OnClose): Unsubscribe the Subscriptions made in
__init__
(TableFrame.close_on_session_replaced)
(TableFrame.close_on_table_removed): New. Subscribers that close
the window

* test/test_session.py (TestSessionMessages.test_remove_table)
(TestSessionSimple.test_remove_table): Move the test to
TestSessionSimple and add test for the TABLE_REMOVED message
(TestSessionBase.setUp): Also subscribe to TABLE_REMOVED
(TestSessionSimple.test_unreferenced_tables) New. Test for the
UnreferencedTables method.
(UnreferencedTablesTests): New. Class with some more sophisticated
tests for UnreferencedTables.

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 34 class TableGrid(wxGrid, Publisher):
106 bh 6
107 bh 808 """A grid view for a Thuban table
108 bh 6
109 bh 808 When rows are selected by the user the table issues ROW_SELECTED
110     messages. wx sends selection events even when the selection is
111     manipulated by code (instead of by the user) which usually lead to
112     ROW_SELECTED messages being sent in turn. Therefore sending messages
113     can be switched on and off with the allow_messages and
114     disallow_messages methods.
115     """
116    
117 bh 6 def __init__(self, parent, table = None):
118     wxGrid.__init__(self, parent, -1)
119    
120 bh 808 self.allow_messages_count = 0
121    
122 bh 6 self.table = DataTable(table)
123    
124     # The second parameter means that the grid is to take ownership
125     # of the table and will destroy it when done. Otherwise you
126 bh 77 # would need to keep a reference to it and call its Destroy
127 bh 6 # method later.
128 jan 1035 self.SetTable(self.table, True)
129 bh 6
130     #self.SetMargins(0,0)
131    
132 bh 173 # AutoSizeColumns would allow us to make the grid have optimal
133     # column widths automatically but it would cause a traversal of
134     # the entire table which for large .dbf files can take a very
135     # long time.
136 jan 1035 #self.AutoSizeColumns(False)
137 bh 173
138 bh 6 self.SetSelectionMode(wxGrid.wxGridSelectRows)
139 bh 278
140 jonathan 966 #EVT_GRID_RANGE_SELECT(self, None)
141     #EVT_GRID_SELECT_CELL(self, None)
142     #EVT_GRID_RANGE_SELECT(self, self.OnRangeSelect)
143     #EVT_GRID_SELECT_CELL(self, self.OnSelectCell)
144 bh 6
145 jonathan 966 self.ToggleEventListeners(True)
146    
147 bh 6 def SetTableObject(self, table):
148     self.table.SetTable(table)
149    
150     def OnRangeSelect(self, event):
151 jonathan 881 rows = dict([(i, 0) for i in self.GetSelectedRows()])
152    
153     # if we're selecting we need to include the selected range and
154     # make sure that the current row is also included, which may
155     # not be the case if you just click on a single row!
156 bh 6 if event.Selecting():
157 jonathan 881 for i in range(event.GetTopRow(), event.GetBottomRow() + 1):
158     rows[i] = 0
159     rows[event.GetTopLeftCoords().GetRow()] = 0
160 bh 6
161 jonathan 881 self.issue(ROW_SELECTED, rows.keys())
162     event.Skip()
163    
164 bh 6 def OnSelectCell(self, event):
165 jonathan 881 self.issue(ROW_SELECTED, self.GetSelectedRows())
166     event.Skip()
167 bh 6
168 jonathan 966 def ToggleEventListeners(self, on):
169     if on:
170     EVT_GRID_RANGE_SELECT(self, self.OnRangeSelect)
171     EVT_GRID_SELECT_CELL(self, self.OnSelectCell)
172     else:
173     EVT_GRID_RANGE_SELECT(self, None)
174     EVT_GRID_SELECT_CELL(self, None)
175    
176 bh 808 def disallow_messages(self):
177     """Disallow messages to be send.
178 bh 278
179 bh 808 This method only increases a counter so that calls to
180     disallow_messages and allow_messages can be nested. Only the
181     outermost calls will actually switch message sending on and off.
182     """
183     self.allow_messages_count += 1
184    
185     def allow_messages(self):
186     """Allow messages to be send.
187    
188     This method only decreases a counter so that calls to
189     disallow_messages and allow_messages can be nested. Only the
190     outermost calls will actually switch message sending on and off.
191     """
192     self.allow_messages_count -= 1
193    
194     def issue(self, *args):
195     """Issue a message unless disallowed.
196    
197     See the allow_messages and disallow_messages methods.
198     """
199     if self.allow_messages_count == 0:
200     Publisher.issue(self, *args)
201    
202    
203 bh 278 class LayerTableGrid(TableGrid):
204    
205     """Table grid for the layer tables.
206    
207     The LayerTableGrid is basically the same as a TableGrid but it's
208     selection is usually coupled to the selected object in the map.
209     """
210    
211 jonathan 881 def select_shapes(self, layer, shapes):
212 bh 278 """Select the row corresponding to the specified shape and layer
213    
214     If layer is not the layer the table is associated with do
215     nothing. If shape or layer is None also do nothing.
216     """
217 jonathan 881 if layer is not None \
218     and layer.table is self.table.table:
219    
220 bh 808 self.disallow_messages()
221     try:
222 jonathan 881 self.ClearSelection()
223     if len(shapes) > 0:
224     #
225     # keep track of the lowest id so we can make it
226     # the first visible item
227     #
228     first = shapes[0]
229    
230     for shape in shapes:
231     self.SelectRow(shape, True)
232     if shape < first:
233     first = shape
234    
235     self.SetGridCursor(first, 0)
236     self.MakeCellVisible(first, 0)
237 bh 808 finally:
238     self.allow_messages()
239 bh 6
240    
241 frank 1058 class TableFrame(NonModalNonParentDialog):
242 bh 6
243 bh 34 """Frame that displays a Thuban table in a grid view"""
244    
245 bh 535 def __init__(self, parent, name, title, table):
246 frank 1058 NonModalNonParentDialog.__init__(self, parent, name, title)
247 bh 278 self.table = table
248     self.grid = self.make_grid(self.table)
249 bh 1068 self.app = self.parent.application
250     self.app.Subscribe(SESSION_REPLACED, self.close_on_session_replaced)
251     self.session = self.app.Session()
252     self.session.Subscribe(TABLE_REMOVED, self.close_on_table_removed)
253 bh 278
254     def make_grid(self, table):
255     """Return the table grid to use in the frame.
256    
257     The default implementation returns a TableGrid instance.
258     Override in derived classes to use different grid classes.
259     """
260     return TableGrid(self, table)
261    
262 bh 1068 def OnClose(self, event):
263     self.app.Unsubscribe(SESSION_REPLACED, self.close_on_session_replaced)
264     self.session.Unsubscribe(TABLE_REMOVED, self.close_on_table_removed)
265     NonModalNonParentDialog.OnClose(self, event)
266 bh 278
267 bh 1068 def close_on_session_replaced(self, *args):
268     """Subscriber for the SESSION_REPLACED messages.
269    
270     The table frame is tied to a session so close the window when
271     the session changes.
272     """
273     self.Close()
274    
275     def close_on_table_removed(self, table):
276     """Subscriber for the TABLE_REMOVED messages.
277    
278     The table frame is tied to a particular table so close the
279     window when the table is removed.
280     """
281     if table is self.table:
282     self.Close()
283    
284    
285 jonathan 881 ID_QUERY = 4001
286 jan 1013 ID_EXPORT = 4002
287 jonathan 881
288 jan 1013 class QueryTableFrame(TableFrame):
289 bh 278
290 jan 1013 """Frame that displays a table in a grid view and offers user actions
291     selection and export
292 bh 278
293     A LayerTableFrame is TableFrame whose selection is connected to the
294     selected object in a map.
295     """
296    
297 jan 1013 def __init__(self, parent, name, title, table):
298 bh 535 TableFrame.__init__(self, parent, name, title, table)
299 bh 30
300 jonathan 881 self.combo_fields = wxComboBox(self, -1, style=wxCB_READONLY)
301     self.choice_comp = wxChoice(self, -1,
302 jonathan 939 choices=["<", "<=", "==", "!=", ">=", ">"])
303 jonathan 881 self.combo_value = wxComboBox(self, -1)
304     self.choice_action = wxChoice(self, -1,
305     choices=[_("Replace Selection"),
306     _("Refine Selection"),
307     _("Add to Selection")])
308    
309     button_query = wxButton(self, ID_QUERY, _("Query"))
310 jan 1013 button_saveas = wxButton(self, ID_EXPORT, _("Export"))
311 jonathan 881
312     self.grid.SetSize((400, 200))
313    
314     self.combo_value.Append("")
315 jonathan 939 for i in range(table.NumColumns()):
316     name = table.Column(i).name
317 jonathan 881 self.combo_fields.Append(name)
318     self.combo_value.Append(name)
319 jonathan 939
320 jonathan 881 # assume at least one field?
321     self.combo_fields.SetSelection(0)
322     self.combo_value.SetSelection(0)
323    
324     topBox = wxBoxSizer(wxVERTICAL)
325    
326 frank 1045 sizer = wxStaticBoxSizer(wxStaticBox(self, -1,
327 frank 1066 _("Selection")),
328 jonathan 881 wxHORIZONTAL)
329     sizer.Add(self.combo_fields, 1, wxEXPAND|wxALL, 4)
330     sizer.Add(self.choice_comp, 0, wxALL, 4)
331     sizer.Add(self.combo_value, 1, wxEXPAND|wxALL, 4)
332     sizer.Add(self.choice_action, 0, wxALL, 4)
333 frank 979 sizer.Add(button_query, 0, wxALL | wxALIGN_CENTER_VERTICAL, 4)
334 jonathan 881 sizer.Add(40, 20, 0, wxALL, 4)
335 frank 979 sizer.Add(button_saveas, 0, wxALL | wxALIGN_CENTER_VERTICAL, 4)
336 jonathan 881
337     topBox.Add(sizer, 0, wxEXPAND|wxALL, 4)
338     topBox.Add(self.grid, 1, wxEXPAND|wxALL, 0)
339    
340     self.SetAutoLayout(True)
341     self.SetSizer(topBox)
342     topBox.Fit(self)
343     topBox.SetSizeHints(self)
344    
345 frank 979 self.grid.SetFocus()
346 jonathan 881 EVT_BUTTON(self, ID_QUERY, self.OnQuery)
347 jan 1013 EVT_BUTTON(self, ID_EXPORT, self.OnSaveAs)
348 frank 979 EVT_KEY_DOWN(self.grid, self.OnKeyDown)
349 jonathan 881
350 frank 979 def OnKeyDown(self, event):
351     """Catch query key from grid"""
352     if event.AltDown() and event.GetKeyCode() == ord(QUERY_KEY):
353     self.combo_fields.SetFocus()
354     self.combo_fields.refocus = True
355     else:
356     event.Skip()
357    
358    
359 jonathan 881 def OnQuery(self, event):
360     wxBeginBusyCursor()
361    
362     if self.combo_value.GetSelection() < 1:
363     value = self.combo_value.GetValue()
364     else:
365     value = self.table.Column(self.combo_value.GetValue())
366    
367 jonathan 939 ids = self.table.SimpleQuery(
368     self.table.Column(self.combo_fields.GetStringSelection()),
369     self.choice_comp.GetStringSelection(),
370     value)
371 jonathan 881
372     choice = self.choice_action.GetSelection()
373    
374 jonathan 966 #
375     # what used to be nice code got became a bit ugly because
376     # each time we select a row a message is sent to the grid
377     # which we are listening for and then we send further
378     # messages.
379     #
380     # now, we disable those listeners select everything but
381     # the first item, reenable the listeners, and select
382     # the first element, which causes everything to be
383     # updated properly.
384     #
385     self.grid.ToggleEventListeners(False)
386    
387 jonathan 881 if choice == 0:
388     # Replace Selection
389     self.grid.ClearSelection()
390     elif choice == 1:
391     # Refine Selection
392 jan 1013 sel = self.get_selected()
393 jonathan 881 self.grid.ClearSelection()
394 jonathan 966 ids = filter(sel.has_key, ids)
395 jonathan 881 elif choice == 2:
396     # Add to Selection
397 jonathan 966 pass
398    
399     #
400     # select the rows (all but the first)
401     #
402     firsttime = True
403     for id in ids:
404     if firsttime:
405     firsttime = False
406     else:
407 jonathan 881 self.grid.SelectRow(id, True)
408    
409 jonathan 966 self.grid.ToggleEventListeners(True)
410    
411     #
412     # select the first row
413     #
414     if ids:
415     self.grid.SelectRow(ids[0], True)
416    
417 jonathan 881 wxEndBusyCursor()
418    
419     def OnSaveAs(self, event):
420 jan 1013 dlg = wxFileDialog(self, _("Export Table To"), ".", "",
421     _("DBF Files (*.dbf)|*.dbf|") +
422     _("CSV Files (*.csv)|*.csv|") +
423     _("All Files (*.*)|*.*"),
424 jonathan 881 wxSAVE|wxOVERWRITE_PROMPT)
425     if dlg.ShowModal() == wxID_OK:
426 frank 1027 filename = dlg.GetPath()
427     type = os.path.basename(filename).split('.')[-1:][0]
428     dlg.Destroy()
429     if type.upper() == "DBF":
430     table_to_dbf(self.table, filename)
431     elif type.upper() == 'CSV':
432     table_to_csv(self.table, filename)
433     else:
434     dlg = wxMessageDialog(None, "Unsupported format: %s" % type,
435     "Table Export", wxOK|wxICON_WARNING)
436     dlg.ShowModal()
437     dlg.Destroy()
438     else:
439     dlg.Destroy()
440 jonathan 881
441 jan 1013 def OnClose(self, event):
442     TableFrame.OnClose(self, event)
443    
444     def get_selected(self):
445     """Return a dictionary of the selected rows.
446    
447     The dictionary has sthe indexes as keys."""
448     return dict([(i, 0) for i in self.grid.GetSelectedRows()])
449    
450     class LayerTableFrame(QueryTableFrame):
451    
452     """Frame that displays a layer table in a grid view
453    
454     A LayerTableFrame is a QueryTableFrame whose selection is connected to the
455     selected object in a map.
456     """
457    
458     def __init__(self, parent, name, title, layer, table):
459     QueryTableFrame.__init__(self, parent, name, title, table)
460     self.layer = layer
461     self.grid.Subscribe(ROW_SELECTED, self.rows_selected)
462     self.parent.Subscribe(SHAPES_SELECTED, self.select_shapes)
463    
464 jan 1032 # if there is already a selection present, update the grid
465     # accordingly
466     sel = self.get_selected().keys()
467     for i in sel:
468     self.grid.SelectRow(i, True)
469    
470 bh 278 def make_grid(self, table):
471     """Override the derived method to return a LayerTableGrid.
472     """
473     return LayerTableGrid(self, table)
474    
475 jan 1013 def get_selected(self):
476     """Override the derived method to return a dictionary of the selected
477     rows.
478     """
479     return dict([(i, 0) for i in self.parent.SelectedShapes()])
480    
481 bh 30 def OnClose(self, event):
482 jan 1013 """Override the derived method to first unsubscribed."""
483 jonathan 881 self.parent.Unsubscribe(SHAPES_SELECTED, self.select_shapes)
484 jan 1013 QueryTableFrame.OnClose(self, event)
485 bh 30
486 jonathan 881 def select_shapes(self, layer, shapes):
487 bh 535 """Subscribed to the SHAPES_SELECTED message.
488    
489     If shapes contains exactly one shape id, select that shape in
490     the grid. Otherwise deselect all.
491     """
492 jonathan 881 self.grid.select_shapes(layer, shapes)
493 bh 34
494 jonathan 881 def rows_selected(self, rows):
495 jan 1013 """Return the selected rows of the layer as they are returned
496     by Layer.SelectShapes().
497     """
498 bh 34 if self.layer is not None:
499 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