http://helm.cs.unibo.it/mml-widget/ (MathML widget in C++)
"""
+from __future__ import with_statement
+
import logging
-from PyQt4 import QtGui
-from PyQt4 import QtCore
+import util.qt_compat as qt_compat
+QtCore = qt_compat.QtCore
+QtGui = qt_compat.import_module("QtGui")
+from util import qui_utils
import util.misc as misc_utils
import history
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
- elif role == QtCore.Qt.DecorationRole:
- if index.column() == RowData.CLOSE_COLUMN:
- return None
- else:
- return None
- elif role == QtCore.Qt.TextAlignmentRole:
- return RowData.ALIGNMENT[index.column()]
- elif role != QtCore.Qt.DisplayRole:
- return None
-
- item = index.internalPointer()
- if isinstance(item, RowData):
- return item.data(index.column())
- elif item is RowData.HEADERS:
- return item[index.column()]
-
- @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()
-
- if parent.isValid():
- return QtCore.QModelIndex()
-
- parentItem = RowData.HEADERS
- childItem = self._children[row]
- if 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():
- return len(self._children)
- else:
- return len(self._children)
-
- def push(self, row):
- self._children.append(row)
- self._signal_rows_added()
-
- def pop(self):
- data = self._children[-1]
- del self._children[-1]
- self._signal_rows_removed()
- 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):
- 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._errorLog = 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)
self._historyView.setUniformRowHeights(True)
+ self._historyView.setRootIsDecorated(False)
self._historyView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self._historyView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
self._historyView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
self._historyView.setHeaderHidden(True)
+ self._historyView.activated.connect(self._on_row_activated)
viewHeader = self._historyView.header()
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
+ self._closeIcon = qui_utils.get_theme_icon(("window-close", "general_close", "gtk-close"))
+
@property
def toplevel(self):
return self._historyView
def push(self, node):
simpleNode = node.simplify()
- row = RowData(self._prettyRenderer, node, simpleNode)
- self._historyStore.push(row)
- # @todo Scroll to bottom
+ closeIcon = self._closeIcon
+ icon = QtGui.QStandardItem(closeIcon, "")
+ icon.setEditable(False)
+ icon.setCheckable(False)
+ equation = QtGui.QStandardItem(operation.render_operation(self._prettyRenderer, node))
+ equation.setData(node)
+ equation.setCheckable(False)
+ eqFont = equation.font()
+ eqFont.setPointSize(max(eqFont.pointSize() - 3, 5))
+ equation.setFont(eqFont)
+
+ result = QtGui.QStandardItem(operation.render_operation(self._prettyRenderer, simpleNode))
+ result.setData(simpleNode)
+ result.setEditable(False)
+ result.setCheckable(False)
+
+ row = (icon, equation, result)
+ self._historyStore.appendRow(row)
+
+ index = result.index()
+ self._historyView.scrollToBottom()
+ 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()
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()
def clear(self):
self._historyStore.clear()
+ self._rowCount = 0
+
+ def scroll_to_bottom(self):
+ self._historyView.scrollToBottom()
+
+ @misc_utils.log_exception(_moduleLogger)
+ def _on_row_activated(self, index):
+ with qui_utils.notify_error(self._errorLog):
+ if index.column() == self._CLOSE_COLUMN:
+ self._historyStore.removeRow(index.row(), index.parent())
+ self._rowCount -= 1
+ elif index.column() == self._EQ_COLUMN:
+ self._duplicate_row(index)
+ elif index.column() == self._RESULT_COLUMN:
+ self._duplicate_row(index)
+ else:
+ raise NotImplementedError("Unsupported column to activate %s" % index.column())
+
+ @misc_utils.log_exception(_moduleLogger)
+ def _on_item_changed(self, item):
+ with qui_utils.notify_error(self._errorLog):
+ 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 _duplicate_row(self, index):
+ item = self._historyStore.item(index.row(), self._EQ_COLUMN)
+ self.push(item.data())
+
+ def _parse_value(self, value):
+ raise NotImplementedError("What?")
+
+ def _update_input(self, item):
+ node = item.data()
+ 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):
+ item = self._historyStore.item(i, self._EQ_COLUMN)
+ if item is None:
+ continue
+ yield item.data()