4 from __future__ import with_statement
12 from PyQt4 import QtGui
13 from PyQt4 import QtCore
17 from util import misc as misc_utils
19 from util import qtpie
20 from util import qtpieboard
26 _moduleLogger = logging.getLogger(__name__)
29 class Calculator(object):
31 def __init__(self, app):
34 self._hiddenCategories = set()
35 self._hiddenUnits = {}
36 self._clipboard = QtGui.QApplication.clipboard()
38 self._mainWindow = None
40 self._fullscreenAction = QtGui.QAction(None)
41 self._fullscreenAction.setText("Fullscreen")
42 self._fullscreenAction.setCheckable(True)
43 self._fullscreenAction.setShortcut(QtGui.QKeySequence("CTRL+Enter"))
44 self._fullscreenAction.toggled.connect(self._on_toggle_fullscreen)
46 self._logAction = QtGui.QAction(None)
47 self._logAction.setText("Log")
48 self._logAction.setShortcut(QtGui.QKeySequence("CTRL+l"))
49 self._logAction.triggered.connect(self._on_log)
51 self._quitAction = QtGui.QAction(None)
52 self._quitAction.setText("Quit")
53 self._quitAction.setShortcut(QtGui.QKeySequence("CTRL+q"))
54 self._quitAction.triggered.connect(self._on_quit)
56 self._app.lastWindowClosed.connect(self._on_app_quit)
59 self._mainWindow = MainWindow(None, self)
60 self._mainWindow.window.destroyed.connect(self._on_child_close)
62 def load_settings(self):
64 with open(constants._user_settings_, "r") as settingsFile:
65 settings = simplejson.load(settingsFile)
67 _moduleLogger.info("No settings")
70 _moduleLogger.info("Settings were corrupt")
73 self._fullscreenAction.setChecked(settings.get("isFullScreen", False))
75 def save_settings(self):
77 "isFullScreen": self._fullscreenAction.isChecked(),
79 with open(constants._user_settings_, "w") as settingsFile:
80 simplejson.dump(settings, settingsFile)
83 def fullscreenAction(self):
84 return self._fullscreenAction
88 return self._logAction
92 return self._quitAction
94 def _close_windows(self):
95 if self._mainWindow is not None:
96 self._mainWindow.window.destroyed.disconnect(self._on_child_close)
97 self._mainWindow.close()
98 self._mainWindow = None
100 @misc_utils.log_exception(_moduleLogger)
101 def _on_app_quit(self, checked = False):
104 @misc_utils.log_exception(_moduleLogger)
105 def _on_child_close(self, obj = None):
106 self._mainWindow = None
108 @misc_utils.log_exception(_moduleLogger)
109 def _on_toggle_fullscreen(self, checked = False):
110 for window in self._walk_children():
111 window.set_fullscreen(checked)
113 @misc_utils.log_exception(_moduleLogger)
114 def _on_log(self, checked = False):
115 with open(constants._user_logpath_, "r") as f:
116 logLines = f.xreadlines()
117 log = "".join(logLines)
118 self._clipboard.setText(log)
120 @misc_utils.log_exception(_moduleLogger)
121 def _on_quit(self, checked = False):
122 self._close_windows()
125 class QErrorDisplay(object):
130 errorIcon = maeqt.get_theme_icon(("dialog-error", "app_install_error", "gtk-dialog-error"))
131 self._severityIcon = errorIcon.pixmap(32, 32)
132 self._severityLabel = QtGui.QLabel()
133 self._severityLabel.setPixmap(self._severityIcon)
135 self._message = QtGui.QLabel()
136 self._message.setText("Boo")
138 closeIcon = maeqt.get_theme_icon(("window-close", "general_close", "gtk-close"))
139 self._closeLabel = QtGui.QPushButton(closeIcon, "")
140 self._closeLabel.clicked.connect(self._on_close)
142 self._controlLayout = QtGui.QHBoxLayout()
143 self._controlLayout.addWidget(self._severityLabel)
144 self._controlLayout.addWidget(self._message)
145 self._controlLayout.addWidget(self._closeLabel)
147 self._topLevelLayout = QtGui.QHBoxLayout()
148 self._topLevelLayout.addLayout(self._controlLayout)
149 self._widget = QtGui.QWidget()
150 self._widget.setLayout(self._topLevelLayout)
157 def push_message(self, message):
158 self._messages.append(message)
159 if 1 == len(self._messages):
160 self._show_message(message)
162 def push_exception(self):
163 userMessage = str(sys.exc_info()[1])
164 _moduleLogger.exception(userMessage)
165 self.push_message(userMessage)
167 def pop_message(self):
168 del self._messages[0]
169 if 0 == len(self._messages):
172 self._message.setText(self._messages[0])
174 def _on_close(self, *args):
177 def _show_message(self, message):
178 self._message.setText(message)
181 def _hide_message(self):
182 self._message.setText("")
186 class QValueEntry(object):
189 self._widget = QtGui.QLineEdit("")
190 maeqt.mark_numbers_preferred(self._widget)
201 value = str(self._widget.text()).strip()
203 0 < value.find(whitespace)
204 for whitespace in string.whitespace
207 raise ValueError('Invalid input "%s"' % value)
210 def set_value(self, value):
211 value = value.strip()
213 0 < value.find(whitespace)
214 for whitespace in string.whitespace
216 raise ValueError('Invalid input "%s"' % value)
217 self._widget.setText(value)
219 def append(self, value):
220 value = value.strip()
222 0 < value.find(whitespace)
223 for whitespace in string.whitespace
225 raise ValueError('Invalid input "%s"' % value)
226 self.set_value(self.get_value() + value)
229 value = self.get_value()[0:-1]
230 self.set_value(value)
235 value = property(get_value, set_value, clear)
238 class MainWindow(object):
240 _plugin_search_paths = [
241 os.path.join(os.path.dirname(__file__), "plugins/"),
244 _user_history = "%s/history.stack" % constants._data_path_
246 def __init__(self, parent, app):
249 self._errorDisplay = QErrorDisplay()
250 self._historyView = qhistory.QCalcHistory(self._errorDisplay)
251 self._userEntry = QValueEntry()
252 self._userEntry.entry.returnPressed.connect(self._on_push)
253 self._userEntryLayout = QtGui.QHBoxLayout()
254 self._userEntryLayout.addWidget(self._userEntry.toplevel, 10)
256 self._controlLayout = QtGui.QVBoxLayout()
257 self._controlLayout.addWidget(self._errorDisplay.toplevel)
258 self._controlLayout.addWidget(self._historyView.toplevel)
259 self._controlLayout.addLayout(self._userEntryLayout)
261 self._keyboardTabs = QtGui.QTabWidget()
263 if maeqt.screen_orientation() == QtCore.Qt.Vertical:
264 defaultLayoutOrientation = QtGui.QBoxLayout.TopToBottom
265 self._keyboardTabs.setTabPosition(QtGui.QTabWidget.East)
267 defaultLayoutOrientation = QtGui.QBoxLayout.LeftToRight
268 self._keyboardTabs.setTabPosition(QtGui.QTabWidget.North)
269 self._layout = QtGui.QBoxLayout(defaultLayoutOrientation)
270 self._layout.addLayout(self._controlLayout)
271 self._layout.addWidget(self._keyboardTabs)
273 centralWidget = QtGui.QWidget()
274 centralWidget.setLayout(self._layout)
276 self._window = QtGui.QMainWindow(parent)
277 self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
278 maeqt.set_autorient(self._window, True)
279 maeqt.set_stackable(self._window, True)
280 self._window.setWindowTitle("%s" % constants.__pretty_app_name__)
281 self._window.setCentralWidget(centralWidget)
282 self._window.destroyed.connect(self._on_window_closed)
284 self._copyItemAction = QtGui.QAction(None)
285 self._copyItemAction.setText("Copy")
286 self._copyItemAction.setShortcut(QtGui.QKeySequence("CTRL+c"))
287 self._copyItemAction.triggered.connect(self._on_copy)
289 self._pasteItemAction = QtGui.QAction(None)
290 self._pasteItemAction.setText("Paste")
291 self._pasteItemAction.setShortcut(QtGui.QKeySequence("CTRL+v"))
292 self._pasteItemAction.triggered.connect(self._on_paste)
294 self._closeWindowAction = QtGui.QAction(None)
295 self._closeWindowAction.setText("Close")
296 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
297 self._closeWindowAction.triggered.connect(self._on_close_window)
299 self._window.addAction(self._copyItemAction)
300 self._window.addAction(self._pasteItemAction)
301 self._window.addAction(self._closeWindowAction)
302 self._window.addAction(self._app.quitAction)
303 self._window.addAction(self._app.fullscreenAction)
305 self._window.addAction(self._app.logAction)
307 self._constantPlugins = plugin_utils.ConstantPluginManager()
308 self._constantPlugins.add_path(*self._plugin_search_paths)
309 for pluginName in ["Builtins", "Trigonometry", "Computer", "Alphabet"]:
311 pluginId = self._constantPlugins.lookup_plugin(pluginName)
312 self._constantPlugins.enable_plugin(pluginId)
314 _moduleLogger.info("Failed to load plugin %s" % pluginName)
316 self._operatorPlugins = plugin_utils.OperatorPluginManager()
317 self._operatorPlugins.add_path(*self._plugin_search_paths)
318 for pluginName in ["Builtins", "Trigonometry", "Computer", "Alphabet"]:
320 pluginId = self._operatorPlugins.lookup_plugin(pluginName)
321 self._operatorPlugins.enable_plugin(pluginId)
323 _moduleLogger.info("Failed to load plugin %s" % pluginName)
325 self._keyboardPlugins = plugin_utils.KeyboardPluginManager()
326 self._keyboardPlugins.add_path(*self._plugin_search_paths)
327 self._activeKeyboards = []
329 self._history = history.RpnCalcHistory(
331 self._userEntry, self._errorDisplay,
332 self._constantPlugins.constants, self._operatorPlugins.operators
336 # Basic keyboard stuff
337 self._handler = qtpieboard.KeyboardHandler(self._on_entry_direct)
338 self._handler.register_command_handler("push", self._on_push)
339 self._handler.register_command_handler("unpush", self._on_unpush)
340 self._handler.register_command_handler("backspace", self._on_entry_backspace)
341 self._handler.register_command_handler("clear", self._on_entry_clear)
344 entryKeyboardId = self._keyboardPlugins.lookup_plugin("Entry")
345 self._keyboardPlugins.enable_plugin(entryKeyboardId)
346 entryPlugin = self._keyboardPlugins.keyboards["Entry"].construct_keyboard()
347 entryKeyboard = entryPlugin.setup(self._history, self._handler)
348 self._userEntryLayout.addWidget(entryKeyboard.toplevel)
351 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Builtins"))
352 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Trigonometry"))
353 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Computer"))
354 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Alphabet"))
356 self.set_fullscreen(self._app.fullscreenAction.isChecked())
364 def walk_children(self):
369 for child in self.walk_children():
373 for child in self.walk_children():
378 for child in self.walk_children():
379 child.window.destroyed.disconnect(self._on_child_close)
383 def set_fullscreen(self, isFullscreen):
385 self._window.showFullScreen()
387 self._window.showNormal()
388 for child in self.walk_children():
389 child.set_fullscreen(isFullscreen)
391 def enable_plugin(self, pluginId):
392 self._keyboardPlugins.enable_plugin(pluginId)
393 pluginData = self._keyboardPlugins.plugin_info(pluginId)
394 pluginName = pluginData[0]
395 plugin = self._keyboardPlugins.keyboards[pluginName].construct_keyboard()
396 relIcon = self._keyboardPlugins.keyboards[pluginName].icon
397 for iconPath in self._keyboardPlugins.keyboards[pluginName].iconPaths:
398 absIconPath = os.path.join(iconPath, relIcon)
399 if os.path.exists(absIconPath):
400 icon = QtGui.QIcon(absIconPath)
404 pluginKeyboard = plugin.setup(self._history, self._handler)
406 self._activeKeyboards.append({
407 "pluginName": pluginName,
409 "pluginKeyboard": pluginKeyboard,
412 self._keyboardTabs.addTab(pluginKeyboard.toplevel, pluginName)
414 self._keyboardTabs.addTab(pluginKeyboard.toplevel, icon, "")
416 def _load_history(self):
419 with open(self._user_history, "rU") as f:
421 (part.strip() for part in line.split(" "))
422 for line in f.readlines()
427 self._history.deserialize_stack(serialized)
429 def _save_history(self):
430 serialized = self._history.serialize_stack()
431 with open(self._user_history, "w") as f:
432 for lineData in serialized:
433 line = " ".join(data for data in lineData)
434 f.write("%s\n" % line)
436 @misc_utils.log_exception(_moduleLogger)
437 def _on_copy(self, *args):
438 eqNode = self._historyView.peek()
439 resultNode = eqNode.simplify()
440 self._app._clipboard.setText(str(resultNode))
442 @misc_utils.log_exception(_moduleLogger)
443 def _on_paste(self, *args):
444 result = str(self._app._clipboard.text())
445 self._userEntry.append(result)
447 @misc_utils.log_exception(_moduleLogger)
448 def _on_entry_direct(self, keys, modifiers):
449 if "shift" in modifiers:
451 self._userEntry.append(keys)
453 @misc_utils.log_exception(_moduleLogger)
454 def _on_push(self, *args):
455 self._history.push_entry()
457 @misc_utils.log_exception(_moduleLogger)
458 def _on_unpush(self, *args):
459 self._historyView.unpush()
461 @misc_utils.log_exception(_moduleLogger)
462 def _on_entry_backspace(self, *args):
463 self._userEntry.pop()
465 @misc_utils.log_exception(_moduleLogger)
466 def _on_entry_clear(self, *args):
467 self._userEntry.clear()
469 @misc_utils.log_exception(_moduleLogger)
470 def _on_clear_all(self, *args):
471 self._history.clear()
473 @misc_utils.log_exception(_moduleLogger)
474 def _on_window_closed(self, checked = True):
477 @misc_utils.log_exception(_moduleLogger)
478 def _on_close_window(self, checked = True):
483 app = QtGui.QApplication([])
484 handle = Calculator(app)
489 if __name__ == "__main__":
490 logging.basicConfig(level = logging.DEBUG)
492 os.makedirs(constants._data_path_)