/[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 966 by jonathan, Wed May 21 17:24:40 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 _  from Thuban import _
13    
14  from wxPython.wx import *  from wxPython.wx import *
# Line 14  from wxPython.grid import * Line 16  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 25  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 97  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 114  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 128  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    
         #EVT_GRID_RANGE_SELECT(self, None)  
         #EVT_GRID_SELECT_CELL(self, None)  
         #EVT_GRID_RANGE_SELECT(self, self.OnRangeSelect)  
         #EVT_GRID_SELECT_CELL(self, self.OnSelectCell)  
   
174          self.ToggleEventListeners(True)          self.ToggleEventListeners(True)
175            EVT_GRID_RANGE_SELECT(self, self.OnRangeSelect)
176            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          rows = dict([(i, 0) for i in self.GetSelectedRows()])          if self.handleSelectEvents:
192                self.rows = dict([(i, 0) for i in self.GetSelectedRows()])
193    
194          # if we're selecting we need to include the selected range and              # if we're selecting we need to include the selected range and
195          # make sure that the current row is also included, which may              # make sure that the current row is also included, which may
196          # not be the case if you just click on a single row!              # not be the case if you just click on a single row!
197          if event.Selecting():              if event.Selecting():
198              for i in range(event.GetTopRow(), event.GetBottomRow() + 1):                  for i in range(event.GetTopRow(), event.GetBottomRow() + 1):
199                  rows[i] = 0                      self.rows[i] = 0
200              rows[event.GetTopLeftCoords().GetRow()] = 0                  self.rows[event.GetTopLeftCoords().GetRow()] = 0
201        
202                self.issue(ROW_SELECTED, self.rows.keys())
203    
         self.issue(ROW_SELECTED, rows.keys())  
204          event.Skip()          event.Skip()
205    
206      def OnSelectCell(self, event):      def OnSelectCell(self, event):
207          self.issue(ROW_SELECTED, self.GetSelectedRows())          if self.handleSelectEvents:
208                self.issue(ROW_SELECTED, self.GetSelectedRows())
209          event.Skip()          event.Skip()
210    
211      def ToggleEventListeners(self, on):      def ToggleEventListeners(self, on):
212          if on:          self.handleSelectEvents = on
             EVT_GRID_RANGE_SELECT(self, self.OnRangeSelect)  
             EVT_GRID_SELECT_CELL(self, self.OnSelectCell)  
         else:  
             EVT_GRID_RANGE_SELECT(self, None)  
             EVT_GRID_SELECT_CELL(self, None)  
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.
219    
# Line 233  class LayerTableGrid(TableGrid): Line 279  class LayerTableGrid(TableGrid):
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 250  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  ID_QUERY = 4001  ID_QUERY = 4001
327  ID_SAVEAS = 4002  ID_EXPORT = 4002
328    ID_COMBOVALUE = 4003
329    
330  class LayerTableFrame(TableFrame):  class QueryTableFrame(TableFrame):
331    
332      """Frame that displays a layer table in a grid view      """Frame that displays a table in a grid view and offers user actions
333        selection and export
334    
335      A LayerTableFrame is TableFrame whose selection is connected to the      A QueryTableFrame is TableFrame whose selection is connected to the
336      selected object in a map.      selected object in a map.
337      """      """
338    
339      def __init__(self, parent, name, title, layer, table):      def __init__(self, parent, name, title, table):
340          TableFrame.__init__(self, parent, name, title, table)          TableFrame.__init__(self, parent, name, title, table)
         self.layer = layer  
         self.grid.Subscribe(ROW_SELECTED, self.rows_selected)  
         self.parent.Subscribe(SHAPES_SELECTED, self.select_shapes)  
341    
342          self.combo_fields = wxComboBox(self, -1, style=wxCB_READONLY)          self.combo_fields = wxComboBox(self, -1, style=wxCB_READONLY)
343          self.choice_comp = wxChoice(self, -1,          self.choice_comp = wxChoice(self, -1,
344                                choices=["<", "<=", "==", "!=", ">=", ">"])                                choices=["<", "<=", "==", "!=", ">=", ">"])
345          self.combo_value = wxComboBox(self, -1)          self.combo_value = wxComboBox(self, ID_COMBOVALUE)
346          self.choice_action = wxChoice(self, -1,          self.choice_action = wxChoice(self, -1,
347                                  choices=[_("Replace Selection"),                                  choices=[_("Replace Selection"),
348                                          _("Refine Selection"),                                          _("Refine Selection"),
349                                          _("Add to Selection")])                                          _("Add to Selection")])
350    
351          button_query = wxButton(self, ID_QUERY, _("Query"))          button_query = wxButton(self, ID_QUERY, _("Query"))
352          button_saveas = wxButton(self, ID_SAVEAS, _("Save As..."))          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))          self.grid.SetSize((400, 200))
358    
# Line 291  class LayerTableFrame(TableFrame): Line 365  class LayerTableFrame(TableFrame):
365          # assume at least one field?          # assume at least one field?
366          self.combo_fields.SetSelection(0)          self.combo_fields.SetSelection(0)
367          self.combo_value.SetSelection(0)          self.combo_value.SetSelection(0)
368            self.choice_action.SetSelection(0)
369            self.choice_comp.SetSelection(0)
370    
371          topBox = wxBoxSizer(wxVERTICAL)          topBox = wxBoxSizer(wxVERTICAL)
372    
373          sizer = wxStaticBoxSizer(wxStaticBox(self, -1, _("Selections")),          sizer = wxStaticBoxSizer(wxStaticBox(self, -1,
374                                      _("Selection")),
375                                    wxHORIZONTAL)                                    wxHORIZONTAL)
376          sizer.Add(self.combo_fields, 1, wxEXPAND|wxALL, 4)          sizer.Add(self.combo_fields, 1, wxEXPAND|wxALL, 4)
377          sizer.Add(self.choice_comp, 0, wxALL, 4)          sizer.Add(self.choice_comp, 0, wxALL, 4)
378          sizer.Add(self.combo_value, 1, wxEXPAND|wxALL, 4)          sizer.Add(self.combo_value, 1, wxEXPAND|wxALL, 4)
379          sizer.Add(self.choice_action, 0, wxALL, 4)          sizer.Add(self.choice_action, 0, wxALL, 4)
380          sizer.Add(button_query, 0, wxALL, 4)          sizer.Add(button_query, 0, wxALL | wxALIGN_CENTER_VERTICAL, 4)
381          sizer.Add(40, 20, 0, wxALL, 4)          sizer.Add(40, 20, 0, wxALL, 4)
382          sizer.Add(button_saveas, 0, wxALL, 4)          sizer.Add(button_saveas, 0, wxALL | wxALIGN_CENTER_VERTICAL, 4)
383    
384          topBox.Add(sizer, 0, wxEXPAND|wxALL, 4)          topBox.Add(sizer, 0, wxEXPAND|wxALL, 4)
385          topBox.Add(self.grid, 1, wxEXPAND|wxALL, 0)          topBox.Add(self.grid, 1, wxEXPAND|wxALL, 0)
# Line 312  class LayerTableFrame(TableFrame): Line 389  class LayerTableFrame(TableFrame):
389          topBox.Fit(self)          topBox.Fit(self)
390          topBox.SetSizeHints(self)          topBox.SetSizeHints(self)
391    
392            self.grid.SetFocus()
393          EVT_BUTTON(self, ID_QUERY, self.OnQuery)          EVT_BUTTON(self, ID_QUERY, self.OnQuery)
394          EVT_BUTTON(self, ID_SAVEAS, self.OnSaveAs)          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 OnQuery(self, event):      def OnGridSelectRange(self, event):
406          wxBeginBusyCursor()          self.UpdateStatusText()
407            event.Skip()
408    
409          if self.combo_value.GetSelection() < 1:      def OnGridSelectCell(self, event):
410              value = self.combo_value.GetValue()          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:          else:
419              value = self.table.Column(self.combo_value.GetValue())              event.Skip()
420    
421          ids = self.table.SimpleQuery(      def OnQuery(self, event):
422                  self.table.Column(self.combo_fields.GetStringSelection()),          wxBeginBusyCursor()
423                  self.choice_comp.GetStringSelection(),          try:
                 value)  
