4 from __future__ import with_statement
8 import json as simplejson
10 print "json not available, falling back to simplejson"
14 import logging.handlers
16 import util.qt_compat as qt_compat
17 QtCore = qt_compat.QtCore
18 QtGui = qt_compat.import_module("QtGui")
21 from util import misc as misc_utils
22 from util import linux as linux_utils
24 from util import qui_utils
25 from util import qwrappers
26 from util import qtpie
27 from util import qtpieboard
33 _moduleLogger = logging.getLogger(__name__)
36 class Calculator(qwrappers.ApplicationWrapper):
38 def __init__(self, app):
40 self._hiddenCategories = set()
41 self._hiddenUnits = {}
42 qwrappers.ApplicationWrapper.__init__(self, app, constants)
44 def load_settings(self):
45 settingsPath = linux_utils.get_resource_path(
46 "config", constants.__app_name__, "settings.json"
49 with open(settingsPath) as settingsFile:
50 settings = simplejson.load(settingsFile)
52 _moduleLogger.info("No settings")
55 _moduleLogger.info("Settings were corrupt")
58 isPortraitDefault = qui_utils.screen_orientation() == QtCore.Qt.Vertical
59 self._fullscreenAction.setChecked(settings.get("isFullScreen", False))
60 self._orientationAction.setChecked(settings.get("isPortrait", isPortraitDefault))
62 def save_settings(self):
64 "isFullScreen": self._fullscreenAction.isChecked(),
65 "isPortrait": self._orientationAction.isChecked(),
68 settingsPath = linux_utils.get_resource_path(
69 "config", constants.__app_name__, "settings.json"
71 with open(settingsPath, "w") as settingsFile:
72 simplejson.dump(settings, settingsFile)
78 def _new_main_window(self):
79 return MainWindow(None, self)
81 @misc_utils.log_exception(_moduleLogger)
82 def _on_about(self, checked = True):
83 raise NotImplementedError("Booh")
86 class QValueEntry(object):
89 self._widget = QtGui.QLineEdit("")
90 qui_utils.mark_numbers_preferred(self._widget)
101 value = str(self._widget.text()).strip()
103 0 < value.find(whitespace)
104 for whitespace in string.whitespace
107 raise ValueError('Invalid input "%s"' % value)
110 def set_value(self, value):
111 value = value.strip()
113 0 < value.find(whitespace)
114 for whitespace in string.whitespace
116 raise ValueError('Invalid input "%s"' % value)
117 self._widget.setText(value)
119 def append(self, value):
120 value = value.strip()
122 0 < value.find(whitespace)
123 for whitespace in string.whitespace
125 raise ValueError('Invalid input "%s"' % value)
126 self.set_value(self.get_value() + value)
129 value = self.get_value()[0:-1]
130 self.set_value(value)
135 value = property(get_value, set_value, clear)
138 class MainWindow(qwrappers.WindowWrapper):
140 _plugin_search_paths = [
141 os.path.join(os.path.dirname(__file__), "plugins/"),
144 _user_history = linux_utils.get_resource_path("config", constants.__app_name__, "history.stack")
146 def __init__(self, parent, app):
147 qwrappers.WindowWrapper.__init__(self, parent, app)
148 self._window.setWindowTitle("%s" % constants.__pretty_app_name__)
149 #self._freezer = qwrappers.AutoFreezeWindowFeature(self._app, self._window)
151 self._historyView = qhistory.QCalcHistory(self._app.errorLog)
152 self._userEntry = QValueEntry()
153 self._userEntry.entry.returnPressed.connect(self._on_push)
154 self._userEntryLayout = QtGui.QHBoxLayout()
155 self._userEntryLayout.setContentsMargins(0, 0, 0, 0)
156 self._userEntryLayout.addWidget(self._userEntry.toplevel, 10)
158 self._controlLayout = QtGui.QVBoxLayout()
159 self._controlLayout.setContentsMargins(0, 0, 0, 0)
160 self._controlLayout.addWidget(self._historyView.toplevel, 1000)
161 self._controlLayout.addLayout(self._userEntryLayout, 0)
163 self._keyboardTabs = QtGui.QTabWidget()
165 self._layout.addLayout(self._controlLayout)
166 self._layout.addWidget(self._keyboardTabs)
168 self._copyItemAction = QtGui.QAction(None)
169 self._copyItemAction.setText("Copy")
170 self._copyItemAction.setShortcut(QtGui.QKeySequence("CTRL+c"))
171 self._copyItemAction.triggered.connect(self._on_copy)
173 self._pasteItemAction = QtGui.QAction(None)
174 self._pasteItemAction.setText("Paste")
175 self._pasteItemAction.setShortcut(QtGui.QKeySequence("CTRL+v"))
176 self._pasteItemAction.triggered.connect(self._on_paste)
178 self._closeWindowAction = QtGui.QAction(None)
179 self._closeWindowAction.setText("Close")
180 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
181 self._closeWindowAction.triggered.connect(self._on_close_window)
183 self._window.addAction(self._copyItemAction)
184 self._window.addAction(self._pasteItemAction)
186 self._constantPlugins = plugin_utils.ConstantPluginManager()
187 self._constantPlugins.add_path(*self._plugin_search_paths)
188 for pluginName in ["Builtins", "Trigonometry", "Computer", "Alphabet"]:
190 pluginId = self._constantPlugins.lookup_plugin(pluginName)
191 self._constantPlugins.enable_plugin(pluginId)
193 _moduleLogger.exception("Failed to load plugin %s" % pluginName)
195 self._operatorPlugins = plugin_utils.OperatorPluginManager()
196 self._operatorPlugins.add_path(*self._plugin_search_paths)
197 for pluginName in ["Builtins", "Trigonometry", "Computer", "Alphabet"]:
199 pluginId = self._operatorPlugins.lookup_plugin(pluginName)
200 self._operatorPlugins.enable_plugin(pluginId)
202 _moduleLogger.exception("Failed to load plugin %s" % pluginName)
204 self._keyboardPlugins = plugin_utils.KeyboardPluginManager()
205 self._keyboardPlugins.add_path(*self._plugin_search_paths)
206 self._activeKeyboards = []
208 self._history = history.RpnCalcHistory(
210 self._userEntry, self._app.errorLog,
211 self._constantPlugins.constants, self._operatorPlugins.operators
215 # Basic keyboard stuff
216 self._handler = qtpieboard.KeyboardHandler(self._on_entry_direct)
217 self._handler.register_command_handler("push", self._on_push)
218 self._handler.register_command_handler("unpush", self._on_unpush)
219 self._handler.register_command_handler("backspace", self._on_entry_backspace)
220 self._handler.register_command_handler("clear", self._on_entry_clear)
223 entryKeyboardId = self._keyboardPlugins.lookup_plugin("Entry")
224 self._keyboardPlugins.enable_plugin(entryKeyboardId)
225 entryPlugin = self._keyboardPlugins.keyboards["Entry"].construct_keyboard()
226 entryKeyboard = entryPlugin.setup(self._history, self._handler)
227 self._userEntryLayout.addWidget(entryKeyboard.toplevel)
230 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Builtins"))
231 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Trigonometry"))
232 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Computer"))
233 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Alphabet"))
235 self._scrollTimer = QtCore.QTimer()
236 self._scrollTimer.setInterval(0)
237 self._scrollTimer.setSingleShot(True)
238 self._scrollTimer.timeout.connect(self._on_delayed_scroll_to_bottom)
239 self._scrollTimer.start()
241 self.set_fullscreen(self._app.fullscreenAction.isChecked())
242 self.update_orientation(self._app.orientation)
244 def walk_children(self):
247 def update_orientation(self, orientation):
248 qwrappers.WindowWrapper.update_orientation(self, orientation)
249 windowOrientation = self.idealWindowOrientation
250 if windowOrientation == QtCore.Qt.Horizontal:
251 defaultLayoutOrientation = QtGui.QBoxLayout.LeftToRight
252 tabPosition = QtGui.QTabWidget.North
254 defaultLayoutOrientation = QtGui.QBoxLayout.TopToBottom
255 #tabPosition = QtGui.QTabWidget.South
256 tabPosition = QtGui.QTabWidget.West
257 self._layout.setDirection(defaultLayoutOrientation)
258 self._keyboardTabs.setTabPosition(tabPosition)
260 def enable_plugin(self, pluginId):
261 self._keyboardPlugins.enable_plugin(pluginId)
262 pluginData = self._keyboardPlugins.plugin_info(pluginId)
263 pluginName = pluginData[0]
264 plugin = self._keyboardPlugins.keyboards[pluginName].construct_keyboard()
265 relIcon = self._keyboardPlugins.keyboards[pluginName].icon
266 for iconPath in self._keyboardPlugins.keyboards[pluginName].iconPaths:
267 absIconPath = os.path.join(iconPath, relIcon)
268 if os.path.exists(absIconPath):
269 icon = QtGui.QIcon(absIconPath)
273 pluginKeyboard = plugin.setup(self._history, self._handler)
275 self._activeKeyboards.append({
276 "pluginName": pluginName,
278 "pluginKeyboard": pluginKeyboard,
281 self._keyboardTabs.addTab(pluginKeyboard.toplevel, pluginName)
283 self._keyboardTabs.addTab(pluginKeyboard.toplevel, icon, "")
286 qwrappers.WindowWrapper.close(self)
289 def _load_history(self):
292 with open(self._user_history, "rU") as f:
294 (part.strip() for part in line.split(" "))
295 for line in f.readlines()
300 self._history.deserialize_stack(serialized)
302 def _save_history(self):
303 serialized = self._history.serialize_stack()
304 with open(self._user_history, "w") as f:
305 for lineData in serialized:
306 line = " ".join(data for data in lineData)
307 f.write("%s\n" % line)
309 @misc_utils.log_exception(_moduleLogger)
310 def _on_delayed_scroll_to_bottom(self):
311 with qui_utils.notify_error(self._app.errorLog):
312 self._historyView.scroll_to_bottom()
314 @misc_utils.log_exception(_moduleLogger)
315 def _on_child_close(self, something = None):
316 with qui_utils.notify_error(self._app.errorLog):
319 @misc_utils.log_exception(_moduleLogger)
320 def _on_copy(self, *args):
321 with qui_utils.notify_error(self._app.errorLog):
322 eqNode = self._historyView.peek()
323 resultNode = eqNode.simplify()
324 self._app._clipboard.setText(str(resultNode))
326 @misc_utils.log_exception(_moduleLogger)
327 def _on_paste(self, *args):
328 with qui_utils.notify_error(self._app.errorLog):
329 result = str(self._app._clipboard.text())
330 self._userEntry.append(result)
332 @misc_utils.log_exception(_moduleLogger)
333 def _on_entry_direct(self, keys, modifiers):
334 with qui_utils.notify_error(self._app.errorLog):
335 if "shift" in modifiers:
337 self._userEntry.append(keys)
339 @misc_utils.log_exception(_moduleLogger)
340 def _on_push(self, *args):
341 with qui_utils.notify_error(self._app.errorLog):
342 self._history.push_entry()
344 @misc_utils.log_exception(_moduleLogger)
345 def _on_unpush(self, *args):
346 with qui_utils.notify_error(self._app.errorLog):
347 self._historyView.unpush()
349 @misc_utils.log_exception(_moduleLogger)
350 def _on_entry_backspace(self, *args):
351 with qui_utils.notify_error(self._app.errorLog):
352 self._userEntry.pop()
354 @misc_utils.log_exception(_moduleLogger)
355 def _on_entry_clear(self, *args):
356 with qui_utils.notify_error(self._app.errorLog):
357 self._userEntry.clear()
359 @misc_utils.log_exception(_moduleLogger)
360 def _on_clear_all(self, *args):
361 with qui_utils.notify_error(self._app.errorLog):
362 self._history.clear()
367 os.makedirs(linux_utils.get_resource_path("config", constants.__app_name__))
372 os.makedirs(linux_utils.get_resource_path("cache", constants.__app_name__))
377 os.makedirs(linux_utils.get_resource_path("data", constants.__app_name__))
382 logPath = linux_utils.get_resource_path("cache", constants.__app_name__, "%s.log" % constants.__app_name__)
383 logFormat = '(%(relativeCreated)5d) %(levelname)-5s %(threadName)s.%(name)s.%(funcName)s: %(message)s'
384 logging.basicConfig(level=logging.DEBUG, format=logFormat)
385 rotating = logging.handlers.RotatingFileHandler(logPath, maxBytes=512*1024, backupCount=1)
386 rotating.setFormatter(logging.Formatter(logFormat))
387 root = logging.getLogger()
388 root.addHandler(rotating)
389 _moduleLogger.info("%s %s-%s" % (constants.__app_name__, constants.__version__, constants.__build__))
390 _moduleLogger.info("OS: %s" % (os.uname()[0], ))
391 _moduleLogger.info("Kernel: %s (%s) for %s" % os.uname()[2:])
392 _moduleLogger.info("Hostname: %s" % os.uname()[1])
394 app = QtGui.QApplication([])
395 handle = Calculator(app)
400 if __name__ == "__main__":