If editing isn't supported (Maemo 5 it seems) then fall back to duplicating
[ejpi] / src / qhistory.py
1 #!/usr/bin/env python
2
3 """
4 http://www.grigoriev.ru/svgmath/ (MathML->SVG in Python)
5 http://helm.cs.unibo.it/mml-widget/ (MathML widget in C++)
6 """
7
8 import logging
9
10 from PyQt4 import QtGui
11 from PyQt4 import QtCore
12
13 import util.misc as misc_utils
14 import history
15 import operation
16
17
18 _moduleLogger = logging.getLogger(__name__)
19
20
21 class QCalcHistory(history.AbstractHistory):
22
23         _CLOSE_COLUMN = 0
24         _EQ_COLUMN = 1
25         _RESULT_COLUMN = 2
26
27         def __init__(self, errorReporter):
28                 super(QCalcHistory, self).__init__()
29                 self._prettyRenderer = operation.render_number()
30                 self._errorReporter = errorReporter
31
32                 self._historyStore = QtGui.QStandardItemModel()
33                 self._historyStore.setHorizontalHeaderLabels(["", "Equation", "Result"])
34                 self._historyStore.itemChanged.connect(self._on_item_changed)
35
36                 self._historyView = QtGui.QTreeView()
37                 self._historyView.setModel(self._historyStore)
38                 self._historyView.setUniformRowHeights(True)
39                 self._historyView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
40                 self._historyView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
41                 self._historyView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
42                 self._historyView.setHeaderHidden(True)
43                 self._historyView.activated.connect(self._on_row_activated)
44
45                 viewHeader = self._historyView.header()
46                 viewHeader.setSortIndicatorShown(True)
47                 viewHeader.setClickable(True)
48
49                 viewHeader.setResizeMode(self._CLOSE_COLUMN, QtGui.QHeaderView.ResizeToContents)
50                 viewHeader.setResizeMode(self._EQ_COLUMN, QtGui.QHeaderView.Stretch)
51                 viewHeader.setResizeMode(self._RESULT_COLUMN, QtGui.QHeaderView.ResizeToContents)
52                 viewHeader.setStretchLastSection(False)
53
54                 self._rowCount = 0
55                 self._programmaticUpdate = False
56
57         @property
58         def toplevel(self):
59                 return self._historyView
60
61         @property
62         def errorReporter(self):
63                 return self._errorReporter
64
65         def push(self, node):
66                 simpleNode = node.simplify()
67
68                 icon = QtGui.QStandardItem(QtGui.QIcon.fromTheme("gtk-close"), "")
69                 icon.setEditable(False)
70                 icon.setCheckable(False)
71                 equation = QtGui.QStandardItem(operation.render_operation(self._prettyRenderer, node))
72                 equation.setData(node)
73                 equation.setCheckable(False)
74                 result = QtGui.QStandardItem(operation.render_operation(self._prettyRenderer, simpleNode))
75                 result.setData(simpleNode)
76                 result.setEditable(False)
77                 result.setCheckable(False)
78
79                 row = (icon, equation, result)
80                 self._historyStore.appendRow(row)
81
82                 index = result.index()
83                 self._historyView.scrollTo(index)
84                 self._rowCount += 1
85
86         def pop(self):
87                 if len(self) == 0:
88                         raise IndexError("Not enough items in the history for the operation")
89
90                 icon, equation, result = self._historyStore.takeRow(self._rowCount - 1)
91                 self._rowCount -= 1
92                 return equation.data().toPyObject()
93
94         def peek(self):
95                 if len(self) == 0:
96                         raise IndexError("Not enough items in the history for the operation")
97
98                 icon, equation, result = self._historyStore.takeRow(self._rowCount - 1)
99                 row = (icon, equation, result)
100                 self._historyStore.appendRow(row)
101
102                 return equation.data().toPyObject()
103
104         def clear(self):
105                 self._historyStore.clear()
106                 self._rowCount = 0
107
108         @misc_utils.log_exception(_moduleLogger)
109         def _on_row_activated(self, index):
110                 if index.column() == self._CLOSE_COLUMN:
111                         self._historyStore.removeRow(index.row(), index.parent())
112                         self._rowCount -= 1
113                 elif index.column() == self._EQ_COLUMN:
114                         self._duplicate_row(index)
115                 elif index.column() == self._RESULT_COLUMN:
116                         self._duplicate_row(index)
117                 else:
118                         raise NotImplementedError("Unsupported column to activate %s" % index.column())
119
120         @misc_utils.log_exception(_moduleLogger)
121         def _on_item_changed(self, item):
122                 if self._programmaticUpdate:
123                         _moduleLogger.info("Blocking updating %r recursively" % item)
124                         return
125                 self._programmaticUpdate = True
126                 try:
127                         if item.column() in [self._EQ_COLUMN, self._RESULT_COLUMN]:
128                                 self._update_input(item)
129                         else:
130                                 raise NotImplementedError("Unsupported column to edit %s" % item.column())
131                 except StandardError, e:
132                         self.errorReporter.push_exception()
133                 finally:
134                         self._programmaticUpdate = False
135
136         def _duplicate_row(self, index):
137                 item = self._historyStore.item(index.row(), self._EQ_COLUMN)
138                 self.push(item.data().toPyObject())
139
140         def _parse_value(self, value):
141                 raise NotImplementedError("What?")
142
143         def _update_input(self, item):
144                 node = item.data().toPyObject()
145                 try:
146                         eqNode = self._parse_value(str(item.text()))
147                         newText = operation.render_operation(self._prettyRenderer, eqNode)
148
149                         eqItem = self._historyStore.item(item.row(), self._EQ_COLUMN)
150                         eqItem.setData(eqNode)
151                         eqItem.setText(newText)
152
153                         resultNode = eqNode.simplify()
154                         resultText = operation.render_operation(self._prettyRenderer, resultNode)
155                         resultItem = self._historyStore.item(item.row(), self._RESULT_COLUMN)
156                         resultItem.setData(resultNode)
157                         resultItem.setText(resultText)
158                 except:
159                         oldText = operation.render_operation(self._prettyRenderer, node)
160                         item.setText(oldText)
161                         raise
162
163         def __len__(self):
164                 return self._rowCount
165
166         def __iter__(self):
167                 for i in xrange(self._rowCount):
168                         item = self._historyStore.item(i, self._EQ_COLUMN)
169                         if item is None:
170                                 continue
171                         yield item.data().toPyObject()