424    
425          choice = self.choice_action.GetSelection()              text = self.combo_value.GetValue()
426                            if self.combo_value.GetSelection() < 1 \
427          #                  or self.combo_value.FindString(text) == -1:
428          # what used to be nice code got became a bit ugly because                  value = text
         # each time we select a row a message is sent to the grid  
         # which we are listening for and then we send further  
         # messages.  
         #  
         # now, we disable those listeners select everything but  
         # the first item, reenable the listeners, and select  
         # the first element, which causes everything to be  
         # updated properly.  
         #  
         self.grid.ToggleEventListeners(False)  
   
         if choice == 0:  
             # Replace Selection  
             self.grid.ClearSelection()  
         elif choice == 1:  
             # Refine Selection  
             sel = dict([(i, 0) for i in self.parent.SelectedShapes()])  
             self.grid.ClearSelection()  
             ids = filter(sel.has_key, ids)  
         elif choice == 2:  
             # Add to Selection  
             pass  
   
         #  
         # select the rows (all but the first)  
         #  
         firsttime = True  
         for id in ids:  
             if firsttime:  
                 firsttime = False  
429              else:              else:
430                  self.grid.SelectRow(id, True)                  value = self.table.Column(text)
431    
432          self.grid.ToggleEventListeners(True)              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          # select the first row              
439          #              #
440          if ids:              # what used to be nice code got became a bit ugly because
441              self.grid.SelectRow(ids[0], True)              # 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          wxEndBusyCursor()          finally:
484                wxEndBusyCursor()
485                    
486      def OnSaveAs(self, event):      def OnSaveAs(self, event):
487          dlg = wxFileDialog(self, _("Save Table As"), ".", "",          dlg = wxFileDialog(self, _("Export Table To"), ".", "",
488                             "DBF Files (*.dbf)|*.dbf|" +                             _("DBF Files (*.dbf)|*.dbf|") +
489                             "CSV Files (*.csv)|*.csv|" +                             _("CSV Files (*.csv)|*.csv|") +
490                             "All Files (*.*)|*.*",                             _("All Files (*.*)|*.*"),
491                             wxSAVE|wxOVERWRITE_PROMPT)                             wxSAVE|wxOVERWRITE_PROMPT)
492          if dlg.ShowModal() == wxID_OK:          if dlg.ShowModal() == wxID_OK:
493              pass              filename = dlg.GetPath()
494                                                                                                type = os.path.basename(filename).split('.')[-1:][0]
495          dlg.Destroy()              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
520    
521        A LayerTableFrame is a QueryTableFrame whose selection is connected to the
522        selected object in a map.
523        """
524    
525        def __init__(self, parent, name, title, layer, table):
526            QueryTableFrame.__init__(self, parent, name, title, table)
527            self.layer = layer
528            self.grid.Subscribe(ROW_SELECTED, self.rows_selected)
529            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            """Override the derived method to first unsubscribed."""
552          self.parent.Unsubscribe(SHAPES_SELECTED, self.select_shapes)          self.parent.Unsubscribe(SHAPES_SELECTED, self.select_shapes)
553          TableFrame.OnClose(self, event)          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_shapes(self, layer, shapes):      def select_shapes(self, layer, shapes):
558          """Subscribed to the SHAPES_SELECTED message.          """Subscribed to the SHAPES_SELECTED message.
# Line 404  class LayerTableFrame(TableFrame): Line 563  class LayerTableFrame(TableFrame):
563          self.grid.select_shapes(layer, shapes)          self.grid.select_shapes(layer, shapes)
564    
565      def rows_selected(self, rows):      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, rows)              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.966  
changed lines
  Added in v.1199

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26