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 PLUGIN_SEARCH_PATHS = [
33 os.path.join(os.path.dirname(__file__), "plugins/"),
37 class Calculator(object):
39 def __init__(self, app):
42 self._hiddenCategories = set()
43 self._hiddenUnits = {}
44 self._clipboard = QtGui.QApplication.clipboard()
45 self._mainWindow = None
47 self._fullscreenAction = QtGui.QAction(None)
48 self._fullscreenAction.setText("Fullscreen")
49 self._fullscreenAction.setCheckable(True)
50 self._fullscreenAction.setShortcut(QtGui.QKeySequence("CTRL+Enter"))
51 self._fullscreenAction.toggled.connect(self._on_toggle_fullscreen)
53 self._logAction = QtGui.QAction(None)
54 self._logAction.setText("Log")
55 self._logAction.setShortcut(QtGui.QKeySequence("CTRL+l"))
56 self._logAction.triggered.connect(self._on_log)
58 self._quitAction = QtGui.QAction(None)
59 self._quitAction.setText("Quit")
60 self._quitAction.setShortcut(QtGui.QKeySequence("CTRL+q"))
61 self._quitAction.triggered.connect(self._on_quit)
63 self._app.lastWindowClosed.connect(self._on_app_quit)
66 self._mainWindow = MainWindow(None, self)
67 self._mainWindow.window.destroyed.connect(self._on_child_close)
69 def load_settings(self):
71 with open(constants._user_settings_, "r") as settingsFile:
72 settings = simplejson.load(settingsFile)
74 _moduleLogger.info("No settings")
77 _moduleLogger.info("Settings were corrupt")
80 self._fullscreenAction.setChecked(settings.get("isFullScreen", False))
82 def save_settings(self):
84 "isFullScreen": self._fullscreenAction.isChecked(),
86 with open(constants._user_settings_, "w") as settingsFile:
87 simplejson.dump(settings, settingsFile)
90 def fullscreenAction(self):
91 return self._fullscreenAction
95 return self._logAction
99 return self._quitAction
101 def _close_windows(self):
102 if self._mainWindow is not None:
103 self._mainWindow.window.destroyed.disconnect(self._on_child_close)
104 self._mainWindow.close()
105 self._mainWindow = None
107 @misc_utils.log_exception(_moduleLogger)
108 def _on_app_quit(self, checked = False):
111 @misc_utils.log_exception(_moduleLogger)
112 def _on_child_close(self, obj = None):
113 self._mainWindow = None
115 @misc_utils.log_exception(_moduleLogger)
116 def _on_toggle_fullscreen(self, checked = False):
117 for window in self._walk_children():
118 window.set_fullscreen(checked)
120 @misc_utils.log_exception(_moduleLogger)
121 def _on_log(self, checked = False):
122 with open(constants._user_logpath_, "r") as f:
123 logLines = f.xreadlines()
124 log = "".join(logLines)
125 self._clipboard.setText(log)
127 @misc_utils.log_exception(_moduleLogger)
128 def _on_quit(self, checked = False):
129 self._close_windows()
132 class QErrorDisplay(object):
137 icon = QtGui.QIcon.fromTheme("gtk-dialog-error")
138 self._severityIcon = icon.pixmap(32, 32)
139 self._severityLabel = QtGui.QLabel()
140 self._severityLabel.setPixmap(self._severityIcon)
142 self._message = QtGui.QLabel()
143 self._message.setText("Boo")
145 icon = QtGui.QIcon.fromTheme("gtk-close")
146 self._closeLabel = QtGui.QPushButton(icon, "")
147 self._closeLabel.clicked.connect(self._on_close)
149 self._controlLayout = QtGui.QHBoxLayout()
150 self._controlLayout.addWidget(self._severityLabel)
151 self._controlLayout.addWidget(self._message)
152 self._controlLayout.addWidget(self._closeLabel)
154 self._topLevelLayout = QtGui.QHBoxLayout()
155 self._topLevelLayout.addLayout(self._controlLayout)
156 self._widget = QtGui.QWidget()
157 self._widget.setLayout(self._topLevelLayout)
164 def push_message(self, message):
165 self._messages.append(message)
166 if 1 == len(self._messages):
167 self._show_message(message)
169 def push_exception(self):
170 userMessage = str(sys.exc_info()[1])
171 _moduleLogger.exception(userMessage)
172 self.push_message(userMessage)
174 def pop_message(self):
175 del self._messages[0]
176 if 0 == len(self._messages):
179 self._message.setText(self._messages[0])
181 def _on_close(self, *args):
184 def _show_message(self, message):
185 self._message.setText(message)
188 def _hide_message(self):
189 self._message.setText("")
193 class QValueEntry(object):
196 self._widget = QtGui.QLineEdit("")
197 maeqt.mark_numbers_preferred(self._widget)
208 value = str(self._widget.text()).strip()
210 0 < value.find(whitespace)
211 for whitespace in string.whitespace
214 raise ValueError('Invalid input "%s"' % value)
217 def set_value(self, value):
218 value = value.strip()
220 0 < value.find(whitespace)
221 for whitespace in string.whitespace
223 raise ValueError('Invalid input "%s"' % value)
224 self._widget.setText(value)
226 def append(self, value):
227 value = value.strip()
229 0 < value.find(whitespace)
230 for whitespace in string.whitespace
232 raise ValueError('Invalid input "%s"' % value)
233 self.set_value(self.get_value() + value)
236 value = self.get_value()[0:-1]
237 self.set_value(value)
242 value = property(get_value, set_value, clear)
245 class MainWindow(object):
247 _plugin_search_paths = [
248 "/opt/epi/lib/plugins/",
249 "/usr/lib/ejpi/plugins/",
250 os.path.join(os.path.dirname(__file__), "plugins/"),
253 _user_history = "%s/history.stack" % constants._data_path_
255 def __init__(self, parent, app):
258 self._errorDisplay = QErrorDisplay()
259 self._historyView = qhistory.QCalcHistory(self._errorDisplay)
260 self._userEntry = QValueEntry()
261 self._userEntry.entry.returnPressed.connect(self._on_push)
262 self._userEntryLayout = QtGui.QHBoxLayout()
263 self._userEntryLayout.addWidget(self._userEntry.toplevel)
265 self._controlLayout = QtGui.QVBoxLayout()
266 self._controlLayout.addWidget(self._errorDisplay.toplevel)
267 self._controlLayout.addWidget(self._historyView.toplevel)
268 self._controlLayout.addLayout(self._userEntryLayout)
270 self._keyboardTabs = QtGui.QTabWidget()
272 if maeqt.screen_orientation() == QtCore.Qt.Vertical:
273 defaultLayoutOrientation = QtGui.QBoxLayout.TopToBottom
274 self._keyboardTabs.setTabPosition(QtGui.QTabWidget.East)
276 defaultLayoutOrientation = QtGui.QBoxLayout.LeftToRight
277 self._keyboardTabs.setTabPosition(QtGui.QTabWidget.North)
278 self._layout = QtGui.QBoxLayout(defaultLayoutOrientation)
279 self._layout.addLayout(self._controlLayout)
280 self._layout.addWidget(self._keyboardTabs)
282 centralWidget = QtGui.QWidget()
283 centralWidget.setLayout(self._layout)
285 self._window = QtGui.QMainWindow(parent)
286 self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
287 #maeqt.set_autorient(self._window, True)
288 maeqt.set_stackable(self._window, True)
289 self._window.setWindowTitle("%s" % constants.__pretty_app_name__)
290 self._window.setCentralWidget(centralWidget)
291 self._window.destroyed.connect(self._on_close_window)
293 self._copyItemAction = QtGui.QAction(None)
294 self._copyItemAction.setText("Copy")
295 self._copyItemAction.setShortcut(QtGui.QKeySequence("CTRL+c"))
296 self._copyItemAction.triggered.connect(self._on_copy)
298 self._pasteItemAction = QtGui.QAction(None)
299 self._pasteItemAction.setText("Paste")
300 self._pasteItemAction.setShortcut(QtGui.QKeySequence("CTRL+v"))
301 self._pasteItemAction.triggered.connect(self._on_paste)
303 self._closeWindowAction = QtGui.QAction(None)
304 self._closeWindowAction.setText("Close")
305 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
306 self._closeWindowAction.triggered.connect(self._on_close_window)
309 #fileMenu = self._window.menuBar().addMenu("&File")
311 #viewMenu = self._window.menuBar().addMenu("&View")
313 self._window.addAction(self._copyItemAction)
314 self._window.addAction(self._pasteItemAction)
315 self._window.addAction(self._closeWindowAction)
316 self._window.addAction(self._app.quitAction)
317 self._window.addAction(self._app.fullscreenAction)
319 fileMenu = self._window.menuBar().addMenu("&Units")
320 fileMenu.addAction(self._closeWindowAction)
321 fileMenu.addAction(self._app.quitAction)
323 viewMenu = self._window.menuBar().addMenu("&View")
324 viewMenu.addAction(self._app.fullscreenAction)
326 self._window.addAction(self._app.logAction)
328 self._constantPlugins = plugin_utils.ConstantPluginManager()
329 self._constantPlugins.add_path(*self._plugin_search_paths)
330 for pluginName in ["Builtins", "Trigonometry", "Computer", "Alphabet"]:
332 pluginId = self._constantPlugins.lookup_plugin(pluginName)
333 self._constantPlugins.enable_plugin(pluginId)
335 _moduleLogger.info("Failed to load plugin %s" % pluginName)
337 self._operatorPlugins = plugin_utils.OperatorPluginManager()
338 self._operatorPlugins.add_path(*self._plugin_search_paths)
339 for pluginName in ["Builtins", "Trigonometry", "Computer", "Alphabet"]:
341 pluginId = self._operatorPlugins.lookup_plugin(pluginName)
342 self._operatorPlugins.enable_plugin(pluginId)
344 _moduleLogger.info("Failed to load plugin %s" % pluginName)
346 self._keyboardPlugins = plugin_utils.KeyboardPluginManager()
347 self._keyboardPlugins.add_path(*self._plugin_search_paths)
348 self._activeKeyboards = []
350 self._history = history.RpnCalcHistory(
352 self._userEntry, self._errorDisplay,
353 self._constantPlugins.constants, self._operatorPlugins.operators
357 # Basic keyboard stuff
358 self._handler = qtpieboard.KeyboardHandler(self._on_entry_direct)
359 self._handler.register_command_handler("push", self._on_push)
360 self._handler.register_command_handler("unpush", self._on_unpush)
361 self._handler.register_command_handler("backspace", self._on_entry_backspace)
362 self._handler.register_command_handler("clear", self._on_entry_clear)
365 entryKeyboardId = self._keyboardPlugins.lookup_plugin("Entry")
366 self._keyboardPlugins.enable_plugin(entryKeyboardId)
367 entryPlugin = self._keyboardPlugins.keyboards["Entry"].construct_keyboard()
368 entryKeyboard = entryPlugin.setup(self._history, self._handler)
369 self._userEntryLayout.addWidget(entryKeyboard.toplevel)
372 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Builtins"))
373 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Trigonometry"))
374 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Computer"))
375 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Alphabet"))
377 self.set_fullscreen(self._app.fullscreenAction.isChecked())
380 self._set_plugin_kb(0)
386 def walk_children(self):
391 for child in self.walk_children():
395 for child in self.walk_children():
400 for child in self.walk_children():
401 child.window.destroyed.disconnect(self._on_child_close)
405 def set_fullscreen(self, isFullscreen):
407 self._window.showFullScreen()
409 self._window.showNormal()
410 for child in self.walk_children():
411 child.set_fullscreen(isFullscreen)
413 def enable_plugin(self, pluginId):
414 self._keyboardPlugins.enable_plugin(pluginId)
415 pluginData = self._keyboardPlugins.plugin_info(pluginId)
416 pluginName = pluginData[0]
417 plugin = self._keyboardPlugins.keyboards[pluginName].construct_keyboard()
418 relIcon = self._keyboardPlugins.keyboards[pluginName].icon
419 for iconPath in self._keyboardPlugins.keyboards[pluginName].iconPaths:
420 absIconPath = os.path.join(iconPath, relIcon)
421 if os.path.exists(absIconPath):
422 icon = QtGui.QIcon(absIconPath)
426 pluginKeyboard = plugin.setup(self._history, self._handler)
428 self._activeKeyboards.append({
429 "pluginName": pluginName,
431 "pluginKeyboard": pluginKeyboard,
434 self._keyboardTabs.addTab(pluginKeyboard.toplevel, pluginName)
436 self._keyboardTabs.addTab(pluginKeyboard.toplevel, icon, "")
438 def _set_plugin_kb(self, pluginIndex):
439 plugin = self._activeKeyboards[pluginIndex]
441 #for keyboardData in self._activeKeyboards:
442 # if plugin["pluginName"] != keyboardData["pluginName"]:
443 # keyboardData["pluginKeyboard"].hide()
445 # @todo self._pluginButton.set_label(plugin["pluginName"])
446 pluginKeyboard = plugin["pluginKeyboard"]
448 def _load_history(self):
451 with open(self._user_history, "rU") as f:
453 (part.strip() for part in line.split(" "))
454 for line in f.readlines()
459 self._history.deserialize_stack(serialized)
461 def _save_history(self):
462 serialized = self._history.serialize_stack()
463 with open(self._user_history, "w") as f:
464 for lineData in serialized:
465 line = " ".join(data for data in lineData)
466 f.write("%s\n" % line)
468 @misc_utils.log_exception(_moduleLogger)
469 def _on_copy(self, *args):
470 eqNode = self._historyView.peek()
471 resultNode = eqNode.simplify()
472 self._app._clipboard.setText(str(resultNode))
474 @misc_utils.log_exception(_moduleLogger)
475 def _on_paste(self, *args):
476 result = str(self._app._clipboard.text())
477 self._userEntry.append(result)
479 @misc_utils.log_exception(_moduleLogger)
480 def _on_entry_direct(self, keys, modifiers):
481 if "shift" in modifiers:
483 self._userEntry.append(keys)
485 @misc_utils.log_exception(_moduleLogger)
486 def _on_push(self, *args):
487 self._history.push_entry()
489 @misc_utils.log_exception(_moduleLogger)
490 def _on_unpush(self, *args):
491 self._historyView.unpush()
493 @misc_utils.log_exception(_moduleLogger)
494 def _on_entry_backspace(self, *args):
495 self._userEntry.pop()
497 @misc_utils.log_exception(_moduleLogger)
498 def _on_entry_clear(self, *args):
499 self._userEntry.clear()
501 @misc_utils.log_exception(_moduleLogger)
502 def _on_clear_all(self, *args):
503 self._history.clear()
505 @misc_utils.log_exception(_moduleLogger)
506 def _on_close_window(self, checked = True):
511 app = QtGui.QApplication([])
512 handle = Calculator(app)
517 if __name__ == "__main__":
518 logging.basicConfig(level = logging.DEBUG)
520 os.makedirs(constants._data_path_)