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