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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26