/[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 1032 - (show annotations)
Mon May 26 15:33:20 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: 15514 byte(s)
(LayerTableFrame.__init__): If there is already a selection present,
update the grid accordingly.

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 NonModalDialog
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(NonModalDialog):
240
241 """Frame that displays a Thuban table in a grid view"""
242
243 def __init__(self, parent, name, title, table):
244 NonModalDialog.__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, _("Selection")),
299 wxHORIZONTAL)
300 sizer.Add(self.combo_fields, 1, wxEXPAND|wxALL, 4)
301 sizer.Add(self.choice_comp, 0, wxALL, 4)
302 sizer.Add(self.combo_value, 1, wxEXPAND|wxALL, 4)
303 sizer.Add(self.choice_action, 0, wxALL, 4)
304 sizer.Add(button_query, 0, wxALL | wxALIGN_CENTER_VERTICAL, 4)
305 sizer.Add(40, 20, 0, wxALL, 4)
306 sizer.Add(button_saveas, 0, wxALL | wxALIGN_CENTER_VERTICAL, 4)
307
308 topBox.Add(sizer, 0, wxEXPAND|wxALL, 4)
309 topBox.Add(self.grid, 1, wxEXPAND|wxALL, 0)
310
311 self.SetAutoLayout(True)
312 self.SetSizer(topBox)
313 topBox.Fit(self)
314 topBox.SetSizeHints(self)
315
316 self.grid.SetFocus()
317 EVT_BUTTON(self, ID_QUERY, self.OnQuery)
318 EVT_BUTTON(self, ID_EXPORT, self.OnSaveAs)
319 EVT_KEY_DOWN(self.grid, self.OnKeyDown)
320
321 def OnKeyDown(self, event):
322 """Catch query key from grid"""
323 if event.AltDown() and event.GetKeyCode() == ord(QUERY_KEY):
324 self.combo_fields.SetFocus()
325 self.combo_fields.refocus = True
326 else:
327 event.Skip()
328
329
330 def OnQuery(self, event):
331 wxBeginBusyCursor()
332
333 if self.combo_value.GetSelection() < 1:
334 value = self.combo_value.GetValue()
335 else:
336 value = self.table.Column(self.combo_value.GetValue())
337
338 ids = self.table.SimpleQuery(
339 self.table.Column(self.combo_fields.GetStringSelection()),
340 self.choice_comp.GetStringSelection(),
341 value)
342
343 choice = self.choice_action.GetSelection()
344
345 #
346 # what used to be nice code got became a bit ugly because
347 # each time we select a row a message is sent to the grid
348 # which we are listening for and then we send further
349 # messages.
350 #
351 # now, we disable those listeners select everything but
352 # the first item, reenable the listeners, and select
353 # the first element, which causes everything to be
354 # updated properly.
355 #
356 self.grid.ToggleEventListeners(False)
357
358 if choice == 0:
359 # Replace Selection
360 self.grid.ClearSelection()
361 elif choice == 1:
362 # Refine Selection
363 sel = self.get_selected()
364 self.grid.ClearSelection()
365 ids = filter(sel.has_key, ids)
366 elif choice == 2:
367 # Add to Selection
368 pass
369
370 #
371 # select the rows (all but the first)
372 #
373 firsttime = True
374 for id in ids:
375 if firsttime:
376 firsttime = False
377 else:
378 self.grid.SelectRow(id, True)
379
380 self.grid.ToggleEventListeners(True)
381
382 #
383 # select the first row
384 #
385 if ids:
386 self.grid.SelectRow(ids[0], True)
387
388 wxEndBusyCursor()
389
390 def OnSaveAs(self, event):
391 dlg = wxFileDialog(self, _("Export Table To"), ".", "",
392 _("DBF Files (*.dbf)|*.dbf|") +
393 _("CSV Files (*.csv)|*.csv|") +
394 _("All Files (*.*)|*.*"),
395 wxSAVE|wxOVERWRITE_PROMPT)
396 if dlg.ShowModal() == wxID_OK:
397 filename = dlg.GetPath()
398 type = os.path.basename(filename).split('.')[-1:][0]
399 dlg.Destroy()
400 if type.upper() == "DBF":
401 table_to_dbf(self.table, filename)
402 elif type.upper() == 'CSV':
403 table_to_csv(self.table, filename)
404 else:
405 dlg = wxMessageDialog(None, "Unsupported format: %s" % type,
406 "Table Export", wxOK|wxICON_WARNING)
407 dlg.ShowModal()
408 dlg.Destroy()
409 else:
410 dlg.Destroy()
411
412 def OnClose(self, event):
413 TableFrame.OnClose(self, event)
414
415 def get_selected(self):
416 """Return a dictionary of the selected rows.
417
418 The dictionary has sthe indexes as keys."""
419 return dict([(i, 0) for i in self.grid.GetSelectedRows()])
420
421 class LayerTableFrame(QueryTableFrame):
422
423 """Frame that displays a layer table in a grid view
424
425 A LayerTableFrame is a QueryTableFrame whose selection is connected to the
426 selected object in a map.
427 """
428
429 def __init__(self, parent, name, title, layer, table):
430 QueryTableFrame.__init__(self, parent, name, title, table)
431 self.layer = layer
432 self.grid.Subscribe(ROW_SELECTED, self.rows_selected)
433 self.parent.Subscribe(SHAPES_SELECTED, self.select_shapes)
434
435 # if there is already a selection present, update the grid
436 # accordingly
437 sel = self.get_selected().keys()
438 for i in sel:
439 self.grid.SelectRow(i, True)
440
441 def make_grid(self, table):
442 """Override the derived method to return a LayerTableGrid.
443 """
444 return LayerTableGrid(self, table)
445
446 def get_selected(self):
447 """Override the derived method to return a dictionary of the selected
448 rows.
449 """
450 return dict([(i, 0) for i in self.parent.SelectedShapes()])
451
452 def OnClose(self, event):
453 """Override the derived method to first unsubscribed."""
454 self.parent.Unsubscribe(SHAPES_SELECTED, self.select_shapes)
455 QueryTableFrame.OnClose(self, event)
456
457 def select_shapes(self, layer, shapes):
458 """Subscribed to the SHAPES_SELECTED message.
459
460 If shapes contains exactly one shape id, select that shape in
461 the grid. Otherwise deselect all.
462 """
463 self.grid.select_shapes(layer, shapes)
464
465 def rows_selected(self, rows):
466 """Return the selected rows of the layer as they are returned
467 by Layer.SelectShapes().
468 """
469 if self.layer is not None:
470 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