diff --git a/vitables/plugins/columnorg/columnar_org.py b/vitables/plugins/columnorg/columnar_org.py index 71d8b7c3..ba13efca 100644 --- a/vitables/plugins/columnorg/columnar_org.py +++ b/vitables/plugins/columnorg/columnar_org.py @@ -146,7 +146,7 @@ def helpAbout(self, parent): 'folder': os.path.join(os.path.dirname(__file__)), 'author': 'Vicent Mas ', 'comment': translate('ArrayColsOrganizer', - """ + """

Plugin that provides an alternative view for arrays with the same number of rows.

The user has to open all the arrays she whishes to see in @@ -158,7 +158,7 @@ def helpAbout(self, parent):

Note that all ticked arrays must have the same dimensions for the `Group` menu item to enable. """, - 'Text of an About plugin message box')} + 'Text of an About plugin message box')} about_page = AboutPage(desc, parent) return about_page @@ -198,10 +198,10 @@ def addEntry(self): triggered=self.groupArrays, icon=object_group_icon, statusTip=translate('MenuUpdater', - """ + """ Use a unique widget to display Arrays as if they where columns of a Table""", - "Status bar text for the Node -> Group Arrays action")) + "Status bar text for the Node -> Group Arrays action")) object_ungroup_icon = QtGui.QIcon() pixmap = QtGui.QPixmap(os.path.join(_PLUGIN_FOLDER, @@ -219,8 +219,8 @@ def addEntry(self): triggered=self.ungroupArrays, icon=object_ungroup_icon, statusTip=translate('MenuUpdater', - """Ungroup previously grouped arrays.""", - "Status bar text for the Node -> Ungroup Arrays action")) + """Ungroup previously grouped arrays.""", + "Status bar text for the Node -> Ungroup Arrays action")) # Add the actions to the Node menu vitables.utils.addToMenu(self.vtgui.node_menu, (self.group_action, @@ -468,12 +468,12 @@ def customizeGroupedViews(self): nd = len(datasheets) for i in range(nd): - datasheets[nd-1].widget().verticalScrollBar().valueChanged.connect( + datasheets[nd - 1].widget().verticalScrollBar().valueChanged.connect( datasheets[i].widget().verticalScrollBar().setValue) if i < (nd - 1): datasheets[i].widget().verticalScrollBar().hide() datasheets[i].widget().setCornerWidget(None) - datasheets[i+1].widget().verticalHeader().hide() + datasheets[i + 1].widget().verticalHeader().hide() def closeEvent(self, event): """ Propagate the close event. diff --git a/vitables/plugins/timeseries/time_series.py b/vitables/plugins/timeseries/time_series.py index 83f9a8aa..77fda106 100644 --- a/vitables/plugins/timeseries/time_series.py +++ b/vitables/plugins/timeseries/time_series.py @@ -50,7 +50,6 @@ pd = None from qtpy import QtCore -from qtpy import QtGui from qtpy import QtWidgets import vitables.utils @@ -152,7 +151,7 @@ def tsFrequency(ts_kind, leaf): if ts_kind == 'scikits_ts': # The frequency of the time serie. Default is 6000 (daily) special_attrs = getattr(leaf._v_attrs, 'special_attrs', - {'freq': 6000}) + {'freq': 6000}) ts_freq = special_attrs['freq'] return ts_freq @@ -225,14 +224,14 @@ def customiseModel(self, datasheet): 'ts_cols': time_cols, 'ts_freq': tsFrequency(ts_kind, leaf), 'ts_format': datetimeFormat(), - } + } if isinstance(leaf, tables.Table): leaf_kind = 'table' else: leaf_kind = 'array' model_info = { 'leaf_kind': leaf_kind, - 'rbuffer': model.rbuffer, + 'model': model, 'numrows': model.rowCount(), 'formatContent': model.formatContent, } @@ -246,7 +245,6 @@ def customiseModel(self, datasheet): model.tsFormatter = ts_model.tsFormatter model.data = ts_model.data - def helpAbout(self, parent): """Full description of the plugin. @@ -260,17 +258,17 @@ def helpAbout(self, parent): # Plugin full description desc = {'version': __version__, - 'module_name': os.path.join(os.path.basename(__file__)), - 'folder': os.path.join(os.path.dirname(__file__)), - 'author': 'Vicent Mas ', - 'about_text': translate('TimeFormatterPage', - """ + 'module_name': os.path.join(os.path.basename(__file__)), + 'folder': os.path.join(os.path.dirname(__file__)), + 'author': 'Vicent Mas ', + 'about_text': translate('TimeFormatterPage', + """

Plugin that provides nice string formatting for time fields.

