4 from __future__ import with_statement
12 from PyQt4 import QtCore
13 from PyQt4 import QtGui
16 from util import misc as misc_utils
18 from util import qui_utils
19 from util import qwrappers
20 from util import qtpie
21 from util import qtpieboard
27 _moduleLogger = logging.getLogger(__name__)
30 class Calculator(qwrappers.ApplicationWrapper):
32 def __init__(self, app):
34 self._hiddenCategories = set()
35 self._hiddenUnits = {}
36 qwrappers.ApplicationWrapper.__init__(self, app, constants)
38 def load_settings(self):
40 with open(constants._user_settings_, "r") as settingsFile:
41 settings = simplejson.load(settingsFile)
43 _moduleLogger.info("No settings")
46 _moduleLogger.info("Settings were corrupt")
49 isPortraitDefault = qui_utils.screen_orientation() == QtCore.Qt.Vertical
50 self._fullscreenAction.setChecked(settings.get("isFullScreen", False))
51 self._orientationAction.setChecked(settings.get("isPortrait", isPortraitDefault))
53 def save_settings(self):
55 "isFullScreen": self._fullscreenAction.isChecked(),
56 "isPortrait": self._orientationAction.isChecked(),
58 with open(constants._user_settings_, "w") as settingsFile:
59 simplejson.dump(settings, settingsFile)
65 def _new_main_window(self):
66 return MainWindow(None, self)
68 @misc_utils.log_exception(_moduleLogger)
69 def _on_about(self, checked = True):
70 raise NotImplementedError("Booh")
73 class QValueEntry(object):
76 self._widget = QtGui.QLineEdit("")
77 qui_utils.mark_numbers_preferred(self._widget)
88 value = str(self._widget.text()).strip()
90 0 < value.find(whitespace)
91 for whitespace in string.whitespace
94 raise ValueError('Invalid input "%s"' % value)
97 def set_value(self, value):
100 0 < value.find(whitespace)
101 for whitespace in string.whitespace
103 raise ValueError('Invalid input "%s"' % value)
104 self._widget.setText(value)
106 def append(self, value):
107 value = value.strip()
109 0 < value.find(whitespace)
110 for whitespace in string.whitespace
112 raise ValueError('Invalid input "%s"' % value)
113 self.set_value(self.get_value() + value)
116 value = self.get_value()[0:-1]
117 self.set_value(value)
122 value = property(get_value, set_value, clear)
125 class MainWindow(qwrappers.WindowWrapper):
127 _plugin_search_paths = [
128 os.path.join(os.path.dirname(__file__), "plugins/"),
131 _user_history = "%s/history.stack" % constants._data_path_
133 def __init__(self, parent, app):
134 qwrappers.WindowWrapper.__init__(self, parent, app)
135 self._window.setWindowTitle("%s" % constants.__pretty_app_name__)
136 #self._freezer = qwrappers.AutoFreezeWindowFeature(self._app, self._window)
138 self._historyView = qhistory.QCalcHistory(self._app.errorLog)
139 self._userEntry = QValueEntry()
140 self._userEntry.entry.returnPressed.connect(self._on_push)
141 self._userEntryLayout = QtGui.QHBoxLayout()
142 self._userEntryLayout.setContentsMargins(0, 0, 0, 0)
143 self._userEntryLayout.addWidget(self._userEntry.toplevel, 10)
145 self._controlLayout = QtGui.QVBoxLayout()
146 self._controlLayout.setContentsMargins(0, 0, 0, 0)
147 self._controlLayout.addWidget(self._historyView.toplevel, 1000)
148 self._controlLayout.addLayout(self._userEntryLayout, 0)
150 self._keyboardTabs = QtGui.QTabWidget()
152 self._layout.addLayout(self._controlLayout)
153 self._layout.addWidget(self._keyboardTabs)
155 self._copyItemAction = QtGui.QAction(None)
156 self._copyItemAction.setText("Copy")
157 self._copyItemAction.setShortcut(QtGui.QKeySequence("CTRL+c"))
158 self._copyItemAction.triggered.connect(self._on_copy)
160 self._pasteItemAction = QtGui.QAction(None)
161 self._pasteItemAction.setText("Paste")
162 self._pasteItemAction.setShortcut(QtGui.QKeySequence("CTRL+v"))
163 self._pasteItemAction.triggered.connect(self._on_paste)
165 self._closeWindowAction = QtGui.QAction(None)
166 self._closeWindowAction.setText("Close")
167 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
168 self._closeWindowAction.triggered.connect(self._on_close_window)
170 self._window.addAction(self._copyItemAction)
171 self._window.addAction(self._pasteItemAction)
173 self._constantPlugins = plugin_utils.ConstantPluginManager()
174 self._constantPlugins.add_path(*self._plugin_search_paths)
175 for pluginName in ["Builtins", "Trigonometry", "Computer", "Alphabet"]:
177 pluginId = self._constantPlugins.lookup_plugin(pluginName)
178 self._constantPlugins.enable_plugin(pluginId)
180 _moduleLogger.info("Failed to load plugin %s" % pluginName)
182 self._operatorPlugins = plugin_utils.OperatorPluginManager()
183 self._operatorPlugins.add_path(*self._plugin_search_paths)
184 for pluginName in ["Builtins", "Trigonometry", "Computer", "Alphabet"]:
186 pluginId = self._operatorPlugins.lookup_plugin(pluginName)
187 self._operatorPlugins.enable_plugin(pluginId)
189 _moduleLogger.info("Failed to load plugin %s" % pluginName)
191 self._keyboardPlugins = plugin_utils.KeyboardPluginManager()
192 self._keyboardPlugins.add_path(*self._plugin_search_paths)
193 self._activeKeyboards = []
195 self._history = history.RpnCalcHistory(
197 self._userEntry, self._app.errorLog,
198 self._constantPlugins.constants, self._operatorPlugins.operators
202 # Basic keyboard stuff
203 self._handler = qtpieboard.KeyboardHandler(self._on_entry_direct)
204 self._handler.register_command_handler("push", self._on_push)
205 self._handler.register_command_handler("unpush", self._on_unpush)
206 self._handler.register_command_handler("backspace", self._on_entry_backspace)
207 self._handler.register_command_handler("clear", self._on_entry_clear)
210 entryKeyboardId = self._keyboardPlugins.lookup_plugin("Entry")
211 self._keyboardPlugins.enable_plugin(entryKeyboardId)
212 entryPlugin = self._keyboardPlugins.keyboards["Entry"].construct_keyboard()
213 entryKeyboard = entryPlugin.setup(self._history, self._handler)
214 self._userEntryLayout.addWidget(entryKeyboard.toplevel)
217 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Builtins"))
218 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Trigonometry"))
219 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Computer"))
220 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Alphabet"))
222 self._scrollTimer = QtCore.QTimer()
223 self._scrollTimer.setInterval(0)
224 self._scrollTimer.setSingleShot(True)
225 self._scrollTimer.timeout.connect(self._on_delayed_scroll_to_bottom)
226 self._scrollTimer.start()
228 self.set_fullscreen(self._app.fullscreenAction.isChecked())
229 self.set_orientation(self._app.orientationAction.isChecked())
231 def walk_children(self):
234 def set_orientation(self, isPortrait):
235 qwrappers.WindowWrapper.set_orientation(self, isPortrait)
237 defaultLayoutOrientation = QtGui.QBoxLayout.TopToBottom
238 #tabPosition = QtGui.QTabWidget.South
239 tabPosition = QtGui.QTabWidget.West
241 defaultLayoutOrientation = QtGui.QBoxLayout.LeftToRight
242 tabPosition = QtGui.QTabWidget.North
243 self._layout.setDirection(defaultLayoutOrientation)
244 self._keyboardTabs.setTabPosition(tabPosition)
246 def enable_plugin(self, pluginId):
247 self._keyboardPlugins.enable_plugin(pluginId)
248 pluginData = self._keyboardPlugins.plugin_info(pluginId)
249 pluginName = pluginData[0]
250 plugin = self._keyboardPlugins.keyboards[pluginName].construct_keyboard()
251 relIcon = self._keyboardPlugins.keyboards[pluginName].icon
252 for iconPath in self._keyboardPlugins.keyboards[pluginName].iconPaths:
253 absIconPath = os.path.join(iconPath, relIcon)
254 if os.path.exists(absIconPath):
255 icon = QtGui.QIcon(absIconPath)
259 pluginKeyboard = plugin.setup(self._history, self._handler)
261 self._activeKeyboards.append({
262 "pluginName": pluginName,
264 "pluginKeyboard": pluginKeyboard,
267 self._keyboardTabs.addTab(pluginKeyboard.toplevel, pluginName)
269 self._keyboardTabs.addTab(pluginKeyboard.toplevel, icon, "")
272 qwrappers.WindowWrapper.close(self)
275 def _load_history(self):
278 with open(self._user_history, "rU") as f:
280 (part.strip() for part in line.split(" "))
281 for line in f.readlines()
286 self._history.deserialize_stack(serialized)
288 def _save_history(self):
289 serialized = self._history.serialize_stack()
290 with open(self._user_history, "w") as f:
291 for lineData in serialized:
292 line = " ".join(data for data in lineData)
293 f.write("%s\n" % line)
295 @misc_utils.log_exception(_moduleLogger)
296 def _on_delayed_scroll_to_bottom(self):
297 with qui_utils.notify_error(self._app.errorLog):
298 self._historyView.scroll_to_bottom()
300 @misc_utils.log_exception(_moduleLogger)
301 def _on_child_close(self, something = None):
302 with qui_utils.notify_error(self._app.errorLog):
305 @misc_utils.log_exception(_moduleLogger)
306 def _on_copy(self, *args):
307 with qui_utils.notify_error(self._app.errorLog):
308 eqNode = self._historyView.peek()
309 resultNode = eqNode.simplify()
310 self._app._clipboard.setText(str(resultNode))
312 @misc_utils.log_exception(_moduleLogger)
313 def _on_paste(self, *args):
314 with qui_utils.notify_error(self._app.errorLog):
315 result = str(self._app._clipboard.text())
316 self._userEntry.append(result)
318 @misc_utils.log_exception(_moduleLogger)
319 def _on_entry_direct(self, keys, modifiers):
320 with qui_utils.notify_error(self._app.errorLog):
321 if "shift" in modifiers:
323 self._userEntry.append(keys)
325 @misc_utils.log_exception(_moduleLogger)
326 def _on_push(self, *args):
327 with qui_utils.notify_error(self._app.errorLog):
328 self._history.push_entry()
330 @misc_utils.log_exception(_moduleLogger)
331 def _on_unpush(self, *args):
332 with qui_utils.notify_error(self._app.errorLog):
333 self._historyView.unpush()
335 @misc_utils.log_exception(_moduleLogger)
336 def _on_entry_backspace(self, *args):
337 with qui_utils.notify_error(self._app.errorLog):
338 self._userEntry.pop()
340 @misc_utils.log_exception(_moduleLogger)
341 def _on_entry_clear(self, *args):
342 with qui_utils.notify_error(self._app.errorLog):
343 self._userEntry.clear()
345 @misc_utils.log_exception(_moduleLogger)
346 def _on_clear_all(self, *args):
347 with qui_utils.notify_error(self._app.errorLog):
348 self._history.clear()
352 app = QtGui.QApplication([])
353 handle = Calculator(app)
358 if __name__ == "__main__":
359 logging.basicConfig(level = logging.DEBUG)
361 os.makedirs(constants._data_path_)