/[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 979 - (show annotations)
Thu May 22 11:40:59 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: 13855 byte(s)
(LayerTableFrame.__init__): Rename 'Save As'
	Button to 'Export'. Center Buttons in Selection Box, set Focus to
	Grid.
(LayerTableFrame.OnKeyDown()): New, bound to the grid with EVT_KEY_DOWN,
	changes focus to the Selection when pressing "Alt-S".

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