1# (C) Copyright 2005-2020 Enthought, Inc., Austin, TX
2# All rights reserved.
3#
4# This software is provided without warranty under the terms of the BSD
5# license included in LICENSE.txt and may be redistributed only under
6# the conditions described in the aforementioned license. The license
7# is also available online at http://www.enthought.com/licenses/BSD.txt
8#
9# Thanks for using Enthought open source!
10
11
12from wx.grid import (
13    Grid,
14    PyGridTableBase,
15    GridCellAttr,
16    GridTableMessage,
17    GridCellFloatRenderer,
18)
19from wx.grid import (
20    GRIDTABLE_NOTIFY_ROWS_DELETED,
21    GRIDTABLE_NOTIFY_ROWS_APPENDED,
22)
23from wx.grid import (
24    GRIDTABLE_NOTIFY_COLS_DELETED,
25    GRIDTABLE_NOTIFY_COLS_APPENDED,
26)
27from wx.grid import GRIDTABLE_REQUEST_VIEW_GET_VALUES
28from wx.grid import GRID_VALUE_BOOL
29from wx import ALIGN_LEFT, ALIGN_CENTRE, Colour
30
31from .default_renderer import DefaultRenderer
32
33
34class VirtualModel(PyGridTableBase):
35    """
36    A custom wxGrid Table that expects a user supplied data source.
37    THIS CLASS IS NOT LIMITED TO ONLY DISPLAYING LOG DATA!
38    """
39
40    def __init__(self, data, column_names):
41        """data is currently a list of the form
42        [(rowname, dictionary),
43        dictionary.get(colname, None) returns the data for a cell
44        """
45        ##print 'Initializing virtual model'
46        PyGridTableBase.__init__(self)
47        self.set_data_source(data)
48        self.colnames = column_names
49        # self.renderers = {"DEFAULT_RENDERER":DefaultRenderer()}
50        # self.editors = {}
51
52        # we need to store the row length and col length to see if the table has changed size
53        self._rows = self.GetNumberRows()
54        self._cols = self.GetNumberCols()
55
56    # -------------------------------------------------------------------------------
57    # Implement/override the methods from PyGridTableBase
58    # -------------------------------------------------------------------------------
59
60    def GetNumberCols(self):
61        return len(self.colnames)
62
63    def GetNumberRows(self):
64        return len(self._data)
65
66    def GetColLabelValue(self, col):
67        return self.colnames[col]
68
69    def GetRowLabelValue(self, row):
70        return self._data[row][0]
71
72    def GetValue(self, row, col):
73        return str(self._data[row][1].get(self.GetColLabelValue(col), ""))
74
75    def GetRawValue(self, row, col):
76        return self._data[row][1].get(self.GetColLabelValue(col), "")
77
78    def SetValue(self, row, col, value):
79        print("Setting value %d %d %s" % (row, col, value))
80        print("Before ", self.GetValue(row, col))
81        self._data[row][1][self.GetColLabelValue(col)] = value
82        print("After ", self.GetValue(row, col))
83
84    """ def GetTypeName(self, row, col):
85        if col == 2 or col == 6:
86            res = "MeasurementUnits"
87        elif col == 7:
88            res = GRID_VALUE_BOOL
89        else:
90            res = self.base_GetTypeName(row, col)
91        # print 'asked for type of col ', col, ' ' ,res
92        return res"""
93
94    # -------------------------------------------------------------------------------
95    # Accessors for the Enthought data model (a dict of dicts)
96    # -------------------------------------------------------------------------------
97    def get_data_source(self):
98        """ The data structure we provide the data in.
99        """
100        return self._data
101
102    def set_data_source(self, source):
103        self._data = source
104        return
105
106    # -------------------------------------------------------------------------------
107    # Methods controlling updating and editing of cells in grid
108    # -------------------------------------------------------------------------------
109
110    def ResetView(self, grid):
111        """
112        (wxGrid) -> Reset the grid view.   Call this to
113        update the grid if rows and columns have been added or deleted
114        """
115        ##print 'VirtualModel.reset_view'
116        grid.BeginBatch()
117        for current, new, delmsg, addmsg in [
118            (
119                self._rows,
120                self.GetNumberRows(),
121                GRIDTABLE_NOTIFY_ROWS_DELETED,
122                GRIDTABLE_NOTIFY_ROWS_APPENDED,
123            ),
124            (
125                self._cols,
126                self.GetNumberCols(),
127                GRIDTABLE_NOTIFY_COLS_DELETED,
128                GRIDTABLE_NOTIFY_COLS_APPENDED,
129            ),
130        ]:
131            if new < current:
132                msg = GridTableMessage(self, delmsg, new, current - new)
133                grid.ProcessTableMessage(msg)
134            elif new > current:
135                msg = GridTableMessage(self, addmsg, new - current)
136                grid.ProcessTableMessage(msg)
137                self.UpdateValues(grid)
138        grid.EndBatch()
139
140        self._rows = self.GetNumberRows()
141        self._cols = self.GetNumberCols()
142
143        # update the renderers
144        # self._updateColAttrs(grid)
145        # self._updateRowAttrs(grid) too expensive to use on a large grid
146
147        # update the scrollbars and the displayed part of the grid
148        grid.AdjustScrollbars()
149        grid.ForceRefresh()
150
151    def UpdateValues(self, grid):
152        """Update all displayed values"""
153        # This sends an event to the grid table to update all of the values
154        msg = GridTableMessage(self, GRIDTABLE_REQUEST_VIEW_GET_VALUES)
155        grid.ProcessTableMessage(msg)
156
157    def GetAttr88(self, row, col, someExtraParameter):
158        print("Overridden GetAttr ", row, col)
159        """Part of a workaround to avoid use of attributes, queried by _PropertyGrid's IsCurrentCellReadOnly"""
160        # property = self.GetPropertyForCoordinate( row, col )
161        # object = self.GetObjectForCoordinate( row, col )
162        # if property.ReadOnly( object ):
163        attr = GridCellAttr()
164        attr.SetReadOnly(1)
165        return attr
166        # return None
167
168    def _updateColAttrs88(self, grid):
169        """
170        wxGrid -> update the column attributes to add the
171        appropriate renderer given the column name.
172        """
173        for col, colname in enumerate(self.colnames):
174            attr = GridCellAttr()
175            # attr.SetAlignment(ALIGN_LEFT, ALIGN_CENTRE)
176            if colname in self.renderers:
177                # renderer = self.plugins[colname](self)
178                renderer = self.renderers[colname]
179                # if renderer.colSize:
180                #    grid.SetColSize(col, renderer.colSize)
181                # if renderer.rowSize:
182                #    grid.SetDefaultRowSize(renderer.rowSize)
183                # attr.SetReadOnly(False)
184                # attr.SetRenderer(renderer)
185            else:
186                renderer = self.renderers["DEFAULT_RENDERER"]  # .Clone()
187
188            attr.SetRenderer(renderer)
189
190            """else:
191                #renderer = GridCellFloatRenderer(6,2)
192                #attr.SetReadOnly(True)
193                #attr.SetRenderer(renderer)"""
194
195            if colname in self.editors:
196                editor = self.editors[colname]
197                attr.SetEditor(editor)
198
199            grid.SetColAttr(col, attr)
200        return
201
202    # ------------------------------------------------------------------------------
203    # code to manipulate the table (non wx related)
204    # ------------------------------------------------------------------------------
205
206    def AppendRow(self, row):
207        """ Append a tupe containing (name, data)
208        """
209        name, data = row
210        print("Appending ", name)
211        self._data.append(row)
212        """entry = {}
213        for name in self.colnames:
214            entry[name] = "Appended_%i"%row
215        return"""
216
217    def DeleteCols88(self, cols):
218        """
219        cols -> delete the columns from the dataset
220        cols hold the column indices
221        """
222        # we'll cheat here and just remove the name from the
223        # list of column names.  The data will remain but
224        # it won't be shown
225        deleteCount = 0
226        cols = cols[:]
227        cols.sort()
228        for i in cols:
229            self.colnames.pop(i - deleteCount)
230            # we need to advance the delete count
231            # to make sure we delete the right columns
232            deleteCount += 1
233        if not len(self.colnames):
234            self.data = []
235
236    def DeleteRow(self, row):
237        name, data = row
238        print("Deleting ", name)
239        self._data.remove(row)
240
241    def DeleteRows88(self, rows):
242        """
243        rows -> delete the rows from the dataset
244        rows hold the row indices
245        """
246        deleteCount = 0
247        rows = rows[:]
248        rows.sort()
249        for i in rows:
250            self._data.pop(i - deleteCount)
251            # we need to advance the delete count
252            # to make sure we delete the right rows
253            deleteCount += 1
254
255    def SortColumn88(self, col):
256        """
257        to do - never tested
258        tried to rename data to _data and _data to _tmp_data
259        col -> sort the data based on the column indexed by col
260        """
261        name = self.colnames[col]
262        _tmp_data = []
263        for row in self._data:
264            rowname, entry = row
265            _tmp_data.append((entry.get(name, None), row))
266
267        _tmp_data.sort()
268        self._data = []
269        for sortvalue, row in _tmp_data:
270            self._data.append(row)
271