b96a03fe20959d9c2c0a9d260495e255750af0fc
[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 maeqt
14 import util.misc as misc_utils
15 import history
16 import operation
17
18
19 _moduleLogger = logging.getLogger(__name__)
20
21
22 class QCalcHistory(history.AbstractHistory):
23
24         _CLOSE_COLUMN = 0
25         _EQ_COLUMN = 1
26         _RESULT_COLUMN = 2
27
28         def __init__(self, errorReporter):
29                 super(QCalcHistory, self).__init__()
30                 self._prettyRenderer = operation.render_number()
31                 self._errorReporter = errorReporter
32
33                 self._historyStore = QtGui.QStandardItemModel()
34                 self._historyStore.setHorizontalHeaderLabels(["", "Equation", "Result"])
35                 self._historyStore.itemChanged.connect(self._on_item_changed)
36
37                 self._historyView = QtGui.QTreeView()
38                 self._historyView.setModel(self._historyStore)
39                 self._historyView.setUniformRowHeights(True)
40                 self._historyView.setRootIsDecorated(False)
41                 self._historyView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
42                 self._historyView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
43                 self._historyView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
44                 self._historyView.setHeaderHidden(True)
45                 self._historyView.activated.connect(self._on_row_activated)
46
47                 viewHeader = self._historyView.header()
48                 viewHeader.setSortIndicatorShown(True)
49                 viewHeader.setClickable(True)
50
51                 viewHeader.setResizeMode(self._CLOSE_COLUMN, QtGui.QHeaderView.ResizeToContents)
52                 viewHeader.setResizeMode(self._EQ_COLUMN, QtGui.QHeaderView.Stretch)
53                 viewHeader.setResizeMode(self._RESULT_COLUMN, QtGui.QHeaderView.ResizeToContents)
54                 viewHeader.setStretchLastSection(False)
55
56                 self._rowCount = 0
57                 self._programmaticUpdate = False
58                 self._closeIcon = maeqt.get_theme_icon(("window-close", "general_close", "gtk-close"))
59
60         @property
61         def toplevel(self):
62                 return self._historyView
63
64         @property
65         def errorReporter(self):
66                 return self._errorReporter
67
68         def push(self, node):
69                 simpleNode = node.simplify()
70
71                 closeIcon = self._closeIcon
72                 icon = QtGui.QStandardItem(closeIcon, "")
73                 icon.setEditable(False)
74                 icon.setCheckable(False)
75                 equation = QtGui.QStandardItem(operation.render_operation(self._prettyRenderer, node))
76                 equation.setData(node)
77                 equation.setCheckable(False)
78                 eqFont = equation.font()
79                 eqFont.setPointSize(max(eqFont.pointSize() - 3, 5))
80                 equation.setFont(eqFont)
81
82                 result = QtGui.QStandardItem(operation.render_operation(self._prettyRenderer, simpleNode))
83                 result.setData(simpleNode)
84                 result.setEditable(False)
85                 result.setCheckable(False)
86
87                 row = (icon, equation, result)
88                 self._historyStore.appendRow(row)
89
90                 index = result.index()
91                 self._historyView.scrollTo(index)
92                 self._rowCount += 1
93
94         def pop(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                 self._rowCount -= 1
100                 return equation.data().toPyObject()
101
102         def peek(self):
103                 if len(self) == 0:
104                         raise IndexError("Not enough items in the history for the operation")
105
106                 icon, equation, result = self._historyStore.takeRow(self._rowCount - 1)
107                 row = (icon, equation, result)
108                 self._historyStore.appendRow(row)
109
110                 return equation.data().toPyObject()
111
112         def clear(self):
113                 self._historyStore.clear()
114                 self._rowCount = 0
115
116         @misc_utils.log_exception(_moduleLogger)
117         def _on_row_activated(self, index):
118                 if index.column() == self._CLOSE_COLUMN:
119                         self._historyStore.removeRow(index.row(), index.parent())
120                         self._rowCount -= 1
121                 elif index.column() == self._EQ_COLUMN:
122                         self._duplicate_row(index)
123                 elif index.column() == self._RESULT_COLUMN:
124                         self._duplicate_row(index)
125                 else:
126                         raise NotImplementedError("Unsupported column to activate %s" % index.column())
127
128         @misc_utils.log_exception(_moduleLogger)
129         def _on_item_changed(self, item):
130                 if self._programmaticUpdate:
131                         _moduleLogger.info("Blocking updating %r recursively" % item)
132                         return
133                 self._programmaticUpdate = True
134                 try:
135                         if item.column() in [self._EQ_COLUMN, self._RESULT_COLUMN]:
136                                 self._update_input(item)
137                         else:
138                                 raise NotImplementedError("Unsupported column to edit %s" % item.column())
139                 except StandardError, e:
140                         self.errorReporter.push_exception()
141                 finally:
142                         self._programmaticUpdate = False
143
144         def _duplicate_row(self, index):
145                 item = self._historyStore.item(index.row(), self._EQ_COLUMN)
146                 self.push(item.data().toPyObject())
147
148         def _parse_value(self, value):
149                 raise NotImplementedError("What?")
150
151         def _update_input(self, item):
152                 node = item.data().toPyObject()
153                 try:
154                         eqNode = self._parse_value(str(item.text()))
155                         newText = operation.render_operation(self._prettyRenderer, eqNode)
156
157                         eqItem = self._historyStore.item(item.row(), self._EQ_COLUMN)
158                         eqItem.setData(eqNode)
159                         eqItem.setText(newText)
160
161                         resultNode = eqNode.simplify()
162                         resultText = operation.render_operation(self._prettyRenderer, resultNode)
163                         resultItem = self._historyStore.item(item.row(), self._RESULT_COLUMN)
164                         resultItem.setData(resultNode)
165                         resultItem.setText(resultText)
166                 except:
167                         oldText = operation.render_operation(self._prettyRenderer, node)
168                         item.setText(oldText)
169                         raise
170
171         def __len__(self):
172                 return self._rowCount
173
174         def __iter__(self):
175                 for i in xrange(self._rowCount):
176                         item = self._historyStore.item(i, self._EQ_COLUMN)
177                         if item is None:
178                                 continue
179                         yield item.data().toPyObject()