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

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

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 77 by bh, Mon Feb 4 19:27:13 2002 UTC revision 1013 by jan, Fri May 23 09:18:28 2003 UTC
# Line 1  Line 1 
1  # Copyright (c) 2001, 2002 by Intevation GmbH  # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2  # Authors:  # Authors:
3  # Bernhard Herzog <[email protected]>  # Bernhard Herzog <[email protected]>
4  #  #
# Line 7  Line 7 
7    
8  __version__ = "$Revision$"  __version__ = "$Revision$"
9    
10    from Thuban import _
11    
12  from wxPython.wx import *  from wxPython.wx import *
13  from wxPython.grid import *  from wxPython.grid import *
14    
# Line 15  from Thuban.Model.table import FIELDTYPE Line 17  from Thuban.Model.table import FIELDTYPE
17       FIELDTYPE_STRING       FIELDTYPE_STRING
18  import view  import view
19  from dialogs import NonModalDialog  from dialogs import NonModalDialog
20  from messages import SELECTED_SHAPE  from messages import SHAPES_SELECTED
21    
22  wx_value_type_map = {FIELDTYPE_INT: wxGRID_VALUE_NUMBER,  wx_value_type_map = {FIELDTYPE_INT: wxGRID_VALUE_NUMBER,
23                       FIELDTYPE_DOUBLE: wxGRID_VALUE_FLOAT,                       FIELDTYPE_DOUBLE: wxGRID_VALUE_FLOAT,
# Line 23  wx_value_type_map = {FIELDTYPE_INT: wxGR Line 25  wx_value_type_map = {FIELDTYPE_INT: wxGR
25    
26  ROW_SELECTED = "ROW_SELECTED"  ROW_SELECTED = "ROW_SELECTED"
27    
28    QUERY_KEY = 'S'
29    
30  class DataTable(wxPyGridTableBase):  class DataTable(wxPyGridTableBase):
31    
# Line 38  class DataTable(wxPyGridTableBase): Line 41  class DataTable(wxPyGridTableBase):
41    
42      def SetTable(self, table):      def SetTable(self, table):
43          self.table = table          self.table = table
44          self.num_cols = table.field_count()          self.num_cols = table.NumColumns()
45          self.num_rows = table.record_count()          self.num_rows = table.NumRows()
46    
47          self.columns = []          self.columns = []
48          for i in range(self.num_cols):          for i in range(self.num_cols):
49              type, name, len, decc = table.field_info(i)              col = table.Column(i)
50              self.columns.append((name, wx_value_type_map[type], len, decc))              self.columns.append((col.name, wx_value_type_map[col.type]))
51    
52      #      #
53      # required methods for the wxPyGridTableBase interface      # required methods for the wxPyGridTableBase interface
# Line 64  class DataTable(wxPyGridTableBase): Line 67  class DataTable(wxPyGridTableBase):
67      # Renderer understands the type too,) not just strings as in the      # Renderer understands the type too,) not just strings as in the
68      # C++ version.      # C++ version.
69      def GetValue(self, row, col):      def GetValue(self, row, col):
70          record = self.table.read_record(row)          record = self.table.ReadRowAsDict(row)
71          return record[self.columns[col][0]]          return record[self.columns[col][0]]
72    
73      def SetValue(self, row, col, value):      def SetValue(self, row, col, value):
# Line 97  class DataTable(wxPyGridTableBase): Line 100  class DataTable(wxPyGridTableBase):
100    
101  class TableGrid(wxGrid, Publisher):  class TableGrid(wxGrid, Publisher):
102    
103      """A grid view for a Thuban table"""      """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):      def __init__(self, parent, table = None):
114          wxGrid.__init__(self, parent, -1)          wxGrid.__init__(self, parent, -1)
115    
116            self.allow_messages_count = 0
117    
118          self.table = DataTable(table)          self.table = DataTable(table)
119    
120          # The second parameter means that the grid is to take ownership          # The second parameter means that the grid is to take ownership
# Line 111  class TableGrid(wxGrid, Publisher): Line 124  class TableGrid(wxGrid, Publisher):
124          self.SetTable(self.table, true)          self.SetTable(self.table, true)
125    
126          #self.SetMargins(0,0)          #self.SetMargins(0,0)
127          self.AutoSizeColumns(false)  
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)          self.SetSelectionMode(wxGrid.wxGridSelectRows)
135            
136          EVT_GRID_RANGE_SELECT(self, self.OnRangeSelect)          #EVT_GRID_RANGE_SELECT(self, None)
137          EVT_GRID_SELECT_CELL(self, self.OnSelectCell)          #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):      def SetTableObject(self, table):
144          self.table.SetTable(table)          self.table.SetTable(table)
145    
146      def OnRangeSelect(self, event):      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():          if event.Selecting():
153              self.issue(ROW_SELECTED, event.GetTopLeftCoords().GetRow())              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):      def OnSelectCell(self, event):
161          self.issue(ROW_SELECTED, event.GetRow())          self.issue(ROW_SELECTED, self.GetSelectedRows())
162            event.Skip()
163    
164      def select_shape(self, layer, shape):      def ToggleEventListeners(self, on):
165          if layer is not None and layer.table is self.table.table \          if on:
166             and shape is not None:              EVT_GRID_RANGE_SELECT(self, self.OnRangeSelect)
167              self.SelectRow(shape)              EVT_GRID_SELECT_CELL(self, self.OnSelectCell)
168              self.SetGridCursor(shape, 0)          else:
169              self.MakeCellVisible(shape, 0)              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):  class TableFrame(NonModalDialog):
238    
239      """Frame that displays a Thuban table in a grid view"""      """Frame that displays a Thuban table in a grid view"""
240    
241      def __init__(self, parent, interactor, name, title, layer = None,      def __init__(self, parent, name, title, table):
242                   table = None):          NonModalDialog.__init__(self, parent, name, title)
         NonModalDialog.__init__(self, parent, interactor, name, title)  
         self.layer = layer  
243          self.table = table          self.table = table
244          self.grid = TableGrid(self, table)          self.grid = self.make_grid(self.table)
245          self.grid.Subscribe(ROW_SELECTED, self.row_selected)  
246          self.interactor.Subscribe(SELECTED_SHAPE, self.select_shape)      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_EXPORT = 4002
257    
258    class QueryTableFrame(TableFrame):
259    
260        """Frame that displays a table in a grid view and offers user actions
261        selection and export
262    
263        A LayerTableFrame is TableFrame whose selection is connected to the
264        selected object in a map.
265        """
266    
267        def __init__(self, parent, name, title, table):
268            TableFrame.__init__(self, parent, name, title, table)
269    
270            self.combo_fields = wxComboBox(self, -1, style=wxCB_READONLY)
271            self.choice_comp = wxChoice(self, -1,
272                                  choices=["<", "<=", "==", "!=", ">=", ">"])
273            self.combo_value = wxComboBox(self, -1)
274            self.choice_action = wxChoice(self, -1,
275                                    choices=[_("Replace Selection"),
276                                            _("Refine Selection"),
277                                            _("Add to Selection")])
278    
279            button_query = wxButton(self, ID_QUERY, _("Query"))
280            button_saveas = wxButton(self, ID_EXPORT, _("Export"))
281    
282            self.grid.SetSize((400, 200))
283    
284            self.combo_value.Append("")
285            for i in range(table.NumColumns()):
286                name = table.Column(i).name
287                self.combo_fields.Append(name)
288                self.combo_value.Append(name)
289    
290            # assume at least one field?
291            self.combo_fields.SetSelection(0)
292            self.combo_value.SetSelection(0)
293    
294            topBox = wxBoxSizer(wxVERTICAL)
295    
296            sizer = wxStaticBoxSizer(wxStaticBox(self, -1, _("Selection")),
297                                      wxHORIZONTAL)
298            sizer.Add(self.combo_fields, 1, wxEXPAND|wxALL, 4)
299            sizer.Add(self.choice_comp, 0, wxALL, 4)
300            sizer.Add(self.combo_value, 1, wxEXPAND|wxALL, 4)
301            sizer.Add(self.choice_action, 0, wxALL, 4)
302            sizer.Add(button_query, 0, wxALL | wxALIGN_CENTER_VERTICAL, 4)
303            sizer.Add(40, 20, 0, wxALL, 4)
304            sizer.Add(button_saveas, 0, wxALL | wxALIGN_CENTER_VERTICAL, 4)
305    
306            topBox.Add(sizer, 0, wxEXPAND|wxALL, 4)
307            topBox.Add(self.grid, 1, wxEXPAND|wxALL, 0)
308    
309            self.SetAutoLayout(True)
310            self.SetSizer(topBox)
311            topBox.Fit(self)
312            topBox.SetSizeHints(self)
313    
314            self.grid.SetFocus()
315            EVT_BUTTON(self, ID_QUERY, self.OnQuery)
316            EVT_BUTTON(self, ID_EXPORT, self.OnSaveAs)
317            EVT_KEY_DOWN(self.grid, self.OnKeyDown)
318    
319        def OnKeyDown(self, event):
320            """Catch query key from grid"""
321            if event.AltDown() and event.GetKeyCode() == ord(QUERY_KEY):
322                self.combo_fields.SetFocus()
323                self.combo_fields.refocus = True
324            else:
325                event.Skip()
326    
327    
328        def OnQuery(self, event):
329            wxBeginBusyCursor()
330    
331            if self.combo_value.GetSelection() < 1:
332                value = self.combo_value.GetValue()
333            else:
334                value = self.table.Column(self.combo_value.GetValue())
335    
336            ids = self.table.SimpleQuery(
337                    self.table.Column(self.combo_fields.GetStringSelection()),
338                    self.choice_comp.GetStringSelection(),
339                    value)
340    
341            choice = self.choice_action.GetSelection()
342                
343            #
344            # what used to be nice code got became a bit ugly because
345            # each time we select a row a message is sent to the grid
346            # which we are listening for and then we send further
347            # messages.
348            #
349            # now, we disable those listeners select everything but
350            # the first item, reenable the listeners, and select
351            # the first element, which causes everything to be
352            # updated properly.
353            #
354            self.grid.ToggleEventListeners(False)
355    
356            if choice == 0:
357                # Replace Selection
358                self.grid.ClearSelection()
359            elif choice == 1:
360                # Refine Selection
361                sel = self.get_selected()
362                self.grid.ClearSelection()
363                ids = filter(sel.has_key, ids)
364            elif choice == 2:
365                # Add to Selection
366                pass
367    
368            #
369            # select the rows (all but the first)
370            #
371            firsttime = True
372            for id in ids:
373                if firsttime:
374                    firsttime = False
375                else:
376                    self.grid.SelectRow(id, True)
377    
378            self.grid.ToggleEventListeners(True)
379    
380            #
381            # select the first row
382            #
383            if ids:
384                self.grid.SelectRow(ids[0], True)
385    
386            wxEndBusyCursor()
387            
388        def OnSaveAs(self, event):
389            dlg = wxFileDialog(self, _("Export Table To"), ".", "",
390                               _("DBF Files (*.dbf)|*.dbf|") +
391                               _("CSV Files (*.csv)|*.csv|") +
392                               _("All Files (*.*)|*.*"),
393                               wxSAVE|wxOVERWRITE_PROMPT)
394            if dlg.ShowModal() == wxID_OK:
395                pass
396                                                                                    
397            dlg.Destroy()
398    
399      def OnClose(self, event):      def OnClose(self, event):
400          self.interactor.Unsubscribe(SELECTED_SHAPE, self.select_shape)          TableFrame.OnClose(self, event)
401          NonModalDialog.OnClose(self, event)  
402        def get_selected(self):
403            """Return a dictionary of the selected rows.
404            
405            The dictionary has sthe indexes as keys."""
406            return dict([(i, 0) for i in self.grid.GetSelectedRows()])
407    
408    class LayerTableFrame(QueryTableFrame):
409    
410      def select_shape(self, layer, shape):      """Frame that displays a layer table in a grid view
         self.grid.select_shape(layer, shape)  
411    
412      def row_selected(self, row):      A LayerTableFrame is a QueryTableFrame whose selection is connected to the
413        selected object in a map.
414        """
415    
416        def __init__(self, parent, name, title, layer, table):
417            QueryTableFrame.__init__(self, parent, name, title, table)
418            self.layer = layer
419            self.grid.Subscribe(ROW_SELECTED, self.rows_selected)
420            self.parent.Subscribe(SHAPES_SELECTED, self.select_shapes)
421    
422        def make_grid(self, table):
423            """Override the derived method to return a LayerTableGrid.
424            """
425            return LayerTableGrid(self, table)
426    
427        def get_selected(self):
428            """Override the derived method to return a dictionary of the selected
429            rows.
430            """
431            return dict([(i, 0) for i in self.parent.SelectedShapes()])
432    
433        def OnClose(self, event):
434            """Override the derived method to first unsubscribed."""
435            self.parent.Unsubscribe(SHAPES_SELECTED, self.select_shapes)
436            QueryTableFrame.OnClose(self, event)
437    
438        def select_shapes(self, layer, shapes):
439            """Subscribed to the SHAPES_SELECTED message.
440    
441            If shapes contains exactly one shape id, select that shape in
442            the grid. Otherwise deselect all.
443            """
444            self.grid.select_shapes(layer, shapes)
445    
446        def rows_selected(self, rows):
447            """Return the selected rows of the layer as they are returned
448            by Layer.SelectShapes().
449            """
450          if self.layer is not None:          if self.layer is not None:
451              self.interactor.SelectLayerAndShape(self.layer, row)              self.parent.SelectShapes(self.layer, rows)

Legend:
Removed from v.77  
changed lines
  Added in v.1013

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26