/[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 173 by bh, Wed May 15 13:29:34 2002 UTC revision 1068 by bh, Tue May 27 15:02:37 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  
23    from messages import SHAPES_SELECTED, SESSION_REPLACED
24    from Thuban.Model.messages import TABLE_REMOVED
25    
26  wx_value_type_map = {FIELDTYPE_INT: wxGRID_VALUE_NUMBER,  wx_value_type_map = {FIELDTYPE_INT: wxGRID_VALUE_NUMBER,
27                       FIELDTYPE_DOUBLE: wxGRID_VALUE_FLOAT,                       FIELDTYPE_DOUBLE: wxGRID_VALUE_FLOAT,
# Line 23  wx_value_type_map = {FIELDTYPE_INT: wxGR Line 29  wx_value_type_map = {FIELDTYPE_INT: wxGR
29    
30  ROW_SELECTED = "ROW_SELECTED"  ROW_SELECTED = "ROW_SELECTED"
31    
32    QUERY_KEY = 'S'
33    
34  class DataTable(wxPyGridTableBase):  class DataTable(wxPyGridTableBase):
35    
# Line 38  class DataTable(wxPyGridTableBase): Line 45  class DataTable(wxPyGridTableBase):
45    
46      def SetTable(self, table):      def SetTable(self, table):
47          self.table = table          self.table = table
48          self.num_cols = table.field_count()          self.num_cols = table.NumColumns()
49          self.num_rows = table.record_count()          self.num_rows = table.NumRows()
50    
51          self.columns = []          self.columns = []
52          for i in range(self.num_cols):          for i in range(self.num_cols):
53              type, name, len, decc = table.field_info(i)              col = table.Column(i)
54              self.columns.append((name, wx_value_type_map[type], len, decc))              self.columns.append((col.name, wx_value_type_map[col.type]))
55    
56      #      #
57      # required methods for the wxPyGridTableBase interface      # required methods for the wxPyGridTableBase interface
# Line 64  class DataTable(wxPyGridTableBase): Line 71  class DataTable(wxPyGridTableBase):
71      # Renderer understands the type too,) not just strings as in the      # Renderer understands the type too,) not just strings as in the
72      # C++ version.      # C++ version.
73      def GetValue(self, row, col):      def GetValue(self, row, col):
74          record = self.table.read_record(row)          record = self.table.ReadRowAsDict(row)
75          return record[self.columns[col][0]]          return record[self.columns[col][0]]
76    
77      def SetValue(self, row, col, value):      def SetValue(self, row, col, value):
# Line 97  class DataTable(wxPyGridTableBase): Line 104  class DataTable(wxPyGridTableBase):
104    
105  class TableGrid(wxGrid, Publisher):  class TableGrid(wxGrid, Publisher):
106    
107      """A grid view for a Thuban table"""      """A grid view for a Thuban table
108    
109        When rows are selected by the user the table issues ROW_SELECTED
110        messages. wx sends selection events even when the selection is
111        manipulated by code (instead of by the user) which usually lead to
112        ROW_SELECTED messages being sent in turn. Therefore sending messages
113        can be switched on and off with the allow_messages and
114        disallow_messages methods.
115        """
116    
117      def __init__(self, parent, table = None):      def __init__(self, parent, table = None):
118          wxGrid.__init__(self, parent, -1)          wxGrid.__init__(self, parent, -1)
119    
120            self.allow_messages_count = 0
121    
122          self.table = DataTable(table)          self.table = DataTable(table)
123    
124          # The second parameter means that the grid is to take ownership          # The second parameter means that the grid is to take ownership
125          # of the table and will destroy it when done. Otherwise you          # of the table and will destroy it when done. Otherwise you
126          # would need to keep a reference to it and call its Destroy          # would need to keep a reference to it and call its Destroy
127          # method later.          # method later.
128          self.SetTable(self.table, true)          self.SetTable(self.table, True)
129    
130          #self.SetMargins(0,0)          #self.SetMargins(0,0)
131    
# Line 116  class TableGrid(wxGrid, Publisher): Line 133  class TableGrid(wxGrid, Publisher):
133          # column widths automatically but it would cause a traversal of          # column widths automatically but it would cause a traversal of
134          # the entire table which for large .dbf files can take a very          # the entire table which for large .dbf files can take a very
135          # long time.          # long time.
136          #self.AutoSizeColumns(false)          #self.AutoSizeColumns(False)
137    
138          self.SetSelectionMode(wxGrid.wxGridSelectRows)          self.SetSelectionMode(wxGrid.wxGridSelectRows)
139            
140          EVT_GRID_RANGE_SELECT(self, self.OnRangeSelect)          #EVT_GRID_RANGE_SELECT(self, None)
141          EVT_GRID_SELECT_CELL(self, self.OnSelectCell)          #EVT_GRID_SELECT_CELL(self, None)
142            #EVT_GRID_RANGE_SELECT(self, self.OnRangeSelect)
143            #EVT_GRID_SELECT_CELL(self, self.OnSelectCell)
144    
145            self.ToggleEventListeners(True)
146    
147      def SetTableObject(self, table):      def SetTableObject(self, table):
148          self.table.SetTable(table)          self.table.SetTable(table)
149    
150      def OnRangeSelect(self, event):      def OnRangeSelect(self, event):
151            rows = dict([(i, 0) for i in self.GetSelectedRows()])
152    
153            # if we're selecting we need to include the selected range and
154            # make sure that the current row is also included, which may
155            # not be the case if you just click on a single row!
156          if event.Selecting():          if event.Selecting():
157              self.issue(ROW_SELECTED, event.GetTopLeftCoords().GetRow())              for i in range(event.GetTopRow(), event.GetBottomRow() + 1):
158                    rows[i] = 0
159                rows[event.GetTopLeftCoords().GetRow()] = 0
160    
161            self.issue(ROW_SELECTED, rows.keys())
162            event.Skip()
163    
164      def OnSelectCell(self, event):      def OnSelectCell(self, event):
165          self.issue(ROW_SELECTED, event.GetRow())          self.issue(ROW_SELECTED, self.GetSelectedRows())
166            event.Skip()
167    
168      def select_shape(self, layer, shape):      def ToggleEventListeners(self, on):
169          if layer is not None and layer.table is self.table.table \          if on:
170             and shape is not None:              EVT_GRID_RANGE_SELECT(self, self.OnRangeSelect)
171              self.SelectRow(shape)              EVT_GRID_SELECT_CELL(self, self.OnSelectCell)
172              self.SetGridCursor(shape, 0)          else:
173              self.MakeCellVisible(shape, 0)              EVT_GRID_RANGE_SELECT(self, None)
174                EVT_GRID_SELECT_CELL(self, None)
175                
176        def disallow_messages(self):
177            """Disallow messages to be send.
178    
179            This method only increases a counter so that calls to
180            disallow_messages and allow_messages can be nested. Only the
181            outermost calls will actually switch message sending on and off.
182            """
183            self.allow_messages_count += 1
184    
185        def allow_messages(self):
186            """Allow messages to be send.
187    
188            This method only decreases a counter so that calls to
189            disallow_messages and allow_messages can be nested. Only the
190            outermost calls will actually switch message sending on and off.
191            """
192            self.allow_messages_count -= 1
193    
194        def issue(self, *args):
195            """Issue a message unless disallowed.
196    
197            See the allow_messages and disallow_messages methods.
198            """
199            if self.allow_messages_count == 0:
200                Publisher.issue(self, *args)
201    
202    
203    class LayerTableGrid(TableGrid):
204    
205        """Table grid for the layer tables.
206    
207        The LayerTableGrid is basically the same as a TableGrid but it's
208        selection is usually coupled to the selected object in the map.
209        """
210    
211        def select_shapes(self, layer, shapes):
212            """Select the row corresponding to the specified shape and layer
213    
214            If layer is not the layer the table is associated with do
215            nothing. If shape or layer is None also do nothing.
216            """
217            if layer is not None \
218                and layer.table is self.table.table:
219    
220                self.disallow_messages()
221                try:
222                    self.ClearSelection()
223                    if len(shapes) > 0:
224                        #
225                        # keep track of the lowest id so we can make it
226                        # the first visible item
227                        #
228                        first = shapes[0]
229    
230                        for shape in shapes:
231                            self.SelectRow(shape, True)
232                            if shape < first:
233                                first = shape
234    
235                        self.SetGridCursor(first, 0)
236                        self.MakeCellVisible(first, 0)
237                finally:
238                    self.allow_messages()
239    
240    
241  class TableFrame(NonModalDialog):  class TableFrame(NonModalNonParentDialog):
242    
243      """Frame that displays a Thuban table in a grid view"""      """Frame that displays a Thuban table in a grid view"""
244    
245      def __init__(self, parent, interactor, name, title, layer = None,      def __init__(self, parent, name, title, table):
246                   table = None):          NonModalNonParentDialog.__init__(self, parent, name, title)
         NonModalDialog.__init__(self, parent, interactor, name, title)  
         self.layer = layer  
247          self.table = table          self.table = table
248          self.grid = TableGrid(self, table)          self.grid = self.make_grid(self.table)
249          self.grid.Subscribe(ROW_SELECTED, self.row_selected)          self.app = self.parent.application
250          self.interactor.Subscribe(SELECTED_SHAPE, self.select_shape)          self.app.Subscribe(SESSION_REPLACED, self.close_on_session_replaced)
251            self.session = self.app.Session()
252            self.session.Subscribe(TABLE_REMOVED, self.close_on_table_removed)
253    
254        def make_grid(self, table):
255            """Return the table grid to use in the frame.
256    
257            The default implementation returns a TableGrid instance.
258            Override in derived classes to use different grid classes.
259            """
260            return TableGrid(self, table)
261    
262      def OnClose(self, event):      def OnClose(self, event):
263          self.interactor.Unsubscribe(SELECTED_SHAPE, self.select_shape)          self.app.Unsubscribe(SESSION_REPLACED, self.close_on_session_replaced)
264          NonModalDialog.OnClose(self, event)          self.session.Unsubscribe(TABLE_REMOVED, self.close_on_table_removed)
265            NonModalNonParentDialog.OnClose(self, event)
266    
267        def close_on_session_replaced(self, *args):
268            """Subscriber for the SESSION_REPLACED messages.
269    
270            The table frame is tied to a session so close the window when
271            the session changes.
272            """
273            self.Close()
274    
275        def close_on_table_removed(self, table):
276            """Subscriber for the TABLE_REMOVED messages.
277    
278            The table frame is tied to a particular table so close the
279            window when the table is removed.
280            """
281            if table is self.table:
282                self.Close()
283    
284    
285    ID_QUERY = 4001
286    ID_EXPORT = 4002
287    
288    class QueryTableFrame(TableFrame):
289    
290        """Frame that displays a table in a grid view and offers user actions
291        selection and export
292    
293        A LayerTableFrame is TableFrame whose selection is connected to the
294        selected object in a map.
295        """
296    
297        def __init__(self, parent, name, title, table):
298            TableFrame.__init__(self, parent, name, title, table)
299    
300            self.combo_fields = wxComboBox(self, -1, style=wxCB_READONLY)
301            self.choice_comp = wxChoice(self, -1,
302                                  choices=["<", "<=", "==", "!=", ">=", ">"])
303            self.combo_value = wxComboBox(self, -1)
304            self.choice_action = wxChoice(self, -1,
305                                    choices=[_("Replace Selection"),
306                                            _("Refine Selection"),
307                                            _("Add to Selection")])
308    
309            button_query = wxButton(self, ID_QUERY, _("Query"))
310            button_saveas = wxButton(self, ID_EXPORT, _("Export"))
311    
312            self.grid.SetSize((400, 200))
313    
314            self.combo_value.Append("")
315            for i in range(table.NumColumns()):
316                name = table.Column(i).name
317                self.combo_fields.Append(name)
318                self.combo_value.Append(name)
319    
320            # assume at least one field?
321            self.combo_fields.SetSelection(0)
322            self.combo_value.SetSelection(0)
323    
324            topBox = wxBoxSizer(wxVERTICAL)
325    
326            sizer = wxStaticBoxSizer(wxStaticBox(self, -1,
327                                      _("Selection")),
328                                      wxHORIZONTAL)
329            sizer.Add(self.combo_fields, 1, wxEXPAND|wxALL, 4)
330            sizer.Add(self.choice_comp, 0, wxALL, 4)
331            sizer.Add(self.combo_value, 1, wxEXPAND|wxALL, 4)
332            sizer.Add(self.choice_action, 0, wxALL, 4)
333            sizer.Add(button_query, 0, wxALL | wxALIGN_CENTER_VERTICAL, 4)
334            sizer.Add(40, 20, 0, wxALL, 4)
335            sizer.Add(button_saveas, 0, wxALL | wxALIGN_CENTER_VERTICAL, 4)
336    
337            topBox.Add(sizer, 0, wxEXPAND|wxALL, 4)
338            topBox.Add(self.grid, 1, wxEXPAND|wxALL, 0)
339    
340            self.SetAutoLayout(True)
341            self.SetSizer(topBox)
342            topBox.Fit(self)
343            topBox.SetSizeHints(self)
344    
345            self.grid.SetFocus()
346            EVT_BUTTON(self, ID_QUERY, self.OnQuery)
347            EVT_BUTTON(self, ID_EXPORT, self.OnSaveAs)
348            EVT_KEY_DOWN(self.grid, self.OnKeyDown)
349    
350        def OnKeyDown(self, event):
351            """Catch query key from grid"""
352            if event.AltDown() and event.GetKeyCode() == ord(QUERY_KEY):
353                self.combo_fields.SetFocus()
354                self.combo_fields.refocus = True
355            else:
356                event.Skip()
357    
358    
359        def OnQuery(self, event):
360            wxBeginBusyCursor()
361    
362            if self.combo_value.GetSelection() < 1:
363                value = self.combo_value.GetValue()
364            else:
365                value = self.table.Column(self.combo_value.GetValue())
366    
367            ids = self.table.SimpleQuery(
368                    self.table.Column(self.combo_fields.GetStringSelection()),
369                    self.choice_comp.GetStringSelection(),
370                    value)
371    
372            choice = self.choice_action.GetSelection()
373                
374            #
375            # what used to be nice code got became a bit ugly because
376            # each time we select a row a message is sent to the grid
377            # which we are listening for and then we send further
378            # messages.
379            #
380            # now, we disable those listeners select everything but
381            # the first item, reenable the listeners, and select
382            # the first element, which causes everything to be
383            # updated properly.
384            #
385            self.grid.ToggleEventListeners(False)
386    
387            if choice == 0:
388                # Replace Selection
389                self.grid.ClearSelection()
390            elif choice == 1:
391                # Refine Selection
392                sel = self.get_selected()
393                self.grid.ClearSelection()
394                ids = filter(sel.has_key, ids)
395            elif choice == 2:
396                # Add to Selection
397                pass
398    
399            #
400            # select the rows (all but the first)
401            #
402            firsttime = True
403            for id in ids:
404                if firsttime:
405                    firsttime = False
406                else:
407                    self.grid.SelectRow(id, True)
408    
409            self.grid.ToggleEventListeners(True)
410    
411            #
412            # select the first row
413            #
414            if ids:
415                self.grid.SelectRow(ids[0], True)
416    
417      def select_shape(self, layer, shape):          wxEndBusyCursor()
418          self.grid.select_shape(layer, shape)          
419        def OnSaveAs(self, event):
420            dlg = wxFileDialog(self, _("Export Table To"), ".", "",
421                               _("DBF Files (*.dbf)|*.dbf|") +
422                               _("CSV Files (*.csv)|*.csv|") +
423                               _("All Files (*.*)|*.*"),
424                               wxSAVE|wxOVERWRITE_PROMPT)
425            if dlg.ShowModal() == wxID_OK:
426                filename = dlg.GetPath()
427                type = os.path.basename(filename).split('.')[-1:][0]
428                dlg.Destroy()
429                if type.upper() == "DBF":
430                    table_to_dbf(self.table, filename)
431                elif type.upper() == 'CSV':
432                    table_to_csv(self.table, filename)
433                else:
434                    dlg = wxMessageDialog(None, "Unsupported format: %s" % type,
435                                          "Table Export", wxOK|wxICON_WARNING)
436                    dlg.ShowModal()
437                    dlg.Destroy()
438            else:
439                dlg.Destroy()
440    
441      def row_selected(self, row):      def OnClose(self, event):
442            TableFrame.OnClose(self, event)
443    
444        def get_selected(self):
445            """Return a dictionary of the selected rows.
446            
447            The dictionary has sthe indexes as keys."""
448            return dict([(i, 0) for i in self.grid.GetSelectedRows()])
449    
450    class LayerTableFrame(QueryTableFrame):
451    
452        """Frame that displays a layer table in a grid view
453    
454        A LayerTableFrame is a QueryTableFrame whose selection is connected to the
455        selected object in a map.
456        """
457    
458        def __init__(self, parent, name, title, layer, table):
459            QueryTableFrame.__init__(self, parent, name, title, table)
460            self.layer = layer
461            self.grid.Subscribe(ROW_SELECTED, self.rows_selected)
462            self.parent.Subscribe(SHAPES_SELECTED, self.select_shapes)
463    
464            # if there is already a selection present, update the grid
465            # accordingly
466            sel = self.get_selected().keys()
467            for i in sel:
468                self.grid.SelectRow(i, True)
469    
470        def make_grid(self, table):
471            """Override the derived method to return a LayerTableGrid.
472            """
473            return LayerTableGrid(self, table)
474    
475        def get_selected(self):
476            """Override the derived method to return a dictionary of the selected
477            rows.
478            """
479            return dict([(i, 0) for i in self.parent.SelectedShapes()])
480    
481        def OnClose(self, event):
482            """Override the derived method to first unsubscribed."""
483            self.parent.Unsubscribe(SHAPES_SELECTED, self.select_shapes)
484            QueryTableFrame.OnClose(self, event)
485    
486        def select_shapes(self, layer, shapes):
487            """Subscribed to the SHAPES_SELECTED message.
488    
489            If shapes contains exactly one shape id, select that shape in
490            the grid. Otherwise deselect all.
491            """
492            self.grid.select_shapes(layer, shapes)
493    
494        def rows_selected(self, rows):
495            """Return the selected rows of the layer as they are returned
496            by Layer.SelectShapes().
497            """
498          if self.layer is not None:          if self.layer is not None:
499              self.interactor.SelectLayerAndShape(self.layer, row)              self.parent.SelectShapes(self.layer, rows)

Legend:
Removed from v.173  
changed lines
  Added in v.1068

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26