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 libraries import qtpie
20 from libraries import qtpieboard
26 _moduleLogger = logging.getLogger(__name__)
32 class Calculator(object):
34 def __init__(self, app):
37 self._hiddenCategories = set()
38 self._hiddenUnits = {}
39 self._clipboard = QtGui.QApplication.clipboard()
40 self._mainWindow = None
42 self._fullscreenAction = QtGui.QAction(None)
43 self._fullscreenAction.setText("Fullscreen")
44 self._fullscreenAction.setCheckable(True)
45 self._fullscreenAction.setShortcut(QtGui.QKeySequence("CTRL+Enter"))
46 self._fullscreenAction.toggled.connect(self._on_toggle_fullscreen)
48 self._logAction = QtGui.QAction(None)
49 self._logAction.setText("Log")
50 self._logAction.setShortcut(QtGui.QKeySequence("CTRL+l"))
51 self._logAction.triggered.connect(self._on_log)
53 self._quitAction = QtGui.QAction(None)
54 self._quitAction.setText("Quit")
55 self._quitAction.setShortcut(QtGui.QKeySequence("CTRL+q"))
56 self._quitAction.triggered.connect(self._on_quit)
58 self._app.lastWindowClosed.connect(self._on_app_quit)
61 self._mainWindow = MainWindow(None, self)
62 self._mainWindow.window.destroyed.connect(self._on_child_close)
64 def load_settings(self):
66 with open(constants._user_settings_, "r") as settingsFile:
67 settings = simplejson.load(settingsFile)
69 _moduleLogger.info("No settings")
72 _moduleLogger.info("Settings were corrupt")
75 self._fullscreenAction.setChecked(settings.get("isFullScreen", False))
77 def save_settings(self):
79 "isFullScreen": self._fullscreenAction.isChecked(),
81 with open(constants._user_settings_, "w") as settingsFile:
82 simplejson.dump(settings, settingsFile)
85 def fullscreenAction(self):
86 return self._fullscreenAction
90 return self._logAction
94 return self._quitAction
96 def _close_windows(self):
97 if self._mainWindow is not None:
98 self._mainWindow.window.destroyed.disconnect(self._on_child_close)
99 self._mainWindow.close()
100 self._mainWindow = None
102 @misc_utils.log_exception(_moduleLogger)
103 def _on_app_quit(self, checked = False):
106 @misc_utils.log_exception(_moduleLogger)
107 def _on_child_close(self, obj = None):
108 self._mainWindow = None
110 @misc_utils.log_exception(_moduleLogger)
111 def _on_toggle_fullscreen(self, checked = False):
112 for window in self._walk_children():
113 window.set_fullscreen(checked)
115 @misc_utils.log_exception(_moduleLogger)
116 def _on_log(self, checked = False):
117 with open(constants._user_logpath_, "r") as f:
118 logLines = f.xreadlines()
119 log = "".join(logLines)
120 self._clipboard.setText(log)
122 @misc_utils.log_exception(_moduleLogger)
123 def _on_quit(self, checked = False):
124 self._close_windows()
127 class QErrorDisplay(object):
132 icon = QtGui.QIcon.fromTheme("gtk-dialog-error")
133 self._severityIcon = icon.pixmap(32, 32)
134 self._severityLabel = QtGui.QLabel()
135 self._severityLabel.setPixmap(self._severityIcon)
137 self._message = QtGui.QLabel()
138 self._message.setText("Boo")
140 icon = QtGui.QIcon.fromTheme("gtk-close")
141 self._closeLabel = QtGui.QPushButton(icon, "")
142 self._closeLabel.clicked.connect(self._on_close)
144 self._controlLayout = QtGui.QHBoxLayout()
145 self._controlLayout.addWidget(self._severityLabel)
146 self._controlLayout.addWidget(self._message)
147 self._controlLayout.addWidget(self._closeLabel)
149 self._topLevelLayout = QtGui.QHBoxLayout()
150 self._topLevelLayout.addLayout(self._controlLayout)
151 self._widget = QtGui.QWidget()
152 self._widget.setLayout(self._topLevelLayout)
159 def push_message(self, message):
160 self._messages.append(message)
161 if 1 == len(self._messages):
162 self._show_message(message)
164 def push_exception(self):
165 userMessage = str(sys.exc_info()[1])
166 _moduleLogger.exception(userMessage)
167 self.push_message(userMessage)
169 def pop_message(self):
170 del self._messages[0]
171 if 0 == len(self._messages):
174 self._message.setText(self._messages[0])
176 def _on_close(self, *args):
179 def _show_message(self, message):
180 self._message.setText(message)
183 def _hide_message(self):
184 self._message.setText("")
188 class QValueEntry(object):
191 self._widget = QtGui.QLineEdit("")
192 maeqt.mark_numbers_preferred(self._widget)
203 value = str(self._widget.text()).strip()
205 0 < value.find(whitespace)
206 for whitespace in string.whitespace
209 raise ValueError('Invalid input "%s"' % value)
212 def set_value(self, value):
213 value = value.strip()
215 0 < value.find(whitespace)
216 for whitespace in string.whitespace
218 raise ValueError('Invalid input "%s"' % value)
219 self._widget.setText(value)
221 def append(self, value):
222 value = value.strip()
224 0 < value.find(whitespace)
225 for whitespace in string.whitespace
227 raise ValueError('Invalid input "%s"' % value)
228 self.set_value(self.get_value() + value)
231 value = self.get_value()[0:-1]
232 self.set_value(value)
237 value = property(get_value, set_value, clear)
240 class MainWindow(object):
242 _plugin_search_paths = [
243 os.path.join(os.path.dirname(__file__), "plugins/"),
246 _user_history = "%s/history.stack" % constants._data_path_
248 def __init__(self, parent, app):
251 self._errorDisplay = QErrorDisplay()
252 self._historyView = qhistory.QCalcHistory(self._errorDisplay)
253 self._userEntry = QValueEntry()
254 self._userEntry.entry.returnPressed.connect(self._on_push)
255 self._userEntryLayout = QtGui.QHBoxLayout()
256 self._userEntryLayout.addWidget(self._userEntry.toplevel)
258 self._controlLayout = QtGui.QVBoxLayout()
259 self._controlLayout.addWidget(self._errorDisplay.toplevel)
260 self._controlLayout.addWidget(self._historyView.toplevel)
261 self._controlLayout.addLayout(self._userEntryLayout)
263 self._keyboardTabs = QtGui.QTabWidget()
265 if maeqt.screen_orientation() == QtCore.Qt.Vertical:
266 defaultLayoutOrientation = QtGui.QBoxLayout.TopToBottom
267 self._keyboardTabs.setTabPosition(QtGui.QTabWidget.East)
269 defaultLayoutOrientation = QtGui.QBoxLayout.LeftToRight
270 self._keyboardTabs.setTabPosition(QtGui.QTabWidget.North)
271 self._layout = QtGui.QBoxLayout(defaultLayoutOrientation)
272 self._layout.addLayout(self._controlLayout)
273 self._layout.addWidget(self._keyboardTabs)
275 centralWidget = QtGui.QWidget()
276 centralWidget.setLayout(self._layout)
278 self._window = QtGui.QMainWindow(parent)
279 self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
280 #maeqt.set_autorient(self._window, True)
281 maeqt.set_stackable(self._window, True)
282 self._window.setWindowTitle("%s" % constants.__pretty_app_name__)
283 self._window.setCentralWidget(centralWidget)
284 self._window.destroyed.connect(self._on_close_window)
286 self._copyItemAction = QtGui.QAction(None)
287 self._copyItemAction.setText("Copy")
288 self._copyItemAction.setShortcut(QtGui.QKeySequence("CTRL+c"))
289 self._copyItemAction.triggered.connect(self._on_copy)
291 self._pasteItemAction = QtGui.QAction(None)
292 self._pasteItemAction.setText("Paste")
293 self._pasteItemAction.setShortcut(QtGui.QKeySequence("CTRL+v"))
294 self._pasteItemAction.triggered.connect(self._on_paste)
296 self._closeWindowAction = QtGui.QAction(None)
297 self._closeWindowAction.setText("Close")
298 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
299 self._closeWindowAction.triggered.connect(self._on_close_window)
302 #fileMenu = self._window.menuBar().addMenu("&File")
304 #viewMenu = self._window.menuBar().addMenu("&View")
306 self._window.addAction(self._copyItemAction)
307 self._window.addAction(self._pasteItemAction)
308 self._window.addAction(self._closeWindowAction)
309 self._window.addAction(self._app.quitAction)
310 self._window.addAction(self._app.fullscreenAction)
312 fileMenu = self._window.menuBar().addMenu("&Units")
313 fileMenu.addAction(self._closeWindowAction)
314 fileMenu.addAction(self._app.quitAction)
316 viewMenu = self._window.menuBar().addMenu("&View")
317 viewMenu.addAction(self._app.fullscreenAction)
319 self._window.addAction(self._app.logAction)
321 self._constantPlugins = plugin_utils.ConstantPluginManager()
322 self._constantPlugins.add_path(*self._plugin_search_paths)
323 for pluginName in ["Builtins", "Trigonometry", "Computer", "Alphabet"]:
325 pluginId = self._constantPlugins.lookup_plugin(pluginName)
326 self._constantPlugins.enable_plugin(pluginId)
328 _moduleLogger.info("Failed to load plugin %s" % pluginName)
330 self._operatorPlugins = plugin_utils.OperatorPluginManager()
331 self._operatorPlugins.add_path(*self._plugin_search_paths)
332 for pluginName in ["Builtins", "Trigonometry", "Computer", "Alphabet"]:
334 pluginId = self._operatorPlugins.lookup_plugin(pluginName)
335 self._operatorPlugins.enable_plugin(pluginId)
337 _moduleLogger.info("Failed to load plugin %s" % pluginName)
339 self._keyboardPlugins = plugin_utils.KeyboardPluginManager()
340 self._keyboardPlugins.add_path(*self._plugin_search_paths)
341 self._activeKeyboards = []
343 self._history = history.RpnCalcHistory(
345 self._userEntry, self._errorDisplay,
346 self._constantPlugins.constants, self._operatorPlugins.operators
350 # Basic keyboard stuff
351 self._handler = qtpieboard.KeyboardHandler(self._on_entry_direct)
352 self._handler.register_command_handler("push", self._on_push)
353 self._handler.register_command_handler("unpush", self._on_unpush)
354 self._handler.register_command_handler("backspace", self._on_entry_backspace)
355 self._handler.register_command_handler("clear", self._on_entry_clear)
358 entryKeyboardId = self._keyboardPlugins.lookup_plugin("Entry")
359 self._keyboardPlugins.enable_plugin(entryKeyboardId)
360 entryPlugin = self._keyboardPlugins.keyboards["Entry"].construct_keyboard()
361 entryKeyboard = entryPlugin.setup(self._history, self._handler)
362 self._userEntryLayout.addWidget(entryKeyboard.toplevel)
365 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Builtins"))
366 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Trigonometry"))
367 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Computer"))
368 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Alphabet"))
370 self.set_fullscreen(self._app.fullscreenAction.isChecked())
373 self._set_plugin_kb(0)
379 def walk_children(self):
384 for child in self.walk_children():
388 for child in self.walk_children():
393 for child in self.walk_children():
394 child.window.destroyed.disconnect(self._on_child_close)
398 def set_fullscreen(self, isFullscreen):
400 self._window.showFullScreen()
402 self._window.showNormal()
403 for child in self.walk_children():
404 child.set_fullscreen(isFullscreen)
406 def enable_plugin(self, pluginId):
407 self._keyboardPlugins.enable_plugin(pluginId)
408 pluginData = self._keyboardPlugins.plugin_info(pluginId)
409 pluginName = pluginData[0]
410 plugin = self._keyboardPlugins.keyboards[pluginName].construct_keyboard()
411 relIcon = self._keyboardPlugins.keyboards[pluginName].icon
412 for iconPath in self._keyboardPlugins.keyboards[pluginName].iconPaths:
413 absIconPath = os.path.join(iconPath, relIcon)
414 if os.path.exists(absIconPath):
415 icon = QtGui.QIcon(absIconPath)
419 pluginKeyboard = plugin.setup(self._history, self._handler)
421 self._activeKeyboards.append({
422 "pluginName": pluginName,
424 "pluginKeyboard": pluginKeyboard,
427 self._keyboardTabs.addTab(pluginKeyboard.toplevel, pluginName)
429 self._keyboardTabs.addTab(pluginKeyboard.toplevel, icon, "")
431 def _set_plugin_kb(self, pluginIndex):
432 plugin = self._activeKeyboards[pluginIndex]
434 # @todo Switch the keyboard tab
435 pluginKeyboard = plugin["pluginKeyboard"]
437 def _load_history(self):
440 with open(self._user_history, "rU") as f:
442 (part.strip() for part in line.split(" "))
443 for line in f.readlines()
448 self._history.deserialize_stack(serialized)
450 def _save_history(self):
451 serialized = self._history.serialize_stack()
452 with open(self._user_history, "w") as f:
453 for lineData in serialized:
454 line = " ".join(data for data in lineData)
455 f.write("%s\n" % line)
457 @misc_utils.log_exception(_moduleLogger)
458 def _on_copy(self, *args):
459 eqNode = self._historyView.peek()
460 resultNode = eqNode.simplify()
461 self._app._clipboard.setText(str(resultNode))
463 @misc_utils.log_exception(_moduleLogger)
464 def _on_paste(self, *args):
465 result = str(self._app._clipboard.text())
466 self._userEntry.append(result)
468 @misc_utils.log_exception(_moduleLogger)
469 def _on_entry_direct(self, keys, modifiers):
470 if "shift" in modifiers:
472 self._userEntry.append(keys)
474 @misc_utils.log_exception(_moduleLogger)
475 def _on_push(self, *args):
476 self._history.push_entry()
478 @misc_utils.log_exception(_moduleLogger)
479 def _on_unpush(self, *args):
480 self._historyView.unpush()
482 @misc_utils.log_exception(_moduleLogger)
483 def _on_entry_backspace(self, *args):
484 self._userEntry.pop()
486 @misc_utils.log_exception(_moduleLogger)
487 def _on_entry_clear(self, *args):
488 self._userEntry.clear()
490 @misc_utils.log_exception(_moduleLogger)
491 def _on_clear_all(self, *args):
492 self._history.clear()
494 @misc_utils.log_exception(_moduleLogger)
495 def _on_close_window(self, checked = True):
500 app = QtGui.QApplication([])
501 handle = Calculator(app)
506 if __name__ == "__main__":
507 logging.basicConfig(level = logging.DEBUG)
509 os.makedirs(constants._data_path_)