Switching to StandardItemModel with editable items, as well as fixed bugs in the...
authorEd Page <eopage@byu.net>
Sat, 10 Jul 2010 02:18:26 +0000 (21:18 -0500)
committerEd Page <eopage@byu.net>
Sat, 10 Jul 2010 02:18:26 +0000 (21:18 -0500)
src/ejpi_qt.py
src/history.py
src/qhistory.py

index 02cb3c2..bae3116 100755 (executable)
@@ -143,9 +143,8 @@ class QErrorDisplay(object):
                self._message.setText("Boo")
 
                icon = QtGui.QIcon.fromTheme("gtk-close")
-               self._closeIcon = icon.pixmap(32, 32)
-               self._closeLabel = QtGui.QLabel()
-               self._closeLabel.setPixmap(self._closeIcon)
+               self._closeLabel = QtGui.QPushButton(icon, "")
+               self._closeLabel.clicked.connect(self._on_close)
 
                self._controlLayout = QtGui.QHBoxLayout()
                self._controlLayout.addWidget(self._severityLabel)
@@ -153,6 +152,8 @@ class QErrorDisplay(object):
                self._controlLayout.addWidget(self._closeLabel)
 
                self._topLevelLayout = QtGui.QHBoxLayout()
+               self._topLevelLayout.addLayout(self._controlLayout)
+               self._hide_message()
 
        @property
        def toplevel(self):
@@ -180,11 +181,15 @@ class QErrorDisplay(object):
 
        def _show_message(self, message):
                self._message.setText(message)
-               self._topLevelLayout.addLayout(self._controlLayout)
+               self._severityLabel.show()
+               self._message.show()
+               self._closeLabel.show()
 
        def _hide_message(self):
                self._message.setText("")
-               self._topLevelLayout.removeItem(self._controlLayout)
+               self._severityLabel.hide()
+               self._message.hide()
+               self._closeLabel.hide()
 
 
 class QValueEntry(object):
@@ -250,10 +255,8 @@ class MainWindow(object):
        def __init__(self, parent, app):
                self._app = app
 
-               self._historyView = qhistory.QCalcHistory()
-
                self._errorDisplay = QErrorDisplay()
-
+               self._historyView = qhistory.QCalcHistory(self._errorDisplay)
                self._userEntry = QValueEntry()
 
                self._controlLayout = QtGui.QVBoxLayout()
index eb5c62c..9a0b47f 100644 (file)
@@ -1,6 +1,7 @@
 #!/usr/bin/env python
 
 
+import re
 import weakref
 
 from util import algorithms
@@ -14,6 +15,15 @@ __BASE_MAPPINGS = {
 }
 
 
+_VARIABLE_VALIDATION_RE = re.compile("^[a-zA-Z0-9]+$")
+
+
+def validate_variable_name(variableName):
+       match = _VARIABLE_VALIDATION_RE.match(variableName)
+       if match is None:
+               raise RuntimeError("Invalid characters")
+
+
 def parse_number(userInput):
        try:
                base = __BASE_MAPPINGS.get(userInput[0:2], 10)
@@ -101,6 +111,7 @@ class RpnCalcHistory(object):
 
        def __init__(self, history, entry, errorReporting, constants, operations):
                self.history = history
+               self.history._parse_value = self._parse_value
                self.__entry = weakref.ref(entry)
 
                self.__errorReporter = errorReporting
@@ -176,6 +187,7 @@ class RpnCalcHistory(object):
                except KeyError:
                        pass
 
+               validate_variable_name(userInput)
                return operation.Variable(userInput)
 
        def _apply_operation(self, Node):
index 8268cf4..62d53e7 100644 (file)
@@ -18,186 +18,20 @@ import operation
 _moduleLogger = logging.getLogger(__name__)
 
 
