/[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 278 by bh, Mon Aug 26 12:50:23 2002 UTC revision 1058 by frank, Tue May 27 11:30:29 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    import os.path
11    
12    from Thuban import _
13    
14  from wxPython.wx import *  from wxPython.wx import *
15  from wxPython.grid import *  from wxPython.grid import *
16    
17  from Thuban.Lib.connector import Publisher  from Thuban.Lib.connector import Publisher
18  from Thuban.Model.table import FIELDTYPE_INT, FIELDTYPE_DOUBLE, \  from Thuban.Model.table import FIELDTYPE_INT, FIELDTYPE_DOUBLE, \
19       FIELDTYPE_STRING       FIELDTYPE_STRING, table_to_dbf, table_to_csv
20  import view  import view
21  from dialogs import NonModalDialog  from dialogs import NonModalNonParentDialog
22  from messages import SELECTED_SHAPE  from messages import SHAPES_SELECTED
23    
24  wx_value_type_map = {FIELDTYPE_INT: wxGRID_VALUE_NUMBER,  wx_value_type_map = {FIELDTYPE_INT: wxGRID_VALUE_NUMBER,
25                       FIELDTYPE_DOUBLE: wxGRID_VALUE_FLOAT,                       FIELDTYPE_DOUBLE: wxGRID_VALUE_FLOAT,
# Line 23  wx_value_type_map = {FIELDTYPE_INT: wxGR Line 27  wx_value_type_map = {FIELDTYPE_INT: wxGR
27    
28  ROW_SELECTED = "ROW_SELECTED"  ROW_SELECTED = "ROW_SELECTED"
29    
30    QUERY_KEY = 'S'
31    
32  class DataTable(wxPyGridTableBase):  class DataTable(wxPyGridTableBase):
33    
# Line 38  class DataTable(wxPyGridTableBase): Line 43  class DataTable(wxPyGridTableBase):
43    
44      def SetTable(self, table):      def SetTable(self, table):
45          self.table = table          self.table = table
46          self.num_cols = table.field_count()          self.num_cols = table.NumColumns()
47          self.num_rows = table.record_count()          self.num_rows = table.NumRows()
48    
49          self.columns = []          self.columns = []
50          for i in range(self.num_cols):          for i in range(self.num_cols):
51              type, name, len, decc = table.field_info(i)              col = table.Column(i)
52              self.columns.append((name, wx_value_type_map[type], len, decc))              self.columns.append((col.name, wx_value_type_map[col.type]))
53    
54      #      #
55      # required methods for the wxPyGridTableBase interface      # required methods for the wxPyGridTableBase interface
# Line 64  class DataTable(wxPyGridTableBase): Line 69  class DataTable(wxPyGridTableBase):
69      # Renderer understands the type too,) not just strings as in the      # Renderer understands the type too,) not just strings as in the
70      # C++ version.      # C++ version.
71      def GetValue(self, row, col):      def GetValue(self, row, col):
72          record = self.table.read_record(row)          record = self.table.ReadRowAsDict(row)
73          return record[self.columns[col][0]]          return record[self.columns[col][0]]
74    
75      def SetValue(self, row, col, value):      def SetValue(self, row, col, value):
# Line 97  class DataTable(wxPyGridTableBase): Line 102  class DataTable(wxPyGridTableBase):
102    
103  class TableGrid(wxGrid, Publisher):  class TableGrid(wxGrid, Publisher):
104    
105      """A grid view for a Thuban table"""      """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):      def __init__(self, parent, table = None):
116          wxGrid.__init__(self, parent, -1)          wxGrid.__init__(self, parent, -1)
117    
118            self.allow_messages_count = 0
119    
120          self.table = DataTable(table)          self.table = DataTable(table)
121    
122          # The second parameter means that the grid is to take ownership          # The second parameter means that the grid is to take ownership
123          # of the table and will destroy it when done. Otherwise you          # of the table and will destroy it when done. Otherwise you
124          # would need to keep a reference to it and call its Destroy          # would need to keep a reference to it and call its Destroy
125          # method later.          # method later.
126          self.SetTable(self.table, true)          self.SetTable(self.table, True)
127    
128          #self.SetMargins(0,0)          #self.SetMargins(0,0)
129    
# Line 116  class TableGrid(wxGrid, Publisher): Line 131  class TableGrid(wxGrid, Publisher):
131          # column widths automatically but it would cause a traversal of          # column widths automatically but it would cause a traversal of
132          # the entire table which for large .dbf files can take a very          # the entire table which for large .dbf files can take a very
133          # long time.          # long time.
134          #self.AutoSizeColumns(false)          #self.AutoSizeColumns(False)
135    
136          self.SetSelectionMode(wxGrid.wxGridSelectRows)          self.SetSelectionMode(wxGrid.wxGridSelectRows)
137    
138          EVT_GRID_RANGE_SELECT(self, self.OnRangeSelect)          #EVT_GRID_RANGE_SELECT(self, None)
139          EVT_GRID_SELECT_CELL(self, self.OnSelectCell)          #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):      def SetTableObject(self, table):
146          self.table.SetTable(table)          self.table.SetTable(table)
147    
148      def OnRangeSelect(self, event):      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():          if event.Selecting():
155              self.issue(ROW_SELECTED, event.GetTopLeftCoords().GetRow())              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):      def OnSelectCell(self, event):
163          self.issue(ROW_SELECTED, event.GetRow())          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):  class LayerTableGrid(TableGrid):
# Line 142  class LayerTableGrid(TableGrid): Line 206  class LayerTableGrid(TableGrid):
206      selection is usually coupled to the selected object in the map.      selection is usually coupled to the selected object in the map.
207      """      """
208    
209      def select_shape(self, layer, shape):      def select_shapes(self, layer, shapes):
210          """Select the row corresponding to the specified shape and layer          """Select the row corresponding to the specified shape and layer
211    
212          If layer is not the layer the table is associated with do          If layer is not the layer the table is associated with do
213          nothing. If shape or layer is None also do nothing.          nothing. If shape or layer is None also do nothing.
214          """          """
215          print "LayerTableGrid.select_shape", layer, shape          if layer is not None \
216          if layer is not None and layer.table is self.table.table \              and layer.table is self.table.table:
217             and shape is not None:  
218              self.SelectRow(shape)              self.disallow_messages()
219              self.SetGridCursor(shape, 0)              try:
220              self.MakeCellVisible(shape, 0)                  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):  class TableFrame(NonModalNonParentDialog):
240    
241      """Frame that displays a Thuban table in a grid view"""      """Frame that displays a Thuban table in a grid view"""
242    
243      def __init__(self, parent, interactor, name, title, table):      def __init__(self, parent, name, title, table):
244          NonModalDialog.__init__(self, parent, interactor, name, title)          NonModalNonParentDialog.__init__(self, parent, name, title)
245          self.table = table          self.table = table
246          self.grid = self.make_grid(self.table)          self.grid = self.make_grid(self.table)
247    
# Line 174  class TableFrame(NonModalDialog): Line 254  class TableFrame(NonModalDialog):
254          return TableGrid(self, table)          return TableGrid(self, table)
255    
256    
257  class LayerTableFrame(TableFrame):  ID_QUERY = 4001
258    ID_EXPORT = 4002
259    
260      """Frame that displays a layer table in a grid view  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      A LayerTableFrame is TableFrame whose selection is connected to the
266      selected object in a map.      selected object in a map.
267      """      """
268    
269      def __init__(self, parent, interactor, name, title, layer, table):      def __init__(self, parent, name, title, table):
270          TableFrame.__init__(self, parent, interactor, name, title, table)          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,
299                                      _("&Selection")),
300                                      wxHORIZONTAL)
301            sizer.Add(self.combo_fields, 1, wxEXPAND|wxALL, 4)
302            sizer.Add(self.choice_comp, 0, wxALL, 4)
303            sizer.Add(self.combo_value, 1, wxEXPAND|wxALL, 4)
304            sizer.Add(self.choice_action, 0, wxALL, 4)
305            sizer.Add(button_query, 0, wxALL | wxALIGN_CENTER_VERTICAL, 4)
306            sizer.Add(40, 20, 0, wxALL, 4)
307            sizer.Add(button_saveas, 0, wxALL | wxALIGN_CENTER_VERTICAL, 4)
308    
309            topBox.Add(sizer, 0, wxEXPAND|wxALL, 4)
310            topBox.Add(self.grid, 1, wxEXPAND|wxALL, 0)
311    
312            self.SetAutoLayout(True)
313            self.SetSizer(topBox)
314            topBox.Fit(self)
315            topBox.SetSizeHints(self)
316    
317            self.grid.SetFocus()
318            EVT_BUTTON(self, ID_QUERY, self.OnQuery)
319            EVT_BUTTON(self, ID_EXPORT, self.OnSaveAs)
320            EVT_KEY_DOWN(self.grid, self.OnKeyDown)
321    
322        def OnKeyDown(self, event):
323            """Catch query key from grid"""
324            if event.AltDown() and event.GetKeyCode() == ord(QUERY_KEY):
325                self.combo_fields.SetFocus()
326                self.combo_fields.refocus = True
327            else:
328                event.Skip()
329    
330    
331        def OnQuery(self, event):
332            wxBeginBusyCursor()
333    
334            if self.combo_value.GetSelection() < 1:
335                value = self.combo_value.GetValue()
336            else:
337                value = self.table.Column(self.combo_value.GetValue())
338    
339            ids = self.table.SimpleQuery(
340                    self.table.Column(self.combo_fields.GetStringSelection()),
341                    self.choice_comp.GetStringSelection(),
342                    value)
343    
344            choice = self.choice_action.GetSelection()
345                
346            #
347            # what used to be nice code got became a bit ugly because
348            # each time we select a row a message is sent to the grid
349            # which we are listening for and then we send further
350            # messages.
351            #
352            # now, we disable those listeners select everything but
353            # the first item, reenable the listeners, and select
354            # the first element, which causes everything to be
355            # updated properly.
356            #
357            self.grid.ToggleEventListeners(False)
358    
359            if choice == 0:
360                # Replace Selection
361                self.grid.ClearSelection()
362            elif choice == 1:
363                # Refine Selection
364                sel = self.get_selected()
365                self.grid.ClearSelection()
366                ids = filter(sel.has_key, ids)
367            elif choice == 2:
368                # Add to Selection
369                pass
370    
371            #
372            # select the rows (all but the first)
373            #
374            firsttime = True
375            for id in ids:
376                if firsttime:
377                    firsttime = False
378                else:
379                    self.grid.SelectRow(id, True)
380    
381            self.grid.ToggleEventListeners(True)
382    
383            #
384            # select the first row
385            #
386            if ids:
387                self.grid.SelectRow(ids[0], True)
388    
389            wxEndBusyCursor()
390            
391        def OnSaveAs(self, event):
392            dlg = wxFileDialog(self, _("Export Table To"), ".", "",
393                               _("DBF Files (*.dbf)|*.dbf|") +
394                               _("CSV Files (*.csv)|*.csv|") +
395                               _("All Files (*.*)|*.*"),
396                               wxSAVE|wxOVERWRITE_PROMPT)
397            if dlg.ShowModal() == wxID_OK:
398                filename = dlg.GetPath()
399                type = os.path.basename(filename).split('.')[-1:][0]
400                dlg.Destroy()
401                if type.upper() == "DBF":
402                    table_to_dbf(self.table, filename)
403                elif type.upper() == 'CSV':
404                    table_to_csv(self.table, filename)
405                else:
406                    dlg = wxMessageDialog(None, "Unsupported format: %s" % type,
407                                          "Table Export", wxOK|wxICON_WARNING)
408                    dlg.ShowModal()
409                    dlg.Destroy()
410            else:
411                dlg.Destroy()
412    
413        def OnClose(self, event):
414            TableFrame.OnClose(self, event)
415    
416        def get_selected(self):
417            """Return a dictionary of the selected rows.
418            
419            The dictionary has sthe indexes as keys."""
420            return dict([(i, 0) for i in self.grid.GetSelectedRows()])
421    
422    class LayerTableFrame(QueryTableFrame):
423    
424        """Frame that displays a layer table in a grid view
425    
426        A LayerTableFrame is a QueryTableFrame whose selection is connected to the
427        selected object in a map.
428        """
429    
430        def __init__(self, parent, name, title, layer, table):
431            QueryTableFrame.__init__(self, parent, name, title, table)
432          self.layer = layer          self.layer = layer
433          self.grid.Subscribe(ROW_SELECTED, self.row_selected)          self.grid.Subscribe(ROW_SELECTED, self.rows_selected)
434          self.interactor.Subscribe(SELECTED_SHAPE, self.select_shape)          self.parent.Subscribe(SHAPES_SELECTED, self.select_shapes)
435    
436            # if there is already a selection present, update the grid
437            # accordingly
438            sel = self.get_selected().keys()
439            for i in sel:
440                self.grid.SelectRow(i, True)
441    
442      def make_grid(self, table):      def make_grid(self, table):
443          """Override the derived method to return a LayerTableGrid.          """Override the derived method to return a LayerTableGrid.
444          """          """
445          return LayerTableGrid(self, table)          return LayerTableGrid(self, table)
446    
447        def get_selected(self):
448            """Override the derived method to return a dictionary of the selected
449            rows.
450            """
451            return dict([(i, 0) for i in self.parent.SelectedShapes()])
452    
453      def OnClose(self, event):      def OnClose(self, event):
454          self.interactor.Unsubscribe(SELECTED_SHAPE, self.select_shape)          """Override the derived method to first unsubscribed."""
455          TableFrame.OnClose(self, event)          self.parent.Unsubscribe(SHAPES_SELECTED, self.select_shapes)
456            QueryTableFrame.OnClose(self, event)
457    
458        def select_shapes(self, layer, shapes):
459            """Subscribed to the SHAPES_SELECTED message.
460    
461      def select_shape(self, layer, shape):          If shapes contains exactly one shape id, select that shape in
462          self.grid.select_shape(layer, shape)          the grid. Otherwise deselect all.
463            """
464            self.grid.select_shapes(layer, shapes)
465    
466      def row_selected(self, row):      def rows_selected(self, rows):
467            """Return the selected rows of the layer as they are returned
468            by Layer.SelectShapes().
469            """
470          if self.layer is not None:          if self.layer is not None:
471              self.interactor.SelectLayerAndShape(self.layer, row)              self.parent.SelectShapes(self.layer, rows)

Legend:
Removed from v.278  
changed lines
  Added in v.1058

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26