/[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 808 by bh, Mon May 5 10:33:47 2003 UTC revision 1146 by bh, Tue Jun 10 12:00:16 2003 UTC
# 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 SHAPES_SELECTED  
23    from messages import SHAPES_SELECTED, SESSION_REPLACED
24    from Thuban.Model.messages import TABLE_REMOVED, MAP_LAYERS_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 95  class DataTable(wxPyGridTableBase): Line 102  class DataTable(wxPyGridTableBase):
102          return self.CanGetValueAs(row, col, typeName)          return self.CanGetValueAs(row, col, typeName)
103    
104    
105    
106    class NullRenderer(wxPyGridCellRenderer):
107    
108        """Renderer that draws NULL as a gray rectangle
109    
110        Other values are delegated to a normal renderer which is given as
111        the parameter to the constructor.
112        """
113    
114        def __init__(self, non_null_renderer):
115            wxPyGridCellRenderer.__init__(self)
116            self.non_null_renderer = non_null_renderer
117    
118        def Draw(self, grid, attr, dc, rect, row, col, isSelected):
119            value = grid.table.GetValue(row, col)
120            if value is None:
121                dc.SetBackgroundMode(wxSOLID)
122                dc.SetBrush(wxBrush(wxColour(192, 192, 192), wxSOLID))
123                dc.SetPen(wxTRANSPARENT_PEN)
124                dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height)
125            else:
126                self.non_null_renderer.Draw(grid, attr, dc, rect, row, col,
127                                            isSelected)
128    
129        def GetBestSize(self, grid, attr, dc, row, col):
130            self.non_null_renderer.GetBestSize(grid, attr, dc, row, col)
131    
132        def Clone(self):
133            return NullRenderer(self.non_null_renderer)
134    
135    
136  class TableGrid(wxGrid, Publisher):  class TableGrid(wxGrid, Publisher):
137    
138      """A grid view for a Thuban table      """A grid view for a Thuban table
# Line 118  class TableGrid(wxGrid, Publisher): Line 156  class TableGrid(wxGrid, Publisher):
156          # of the table and will destroy it when done. Otherwise you          # of the table and will destroy it when done. Otherwise you
157          # would need to keep a reference to it and call its Destroy          # would need to keep a reference to it and call its Destroy
158          # method later.          # method later.
159          self.SetTable(self.table, true)          self.SetTable(self.table, True)
160    
161          #self.SetMargins(0,0)          #self.SetMargins(0,0)
162    
# Line 126  class TableGrid(wxGrid, Publisher): Line 164  class TableGrid(wxGrid, Publisher):
164          # column widths automatically but it would cause a traversal of          # column widths automatically but it would cause a traversal of
165          # the entire table which for large .dbf files can take a very          # the entire table which for large .dbf files can take a very
166          # long time.          # long time.
167          #self.AutoSizeColumns(false)          #self.AutoSizeColumns(False)
168    
169          self.SetSelectionMode(wxGrid.wxGridSelectRows)          self.SetSelectionMode(wxGrid.wxGridSelectRows)
170    
171          EVT_GRID_RANGE_SELECT(self, self.OnRangeSelect)          self.ToggleEventListeners(True)
172          EVT_GRID_SELECT_CELL(self, self.OnSelectCell)  
173            # Replace the normal renderers with our own versions which
174            # render NULL/None values specially
175            self.RegisterDataType(wxGRID_VALUE_STRING,
176                                  NullRenderer(wxGridCellStringRenderer()), None)
177            self.RegisterDataType(wxGRID_VALUE_NUMBER,
178                                  NullRenderer(wxGridCellNumberRenderer()), None)
179            self.RegisterDataType(wxGRID_VALUE_FLOAT,
180                                  NullRenderer(wxGridCellFloatRenderer()), None)
181    
182      def SetTableObject(self, table):      def SetTableObject(self, table):
183          self.table.SetTable(table)          self.table.SetTable(table)
184    
185      def OnRangeSelect(self, event):      def OnRangeSelect(self, event):
186            rows = dict([(i, 0) for i in self.GetSelectedRows()])
187    
188            # if we're selecting we need to include the selected range and
189            # make sure that the current row is also included, which may
190            # not be the case if you just click on a single row!
191          if event.Selecting():          if event.Selecting():
192              self.issue(ROW_SELECTED, event.GetTopLeftCoords().GetRow())              for i in range(event.GetTopRow(), event.GetBottomRow() + 1):
193                    rows[i] = 0
194                rows[event.GetTopLeftCoords().GetRow()] = 0
195    
196            self.issue(ROW_SELECTED, rows.keys())
197            event.Skip()
198    
199      def OnSelectCell(self, event):      def OnSelectCell(self, event):
200          self.issue(ROW_SELECTED, event.GetRow())          self.issue(ROW_SELECTED, self.GetSelectedRows())
201            event.Skip()
202    
203        def ToggleEventListeners(self, on):
204            if on:
205                EVT_GRID_RANGE_SELECT(self, self.OnRangeSelect)
206                EVT_GRID_SELECT_CELL(self, self.OnSelectCell)
207            else:
208                EVT_GRID_RANGE_SELECT(self, None)
209                EVT_GRID_SELECT_CELL(self, None)
210                
211      def disallow_messages(self):      def disallow_messages(self):
212          """Disallow messages to be send.          """Disallow messages to be send.
213    
# Line 178  class LayerTableGrid(TableGrid): Line 243  class LayerTableGrid(TableGrid):
243      selection is usually coupled to the selected object in the map.      selection is usually coupled to the selected object in the map.
244      """      """
245    
246      def select_shape(self, layer, shape):      def select_shapes(self, layer, shapes):
247          """Select the row corresponding to the specified shape and layer          """Select the row corresponding to the specified shape and layer
248    
249          If layer is not the layer the table is associated with do          If layer is not the layer the table is associated with do
250          nothing. If shape or layer is None also do nothing.          nothing. If shape or layer is None also do nothing.
251          """          """
252          if layer is not None and layer.table is self.table.table \          if layer is not None \
253             and shape is not None:              and layer.table is self.table.table:
254    
255              self.disallow_messages()              self.disallow_messages()
256              try:              try:
257                  self.SelectRow(shape)                  self.ClearSelection()
258                  self.SetGridCursor(shape, 0)                  if len(shapes) > 0:
259                  self.MakeCellVisible(shape, 0)                      #
260                        # keep track of the lowest id so we can make it
261                        # the first visible item
262                        #
263                        first = shapes[0]
264    
265                        for shape in shapes:
266                            self.SelectRow(shape, True)
267                            if shape < first:
268                                first = shape
269    
270                        self.SetGridCursor(first, 0)
271                        self.MakeCellVisible(first, 0)
272              finally:              finally:
273                  self.allow_messages()                  self.allow_messages()
274    
275    
276  class TableFrame(NonModalDialog):  class TableFrame(NonModalNonParentDialog):
277    
278      """Frame that displays a Thuban table in a grid view"""      """Frame that displays a Thuban table in a grid view"""
279    
280      def __init__(self, parent, name, title, table):      def __init__(self, parent, name, title, table):
281          NonModalDialog.__init__(self, parent, name, title)          NonModalNonParentDialog.__init__(self, parent, name, title)
282          self.table = table          self.table = table
283          self.grid = self.make_grid(self.table)          self.grid = self.make_grid(self.table)
284            self.app = self.parent.application
285            self.app.Subscribe(SESSION_REPLACED, self.close_on_session_replaced)
286            self.session = self.app.Session()
287            self.session.Subscribe(TABLE_REMOVED, self.close_on_table_removed)
288    
289      def make_grid(self, table):      def make_grid(self, table):
290          """Return the table grid to use in the frame.          """Return the table grid to use in the frame.
# Line 212  class TableFrame(NonModalDialog): Line 294  class TableFrame(NonModalDialog):
294          """          """
295          return TableGrid(self, table)          return TableGrid(self, table)
296    
297        def OnClose(self, event):
298            self.app.Unsubscribe(SESSION_REPLACED, self.close_on_session_replaced)
299            self.session.Unsubscribe(TABLE_REMOVED, self.close_on_table_removed)
300            NonModalNonParentDialog.OnClose(self, event)
301    
302        def close_on_session_replaced(self, *args):
303            """Subscriber for the SESSION_REPLACED messages.
304    
305            The table frame is tied to a session so close the window when
306            the session changes.
307            """
308            self.Close()
309    
310        def close_on_table_removed(self, table):
311            """Subscriber for the TABLE_REMOVED messages.
312    
313            The table frame is tied to a particular table so close the
314            window when the table is removed.
315            """
316            if table is self.table:
317                self.Close()
318    
319    
320    ID_QUERY = 4001
321    ID_EXPORT = 4002
322    
323  class LayerTableFrame(TableFrame):  class QueryTableFrame(TableFrame):
324    
325        """Frame that displays a table in a grid view and offers user actions
326        selection and export
327    
328        A QueryTableFrame is TableFrame whose selection is connected to the
329        selected object in a map.
330        """
331    
332        def __init__(self, parent, name, title, table):
333            TableFrame.__init__(self, parent, name, title, table)
334    
335            self.combo_fields = wxComboBox(self, -1, style=wxCB_READONLY)
336            self.choice_comp = wxChoice(self, -1,
337                                  choices=["<", "<=", "==", "!=", ">=", ">"])
338            self.combo_value = wxComboBox(self, -1)
339            self.choice_action = wxChoice(self, -1,
340                                    choices=[_("Replace Selection"),
341                                            _("Refine Selection"),
342                                            _("Add to Selection")])
343    
344            button_query = wxButton(self, ID_QUERY, _("Query"))
345            button_saveas = wxButton(self, ID_EXPORT, _("Export"))
346    
347            self.grid.SetSize((400, 200))
348    
349            self.combo_value.Append("")
350            for i in range(table.NumColumns()):
351                name = table.Column(i).name
352                self.combo_fields.Append(name)
353                self.combo_value.Append(name)
354    
355            # assume at least one field?
356            self.combo_fields.SetSelection(0)
357            self.combo_value.SetSelection(0)
358    
359            topBox = wxBoxSizer(wxVERTICAL)
360    
361            sizer = wxStaticBoxSizer(wxStaticBox(self, -1,
362                                      _("Selection")),
363                                      wxHORIZONTAL)
364            sizer.Add(self.combo_fields, 1, wxEXPAND|wxALL, 4)
365            sizer.Add(self.choice_comp, 0, wxALL, 4)
366            sizer.Add(self.combo_value, 1, wxEXPAND|wxALL, 4)
367            sizer.Add(self.choice_action, 0, wxALL, 4)
368            sizer.Add(button_query, 0, wxALL | wxALIGN_CENTER_VERTICAL, 4)
369            sizer.Add(40, 20, 0, wxALL, 4)
370            sizer.Add(button_saveas, 0, wxALL | wxALIGN_CENTER_VERTICAL, 4)
371    
372            topBox.Add(sizer, 0, wxEXPAND|wxALL, 4)
373            topBox.Add(self.grid, 1, wxEXPAND|wxALL, 0)
374    
375            self.SetAutoLayout(True)
376            self.SetSizer(topBox)
377            topBox.Fit(self)
378            topBox.SetSizeHints(self)
379    
380            self.grid.SetFocus()
381            EVT_BUTTON(self, ID_QUERY, self.OnQuery)
382            EVT_BUTTON(self, ID_EXPORT, self.OnSaveAs)
383            EVT_KEY_DOWN(self.grid, self.OnKeyDown)
384    
385        def OnKeyDown(self, event):
386            """Catch query key from grid"""
387            if event.AltDown() and event.GetKeyCode() == ord(QUERY_KEY):
388                self.combo_fields.SetFocus()
389                self.combo_fields.refocus = True
390            else:
391                event.Skip()
392    
393    
394        def OnQuery(self, event):
395            wxBeginBusyCursor()
396            try:
397    
398                if self.combo_value.GetSelection() < 1:
399                    value = self.combo_value.GetValue()
400                else:
401                    value = self.table.Column(self.combo_value.GetValue())
402    
403                ids = self.table.SimpleQuery(
404                        self.table.Column(self.combo_fields.GetStringSelection()),
405                        self.choice_comp.GetStringSelection(),
406                        value)
407    
408                choice = self.choice_action.GetSelection()
409                
410                #
411                # what used to be nice code got became a bit ugly because
412                # each time we select a row a message is sent to the grid
413                # which we are listening for and then we send further
414                # messages.
415                #
416                # now, we disable those listeners select everything but
417                # the first item, reenable the listeners, and select
418                # the first element, which causes everything to be
419                # updated properly.
420                #
421                self.grid.ToggleEventListeners(False)
422    
423                if choice == 0:
424                    # Replace Selection
425                    self.grid.ClearSelection()
426                elif choice == 1:
427                    # Refine Selection
428                    sel = self.get_selected()
429                    self.grid.ClearSelection()
430                    ids = filter(sel.has_key, ids)
431                elif choice == 2:
432                    # Add to Selection
433                    pass
434    
435                #
436                # select the rows (all but the first)
437                #
438                firsttime = True
439                for id in ids:
440                    if firsttime:
441                        firsttime = False
442                    else:
443                        self.grid.SelectRow(id, True)
444    
445                self.grid.ToggleEventListeners(True)
446    
447                #
448                # select the first row
449                #
450                if ids:
451                    self.grid.SelectRow(ids[0], True)
452            finally:
453                wxEndBusyCursor()
454            
455        def OnSaveAs(self, event):
456            dlg = wxFileDialog(self, _("Export Table To"), ".", "",
457                               _("DBF Files (*.dbf)|*.dbf|") +
458                               _("CSV Files (*.csv)|*.csv|") +
459                               _("All Files (*.*)|*.*"),
460                               wxSAVE|wxOVERWRITE_PROMPT)
461            if dlg.ShowModal() == wxID_OK:
462                filename = dlg.GetPath()
463                type = os.path.basename(filename).split('.')[-1:][0]
464                dlg.Destroy()
465                if type.upper() == "DBF":
466                    table_to_dbf(self.table, filename)
467                elif type.upper() == 'CSV':
468                    table_to_csv(self.table, filename)
469                else:
470                    dlg = wxMessageDialog(None, "Unsupported format: %s" % type,
471                                          "Table Export", wxOK|wxICON_WARNING)
472                    dlg.ShowModal()
473                    dlg.Destroy()
474            else:
475                dlg.Destroy()
476    
477        def OnClose(self, event):
478            TableFrame.OnClose(self, event)
479    
480        def get_selected(self):
481            """Return a dictionary of the selected rows.
482            
483            The dictionary has sthe indexes as keys."""
484            return dict([(i, 0) for i in self.grid.GetSelectedRows()])
485    
486    class LayerTableFrame(QueryTableFrame):
487    
488      """Frame that displays a layer table in a grid view      """Frame that displays a layer table in a grid view
489    
490      A LayerTableFrame is TableFrame whose selection is connected to the      A LayerTableFrame is a QueryTableFrame whose selection is connected to the
491      selected object in a map.      selected object in a map.
492      """      """
493    
494      def __init__(self, parent, name, title, layer, table):      def __init__(self, parent, name, title, layer, table):
495          TableFrame.__init__(self, parent, name, title, table)          QueryTableFrame.__init__(self, parent, name, title, table)
496          self.layer = layer          self.layer = layer
497          self.grid.Subscribe(ROW_SELECTED, self.row_selected)          self.grid.Subscribe(ROW_SELECTED, self.rows_selected)
498          self.parent.Subscribe(SHAPES_SELECTED, self.select_shape)          self.parent.Subscribe(SHAPES_SELECTED, self.select_shapes)
499            self.map = self.parent.Map()
500            self.map.Subscribe(MAP_LAYERS_REMOVED, self.map_layers_removed)
501    
502            # if there is already a selection present, update the grid
503            # accordingly
504            sel = self.get_selected().keys()
505            for i in sel:
506                self.grid.SelectRow(i, True)
507    
508      def make_grid(self, table):      def make_grid(self, table):
509          """Override the derived method to return a LayerTableGrid.          """Override the derived method to return a LayerTableGrid.
510          """          """
511          return LayerTableGrid(self, table)          return LayerTableGrid(self, table)
512    
513        def get_selected(self):
514            """Override the derived method to return a dictionary of the selected
515            rows.
516            """
517            return dict([(i, 0) for i in self.parent.SelectedShapes()])
518    
519      def OnClose(self, event):      def OnClose(self, event):
520          self.parent.Unsubscribe(SHAPES_SELECTED, self.select_shape)          """Override the derived method to first unsubscribed."""
521          TableFrame.OnClose(self, event)          self.parent.Unsubscribe(SHAPES_SELECTED, self.select_shapes)
522            self.grid.Unsubscribe(ROW_SELECTED, self.rows_selected)
523            self.map.Unsubscribe(MAP_LAYERS_REMOVED, self.map_layers_removed)
524            QueryTableFrame.OnClose(self, event)
525    
526      def select_shape(self, layer, shapes):      def select_shapes(self, layer, shapes):
527          """Subscribed to the SHAPES_SELECTED message.          """Subscribed to the SHAPES_SELECTED message.
528    
529          If shapes contains exactly one shape id, select that shape in          If shapes contains exactly one shape id, select that shape in
530          the grid. Otherwise deselect all.          the grid. Otherwise deselect all.
531          """          """
532          if len(shapes):          self.grid.select_shapes(layer, shapes)
             shape = shapes[0]  
         else:  
             shape = None  
         self.grid.select_shape(layer, shape)  
533    
534      def row_selected(self, row):      def rows_selected(self, rows):
535            """Return the selected rows of the layer as they are returned
536            by Layer.SelectShapes().
537            """
538          if self.layer is not None:          if self.layer is not None:
539              self.parent.SelectShapes(self.layer, [row])              self.parent.SelectShapes(self.layer, rows)
540    
541        def map_layers_removed(self, *args):
542            """Receiver for the map's MAP_LAYERS_REMOVED message
543    
544            Close the dialog if the layer whose table we're showing is not
545            in the map anymore.
546            """
547            if self.layer not in self.map.Layers():
548                self.Close()
549    

Legend:
Removed from v.808  
changed lines
  Added in v.1146

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26