-class RowData(object):
-
-       HEADERS = ["", "Equation", "Result"]
-       ALIGNMENT = [QtCore.Qt.AlignLeft, QtCore.Qt.AlignLeft, QtCore.Qt.AlignLeft]
-       CLOSE_COLUMN = 0
-       EQ_COLUMN = 1
-       RESULT_COLUMN = 2
-
-       def __init__(self, renderer, node, simpleNode):
-               self._node = node
-               self._simpleNode = simpleNode
-               self._prettyRenderer = renderer
-
-       @property
-       def node(self):
-               return self._node
-
-       @property
-       def simpleNode(self):
-               return self._simpleNode
-
-       @property
-       def equation(self):
-               return operation.render_operation(self._prettyRenderer, self._node)
-
-       @property
-       def result(self):
-               return operation.render_operation(self._prettyRenderer, self._simpleNode)
-
-       def data(self, column):
-               if column == self.CLOSE_COLUMN:
-                       return ""
-               elif column == self.EQ_COLUMN:
-                       return self.equation
-               elif column == self.RESULT_COLUMN:
-                       return self.result
-               else:
-                       return None
-
-
-class HistoryModel(QtCore.QAbstractItemModel):
-
-       def __init__(self, parent=None):
-               super(HistoryModel, self).__init__(parent)
-
-               self._children = []
-
-       @misc_utils.log_exception(_moduleLogger)
-       def columnCount(self, parent):
-               if parent.isValid():
-                       return 0
-               else:
-                       return len(RowData.HEADERS)
-
-       @misc_utils.log_exception(_moduleLogger)
-       def data(self, index, role):
-               if not index.isValid():
-                       return None
-
-               if role == QtCore.Qt.DisplayRole:
-                       item = index.internalPointer()
-                       if isinstance(item, RowData):
-                               print "d-rw", item.data(index.column())
-                               return item.data(index.column())
-                       elif item is RowData.HEADERS:
-                               print "d-h", item[index.column()]
-                               return item[index.column()]
-               elif role == QtCore.Qt.TextAlignmentRole:
-                       return RowData.ALIGNMENT[index.column()]
-               elif role == QtCore.Qt.DecorationRole:
-                       if index.column() == RowData.CLOSE_COLUMN:
-                               return None
-                       else:
-                               return None
-               else:
-                       return None
-
-       @misc_utils.log_exception(_moduleLogger)
-       def flags(self, index):
-               if not index.isValid():
-                       return QtCore.Qt.NoItemFlags
-
-               return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
-
-       @misc_utils.log_exception(_moduleLogger)
-       def headerData(self, section, orientation, role):
-               if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
-                       return RowData.HEADERS[section]
-
-               return None
-
-       @misc_utils.log_exception(_moduleLogger)
-       def index(self, row, column, parent):
-               if not self.hasIndex(row, column, parent):
-                       return QtCore.QModelIndex()
-
-               elif parent.isValid():
-                       return QtCore.QModelIndex()
-
-               parentItem = RowData.HEADERS
-               childItem = self._children[row]
-               if childItem:
-                       print "i", row, column, childItem
-                       return self.createIndex(row, column, childItem)
-               else:
-                       return QtCore.QModelIndex()
-
-       @misc_utils.log_exception(_moduleLogger)
-       def parent(self, index):
-               if not index.isValid():
-                       return QtCore.QModelIndex()
-
-               childItem = index.internalPointer()
-               if isinstance(childItem, RowData):
-                       return QtCore.QModelIndex()
-               elif childItem is RowData.HEADERS:
-                       return None
-
-       @misc_utils.log_exception(_moduleLogger)
-       def rowCount(self, parent):
-               if 0 < parent.column():
-                       return 0
-
-               if not parent.isValid():
-                       print "rc", len(self._children)
-                       return len(self._children)
-               else:
-                       print "rc", len(self._children)
-                       return len(self._children)
-
-       def push(self, row):
-               self._children.append(row)
-               self._signal_rows_added()
-               print "push", row
-               print "push", len(self._children)
-
-       def pop(self):
-               data = self._children[-1]
-               del self._children[-1]
-               self._signal_rows_removed()
-               print "pop", data
-               return data
-
-       def peek(self):
-               data = self._children[-1]
-               return data
-
-       def clear(self):
-               del self._children[:]
-               self._all_changed
-
-       def __len__(self):
-               return len(self._children)
-
-       def __iter__(self):
-               return iter(self._children)
-
-       def _signal_rows_added(self):
-               # @todo Only signal new rows
-               self._all_changed()
-
-       def _signal_rows_removed(self):
-               # @todo Only signal new rows
-               self._all_changed()
-
-       def _all_changed(self):
-               if not self._children:
-                       return
-               topLeft = self.createIndex(0, 0, self._children[0])
-               bottomRight = self.createIndex(len(self._children)-1, len(RowData.HEADERS)-1, self._children[-1])
-               self.dataChanged.emit(topLeft, bottomRight)
-
-
 class QCalcHistory(history.AbstractHistory):
 
-       def __init__(self):
+       _CLOSE_COLUMN = 0
+       _EQ_COLUMN = 1
+       _RESULT_COLUMN = 2
+
+       def __init__(self, errorReporter):
                super(QCalcHistory, self).__init__()
                self._prettyRenderer = operation.render_number()
+               self._errorReporter = errorReporter
 
-               self._historyStore = HistoryModel()
+               self._historyStore = QtGui.QStandardItemModel()
+               self._historyStore.setHorizontalHeaderLabels(["", "Equation", "Result"])
+               self._historyStore.itemChanged.connect(self._on_item_changed)
 
                self._historyView = QtGui.QTreeView()
                self._historyView.setModel(self._historyStore)
