/[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 1199 by jonathan, Fri Jun 13 15:04:28 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.table = table          self.table = table
289          self.grid = self.make_grid(self.table)          self.grid = self.make_grid(self.table)
290            self.app = self.parent.application
291            self.app.Subscribe(SESSION_REPLACED, self.close_on_session_replaced)
292            self.session = self.app.Session()
293            self.session.Subscribe(TABLE_REMOVED, self.close_on_table_removed)
294    
295      def make_grid(self, table):      def make_grid(self, table):
296          """Return the table grid to use in the frame.          """Return the table grid to use in the frame.
# Line 212  class TableFrame(NonModalDialog): Line 300  class TableFrame(NonModalDialog):
300          """          """
301          return TableGrid(self, table)          return TableGrid(self, table)
302    
303        def OnClose(self, event):
304            self.app.Unsubscribe(SESSION_REPLACED, self.close_on_session_replaced)
305            self.session.Unsubscribe(TABLE_REMOVED, self.close_on_table_removed)
306            ThubanFrame.OnClose(self, event)
307    
308        def close_on_session_replaced(self, *args):
309            """Subscriber for the SESSION_REPLACED messages.
310    
311            The table frame is tied to a session so close the window when
312            the session changes.
313            """
314            self.Close()
315    
316        def close_on_table_removed(self, table):
317            """Subscriber for the TABLE_REMOVED messages.
318    
319            The table frame is tied to a particular table so close the
320            window when the table is removed.
321            """
322            if table is self.table:
323                self.Close()
324    
325    
326  class LayerTableFrame(TableFrame):  ID_QUERY = 4001
327    ID_EXPORT = 4002
328    ID_COMBOVALUE = 4003
329    
330    class QueryTableFrame(TableFrame):
331    
332        """Frame that displays a table in a grid view and offers user actions
333        selection and export
334    
335        A QueryTableFrame is TableFrame whose selection is connected to the
336        selected object in a map.
337        """
338    
339        def __init__(self, parent, name, title, table):
340            TableFrame.__init__(self, parent, name, title, table)
341    
342            self.combo_fields = wxComboBox(self, -1, style=wxCB_READONLY)
343            self.choice_comp = wxChoice(self, -1,
344                                  choices=["<", "<=", "==", "!=", ">=", ">"])
345            self.combo_value = wxComboBox(self, ID_COMBOVALUE)
346            self.choice_action = wxChoice(self, -1,
347                                    choices=[_("Replace Selection"),
348                                            _("Refine Selection"),
349                                            _("Add to Selection")])
350    
351            button_query = wxButton(self, ID_QUERY, _("Query"))
352            button_saveas = wxButton(self, ID_EXPORT, _("Export"))
353    
354            self.CreateStatusBar()
355            self.SetStatusText(_("0 rows (0 selected), 0 columns"))
356    
357            self.grid.SetSize((400, 200))
358    
359            self.combo_value.Append("")
360            for i in range(table.NumColumns()):
361                name = table.Column(i).name
362                self.combo_fields.Append(name)
363                self.combo_value.Append(name)
364    
365            # assume at least one field?
366            self.combo_fields.SetSelection(0)
367            self.combo_value.SetSelection(0)
368            self.choice_action.SetSelection(0)
369            self.choice_comp.SetSelection(0)
370    
371            topBox = wxBoxSizer(wxVERTICAL)
372    
373            sizer = wxStaticBoxSizer(wxStaticBox(self, -1,
374                                      _("Selection")),
375                                      wxHORIZONTAL)
376            sizer.Add(self.combo_fields, 1, wxEXPAND|wxALL, 4)
377            sizer.Add(self.choice_comp, 0, wxALL, 4)
378            sizer.Add(self.combo_value, 1, wxEXPAND|wxALL, 4)
379            sizer.Add(self.choice_action, 0, wxALL, 4)
380            sizer.Add(button_query, 0, wxALL | wxALIGN_CENTER_VERTICAL, 4)
381            sizer.Add(40, 20, 0, wxALL, 4)
382            sizer.Add(button_saveas, 0, wxALL | wxALIGN_CENTER_VERTICAL, 4)
383    
384            topBox.Add(sizer, 0, wxEXPAND|wxALL, 4)
385            topBox.Add(self.grid, 1, wxEXPAND|wxALL, 0)
386    
387            self.SetAutoLayout(True)
388            self.SetSizer(topBox)
389            topBox.Fit(self)
390            topBox.SetSizeHints(self)
391    
392            self.grid.SetFocus()
393            EVT_BUTTON(self, ID_QUERY, self.OnQuery)
394            EVT_BUTTON(self, ID_EXPORT, self.OnSaveAs)
395            EVT_KEY_DOWN(self.grid, self.OnKeyDown)
396            EVT_GRID_RANGE_SELECT(self.grid, self.OnGridSelectRange)
397            EVT_GRID_SELECT_CELL(self.grid, self.OnGridSelectCell)
398    
399        def UpdateStatusText(self):
400            self.SetStatusText(_("%i rows (%i selected), %i columns")
401                % (self.grid.GetNumberRows(),
402                   self.grid.GetNumberSelected(),
403                   self.grid.GetNumberCols()))
404    
405        def OnGridSelectRange(self, event):
406            self.UpdateStatusText()
407            event.Skip()
408    
409        def OnGridSelectCell(self, event):
410            self.UpdateStatusText()
411            event.Skip()
412            
413        def OnKeyDown(self, event):
414            """Catch query key from grid"""
415            if event.AltDown() and event.GetKeyCode() == ord(QUERY_KEY):
416                self.combo_fields.SetFocus()
417                self.combo_fields.refocus = True
418            else:
419                event.Skip()
420    
421        def OnQuery(self, event):
422            wxBeginBusyCursor()
423            try:
424    
425                text = self.combo_value.GetValue()
426                if self.combo_value.GetSelection() < 1 \
427                    or self.combo_value.FindString(text) == -1:
428                    value = text
429                else:
430                    value = self.table.Column(text)
431    
432                ids = self.table.SimpleQuery(
433                        self.table.Column(self.combo_fields.GetStringSelection()),
434                        self.choice_comp.GetStringSelection(),
435                        value)
436    
437                choice = self.choice_action.GetSelection()
438                
439                #
440                # what used to be nice code got became a bit ugly because
441                # each time we select a row a message is sent to the grid
442                # which we are listening for and then we send further
443                # messages.
444                #
445                # now, we disable those listeners select everything but
446                # the first item, reenable the listeners, and select
447                # the first element, which causes everything to be
448                # updated properly.
449                #
450                if ids:
451                    self.grid.ToggleEventListeners(False)
452    
453                if choice == 0:
454                    # Replace Selection
455                    self.grid.ClearSelection()
456                elif choice == 1:
457                    # Refine Selection
458                    sel = self.get_selected()
459                    self.grid.ClearSelection()
460                    ids = filter(sel.has_key, ids)
461                elif choice == 2:
462                    # Add to Selection
463                    pass
464    
465                #
466                # select the rows (all but the first)
467                #
468                firsttime = True
469                for id in ids:
470                    if firsttime:
471                        firsttime = False
472                    else:
473                        self.grid.SelectRow(id, True)
474    
475                self.grid.ToggleEventListeners(True)
476    
477                #
478                # select the first row
479                #
480                if ids:
481                    self.grid.SelectRow(ids[0], True)
482    
483            finally:
484                wxEndBusyCursor()
485            
486        def OnSaveAs(self, event):
487            dlg = wxFileDialog(self, _("Export Table To"), ".", "",
488                               _("DBF Files (*.dbf)|*.dbf|") +
489                               _("CSV Files (*.csv)|*.csv|") +
490                               _("All Files (*.*)|*.*"),
491                               wxSAVE|wxOVERWRITE_PROMPT)
492            if dlg.ShowModal() == wxID_OK:
493                filename = dlg.GetPath()
494                type = os.path.basename(filename).split('.')[-1:][0]
495                dlg.Destroy()
496                if type.upper() == "DBF":
497                    table_to_dbf(self.table, filename)
498                elif type.upper() == 'CSV':
499                    table_to_csv(self.table, filename)
500                else:
501                    dlg = wxMessageDialog(None, "Unsupported format: %s" % type,
502                                          "Table Export", wxOK|wxICON_WARNING)
503                    dlg.ShowModal()
504                    dlg.Destroy()
505            else:
506                dlg.Destroy()
507    
508        def OnClose(self, event):
509            TableFrame.OnClose(self, event)
510    
511        def get_selected(self):
512            """Return a dictionary of the selected rows.
513            
514            The dictionary has sthe indexes as keys."""
515            return dict([(i, 0) for i in self.grid.GetSelectedRows()])
516    
517    class LayerTableFrame(QueryTableFrame):
518    
519      """Frame that displays a layer table in a grid view      """Frame that displays a layer table in a grid view
520    
521      A LayerTableFrame is TableFrame whose selection is connected to the      A LayerTableFrame is a QueryTableFrame whose selection is connected to the
522      selected object in a map.      selected object in a map.
523      """      """
524    
525      def __init__(self, parent, name, title, layer, table):      def __init__(self, parent, name, title, layer, table):
526          TableFrame.__init__(self, parent, name, title, table)          QueryTableFrame.__init__(self, parent, name, title, table)
527          self.layer = layer          self.layer = layer
528          self.grid.Subscribe(ROW_SELECTED, self.row_selected)          self.grid.Subscribe(ROW_SELECTED, self.rows_selected)
529          self.parent.Subscribe(SHAPES_SELECTED, self.select_shape)          self.parent.Subscribe(SHAPES_SELECTED, self.select_shapes)
530            self.map = self.parent.Map()
531            self.map.Subscribe(MAP_LAYERS_REMOVED, self.map_layers_removed)
532    
533            # if there is already a selection present, update the grid
534            # accordingly
535            sel = self.get_selected().keys()
536            for i in sel:
537                self.grid.SelectRow(i, True)
538    
539      def make_grid(self, table):      def make_grid(self, table):
540          """Override the derived method to return a LayerTableGrid.          """Override the derived method to return a LayerTableGrid.
541          """          """
542          return LayerTableGrid(self, table)          return LayerTableGrid(self, table)
543    
544        def get_selected(self):
545            """Override the derived method to return a dictionary of the selected
546            rows.
547            """
548            return dict([(i, 0) for i in self.parent.SelectedShapes()])
549    
550      def OnClose(self, event):      def OnClose(self, event):
551          self.parent.Unsubscribe(SHAPES_SELECTED, self.select_shape)          """Override the derived method to first unsubscribed."""
552          TableFrame.OnClose(self, event)          self.parent.Unsubscribe(SHAPES_SELECTED, self.select_shapes)
553            self.grid.Unsubscribe(ROW_SELECTED, self.rows_selected)
554            self.map.Unsubscribe(MAP_LAYERS_REMOVED, self.map_layers_removed)
555            QueryTableFrame.OnClose(self, event)
556    
557      def select_shape(self, layer, shapes):      def select_shapes(self, layer, shapes):
558          """Subscribed to the SHAPES_SELECTED message.          """Subscribed to the SHAPES_SELECTED message.
559    
560          If shapes contains exactly one shape id, select that shape in          If shapes contains exactly one shape id, select that shape in
561          the grid. Otherwise deselect all.          the grid. Otherwise deselect all.
562          """          """
563          if len(shapes):          self.grid.select_shapes(layer, shapes)
             shape = shapes[0]  
         else:  
             shape = None  
         self.grid.select_shape(layer, shape)  
564    
565      def row_selected(self, row):      def rows_selected(self, rows):
566            """Return the selected rows of the layer as they are returned
567            by Layer.SelectShapes().
568            """
569          if self.layer is not None:          if self.layer is not None:
570              self.parent.SelectShapes(self.layer, [row])              self.parent.SelectShapes(self.layer, rows)
571    
572        def map_layers_removed(self, *args):
573            """Receiver for the map's MAP_LAYERS_REMOVED message
574    
575            Close the dialog if the layer whose table we're showing is not
576            in the map anymore.
577            """
578            if self.layer not in self.map.Layers():
579                self.Close()
580    

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26