/[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 6 by bh, Tue Aug 28 15:41:52 2001 UTC revision 1035 by jan, Mon May 26 17:03:08 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    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
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 messages import SELECTED_SHAPE  from dialogs import NonModalDialog
22    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,
26                       FIELDTYPE_STRING: wxGRID_VALUE_STRING}                       FIELDTYPE_STRING: wxGRID_VALUE_STRING}
27    
28    ROW_SELECTED = "ROW_SELECTED"
29    
30    QUERY_KEY = 'S'
31    
32  class DataTable(wxPyGridTableBase):  class DataTable(wxPyGridTableBase):
33    
34        """Wrapper around a Thuban table object suitable for a wxGrid"""
35    
36      def __init__(self, table = None):      def __init__(self, table = None):
37          wxPyGridTableBase.__init__(self)          wxPyGridTableBase.__init__(self)
38          self.num_cols = 0          self.num_cols = 0
# Line 31  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 57  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 88  class DataTable(wxPyGridTableBase): Line 100  class DataTable(wxPyGridTableBase):
100          return self.CanGetValueAs(row, col, typeName)          return self.CanGetValueAs(row, col, typeName)
101    
102    
103      # Thuban stuff  class TableGrid(wxGrid, Publisher):
     def SelectRow(self, row):  
         import main  
         interactor = main.app.interactor  
         return interactor.SelectShape(self.table, row)  
   
   
   
104    
105        """A grid view for a Thuban table
106    
107  class TableGrid(wxGrid):      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 it's 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          self.AutoSizeColumns(false)  
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)          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          import main          #EVT_GRID_RANGE_SELECT(self, self.OnRangeSelect)
141          main.app.interactor.Subscribe(SELECTED_SHAPE, self.select_shape)          #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              if not self.table.SelectRow(event.GetTopLeftCoords().GetRow()):              for i in range(event.GetTopRow(), event.GetBottomRow() + 1):
156                  event.Skip()                  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          if not self.table.SelectRow(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):
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()              event.Skip()
328    
     def select_shape(self, layer, shape):  
         if layer is not None and layer.table is self.table.table:  
             self.SelectRow(shape)  
             self.SetGridCursor(shape, 0)  
             self.MakeCellVisible(shape, 0)  
329    
330        def OnQuery(self, event):
331            wxBeginBusyCursor()
332    
333  class TableFrame(wxFrame):          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      def __init__(self, parent, table = None):          wxEndBusyCursor()
389          wxFrame.__init__(self, parent, -1, "Thuban Table")          
390          grid = TableGrid(self, table)      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)

Legend:
Removed from v.6  
changed lines
  Added in v.1035

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26