@@ -211,41 +45,103 @@ class QCalcHistory(history.AbstractHistory):
                viewHeader.setSortIndicatorShown(True)
                viewHeader.setClickable(True)
 
-               viewHeader.setResizeMode(RowData.CLOSE_COLUMN, QtGui.QHeaderView.ResizeToContents)
-               viewHeader.setResizeMode(RowData.EQ_COLUMN, QtGui.QHeaderView.ResizeToContents)
-               viewHeader.setResizeMode(RowData.RESULT_COLUMN, QtGui.QHeaderView.ResizeToContents)
+               viewHeader.setResizeMode(self._CLOSE_COLUMN, QtGui.QHeaderView.ResizeToContents)
+               viewHeader.setResizeMode(self._EQ_COLUMN, QtGui.QHeaderView.Stretch)
+               viewHeader.setResizeMode(self._RESULT_COLUMN, QtGui.QHeaderView.ResizeToContents)
                viewHeader.setStretchLastSection(False)
 
+               self._rowCount = 0
+               self._programmaticUpdate = False
+
        @property
        def toplevel(self):
                return self._historyView
 
+       @property
+       def errorReporter(self):
+               return self._errorReporter
+
        def push(self, node):
                simpleNode = node.simplify()
-               row = RowData(self._prettyRenderer, node, simpleNode)
-               self._historyStore.push(row)
 
-               # @todo Scroll to bottom
+               icon = QtGui.QStandardItem(QtGui.QIcon.fromTheme("gtk-close"), "")
+               icon.setEditable(False)
+               equation = QtGui.QStandardItem(operation.render_operation(self._prettyRenderer, node))
+               equation.setData(node)
+               result = QtGui.QStandardItem(operation.render_operation(self._prettyRenderer, simpleNode))
+               result.setData(simpleNode)
+
+               row = (icon, equation, result)
+               self._historyStore.appendRow(row)
+
+               index = result.index()
+               self._historyView.scrollTo(index)
+               self._rowCount += 1
 
        def pop(self):
-               if len(self._historyStore) == 0:
+               if len(self) == 0:
                        raise IndexError("Not enough items in the history for the operation")
 
-               row = self._historyStore.pop()
-               return row.node
+               icon, equation, result = self._historyStore.takeRow(self._rowCount - 1)
+               self._rowCount -= 1
+               return equation.data().toPyObject()
 
        def peek(self):
-               if len(self._historyStore) == 0:
+               if len(self) == 0:
                        raise IndexError("Not enough items in the history for the operation")
-               row = self._historyStore.peek()
-               return row.node
+
+               icon, equation, result = self._historyStore.takeRow(self._rowCount - 1)
+               row = (icon, equation, result)
+               self._historyStore.appendRow(row)
+
+               return equation.data().toPyObject()
 
        def clear(self):
                self._historyStore.clear()
+               self._rowCount = 0
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_item_changed(self, item):
+               if self._programmaticUpdate:
+                       _moduleLogger.info("Blocking updating %r recursively" % item)
+                       return
+               self._programmaticUpdate = True
+               try:
+                       if item.column() in [self._EQ_COLUMN, self._RESULT_COLUMN]:
+                               self._update_input(item)
+                       else:
+                               raise NotImplementedError("Unsupported column to edit %s" % item.column())
+               except StandardError, e:
+                       self.errorReporter.push_exception()
+               finally:
+                       self._programmaticUpdate = False
+
+       def _parse_value(self, value):
+               raise NotImplementedError("What?")
+
+       def _update_input(self, item):
+               node = item.data().toPyObject()
+               try:
+                       eqNode = self._parse_value(str(item.text()))
+                       newText = operation.render_operation(self._prettyRenderer, eqNode)
+
+                       eqItem = self._historyStore.item(item.row(), self._EQ_COLUMN)
+                       eqItem.setData(eqNode)
+                       eqItem.setText(newText)
+
+                       resultNode = eqNode.simplify()
+                       resultText = operation.render_operation(self._prettyRenderer, resultNode)
+                       resultItem = self._historyStore.item(item.row(), self._RESULT_COLUMN)
+                       resultItem.setData(resultNode)
+                       resultItem.setText(resultText)
+               except:
+                       oldText = operation.render_operation(self._prettyRenderer, node)
+                       item.setText(oldText)
+                       raise
 
        def __len__(self):
-               return len(self._historyStore)
+               return self._rowCount
 
        def __iter__(self):
-               for row in iter(self._historyStore):
-                       yield row.node
+               for i in xrange(self._rowCount):
+                       yield self._historyStore.item(i, 1).data().toPyObject()