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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26