It supports not only native PyTables time datatypes but also time series generated (and stored in PyTables tables) via Pandas and scikits.timeseries packages. """, - 'Text of an About plugin message box')} + 'Text of an About plugin message box')} self.about_page = AboutPage(desc, parent) # We need to install the event filter because the Preferences dialog @@ -300,7 +298,7 @@ def __init__(self, model_info, ts_info, parent=None): self.ts_freq = ts_info['ts_freq'] self.ts_format = ts_info['ts_format'] # Attributes required by the data() method - self.rbuffer = model_info['rbuffer'] + self.model = model_info['model'] self.numrows = model_info['numrows'] self.ts_cols = ts_info['ts_cols'] self.formatContent = model_info['formatContent'] @@ -313,7 +311,6 @@ def __init__(self, model_info, ts_info, parent=None): else: self.data = self.array_data - def table_data(self, index, role=QtCore.Qt.DisplayRole): """Returns the data stored under the given role for the item referred to by the index. @@ -325,21 +322,21 @@ def table_data(self, index, role=QtCore.Qt.DisplayRole): - `index`: the index of a data item - `role`: the role being returned """ + row, col = index.row(), index.column() - if not index.isValid() or \ - not (0 <= index.row() < self.numrows): + if not index.isValid() or not (0 <= row < self.nrows): return None - cell = self.rbuffer.getCell(self.rbuffer.start + index.row(), - index.column()) + if role == QtCore.Qt.DisplayRole: + cell = self.model.cell(row, col) if index.column() in self.ts_cols: return self.tsFormatter(cell) return self.formatContent(cell) - elif role == QtCore.Qt.TextAlignmentRole: - return int(QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) - else: - return None + if role == QtCore.Qt.TextAlignmentRole: + return QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop + + return None def array_data(self, index, role=QtCore.Qt.DisplayRole): """Returns the data stored under the given role for the item @@ -352,19 +349,19 @@ def array_data(self, index, role=QtCore.Qt.DisplayRole): - `index`: the index of a data item - `role`: the role being returned """ + row, col = index.row(), index.column() - if not index.isValid() or \ - not (0 <= index.row() < self.numrows): + if not index.isValid() or not (0 <= row < self.nrows): return None - cell = self.rbuffer.getCell(self.rbuffer.start + index.row(), - index.column()) + if role == QtCore.Qt.DisplayRole: + cell = self.model.cell(row, col) return self.tsFormatter(cell) - elif role == QtCore.Qt.TextAlignmentRole: - return int(QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) - else: - return None + if role == QtCore.Qt.TextAlignmentRole: + return QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop + + return None def timeFormatter(self): """Return the function to be used for formatting time series. @@ -380,7 +377,6 @@ def timeFormatter(self): time_formatter = self.formatPyTablesTS return time_formatter - def formatPyTablesTS(self, content): """ Format a given date in a user friendly way. @@ -396,7 +392,6 @@ def formatPyTablesTS(self, content): except ValueError: return content - def formatPandasTS(self, content): """Format a given date in a user friendly way. @@ -405,14 +400,13 @@ def formatPandasTS(self, content): :Parameter content: the content of the table cell being formatted """ - + # ImportError if pandas not installed! date = pd.Timestamp(int(content)) try: return date.strftime(self.ts_format) except ValueError: return content - def formatScikitsTS(self, content): """Format a given date in a user friendly way. diff --git a/vitables/vttables/buffer.py b/vitables/vttables/buffer.py index dcbde21a..5571ee44 100644 --- a/vitables/vttables/buffer.py +++ b/vitables/vttables/buffer.py @@ -30,17 +30,14 @@ __docformat__ = 'restructuredtext' -import warnings import logging - -from qtpy import QtGui -from qtpy import QtWidgets +import vitables.utils +import warnings import numpy +from qtpy import QtWidgets import tables -import vitables.utils - translate = QtWidgets.QApplication.translate # Restrict the available flavors to 'numpy' so that reading a leaf @@ -51,6 +48,8 @@ warnings.filterwarnings('ignore', category=tables.FlavorWarning) warnings.filterwarnings('ignore', category=tables.NaturalNameWarning) +log = logging.getLogger(__name__) + class Buffer(object): """Buffer used to access the real data contained in `PyTables` datasets. @@ -84,27 +83,11 @@ def __init__(self, leaf): Initializes the buffer. """ - self.logger = logging.getLogger(__name__) - self.logger.setLevel(logging.INFO) - - self.data_source = leaf - - # The maximum number of rows to be read from the data source - self.chunk_size = 10000 - - # The length of the dimension that is going to be read. It - # is an int64. - self.leaf_numrows = self.leafNumberOfRows() - if self.leaf_numrows <= self.chunk_size: - self.chunk_size = self.leaf_numrows + self.leaf = leaf # The numpy array where read data will be stored self.chunk = numpy.array([]) - # The document row where the current chunk of data starts. - # It must be an int64 in order to address spaces bigger than 2**32 - self.start = numpy.array(0, dtype=numpy.int64) - # The method used for reading data depends on the kind of node. # Setting the reader method at initialization time increases the # speed of reading several orders of magnitude @@ -130,10 +113,11 @@ def __init__(self, leaf): def __del__(self): """Release resources before destroying the buffer. """ + # FIXME: PY3.5+ leaks resources (use finalizer instead). self.chunk = None - def leafNumberOfRows(self): - """The number of rows of the dataset being read. + def total_nrows(self): + """Estimates the number of rows of the dataset being read. We don't use the `Leaf.nrows` attribute because it is not always suitable for displaying the data in a 2D grid. Instead we use the @@ -146,53 +130,21 @@ def leafNumberOfRows(self): :Returns: the size of the first dimension of the document """ - shape = self.data_source.shape + shape = self.leaf.shape if shape is None: # Node is not a Leaf or there was problems getting the shape nrows = 0 elif shape == (): # Node is a rank 0 array (e.g. numpy.array(5)) nrows = 1 - elif isinstance(self.data_source, tables.EArray): + elif isinstance(self.leaf, tables.EArray): # Warning: the number of rows of an EArray, ea, can be different # from the number of rows of the numpy array ea.read() - nrows = self.data_source.shape[0] + nrows = self.leaf.shape[0] else: - nrows = self.data_source.nrows - - return numpy.array(nrows, dtype=numpy.int64) - - def getReadParameters(self, start, buffer_size): - """ - Returns acceptable parameters for the read method. - - :Parameters: - - - `start`: the document row that is the first row of the chunk. - It *must* be a 64 bits integer. - - `buffer_size`: the buffer size, i.e. the number of rows to be read. - - :Returns: - a tuple with tested values for the parameters of the read method - """ - - first_row = numpy.array(0, dtype=numpy.int64) - last_row = self.leaf_numrows - - # When scrolling up we must keep start value >= first_row - if start < first_row: - start = first_row + nrows = self.leaf.nrows - # When scrolling down we must keep stop value <= last_row - stop = start + buffer_size - if stop > last_row: - stop = last_row - - # Ensure that the whole buffer will be filled - if stop - start < self.chunk_size: - start = stop - self.chunk_size - - return start, stop + return nrows def isDataSourceReadable(self): """Find out if the dataset can be read or not. @@ -203,28 +155,27 @@ def isDataSourceReadable(self): """ readable = True - start, stop = self.getReadParameters(numpy.array(0, dtype=numpy.int64), - self.chunk_size) try: - self.data_source.read(start, stop) + self.leaf.read(0, 1) except tables.HDF5ExtError: readable = False - self.logger.error( + # TODO: Fix error msg to include exception or merge with below. + log.error( translate('Buffer', """Problems reading records. The dataset """ """seems to be compressed with the {0} library. """ """Check that it is installed in your system, """ """please.""", 'A dataset readability error').format( - self.data_source.filters.complib)) + self.leaf.filters.complib)) except ValueError as e: readable = False - self.logger.error( + log.error( translate('Buffer', 'Data read error: {}', 'A dataset read error').format(e.message)) return readable - def readBuffer(self, start, buffer_size): + def readBuffer(self, start, stop): """ Read a chunk from the data source. @@ -239,21 +190,20 @@ def readBuffer(self, start, buffer_size): :Parameters: - - `start`: the document row that is the first row of the chunk. - It *must* be a 64 bits integer. - - `buffer_size`: the buffer size, i.e. the number of rows to be read. + :param start: the document row that is the first row of the chunk. + :param stop: the last row to read, inclusive. """ - start, stop = self.getReadParameters(start, buffer_size) try: # data_source is a tables.Table or a tables.XArray # but data is a numpy array # Warning: in a EArray with shape (2,3,3) and extdim attribute # being 1, the read method will have 3 rows. However, the numpy # array returned by EArray.read() will have only 2 rows - data = self.data_source.read(start, stop) + data = self.leaf.read(start, stop) except tables.HDF5ExtError: - self.logger.error( + # TODO: Fix error msg to include exception. + log.error( translate('Buffer', """\nError: problems reading records. """ """The dataset maybe corrupted.""", 'A dataset readability error')) @@ -262,7 +212,6 @@ def readBuffer(self, start, buffer_size): else: # Update the buffer contents and its start position self.chunk = data - self.start = start def scalarCell(self, row, col): """ @@ -273,17 +222,12 @@ def scalarCell(self, row, col): :Parameters: - - `row`: the row to which the cell belongs. It is a 64 bits integer + - `row`: the row to which the cell belongs. - `col`: the column to wich the cell belongs :Returns: the cell at position `(row, col)` of the document """ - - try: - return self.chunk[()] - except IndexError: - self.logger.error('IndexError! buffer start: {0} row, column: ' - '{1}, {2}'.format(self.start, row, col)) + return self.chunk[()] def vectorCell(self, row, col): """ @@ -300,23 +244,17 @@ def vectorCell(self, row, col): :Parameters: - - `row`: the row to which the cell belongs. It is a 64 bits integer + - `row`: the row to which the cell belongs. - `col`: the column to wich the cell belongs :Returns: the cell at position `(row, col)` of the document """ - # We must shift the row value by self.start units in order to - # get the right chunk element. Note that indices of chunk - # needn't to be int64 because they are indexing a fixed size, - # small chunk of data (see ctor docstring). + # The row-coordinate must be shifted by model.start units in order to + # get the right chunk element. # chunk = [row0, row1, row2, ..., rowN] # and columns can be read from a given row using indexing notation - try: - return self.chunk[int(row - self.start)] - except IndexError: - self.logger.error('IndexError! buffer start: {0} row, column: ' - '{1}, {2}'.format(self.start, row, col)) + return self.chunk[row] def EArrayCell(self, row, col): """ @@ -327,25 +265,19 @@ def EArrayCell(self, row, col): :Parameters: - - `row`: the row to which the cell belongs. It is a 64 bits integer + - `row`: the row to which the cell belongs. - `col`: the column to wich the cell belongs :Returns: the cell at position `(row, col)` of the document """ - # We must shift the row value by self.start units in order to - # get the right chunk element. Note that indices of chunk - # needn't to be int64 because they are indexing a fixed size, - # small chunk of data (see ctor docstring). + # The row-coordinate must be shifted by model.start units in order to + # get the right chunk element. # chunk = [row0, row1, row2, ..., rowN] # and columns can be read from a given row using indexing notation # TODO: this method should be improved as it requires to read the # whola array keeping the read data in memory - try: - return self.data_source.read()[int(row - self.start)] - except IndexError: - self.logger.error('IndexError! buffer start: {0} row, column: ' - '{1}, {2}'.format(self.start, row, col)) + return self.leaf.read()[row] def arrayCell(self, row, col): """ @@ -356,24 +288,18 @@ def arrayCell(self, row, col): :Parameters: - - `row`: the row to which the cell belongs. It is a 64 bits integer + - `row`: the row to which the cell belongs. - `col`: the column to wich the cell belongs :Returns: the cell at position `(row, col)` of the document """ - # We must shift the row value by self.start units in order to get the - # right chunk element. Note that indices of chunk needn't to be - # int64 because they are indexing a fixed size, small chunk of - # data (see ctor docstring). + # The row-coordinate must be shifted by model.start units in order to + # get the right chunk element. # For arrays we have # chunk = [row0, row1, row2, ..., rowN] # and columns can be read from a given row using indexing notation # For tables we have # chunk = [nestedrecord0, nestedrecord1, ..., nestedrecordN] # and fields can be read from nestedrecordJ using indexing notation - try: - return self.chunk[int(row - self.start)][col] - except IndexError: - self.logger.error('IndexError! buffer start: {0} row, column: ' - '{1}, {2}'.format(self.start, row, col)) + return self.chunk[row][col] diff --git a/vitables/vttables/datasheet.py b/vitables/vttables/datasheet.py index c462e66e..317f5005 100644 --- a/vitables/vttables/datasheet.py +++ b/vitables/vttables/datasheet.py @@ -37,7 +37,7 @@ import vitables.vtwidgets.zoom_cell as zoom_cell import vitables.vttables.leaf_model as leaf_model import vitables.vttables.leaf_view as leaf_view -import vitables.vttables.buffer as readBuffer + class DataSheet(QtWidgets.QMdiSubWindow): """ @@ -67,8 +67,7 @@ def __init__(self, index): else: leaf = pt_node - rbuffer = readBuffer.Buffer(leaf) - self.leaf_model = leaf_model.LeafModel(rbuffer) + self.leaf_model = leaf_model.LeafModel(leaf) self.leaf_view = leaf_view.LeafView(self.leaf_model) super(DataSheet, self).__init__(self.vtgui.workspace, @@ -95,7 +94,6 @@ def __init__(self, index): self.aboutToActivate.connect(self.syncTreeView) self.leaf_view.doubleClicked.connect(self.zoomCell) - def closeEvent(self, event): """Close the window cleanly with the close button of the title bar. @@ -113,7 +111,6 @@ def closeEvent(self, event): if self.vtgui.workspace.subWindowList() == []: self.vtgui.dbs_tree_view.setFocus(True) - def focusInEvent(self, event): """Specialised handler for focus events. @@ -129,7 +126,6 @@ def focusInEvent(self, event): self.syncTreeView() self.setFocus(True) - def syncTreeView(self): """ If the view is activated select its leaf in the tree of databases view. @@ -144,7 +140,6 @@ def syncTreeView(self): self.vtgui.dbs_tree_view.setCurrentIndex( QtCore.QModelIndex(self.pindex)) - def zoomCell(self, index): """Display the inner dimensions of a cell. @@ -154,7 +149,9 @@ def zoomCell(self, index): row = index.row() column = index.column() tmodel = index.model() - data = tmodel.rbuffer.getCell(tmodel.rbuffer.start + row, column) + data = tmodel.cell(row, column) + if data is None: + return # The title of the zoomed view node = self.dbt_leaf @@ -162,10 +159,10 @@ def zoomCell(self, index): if node.node_kind == 'table': col = info.columns_names[column] title = '{0}: {1}[{2}]'.format(node.name, col, - tmodel.rbuffer.start + row) + tmodel.start + row) else: title = '{0}: ({1},{2})'.format(node.name, - tmodel.rbuffer.start + row, column) + tmodel.start + row, column) zoom_cell.ZoomCell(data, title, self.vtgui.workspace, - self.dbt_leaf) + self.dbt_leaf) diff --git a/vitables/vttables/leaf_delegate.py b/vitables/vttables/leaf_delegate.py index 5fb102ed..5e757b1b 100644 --- a/vitables/vttables/leaf_delegate.py +++ b/vitables/vttables/leaf_delegate.py @@ -67,7 +67,7 @@ def paint(self, painter, option, index): # option.state is an ORed combination of flags if (option.state & QtWidgets.QStyle.State_Selected): model = index.model() - buffer_start = model.rbuffer.start + buffer_start = model.start cell = index.model().selected_cell if ((index == cell['index']) and \ (buffer_start != cell['buffer_start'])): diff --git a/vitables/vttables/leaf_model.py b/vitables/vttables/leaf_model.py index 488673ce..e94ea13f 100644 --- a/vitables/vttables/leaf_model.py +++ b/vitables/vttables/leaf_model.py @@ -26,14 +26,18 @@ __docformat__ = 'restructuredtext' -import collections +import logging +import vitables.utils +from vitables.vttables import buffer +from qtpy import QtCore import tables -import numpy as np -from qtpy import QtCore -import vitables.utils +#: The maximum number of rows to be read from the data source. +CHUNK_SIZE = 10000 + +log = logging.getLogger(__name__) class LeafModel(QtCore.QAbstractTableModel): @@ -42,46 +46,50 @@ class LeafModel(QtCore.QAbstractTableModel): The data is read from data sources (i.e., `HDF5/PyTables` nodes) by the model. + The dataset number of rows is potentially huge but tables are read and + displayed in chunks. + + + :param parent: + The parent of the model, passed as is in the superclass. + :attribute leaf: + the underlying hdf5 data + :attribute rbuffer: + Code for chunking and inspecting the undelying data. + :attribute leaf_numrows: + the total number of rows in the underlying data + :attribute numrows: + The number of rows visible which equals the chunking-size. + :attribute numcols: + The total number of columnss visible, equal to those visible. + :attribute start: + The zero-based starting index of the chunk within the total rows. - :Parameters: - - - `rbuffer`: a buffer used for optimizing read access to data - - `parent`: the parent of the model """ - def __init__(self, rbuffer, parent=None): + def __init__(self, leaf, parent=None): """Create the model. """ # The model data source (a PyTables/HDF5 leaf) and its access buffer - self.data_source = rbuffer.data_source - self.rbuffer = rbuffer - - # The number of digits of the last row - self.last_row_width = len(str(self.rbuffer.leaf_numrows)) - - # - # The table dimensions - # + self.leaf = leaf + self.rbuffer = buffer.Buffer(leaf) - # The dataset number of rows is potentially huge but tables are - # kept small: just the data returned by a read operation of the - # buffer are displayed - self.numrows = self.rbuffer.leafNumberOfRows() - if self.numrows > self.rbuffer.chunk_size: - self.numrows = self.rbuffer.chunk_size + self.leaf_numrows = self.rbuffer.total_nrows() + self.numrows = min(self.leaf_numrows, CHUNK_SIZE) + self.start = 0 # The dataset number of columns doesn't use to be large so, we don't # need set a maximum as we did with rows. The whole set of columns # are displayed - if isinstance(self.data_source, tables.Table): + if isinstance(leaf, tables.Table): # Leaf is a PyTables table - self.numcols = len(self.data_source.colnames) - elif isinstance(self.data_source, tables.EArray): + self.numcols = len(leaf.colnames) + elif isinstance(leaf, tables.EArray): self.numcols = 1 else: # Leaf is some kind of PyTables array - shape = self.data_source.shape + shape = leaf.shape if len(shape) > 1: # The leaf will be displayed as a bidimensional matrix self.numcols = shape[1] @@ -98,9 +106,9 @@ def __init__(self, rbuffer, parent=None): # Time series (if they are found) are formatted transparently # via the time_series.py plugin - if not isinstance(self.data_source, tables.Table): + if not isinstance(leaf, tables.Table): # Leaf is some kind of PyTables array - atom_type = self.data_source.atom.type + atom_type = leaf.atom.type if atom_type == 'object': self.formatContent = vitables.utils.formatObjectContent elif atom_type in ('vlstring', 'vlunicode'): @@ -110,12 +118,57 @@ def __init__(self, rbuffer, parent=None): self.selected_cell = {'index': QtCore.QModelIndex(), 'buffer_start': 0} # Populate the model with the first chunk of data - self.loadData(self.rbuffer.start, self.rbuffer.chunk_size) + self.loadData(0, self.numrows) super(LeafModel, self).__init__(parent) - def _collect_enum_indices(self): - """Initialize structures required to properly display enum columns.""" + def columnCount(self, index=QtCore.QModelIndex()): + """The number of columns of the given model index. + + Overridden to return 0 for valid indices because they have no children; + otherwise return the total number of *columns* exposed by the model. + + :param index: + the model index being inspected. + """ + + return 0 if index.isValid() else self.numcols + + def rowCount(self, index=QtCore.QModelIndex()): + """The number of columns for the children of the given index. + + Overridden to return 0 for valid indices because they have no children; + otherwise return the total number of *rows* exposed by the model. + + :Parameter index: the model index being inspected. + """ + + return 0 if index.isValid() else self.numrows + + def loadData(self, start, length): + """Load the model with fresh data from the buffer. + + :param start: + the document row that is the first row of the chunk. + :param length: + the buffer size, i.e. the number of rows to be read. + + :return: + a tuple with tested values for the parameters of the read method + """ + + # Enforce scrolling limits. + # + start = max(start, 0) + stop = min(start + length, self.leaf_numrows) + + # Ensure buffer filled when scrolled beyond bottom. + # + actual_start = stop - self.numrows + start = max(min(actual_start, start), 0) + + self.rbuffer.readBuffer(start, stop) + self.start = start def headerData(self, section, orientation, role): """Returns the data for the given role and section in the header @@ -133,20 +186,22 @@ def headerData(self, section, orientation, role): # The section alignment if role == QtCore.Qt.TextAlignmentRole: if orientation == QtCore.Qt.Horizontal: - return int(QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) - return int(QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter) + return QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter + return QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter + if role != QtCore.Qt.DisplayRole: return None - # The section label for horizontal header + + # Columns-labels if orientation == QtCore.Qt.Horizontal: # For tables horizontal labels are column names, for arrays # the section numbers are used as horizontal labels - if hasattr(self.data_source, 'description'): - return str(self.data_source.colnames[section]) + if hasattr(self.leaf, 'description'): + return str(self.leaf.colnames[section]) return str(section) - # The section label for vertical header. This is a 64 bits integer - return str(self.rbuffer.start + section) + # Rows-labels + return str(self.start + section) def data(self, index, role=QtCore.Qt.DisplayRole): """Returns the data stored under the given role for the item @@ -159,59 +214,28 @@ def data(self, index, role=QtCore.Qt.DisplayRole): - `index`: the index of a data item - `role`: the role being returned """ + row, col = index.row(), index.column() - if not index.isValid() or not (0 <= index.row() < self.numrows): + if not index.isValid() or not (0 <= row < self.numrows): return None - cell = self.rbuffer.getCell(self.rbuffer.start + index.row(), - index.column()) + if role == QtCore.Qt.DisplayRole: + cell = self.cell(row, col) return self.formatContent(cell) - elif role == QtCore.Qt.TextAlignmentRole: - return int(QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) - else: - return None - - - def columnCount(self, index=QtCore.QModelIndex()): - """The number of columns of the given model index. - - When implementing a table based model this method has to be overriden - -because it is an abstract method- and should return 0 for valid - indices (because they have no children). If the index is not valid the - method should return the number of columns exposed by the model. - - :Parameter index: the model index being inspected. - """ - - if not index.isValid(): - return self.numcols - else: - return 0 - - def rowCount(self, index=QtCore.QModelIndex()): - """The number of columns for the children of the given index. + if role == QtCore.Qt.TextAlignmentRole: + return QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop - When implementing a table based model this method has to be overriden - -because it is an abstract method- and should return 0 for valid - indices (because they have no children). If the index is not valid the - method should return the number of rows exposed by the model. + return None - :Parameter index: the model index being inspected. + def cell(self, row, col): """ + Returns the contents of a cell. - if not index.isValid(): - return self.numrows - else: - return 0 - - - def loadData(self, start, chunk_size): - """Load the model with fresh data from the buffer. - - :Parameters: - - - `start`: the row where the buffer starts - - `chunk_size`: the size of the buffer + :return: none to disable zooming. """ - self.rbuffer.readBuffer(start, chunk_size) + try: + return self.rbuffer.getCell(row, col) + except IndexError: + log.error('IndexError! buffer start: {0} row, column: ' + '{1}, {2}'.format(self.start, row, col)) diff --git a/vitables/vttables/leaf_view.py b/vitables/vttables/leaf_view.py index 1ea107d3..f4b70052 100644 --- a/vitables/vttables/leaf_view.py +++ b/vitables/vttables/leaf_view.py @@ -34,14 +34,16 @@ __docformat__ = 'restructuredtext' -import numpy - from qtpy import QtCore from qtpy import QtGui from qtpy import QtWidgets -import vitables.vttables.scrollbar as scrollbar import vitables.vttables.leaf_delegate as leaf_delegate +import vitables.vttables.scrollbar as scrollbar + + +_aiv = QtWidgets.QAbstractItemView + class LeafView(QtWidgets.QTableView): """ @@ -62,25 +64,25 @@ def __init__(self, tmodel, parent=None): super(LeafView, self).__init__(parent) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.tmodel = tmodel # This is a MUST - self.leaf_numrows = self.tmodel.rbuffer.leaf_numrows + self.leaf_numrows = leaf_numrows = self.tmodel.leaf_numrows self.selection_model = self.selectionModel() - self.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) - self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectItems) + self.setSelectionMode(_aiv.SingleSelection) + self.setSelectionBehavior(_aiv.SelectItems) # Setup the actual vertical scrollbar - self.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerItem) + self.setVerticalScrollMode(_aiv.ScrollPerItem) self.vscrollbar = self.verticalScrollBar() self.setModel(tmodel) # For potentially huge datasets use a customised scrollbar - if self.leaf_numrows > self.tmodel.numrows: + if leaf_numrows > tmodel.numrows: self.setItemDelegate(leaf_delegate.LeafDelegate()) self.rbuffer_fault = False self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) self.tricky_vscrollbar = scrollbar.ScrollBar(self) - self.max_value = self.tvsMaxValue() - self.tricky_vscrollbar.setMaximum(self.max_value) + self.max_value = self.tricky_vscrollbar.setMaxValue( + self.leaf_numrows) self.tricky_vscrollbar.setMinimum(0) self.interval_size = self.mapSlider2Leaf() @@ -90,39 +92,25 @@ def __init__(self, tmodel, parent=None): font = self.vheader.font() font.setBold(True) fmetrics = QtGui.QFontMetrics(font) - max_width = fmetrics.width(" {0} ".format(str(self.leaf_numrows))) + max_width = fmetrics.width(" {0} ".format(str(leaf_numrows))) self.vheader.setMinimumWidth(max_width) self.vheader.setSectionsClickable(True) # Setup the headers' resize mode rmode = QtWidgets.QHeaderView.Stretch - if self.tmodel.columnCount() == 1: + if tmodel.columnCount() == 1: self.horizontalHeader().setSectionResizeMode(rmode) - if self.tmodel.rowCount() == 1: + if tmodel.rowCount() == 1: self.vheader.setSectionResizeMode(rmode) # Setup the text elide mode self.setTextElideMode(QtCore.Qt.ElideRight) # Connect signals to slots - if self.leaf_numrows > self.tmodel.numrows: + if leaf_numrows > tmodel.numrows: self.tricky_vscrollbar.actionTriggered.connect( self.navigateWithMouse) - - def tvsMaxValue(self): - """Calulate the maximum range value of the tricky vertical scrollbar. - """ - - # The scrollbar range must be a signed 32 bits integer so its - # largest range is [0, 2**31 - 1] - top_value = 2**31 - 1 - if self.leaf_numrows <= top_value: - top_value = self.leaf_numrows - return top_value - - - def mapSlider2Leaf(self): """Setup the interval size. @@ -144,11 +132,9 @@ def mapSlider2Leaf(self): # rows and row equals to value interval_size = 1 if self.max_value < self.leaf_numrows: - interval_size = numpy.rint(numpy.array( - self.leaf_numrows/self.max_value, dtype=numpy.int64)) + interval_size = round(self.leaf_numrows / self.max_value) return interval_size - def syncView(self): """Update the tricky scrollbar value after a data navigation. @@ -157,20 +143,18 @@ def syncView(self): update is done using the first visible row as a reference. """ - offset = self.tmodel.rbuffer.start + 1 + offset = self.tmodel.start + 1 fv_label = self.vheader.logicalIndexAt(0) + offset lv_label = self.vheader.logicalIndexAt( self.vheader.viewport().height() - 1) + offset - if lv_label == self.leaf_numrows : + if lv_label == self.leaf_numrows: self.tricky_vscrollbar.setValue(self.max_value) - elif fv_label == 1 : + elif fv_label == 1: self.tricky_vscrollbar.setValue(0) - else : - value = numpy.rint(numpy.array(fv_label/self.interval_size, - dtype=numpy.float64)) + else: + value = round(fv_label / self.interval_size) self.tricky_vscrollbar.setValue(value) - def updateView(self): """Update the view contents after a buffer fault. """ @@ -179,10 +163,9 @@ def updateView(self): QtCore.Qt.Vertical, 0, self.tmodel.numrows - 1) top_left = self.tmodel.index(0, 0) bottom_right = self.tmodel.index(self.tmodel.numrows - 1, - self.tmodel.numcols - 1) + self.tmodel.numcols - 1) self.dataChanged(top_left, bottom_right) - def navigateWithMouse(self, slider_action): """Navigate the view with the mouse. @@ -215,8 +198,8 @@ def navigateWithMouse(self, slider_action): 3: self.addPageStep, 4: self.subPageStep, 7: self.dragSlider - } - if not slider_action in actions.keys(): + } + if slider_action not in actions.keys(): return # Navigate the data dealing with buffer faults actions[slider_action]() @@ -226,7 +209,6 @@ def navigateWithMouse(self, slider_action): # reference self.syncView() - def mouseNavInfo(self, direction): """Gives information about model, vertical header and viewport. @@ -240,7 +222,7 @@ def mouseNavInfo(self, direction): # About the table table_rows = model.numrows - buffer_start = model.rbuffer.start + buffer_start = model.start # The viewport BEFORE navigating the data if (direction == 'u'): @@ -251,7 +233,6 @@ def mouseNavInfo(self, direction): return (model, vh, table_rows, buffer_start, row, page_step) - def addSingleStep(self): """Setup data for moving towards the last section line by line. """ @@ -262,19 +243,18 @@ def addSingleStep(self): # row of the dataset we still can go downwards so we have to # read the next contiguous buffer if (last_vp_row + 1 == table_rows) and \ - (buffer_start + table_rows < self.leaf_numrows): + (buffer_start + table_rows < self.leaf_numrows): # Buffer fault. The new buffer starts just after the current # first row of the viewport. new_start = buffer_start + last_vp_row - page_step + 1 model.loadData(new_start, table_rows) self.updateView() self.scrollTo( - model.index(new_start - model.rbuffer.start, 0), - QtWidgets.QAbstractItemView.PositionAtTop) + model.index(new_start - model.start, 0), + _aiv.PositionAtTop) else: self.vscrollbar.triggerAction(1) - def addPageStep(self): """Setup data for moving towards the last section page by page. """ @@ -285,19 +265,18 @@ def addPageStep(self): # row of the dataset we still can go downwards so we have to # read the next contiguous buffer if (last_vp_row + page_step + 1 > table_rows) and \ - (buffer_start + table_rows < self.leaf_numrows): + (buffer_start + table_rows < self.leaf_numrows): # Buffer fault. The new buffer starts at the current last # row of the viewport. new_start = buffer_start + last_vp_row model.loadData(new_start, table_rows) self.updateView() self.scrollTo( - model.index(new_start - model.rbuffer.start, 0), - QtWidgets.QAbstractItemView.PositionAtTop) + model.index(new_start - model.start, 0), + _aiv.PositionAtTop) else: self.vscrollbar.triggerAction(3) - def subSingleStep(self): """Setup data for moving towards the first section line by line. """ @@ -313,14 +292,12 @@ def subSingleStep(self): model.loadData( buffer_start + page_step - table_rows, table_rows) self.scrollTo( - model.index( - buffer_start + page_step - model.rbuffer.start - 1, 0), - QtWidgets.QAbstractItemView.PositionAtBottom) + model.index(buffer_start + page_step - model.start - 1, 0), + _aiv.PositionAtBottom) self.updateView() else: self.vscrollbar.triggerAction(2) - def subPageStep(self): """Setup data for moving towards the first section page by page. """ @@ -334,17 +311,16 @@ def subPageStep(self): # Buffer fault. The new buffer ends just at the current # first row of the viewport. model.loadData( - buffer_start + first_vp_row - table_rows + 1, - table_rows) + buffer_start + first_vp_row - table_rows + 1, + table_rows) self.updateView() self.scrollTo( model.index( - buffer_start + first_vp_row - model.rbuffer.start, 0), - QtWidgets.QAbstractItemView.PositionAtBottom) + buffer_start + first_vp_row - model.start, 0), + _aiv.PositionAtBottom) else: self.vscrollbar.triggerAction(4) - def dragSlider(self): """Move the slider by dragging it. @@ -371,14 +347,14 @@ def dragSlider(self): elif value >= self.max_value: value = self.max_value row = self.leaf_numrows - 1 - else : - row = numpy.array(self.interval_size*value, dtype=numpy.int64) + else: + row = self.interval_size * value # top buffer fault condition - if row < model.rbuffer.start: + if row < model.start: self.topBF(value, row) # bottom buffer fault condition - elif (row >= model.rbuffer.start + table_rows): + elif (row >= model.start + table_rows): self.bottomBF(value, row) # We are at top of the dataset elif value == self.tricky_vscrollbar.minimum(): @@ -389,16 +365,15 @@ def dragSlider(self): self.vscrollbar.triggerAction( QtWidgets.QAbstractSlider.SliderToMaximum) # we are somewhere in the middle of the dataset - else : + else: self.scrollTo( - model.index(row - model.rbuffer.start, 0), - QtWidgets.QAbstractItemView.PositionAtTop) - + model.index(row - model.start, 0), + _aiv.PositionAtTop) def topBF(self, value, row): """Going out of buffer when browsing upwards. - Buffer fault condition: row < rbuffer.start + Buffer fault condition: row < model.start :Parameters: @@ -410,23 +385,22 @@ def topBF(self, value, row): if value == self.tricky_vscrollbar.minimum(): start = 0 position = 0 - hint = QtWidgets.QAbstractItemView.PositionAtTop + hint = _aiv.PositionAtTop self.vscrollbar.triggerAction( QtWidgets.QAbstractSlider.SliderToMinimum) else: start = row - table_rows position = table_rows - 1 - hint = QtWidgets.QAbstractItemView.PositionAtBottom + hint = _aiv.PositionAtBottom self.tmodel.loadData(start, table_rows) self.updateView() self.scrollTo(self.tmodel.index(position, 0), hint) - def bottomBF(self, value, row): """Going out of buffer when browsing downwards. - Buffer fault condition: row > self.tmodel.rbuffer.start + table_rows - 1 + Buffer fault condition: row > self.tmodel.start + table_rows - 1 :Parameters: @@ -439,19 +413,18 @@ def bottomBF(self, value, row): row = self.leaf_numrows - 1 start = self.leaf_numrows - table_rows position = table_rows - 1 - hint = QtWidgets.QAbstractItemView.PositionAtBottom + hint = _aiv.PositionAtBottom self.vscrollbar.triggerAction( QtWidgets.QAbstractSlider.SliderToMinimum) else: start = row position = 0 - hint = QtWidgets.QAbstractItemView.PositionAtTop + hint = _aiv.PositionAtTop self.tmodel.loadData(start, table_rows) self.updateView() self.scrollTo(self.tmodel.index(position, 0), hint) - def wheelEvent(self, event): """Specialized handler for the wheel events received by the *viewport*. @@ -465,8 +438,7 @@ def wheelEvent(self, event): # has been rotated by 15 degrees. It *seems* that every eight of # degree corresponds to a distance of 1 pixel. delta = event.angleDelta().y() - self.wheel_step = \ - numpy.rint(abs(delta)/height).astype(numpy.int64) - 1 + self.wheel_step = round(abs(delta) / height) - 1 if delta < 0: self.wheelDown(event) else: @@ -477,7 +449,6 @@ def wheelEvent(self, event): else: QtWidgets.QTableView.wheelEvent(self, event) - def wheelDown(self, event): """Setup data for wheeling with the mouse towards the last section. """ @@ -496,13 +467,11 @@ def wheelDown(self, event): buffer_start + last_vp_row + self.wheel_step - page_step model.loadData(new_start, table_rows) self.updateView() - self.scrollTo( - model.index(new_start - model.rbuffer.start, 0), - QtWidgets.QAbstractItemView.PositionAtTop) + self.scrollTo(model.index(new_start - model.start, 0), + _aiv.PositionAtTop) else: QtCore.QCoreApplication.sendEvent(self.vscrollbar, event) - def wheelUp(self, event): """Setup data for wheeling with the mouse towards the first section. """ @@ -522,12 +491,11 @@ def wheelUp(self, event): self.updateView() self.scrollTo( model.index( - new_start + table_rows - model.rbuffer.start - 1, 0), - QtWidgets.QAbstractItemView.PositionAtBottom) + new_start + table_rows - model.start - 1, 0), + _aiv.PositionAtBottom) else: QtCore.QCoreApplication.sendEvent(self.vscrollbar, event) - def keyPressEvent(self, event): """Handle basic cursor movement for key events. @@ -559,7 +527,6 @@ def keyPressEvent(self, event): else: QtWidgets.QTableView.keyPressEvent(self, event) - def homeKeyPressEvent(self): """Specialised handler for the `Home` key press event. @@ -570,7 +537,7 @@ def homeKeyPressEvent(self): table_rows = model.numrows index = model.index(0, 0) # Update buffer if needed - if model.rbuffer.start > 0 : + if model.start > 0: model.loadData(0, table_rows) self.updateView() self.setCurrentIndex(index) @@ -580,7 +547,6 @@ def homeKeyPressEvent(self): # the displayed data self.tricky_vscrollbar.setValue(0) - def endKeyPressEvent(self): """Specialised handler for the `End` key press event. @@ -591,10 +557,10 @@ def endKeyPressEvent(self): table_rows = model.numrows index = model.index(table_rows - 1, model.numcols - 1) # Update buffer if needed - last_row = model.rbuffer.start + table_rows + last_row = model.start + table_rows if last_row < self.leaf_numrows: self.tmodel.loadData(self.leaf_numrows - table_rows, - table_rows) + table_rows) self.updateView() self.setCurrentIndex(index) self.scrollToBottom() @@ -603,7 +569,6 @@ def endKeyPressEvent(self): # the displayed data self.tricky_vscrollbar.setValue(self.max_value) - def keyboardNavInfo(self): """Gives information about model, and current cell. @@ -619,7 +584,7 @@ def keyboardNavInfo(self): # About the table table_rows = model.numrows - buffer_start = model.rbuffer.start + buffer_start = model.start page_step = self.vscrollbar.pageStep() @@ -630,8 +595,7 @@ def keyboardNavInfo(self): dataset_row = buffer_start + buffer_row return (model, table_rows, buffer_start, page_step, current_index, - buffer_row, buffer_column, dataset_row) - + buffer_row, buffer_column, dataset_row) def upKeyPressEvent(self, event): """Specialised handler for the cursor up key press event. @@ -649,13 +613,13 @@ def upKeyPressEvent(self, event): model.loadData(dataset_row - table_rows + page_step, table_rows) self.updateView() # The position of the new current row - row = dataset_row - model.rbuffer.start - 1 + row = dataset_row - model.start - 1 if row < 0: row = 0 index = model.index(row, buffer_column) self.setCurrentIndex(index) self.scrollTo(index, - QtWidgets.QAbstractItemView.PositionAtTop) + _aiv.PositionAtTop) else: QtWidgets.QTableView.keyPressEvent(self, event) @@ -664,7 +628,6 @@ def upKeyPressEvent(self, event): # reference self.syncView() - def pageUpKeyPressEvent(self, event): """Specialised handler for the `PageUp` key press event. @@ -681,13 +644,13 @@ def pageUpKeyPressEvent(self, event): model.loadData(dataset_row - table_rows, table_rows) self.updateView() # The position of the new current row - row = dataset_row - model.rbuffer.start - page_step - 1 + row = dataset_row - model.start - page_step - 1 if row < 0: row = 0 index = model.index(row, buffer_column) self.setCurrentIndex(index) self.scrollTo(index, - QtWidgets.QAbstractItemView.PositionAtTop) + _aiv.PositionAtTop) else: QtWidgets.QTableView.keyPressEvent(self, event) @@ -696,7 +659,6 @@ def pageUpKeyPressEvent(self, event): # reference self.syncView() - def downKeyPressEvent(self, event): """Specialised handler for the cursor down key press event. @@ -714,13 +676,13 @@ def downKeyPressEvent(self, event): model.loadData(dataset_row - page_step + 1, table_rows) self.updateView() # The position of the new current row - row = dataset_row - model.rbuffer.start + 1 + row = dataset_row - model.start + 1 if row > table_rows - 1: row = table_rows - 1 index = model.index(row, buffer_column) self.setCurrentIndex(index) self.scrollTo(index, - QtWidgets.QAbstractItemView.PositionAtBottom) + _aiv.PositionAtBottom) else: QtWidgets.QTableView.keyPressEvent(self, event) @@ -729,7 +691,6 @@ def downKeyPressEvent(self, event): # reference self.syncView() - def pageDownKeyPressEvent(self, event): """Specialised handler for the `PageDown` key press event. @@ -747,13 +708,13 @@ def pageDownKeyPressEvent(self, event): model.loadData(dataset_row + 1, table_rows) self.updateView() # The position of the new current row - row = dataset_row - model.rbuffer.start + page_step + 1 + row = dataset_row - model.start + page_step + 1 if row > table_rows - 1: row = table_rows - 1 index = model.index(row, buffer_column) self.setCurrentIndex(index) self.scrollTo(index, - QtWidgets.QAbstractItemView.PositionAtBottom) + _aiv.PositionAtBottom) else: QtWidgets.QTableView.keyPressEvent(self, event) @@ -788,8 +749,7 @@ def currentChanged(self, current, previous): QtWidgets.QTableView.currentChanged(self, current, previous) if self.tmodel.numrows < self.leaf_numrows: - self.valid_current_buffer = self.tmodel.rbuffer.start - + self.valid_current_buffer = self.tmodel.start # This method has been renamed from loadDatasetCurrentCell to # validCurrentCellBuffer. The method has been debugged too @@ -799,13 +759,12 @@ def validCurrentCellBuffer(self): table_rows = self.tmodel.numrows valid_current = self.currentIndex().row() + self.valid_current_buffer - if not (self.tmodel.rbuffer.start <= - valid_current <= - self.tmodel.rbuffer.start + table_rows - 1): + if not (self.tmodel.start <= + valid_current <= + self.tmodel.start + table_rows - 1): self.tmodel.loadData(self.valid_current_buffer, table_rows) self.updateView() - def selectionChanged(self, selected, deselected): """Track the dataset selected cells. @@ -824,7 +783,7 @@ def selectionChanged(self, selected, deselected): if len(selection): model.selected_cell = { 'index': selection[0], - 'buffer_start': model.rbuffer.start, + 'buffer_start': model.start, } else: QtWidgets.QTableView.selectionChanged(self, selected, deselected) diff --git a/vitables/vttables/scrollbar.py b/vitables/vttables/scrollbar.py index 583ef23e..0d3d7da3 100644 --- a/vitables/vttables/scrollbar.py +++ b/vitables/vttables/scrollbar.py @@ -33,11 +33,12 @@ __docformat__ = 'restructuredtext' -from qtpy import QtCore -from qtpy import QtGui -from qtpy import QtWidgets +from qtpy.QtCore import Qt +from qtpy.QtCore import QEvent +from qtpy.QtWidgets import QScrollBar -class ScrollBar(QtWidgets.QScrollBar): + +class ScrollBar(QScrollBar): """ A specialised scrollbar for views of huge datasets. @@ -60,15 +61,19 @@ def __init__(self, view): super(ScrollBar, self).__init__(parent) view.vscrollbar.setVisible(False) parent.layout().addWidget(self) - self.setOrientation(QtCore.Qt.Vertical) + self.setOrientation(Qt.Vertical) self.setObjectName('tricky_vscrollbar') - def event(self, e): - """Filter wheel events and send them to the table viewport. - """ - - if (e.type() == QtCore.QEvent.Wheel): + """Filter wheel events and send them to the table viewport. """ + if (e.type() == QEvent.Wheel): self.view.wheelEvent(e) return True - return QtWidgets.QScrollBar.event(self, e) + return QScrollBar.event(self, e) + + def setMaxValue(self, max_value): + """Ensure range of scrollbar is a signed 32bit integer.""" + max_value = min(2 ** 31 - 1, max_value) + self.setMaximum(max_value) + + return max_value