8268cf45b80918d3ba5b05d3159d1ee9c6004837
[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 RowData(object):
22
23         HEADERS = ["", "Equation", "Result"]
24         ALIGNMENT = [QtCore.Qt.AlignLeft, QtCore.Qt.AlignLeft, QtCore.Qt.AlignLeft]
25         CLOSE_COLUMN = 0
26         EQ_COLUMN = 1
27         RESULT_COLUMN = 2
28
29         def __init__(self, renderer, node, simpleNode):
30                 self._node = node
31                 self._simpleNode = simpleNode
32                 self._prettyRenderer = renderer
33
34         @property
35         def node(self):
36                 return self._node
37
38         @property
39         def simpleNode(self):
40                 return self._simpleNode
41
42         @property
43         def equation(self):
44                 return operation.render_operation(self._prettyRenderer, self._node)
45
46         @property
47         def result(self):
48                 return operation.render_operation(self._prettyRenderer, self._simpleNode)
49
50         def data(self, column):
51                 if column == self.CLOSE_COLUMN:
52                         return ""
53                 elif column == self.EQ_COLUMN:
54                         return self.equation
55                 elif column == self.RESULT_COLUMN:
56                         return self.result
57                 else:
58                         return None
59
60
61 class HistoryModel(QtCore.QAbstractItemModel):
62
63         def __init__(self, parent=None):
64                 super(HistoryModel, self).__init__(parent)
65
66                 self._children = []
67
68         @misc_utils.log_exception(_moduleLogger)
69         def columnCount(self, parent):
70                 if parent.isValid():
71                         return 0
72                 else:
73                         return len(RowData.HEADERS)
74
75         @misc_utils.log_exception(_moduleLogger)
76         def data(self, index, role):
77                 if not index.isValid():
78                         return None
79
80                 if role == QtCore.Qt.DisplayRole:
81                         item = index.internalPointer()
82                         if isinstance(item, RowData):
83                                 print "d-rw", item.data(index.column())
84                                 return item.data(index.column())
85                         elif item is RowData.HEADERS:
86                                 print "d-h", item[index.column()]
87                                 return item[index.column()]
88                 elif role == QtCore.Qt.TextAlignmentRole:
89                         return RowData.ALIGNMENT[index.column()]
90                 elif role == QtCore.Qt.DecorationRole:
91                         if index.column() == RowData.CLOSE_COLUMN:
92                                 return None
93                         else:
94                                 return None
95                 else:
96                         return None
97
98         @misc_utils.log_exception(_moduleLogger)
99         def flags(self, index):
100                 if not index.isValid():
101                         return QtCore.Qt.NoItemFlags
102
103                 return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
104
105         @misc_utils.log_exception(_moduleLogger)
106         def headerData(self, section, orientation, role):
107                 if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
108                         return RowData.HEADERS[section]
109
110                 return None
111
112         @misc_utils.log_exception(_moduleLogger)
113         def index(self, row, column, parent):
114                 if not self.hasIndex(row, column, parent):
115                         return QtCore.QModelIndex()
116
117                 elif parent.isValid():
118                         return QtCore.QModelIndex()
119
120                 parentItem = RowData.HEADERS
121                 childItem = self._children[row]
122                 if childItem:
123                         print "i", row, column, childItem
124                         return self.createIndex(row, column, childItem)
125                 else:
126                         return QtCore.QModelIndex()
127
128         @misc_utils.log_exception(_moduleLogger)
129         def parent(self, index):
130                 if not index.isValid():
131                         return QtCore.QModelIndex()
132
133                 childItem = index.internalPointer()
134                 if isinstance(childItem, RowData):
135                         return QtCore.QModelIndex()
136                 elif childItem is RowData.HEADERS:
137                         return None
138
139         @misc_utils.log_exception(_moduleLogger)
140         def rowCount(self, parent):
141                 if 0 < parent.column():
142                         return 0
143
144                 if not parent.isValid():
145                         print "rc", len(self._children)
146                         return len(self._children)
147                 else:
148                         print "rc", len(self._children)
149                         return len(self._children)
150
151         def push(self, row):
152                 self._children.append(row)
153                 self._signal_rows_added()
154                 print "push", row
155                 print "push", len(self._children)
156
157         def pop(self):
158                 data = self._children[-1]
159                 del self._children[-1]
160                 self._signal_rows_removed()
161                 print "pop", data
162                 return data
163
164         def peek(self):
165                 data = self._children[-1]
166                 return data
167
168         def clear(self):
169                 del self._children[:]
170                 self._all_changed
171
172         def __len__(self):
173                 return len(self._children)
174
175         def __iter__(self):
176                 return iter(self._children)
177
178         def _signal_rows_added(self):
179                 # @todo Only signal new rows
180                 self._all_changed()
181
182         def _signal_rows_removed(self):
183                 # @todo Only signal new rows
184                 self._all_changed()
185
186         def _all_changed(self):
187                 if not self._children:
188                         return
189                 topLeft = self.createIndex(0, 0, self._children[0])
190                 bottomRight = self.createIndex(len(self._children)-1, len(RowData.HEADERS)-1, self._children[-1])
191                 self.dataChanged.emit(topLeft, bottomRight)
192
193
194 class QCalcHistory(history.AbstractHistory):
195
196         def __init__(self):
197                 super(QCalcHistory, self).__init__()
198                 self._prettyRenderer = operation.render_number()
199
200                 self._historyStore = HistoryModel()
201
202                 self._historyView = QtGui.QTreeView()
203                 self._historyView.setModel(self._historyStore)
204                 self._historyView.setUniformRowHeights(True)
205                 self._historyView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
206                 self._historyView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
207                 self._historyView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
208                 self._historyView.setHeaderHidden(True)
209
210                 viewHeader = self._historyView.header()
211                 viewHeader.setSortIndicatorShown(True)
212                 viewHeader.setClickable(True)
213
214                 viewHeader.setResizeMode(RowData.CLOSE_COLUMN, QtGui.QHeaderView.ResizeToContents)
215                 viewHeader.setResizeMode(RowData.EQ_COLUMN, QtGui.QHeaderView.ResizeToContents)
216                 viewHeader.setResizeMode(RowData.RESULT_COLUMN, QtGui.QHeaderView.ResizeToContents)
217                 viewHeader.setStretchLastSection(False)
218
219         @property
220         def toplevel(self):
221                 return self._historyView
222
223         def push(self, node):
224                 simpleNode = node.simplify()
225                 row = RowData(self._prettyRenderer, node, simpleNode)
226                 self._historyStore.push(row)
227
228                 # @todo Scroll to bottom
229
230         def pop(self):
231                 if len(self._historyStore) == 0:
232                         raise IndexError("Not enough items in the history for the operation")
233
234                 row = self._historyStore.pop()
235                 return row.node
236
237         def peek(self):
238                 if len(self._historyStore) == 0:
239                         raise IndexError("Not enough items in the history for the operation")
240                 row = self._historyStore.peek()
241                 return row.node
242
243         def clear(self):
244                 self._historyStore.clear()
245
246         def __len__(self):
247                 return len(self._historyStore)
248
249         def __iter__(self):
250                 for row in iter(self._historyStore):
251                         yield row.node