/[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 1096 by jan, Thu May 29 10:03:07 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
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    
397            if self.combo_value.GetSelection() < 1:
398                value = self.combo_value.GetValue()
399            else:
400                value = self.table.Column(self.combo_value.GetValue())
401    
402            ids = self.table.SimpleQuery(
403                    self.table.Column(self.combo_fields.GetStringSelection()),
404                    self.choice_comp.GetStringSelection(),
405                    value)
406    
407            choice = self.choice_action.GetSelection()
408                
409            #
410            # what used to be nice code got became a bit ugly because
411            # each time we select a row a message is sent to the grid
412            # which we are listening for and then we send further
413            # messages.
414            #
415            # now, we disable those listeners select everything but
416            # the first item, reenable the listeners, and select
417            # the first element, which causes everything to be
418            # updated properly.
419            #
420            self.grid.ToggleEventListeners(False)
421    
422            if choice == 0:
423                # Replace Selection
424                self.grid.ClearSelection()
425            elif choice == 1:
426                # Refine Selection
427                sel = self.get_selected()
428                self.grid.ClearSelection()
429                ids = filter(sel.has_key, ids)
430            elif choice == 2:
431                # Add to Selection
432                pass
433    
434            #
435            # select the rows (all but the first)
436            #
437            firsttime = True
438            for id in ids:
439                if firsttime:
440                    firsttime = False
441                else:
442                    self.grid.SelectRow(id, True)
443    
444            self.grid.ToggleEventListeners(True)
445    
446            #
447            # select the first row
448            #
449            if ids:
450                self.grid.SelectRow(ids[0], True)
451    
452            wxEndBusyCursor()
453            
454        def OnSaveAs(self, event):
455            dlg = wxFileDialog(self, _("Export Table To"), ".", "",
456                               _("DBF Files (*.dbf)|*.dbf|") +
457                               _("CSV Files (*.csv)|*.csv|") +
458                               _("All Files (*.*)|*.*"),
459                               wxSAVE|wxOVERWRITE_PROMPT)
460            if dlg.ShowModal() == wxID_OK:
461                filename = dlg.GetPath()
462                type = os.path.basename(filename).split('.')[-1:][0]
463                dlg.Destroy()
464                if type.upper() == "DBF":
465                    table_to_dbf(self.table, filename)
466                elif type.upper() == 'CSV':
467                    table_to_csv(self.table, filename)
468                else:
469                    dlg = wxMessageDialog(None, "Unsupported format: %s" % type,
470                                          "Table Export", wxOK|wxICON_WARNING)
471                    dlg.ShowModal()
472                    dlg.Destroy()
473            else:
474                dlg.Destroy()
475    
476        def OnClose(self, event):
477            TableFrame.OnClose(self, event)
478    
479        def get_selected(self):
480            """Return a dictionary of the selected rows.
481            
482            The dictionary has sthe indexes as keys."""
483            return dict([(i, 0) for i in self.grid.GetSelectedRows()])
484    
485    class LayerTableFrame(QueryTableFrame):
486    
487      """Frame that displays a layer table in a grid view      """Frame that displays a layer table in a grid view
488    
489      A LayerTableFrame is TableFrame whose selection is connected to the      A LayerTableFrame is a QueryTableFrame whose selection is connected to the
490      selected object in a map.      selected object in a map.
491      """      """
492    
493      def __init__(self, parent, name, title, layer, table):      def __init__(self, parent, name, title, layer, table):
494          TableFrame.__init__(self, parent, name, title, table)          QueryTableFrame.__init__(self, parent, name, title, table)
495          self.layer = layer          self.layer = layer
496          self.grid.Subscribe(ROW_SELECTED, self.row_selected)          self.grid.Subscribe(ROW_SELECTED, self.rows_selected)
497          self.parent.Subscribe(SHAPES_SELECTED, self.select_shape)          self.parent.Subscribe(SHAPES_SELECTED, self.select_shapes)
498    
499            # if there is already a selection present, update the grid
500            # accordingly
501            sel = self.get_selected().keys()
502            for i in sel:
503                self.grid.SelectRow(i, True)
504    
505      def make_grid(self, table):      def make_grid(self, table):
506          """Override the derived method to return a LayerTableGrid.          """Override the derived method to return a LayerTableGrid.
507          """          """
508          return LayerTableGrid(self, table)          return LayerTableGrid(self, table)
509    
510        def get_selected(self):
511            """Override the derived method to return a dictionary of the selected
512            rows.
513            """
514            return dict([(i, 0) for i in self.parent.SelectedShapes()])
515    
516      def OnClose(self, event):      def OnClose(self, event):
517          self.parent.Unsubscribe(SHAPES_SELECTED, self.select_shape)          """Override the derived method to first unsubscribed."""
518          TableFrame.OnClose(self, event)          self.parent.Unsubscribe(SHAPES_SELECTED, self.select_shapes)
519            self.grid.Unsubscribe(ROW_SELECTED, self.rows_selected)
520            QueryTableFrame.OnClose(self, event)
521    
522      def select_shape(self, layer, shapes):      def select_shapes(self, layer, shapes):
523          """Subscribed to the SHAPES_SELECTED message.          """Subscribed to the SHAPES_SELECTED message.
524    
525          If shapes contains exactly one shape id, select that shape in          If shapes contains exactly one shape id, select that shape in
526          the grid. Otherwise deselect all.          the grid. Otherwise deselect all.
527          """          """
528          if len(shapes):          self.grid.select_shapes(layer, shapes)
             shape = shapes[0]  
         else:  
             shape = None  
         self.grid.select_shape(layer, shape)  
529    
530      def row_selected(self, row):      def rows_selected(self, rows):
531            """Return the selected rows of the layer as they are returned
532            by Layer.SelectShapes().
533            """
534          if self.layer is not None:          if self.layer is not None:
535              self.parent.SelectShapes(self.layer, [row])              self.parent.SelectShapes(self.layer, rows)

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26