/[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 34 by bh, Thu Sep 6 15:32:56 2001 UTC revision 966 by jonathan, Wed May 21 17:24:40 2003 UTC
# Line 1  Line 1 
1  # Copyright (c) 2001 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 38  class DataTable(wxPyGridTableBase): Line 40  class DataTable(wxPyGridTableBase):
40    
41      def SetTable(self, table):      def SetTable(self, table):
42          self.table = table          self.table = table
43          self.num_cols = table.field_count()          self.num_cols = table.NumColumns()
44          self.num_rows = table.record_count()          self.num_rows = table.NumRows()
45    
46          self.columns = []          self.columns = []
47          for i in range(self.num_cols):          for i in range(self.num_cols):
48              type, name, len, decc = table.field_info(i)              col = table.Column(i)
49              self.columns.append((name, wx_value_type_map[type], len, decc))              self.columns.append((col.name, wx_value_type_map[col.type]))
50    
51      #      #
52      # required methods for the wxPyGridTableBase interface      # required methods for the wxPyGridTableBase interface
# Line 64  class DataTable(wxPyGridTableBase): Line 66  class DataTable(wxPyGridTableBase):
66      # Renderer understands the type too,) not just strings as in the      # Renderer understands the type too,) not just strings as in the
67      # C++ version.      # C++ version.
68      def GetValue(self, row, col):      def GetValue(self, row, col):
69          record = self.table.read_record(row)          record = self.table.ReadRowAsDict(row)
70          return record[self.columns[col][0]]          return record[self.columns[col][0]]
71    
72      def SetValue(self, row, col, value):      def SetValue(self, row, col, value):
# Line 97  class DataTable(wxPyGridTableBase): Line 99  class DataTable(wxPyGridTableBase):
99    
100  class TableGrid(wxGrid, Publisher):  class TableGrid(wxGrid, Publisher):
101    
102      """A grid view for a Thuban table"""      """A grid view for a Thuban table
103    
104        When rows are selected by the user the table issues ROW_SELECTED
105        messages. wx sends selection events even when the selection is
106        manipulated by code (instead of by the user) which usually lead to
107        ROW_SELECTED messages being sent in turn. Therefore sending messages
108        can be switched on and off with the allow_messages and
109        disallow_messages methods.
110        """
111    
112      def __init__(self, parent, table = None):      def __init__(self, parent, table = None):
113          wxGrid.__init__(self, parent, -1)          wxGrid.__init__(self, parent, -1)
114    
115            self.allow_messages_count = 0
116    
117          self.table = DataTable(table)          self.table = DataTable(table)
118    
119          # The second parameter means that the grid is to take ownership          # The second parameter means that the grid is to take ownership
120          # of the table and will destroy it when done. Otherwise you          # of the table and will destroy it when done. Otherwise you
121          # would need to keep a reference to it and call it's Destroy          # would need to keep a reference to it and call its Destroy
122          # method later.          # method later.
123          self.SetTable(self.table, true)          self.SetTable(self.table, true)
124    
125          #self.SetMargins(0,0)          #self.SetMargins(0,0)
126          self.AutoSizeColumns(false)  
127            # AutoSizeColumns would allow us to make the grid have optimal
128            # column widths automatically but it would cause a traversal of
129            # the entire table which for large .dbf files can take a very
130            # long time.
131            #self.AutoSizeColumns(false)
132    
133          self.SetSelectionMode(wxGrid.wxGridSelectRows)          self.SetSelectionMode(wxGrid.wxGridSelectRows)
134            
135          EVT_GRID_RANGE_SELECT(self, self.OnRangeSelect)          #EVT_GRID_RANGE_SELECT(self, None)
136          EVT_GRID_SELECT_CELL(self, self.OnSelectCell)          #EVT_GRID_SELECT_CELL(self, None)
137            #EVT_GRID_RANGE_SELECT(self, self.OnRangeSelect)
138            #EVT_GRID_SELECT_CELL(self, self.OnSelectCell)
139    
140            self.ToggleEventListeners(True)
141    
142      def SetTableObject(self, table):      def SetTableObject(self, table):
143          self.table.SetTable(table)          self.table.SetTable(table)
144    
145      def OnRangeSelect(self, event):      def OnRangeSelect(self, event):
146            rows = dict([(i, 0) for i in self.GetSelectedRows()])
147    
148            # if we're selecting we need to include the selected range and
149            # make sure that the current row is also included, which may
150            # not be the case if you just click on a single row!
151          if event.Selecting():          if event.Selecting():
152              self.issue(ROW_SELECTED, event.GetTopLeftCoords().GetRow())              for i in range(event.GetTopRow(), event.GetBottomRow() + 1):
153                    rows[i] = 0
154                rows[event.GetTopLeftCoords().GetRow()] = 0
155    
156            self.issue(ROW_SELECTED, rows.keys())
157            event.Skip()
158    
159      def OnSelectCell(self, event):      def OnSelectCell(self, event):
160          self.issue(ROW_SELECTED, event.GetRow())          self.issue(ROW_SELECTED, self.GetSelectedRows())
161            event.Skip()
162    
163      def select_shape(self, layer, shape):      def ToggleEventListeners(self, on):
164          if layer is not None and layer.table is self.table.table \          if on:
165             and shape is not None:              EVT_GRID_RANGE_SELECT(self, self.OnRangeSelect)
166              self.SelectRow(shape)              EVT_GRID_SELECT_CELL(self, self.OnSelectCell)
167              self.SetGridCursor(shape, 0)          else:
168              self.MakeCellVisible(shape, 0)              EVT_GRID_RANGE_SELECT(self, None)
169                EVT_GRID_SELECT_CELL(self, None)
170                
171        def disallow_messages(self):
172            """Disallow messages to be send.
173    
174            This method only increases a counter so that calls to
175            disallow_messages and allow_messages can be nested. Only the
176            outermost calls will actually switch message sending on and off.
177            """
178            self.allow_messages_count += 1
179    
180        def allow_messages(self):
181            """Allow messages to be send.
182    
183            This method only decreases a counter so that calls to
184            disallow_messages and allow_messages can be nested. Only the
185            outermost calls will actually switch message sending on and off.
186            """
187            self.allow_messages_count -= 1
188    
189        def issue(self, *args):
190            """Issue a message unless disallowed.
191    
192            See the allow_messages and disallow_messages methods.
193            """
194            if self.allow_messages_count == 0:
195                Publisher.issue(self, *args)
196    
197    
198    class LayerTableGrid(TableGrid):
199    
200        """Table grid for the layer tables.
201    
202        The LayerTableGrid is basically the same as a TableGrid but it's
203        selection is usually coupled to the selected object in the map.
204        """
205    
206        def select_shapes(self, layer, shapes):
207            """Select the row corresponding to the specified shape and layer
208    
209            If layer is not the layer the table is associated with do
210            nothing. If shape or layer is None also do nothing.
211            """
212            if layer is not None \
213                and layer.table is self.table.table:
214    
215                self.disallow_messages()
216                try:
217                    self.ClearSelection()
218                    if len(shapes) > 0:
219                        #
220                        # keep track of the lowest id so we can make it
221                        # the first visible item
222                        #
223                        first = shapes[0]
224    
225                        for shape in shapes:
226                            self.SelectRow(shape, True)
227                            if shape < first:
228                                first = shape
229    
230                        self.SetGridCursor(first, 0)
231                        self.MakeCellVisible(first, 0)
232                finally:
233                    self.allow_messages()
234    
235    
236  class TableFrame(NonModalDialog):  class TableFrame(NonModalDialog):
237    
238      """Frame that displays a Thuban table in a grid view"""      """Frame that displays a Thuban table in a grid view"""
239    
240      def __init__(self, parent, interactor, name, title, layer = None,      def __init__(self, parent, name, title, table):
241                   table = None):          NonModalDialog.__init__(self, parent, name, title)
         NonModalDialog.__init__(self, parent, interactor, name, title)  
         self.layer = layer  
242          self.table = table          self.table = table
243          self.grid = TableGrid(self, table)          self.grid = self.make_grid(self.table)
244          self.grid.Subscribe(ROW_SELECTED, self.row_selected)  
245          self.interactor.Subscribe(SELECTED_SHAPE, self.select_shape)      def make_grid(self, table):
246            """Return the table grid to use in the frame.
247    
248            The default implementation returns a TableGrid instance.
249            Override in derived classes to use different grid classes.
250            """
251            return TableGrid(self, table)
252    
253    
254    ID_QUERY = 4001
255    ID_SAVEAS = 4002
256    
257    class LayerTableFrame(TableFrame):
258    
259        """Frame that displays a layer table in a grid view
260    
261        A LayerTableFrame is TableFrame whose selection is connected to the
262        selected object in a map.
263        """
264    
265        def __init__(self, parent, name, title, layer, table):
266            TableFrame.__init__(self, parent, name, title, table)
267            self.layer = layer
268            self.grid.Subscribe(ROW_SELECTED, self.rows_selected)
269            self.parent.Subscribe(SHAPES_SELECTED, self.select_shapes)
270    
271            self.combo_fields = wxComboBox(self, -1, style=wxCB_READONLY)
272            self.choice_comp = wxChoice(self, -1,
273                                  choices=["<", "<=", "==", "!=", ">=", ">"])
274            self.combo_value = wxComboBox(self, -1)
275            self.choice_action = wxChoice(self, -1,
276                                    choices=[_("Replace Selection"),
277                                            _("Refine Selection"),
278                                            _("Add to Selection")])
279    
280            button_query = wxButton(self, ID_QUERY, _("Query"))
281            button_saveas = wxButton(self, ID_SAVEAS, _("Save As..."))
282    
283            self.grid.SetSize((400, 200))
284    
285            self.combo_value.Append("")
286            for i in range(table.NumColumns()):
287                name = table.Column(i).name
288                self.combo_fields.Append(name)
289                self.combo_value.Append(name)
290    
291            # assume at least one field?
292            self.combo_fields.SetSelection(0)
293            self.combo_value.SetSelection(0)
294    
295            topBox = wxBoxSizer(wxVERTICAL)
296    
297            sizer = wxStaticBoxSizer(wxStaticBox(self, -1, _("Selections")),
298                                      wxHORIZONTAL)
299            sizer.Add(self.combo_fields, 1, wxEXPAND|wxALL, 4)
300            sizer.Add(self.choice_comp, 0, wxALL, 4)
301            sizer.Add(self.combo_value, 1, wxEXPAND|wxALL, 4)
302            sizer.Add(self.choice_action, 0, wxALL, 4)
303            sizer.Add(button_query, 0, wxALL, 4)
304            sizer.Add(40, 20, 0, wxALL, 4)
305            sizer.Add(button_saveas, 0, wxALL, 4)
306    
307            topBox.Add(sizer, 0, wxEXPAND|wxALL, 4)
308            topBox.Add(self.grid, 1, wxEXPAND|wxALL, 0)
309    
310            self.SetAutoLayout(True)
311            self.SetSizer(topBox)
312            topBox.Fit(self)
313            topBox.SetSizeHints(self)
314    
315            EVT_BUTTON(self, ID_QUERY, self.OnQuery)
316            EVT_BUTTON(self, ID_SAVEAS, self.OnSaveAs)
317    
318        def OnQuery(self, event):
319            wxBeginBusyCursor()
320    
321            if self.combo_value.GetSelection() < 1:
322                value = self.combo_value.GetValue()
323            else:
324                value = self.table.Column(self.combo_value.GetValue())
325    
326            ids = self.table.SimpleQuery(
327                    self.table.Column(self.combo_fields.GetStringSelection()),
328                    self.choice_comp.GetStringSelection(),
329                    value)
330    
331            choice = self.choice_action.GetSelection()
332                
333            #
334            # what used to be nice code got became a bit ugly because
335            # each time we select a row a message is sent to the grid
336            # which we are listening for and then we send further
337            # messages.
338            #
339            # now, we disable those listeners select everything but
340            # the first item, reenable the listeners, and select
341            # the first element, which causes everything to be
342            # updated properly.
343            #
344            self.grid.ToggleEventListeners(False)
345    
346            if choice == 0:
347                # Replace Selection
348                self.grid.ClearSelection()
349            elif choice == 1:
350                # Refine Selection
351                sel = dict([(i, 0) for i in self.parent.SelectedShapes()])
352                self.grid.ClearSelection()
353                ids = filter(sel.has_key, ids)
354            elif choice == 2:
355                # Add to Selection
356                pass
357    
358            #
359            # select the rows (all but the first)
360            #
361            firsttime = True
362            for id in ids:
363                if firsttime:
364                    firsttime = False
365                else:
366                    self.grid.SelectRow(id, True)
367    
368            self.grid.ToggleEventListeners(True)
369    
370            #
371            # select the first row
372            #
373            if ids:
374                self.grid.SelectRow(ids[0], True)
375    
376            wxEndBusyCursor()
377            
378        def OnSaveAs(self, event):
379            dlg = wxFileDialog(self, _("Save Table As"), ".", "",
380                               "DBF Files (*.dbf)|*.dbf|" +
381                               "CSV Files (*.csv)|*.csv|" +
382                               "All Files (*.*)|*.*",
383                               wxSAVE|wxOVERWRITE_PROMPT)
384            if dlg.ShowModal() == wxID_OK:
385                pass
386                                                                                    
387            dlg.Destroy()
388    
389        def make_grid(self, table):
390            """Override the derived method to return a LayerTableGrid.
391            """
392            return LayerTableGrid(self, table)
393    
394      def OnClose(self, event):      def OnClose(self, event):
395          self.interactor.Unsubscribe(SELECTED_SHAPE, self.select_shape)          self.parent.Unsubscribe(SHAPES_SELECTED, self.select_shapes)
396          NonModalDialog.OnClose(self, event)          TableFrame.OnClose(self, event)
397    
398        def select_shapes(self, layer, shapes):
399            """Subscribed to the SHAPES_SELECTED message.
400    
401      def select_shape(self, layer, shape):          If shapes contains exactly one shape id, select that shape in
402          self.grid.select_shape(layer, shape)          the grid. Otherwise deselect all.
403            """
404            self.grid.select_shapes(layer, shapes)
405    
406      def row_selected(self, row):      def rows_selected(self, rows):
407          if self.layer is not None:          if self.layer is not None:
408              self.interactor.SelectLayerAndShape(self.layer, row)              self.parent.SelectShapes(self.layer, rows)

Legend:
Removed from v.34  
changed lines
  Added in v.966

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26