Simplifying the look and the code
[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                 elif role == QtCore.Qt.DecorationRole:
80                         if index.column() == RowData.CLOSE_COLUMN:
81                                 return None
82                         else:
83                                 return None
84                 elif role == QtCore.Qt.TextAlignmentRole:
85                         return RowData.ALIGNMENT[index.column()]
86                 elif role != QtCore.Qt.DisplayRole:
87                         return None
88
89                 item = index.internalPointer()
90                 if isinstance(item, RowData):
91                         return item.data(index.column())
92                 elif item is RowData.HEADERS:
93                         return item[index.column()]
94
95         @misc_utils.log_exception(_moduleLogger)
96         def flags(self, index):
97                 if not index.isValid():
98                         return QtCore.Qt.NoItemFlags
99
100                 return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
101
102         @misc_utils.log_exception(_moduleLogger)
103         def headerData(self, section, orientation, role):
104                 if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
105                         return RowData.HEADERS[section]
106
107                 return None
108
109         @misc_utils.log_exception(_moduleLogger)
110         def index(self, row, column, parent):
111                 if not self.hasIndex(row, column, parent):
112                         return QtCore.QModelIndex()
113
114                 if parent.isValid():
115                         return QtCore.QModelIndex()
116
117                 parentItem = RowData.HEADERS
118                 childItem = self._children[row]
119                 if childItem:
120                         return self.createIndex(row, column, childItem)
121                 else:
122                         return QtCore.QModelIndex()
123
124         @misc_utils.log_exception(_moduleLogger)
125         def parent(self, index):
126                 if not index.isValid():
127                         return QtCore.QModelIndex()
128
129                 childItem = index.internalPointer()
130                 if isinstance(childItem, RowData):
131                         return QtCore.QModelIndex()
132                 elif childItem is RowData.HEADERS:
133                         return None
134
135         @misc_utils.log_exception(_moduleLogger)
136         def rowCount(self, parent):
137                 if 0 < parent.column():
138                         return 0
139
140                 if not parent.isValid():
141                         return len(self._children)
142                 else:
143                         return len(self._children)
144
145         def push(self, row):
146                 self._children.append(row)
147                 self._signal_rows_added()
148
149         def pop(self):
150                 data = self._children[-1]
151                 del self._children[-1]
152                 self._signal_rows_removed()
153                 return data
154
155         def peek(self):
156                 data = self._children[-1]
157                 return data
158
159         def clear(self):
160                 del self._children[:]
161                 self._all_changed
162
163         def __len__(self):
164                 return len(self._children)
165
166         def __iter__(self):
167                 return iter(self._children)
168
169         def _signal_rows_added(self):
170                 # @todo Only signal new rows
171                 self._all_changed
172
173         def _signal_rows_removed(self):
174                 # @todo Only signal new rows
175                 self._all_changed
176
177         def _all_changed(self):
178                 topLeft = self.createIndex(0, 0, self._children[0])
179                 bottomRight = self.createIndex(len(self._children)-1, len(RowData.HEADERS)-1, self._children[-1])
180                 self.dataChanged.emit(topLeft, bottomRight)
181
182
183 class QCalcHistory(history.AbstractHistory):
184
185         def __init__(self):
186                 super(QCalcHistory, self).__init__()
187                 self._prettyRenderer = operation.render_number()
188
189                 self._historyStore = HistoryModel()
190
191                 self._historyView = QtGui.QTreeView()
192                 self._historyView.setModel(self._historyStore)
193                 self._historyView.setUniformRowHeights(True)
194                 self._historyView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
195                 self._historyView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
196                 self._historyView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
197                 self._historyView.setHeaderHidden(True)
198
199                 viewHeader = self._historyView.header()
200                 viewHeader.setSortIndicatorShown(True)
201                 viewHeader.setClickable(True)
202
203                 viewHeader.setResizeMode(RowData.CLOSE_COLUMN, QtGui.QHeaderView.ResizeToContents)
204                 viewHeader.setResizeMode(RowData.EQ_COLUMN, QtGui.QHeaderView.ResizeToContents)
205                 viewHeader.setResizeMode(RowData.RESULT_COLUMN, QtGui.QHeaderView.ResizeToContents)
206                 viewHeader.setStretchLastSection(False)
207
208         @property
209         def toplevel(self):
210                 return self._historyView
211
212         def push(self, node):
213                 simpleNode = node.simplify()
214                 row = RowData(node, simpleNode)
215                 self._historyStore.push(row)
216
217                 selection = self._historyView.get_selection()
218                 selectionPath = (len(self._historyStore)-1, )
219                 selection.select_path(selectionPath)
220                 self._historyView.scroll_to_cell(selectionPath)
221
222         def pop(self):
223                 if len(self._historyStore) == 0:
224                         raise IndexError("Not enough items in the history for the operation")
225
226                 row = self._historyStore.pop()
227                 return row.node
228
229         def peek(self):
230                 if len(self._historyStore) == 0:
231                         raise IndexError("Not enough items in the history for the operation")
232                 row = self._historyStore.peek()
233                 return row.node
234
235         def clear(self):
236                 self._historyStore.clear()
237
238         def __len__(self):
239                 return len(self._historyStore)
240
241         def __iter__(self):
242                 for row in iter(self._historyStore):
243                         yield row.node