4 from __future__ import with_statement
10 import logging.handlers
12 import util.qt_compat as qt_compat
13 QtCore = qt_compat.QtCore
14 QtGui = qt_compat.import_module("QtGui")
17 from util import misc as misc_utils
18 from util import linux as linux_utils
20 from util import qui_utils
21 from util import qwrappers
22 from util import qtpie
23 from util import qtpieboard
29 _moduleLogger = logging.getLogger(__name__)
32 class Calculator(qwrappers.ApplicationWrapper):
34 def __init__(self, app):
36 self._hiddenCategories = set()
37 self._hiddenUnits = {}
38 qwrappers.ApplicationWrapper.__init__(self, app, constants)
40 def load_settings(self):
41 settingsPath = linux_utils.get_resource_path(
42 "config", constants.__app_name__, "settings.json"
45 with open(settingsPath) as settingsFile:
46 settings = simplejson.load(settingsFile)
48 _moduleLogger.info("No settings")
51 _moduleLogger.info("Settings were corrupt")
54 isPortraitDefault = qui_utils.screen_orientation() == QtCore.Qt.Vertical
55 self._fullscreenAction.setChecked(settings.get("isFullScreen", False))
56 self._orientationAction.setChecked(settings.get("isPortrait", isPortraitDefault))
58 def save_settings(self):
60 "isFullScreen": self._fullscreenAction.isChecked(),
61 "isPortrait": self._orientationAction.isChecked(),
64 settingsPath = linux_utils.get_resource_path(
65 "config", constants.__app_name__, "settings.json"
67 with open(settingsPath, "w") as settingsFile:
68 simplejson.dump(settings, settingsFile)
74 def _new_main_window(self):
75 return MainWindow(None, self)
77 @misc_utils.log_exception(_moduleLogger)
78 def _on_about(self, checked = True):
79 raise NotImplementedError("Booh")
82 class QValueEntry(object):
85 self._widget = QtGui.QLineEdit("")
86 qui_utils.mark_numbers_preferred(self._widget)
97 value = str(self._widget.text()).strip()
99 0 < value.find(whitespace)
100 for whitespace in string.whitespace
103 raise ValueError('Invalid input "%s"' % value)
106 def set_value(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._widget.setText(value)
115 def append(self, value):
116 value = value.strip()
118 0 < value.find(whitespace)
119 for whitespace in string.whitespace
121 raise ValueError('Invalid input "%s"' % value)
122 self.set_value(self.get_value() + value)
125 value = self.get_value()[0:-1]
126 self.set_value(value)
131 value = property(get_value, set_value, clear)
134 class MainWindow(qwrappers.WindowWrapper):
136 _plugin_search_paths = [
137 os.path.join(os.path.dirname(__file__), "plugins/"),
140 _user_history = linux_utils.get_resource_path("config", constants.__app_name__, "history.stack")
142 def __init__(self, parent, app):
143 qwrappers.WindowWrapper.__init__(self, parent, app)
144 self._window.setWindowTitle("%s" % constants.__pretty_app_name__)
145 #self._freezer = qwrappers.AutoFreezeWindowFeature(self._app, self._window)
147 self._historyView = qhistory.QCalcHistory(self._app.errorLog)
148 self._userEntry = QValueEntry()
149 self._userEntry.entry.returnPressed.connect(self._on_push)
150 self._userEntryLayout = QtGui.QHBoxLayout()
151 self._userEntryLayout.setContentsMargins(0, 0, 0, 0)
152 self._userEntryLayout.addWidget(self._userEntry.toplevel, 10)
154 self._controlLayout = QtGui.QVBoxLayout()
155 self._controlLayout.setContentsMargins(0, 0, 0, 0)
156 self._controlLayout.addWidget(self._historyView.toplevel, 1000)
157 self._controlLayout.addLayout(self._userEntryLayout, 0)
159 self._keyboardTabs = QtGui.QTabWidget()
161 self._layout.addLayout(self._controlLayout)
162 self._layout.addWidget(self._keyboardTabs)
164 self._copyItemAction = QtGui.QAction(None)
165 self._copyItemAction.setText("Copy")
166 self._copyItemAction.setShortcut(QtGui.QKeySequence("CTRL+c"))
167 self._copyItemAction.triggered.connect(self._on_copy)
169 self._pasteItemAction = QtGui.QAction(None)
170 self._pasteItemAction.setText("Paste")
171 self._pasteItemAction.setShortcut(QtGui.QKeySequence("CTRL+v"))
172 self._pasteItemAction.triggered.connect(self._on_paste)
174 self._closeWindowAction = QtGui.QAction(None)
175 self._closeWindowAction.setText("Close")
176 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
177 self._closeWindowAction.triggered.connect(self._on_close_window)
179 self._window.addAction(self._copyItemAction)
180 self._window.addAction(self._pasteItemAction)
182 self._constantPlugins = plugin_utils.ConstantPluginManager()
183 self._constantPlugins.add_path(*self._plugin_search_paths)
184 for pluginName in ["Builtins", "Trigonometry", "Computer", "Alphabet"]:
186 pluginId = self._constantPlugins.lookup_plugin(pluginName)
187 self._constantPlugins.enable_plugin(pluginId)
189 _moduleLogger.exception("Failed to load plugin %s" % pluginName)
191 self._operatorPlugins = plugin_utils.OperatorPluginManager()
192 self._operatorPlugins.add_path(*self._plugin_search_paths)
193 for pluginName in ["Builtins", "Trigonometry", "Computer", "Alphabet"]:
195 pluginId = self._operatorPlugins.lookup_plugin(pluginName)
196 self._operatorPlugins.enable_plugin(pluginId)
198 _moduleLogger.exception("Failed to load plugin %s" % pluginName)
200 self._keyboardPlugins = plugin_utils.KeyboardPluginManager()
201 self._keyboardPlugins.add_path(*self._plugin_search_paths)
202 self._activeKeyboards = []
204 self._history = history.RpnCalcHistory(
206 self._userEntry, self._app.errorLog,
207 self._constantPlugins.constants, self._operatorPlugins.operators
211 # Basic keyboard stuff
212 self._handler = qtpieboard.KeyboardHandler(self._on_entry_direct)
213 self._handler.register_command_handler("push", self._on_push)
214 self._handler.register_command_handler("unpush", self._on_unpush)
215 self._handler.register_command_handler("backspace", self._on_entry_backspace)
216 self._handler.register_command_handler("clear", self._on_entry_clear)
219 entryKeyboardId = self._keyboardPlugins.lookup_plugin("Entry")
220 self._keyboardPlugins.enable_plugin(entryKeyboardId)
221 entryPlugin = self._keyboardPlugins.keyboards["Entry"].construct_keyboard()
222 entryKeyboard = entryPlugin.setup(self._history, self._handler)
223 self._userEntryLayout.addWidget(entryKeyboard.toplevel)
226 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Builtins"))
227 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Trigonometry"))
228 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Computer"))
229 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Alphabet"))
231 self._scrollTimer = QtCore.QTimer()
232 self._scrollTimer.setInterval(0)
233 self._scrollTimer.setSingleShot(True)
234 self._scrollTimer.timeout.connect(self._on_delayed_scroll_to_bottom)
235 self._scrollTimer.start()
237 self.set_fullscreen(self._app.fullscreenAction.isChecked())
238 self.update_orientation(self._app.orientation)
240 def walk_children(self):
243 def update_orientation(self, orientation):
244 qwrappers.WindowWrapper.update_orientation(self, orientation)
245 windowOrientation = self.idealWindowOrientation
246 if windowOrientation == QtCore.Qt.Horizontal:
247 defaultLayoutOrientation = QtGui.QBoxLayout.LeftToRight
248 tabPosition = QtGui.QTabWidget.North
250 defaultLayoutOrientation = QtGui.QBoxLayout.TopToBottom
251 #tabPosition = QtGui.QTabWidget.South
252 tabPosition = QtGui.QTabWidget.West
253 self._layout.setDirection(defaultLayoutOrientation)
254 self._keyboardTabs.setTabPosition(tabPosition)
256 def enable_plugin(self, pluginId):
257 self._keyboardPlugins.enable_plugin(pluginId)
258 pluginData = self._keyboardPlugins.plugin_info(pluginId)
259 pluginName = pluginData[0]
260 plugin = self._keyboardPlugins.keyboards[pluginName].construct_keyboard()
261 relIcon = self._keyboardPlugins.keyboards[pluginName].icon
262 for iconPath in self._keyboardPlugins.keyboards[pluginName].iconPaths:
263 absIconPath = os.path.join(iconPath, relIcon)
264 if os.path.exists(absIconPath):
265 icon = QtGui.QIcon(absIconPath)
269 pluginKeyboard = plugin.setup(self._history, self._handler)
271 self._activeKeyboards.append({
272 "pluginName": pluginName,
274 "pluginKeyboard": pluginKeyboard,
277 self._keyboardTabs.addTab(pluginKeyboard.toplevel, pluginName)
279 self._keyboardTabs.addTab(pluginKeyboard.toplevel, icon, "")
282 qwrappers.WindowWrapper.close(self)
285 def _load_history(self):
288 with open(self._user_history, "rU") as f:
290 (part.strip() for part in line.split(" "))
291 for line in f.readlines()
296 self._history.deserialize_stack(serialized)
298 def _save_history(self):
299 serialized = self._history.serialize_stack()
300 with open(self._user_history, "w") as f:
301 for lineData in serialized:
302 line = " ".join(data for data in lineData)
303 f.write("%s\n" % line)
305 @misc_utils.log_exception(_moduleLogger)
306 def _on_delayed_scroll_to_bottom(self):
307 with qui_utils.notify_error(self._app.errorLog):
308 self._historyView.scroll_to_bottom()
310 @misc_utils.log_exception(_moduleLogger)
311 def _on_child_close(self, something = None):
312 with qui_utils.notify_error(self._app.errorLog):
315 @misc_utils.log_exception(_moduleLogger)
316 def _on_copy(self, *args):
317 with qui_utils.notify_error(self._app.errorLog):
318 eqNode = self._historyView.peek()
319 resultNode = eqNode.simplify()
320 self._app._clipboard.setText(str(resultNode))
322 @misc_utils.log_exception(_moduleLogger)
323 def _on_paste(self, *args):
324 with qui_utils.notify_error(self._app.errorLog):
325 result = str(self._app._clipboard.text())
326 self._userEntry.append(result)
328 @misc_utils.log_exception(_moduleLogger)
329 def _on_entry_direct(self, keys, modifiers):
330 with qui_utils.notify_error(self._app.errorLog):
331 if "shift" in modifiers:
333 self._userEntry.append(keys)
335 @misc_utils.log_exception(_moduleLogger)
336 def _on_push(self, *args):
337 with qui_utils.notify_error(self._app.errorLog):
338 self._history.push_entry()
340 @misc_utils.log_exception(_moduleLogger)
341 def _on_unpush(self, *args):
342 with qui_utils.notify_error(self._app.errorLog):
343 self._historyView.unpush()
345 @misc_utils.log_exception(_moduleLogger)
346 def _on_entry_backspace(self, *args):
347 with qui_utils.notify_error(self._app.errorLog):
348 self._userEntry.pop()
350 @misc_utils.log_exception(_moduleLogger)
351 def _on_entry_clear(self, *args):
352 with qui_utils.notify_error(self._app.errorLog):
353 self._userEntry.clear()
355 @misc_utils.log_exception(_moduleLogger)
356 def _on_clear_all(self, *args):
357 with qui_utils.notify_error(self._app.errorLog):
358 self._history.clear()
363 os.makedirs(linux_utils.get_resource_path("config", constants.__app_name__))
368 os.makedirs(linux_utils.get_resource_path("cache", constants.__app_name__))
373 os.makedirs(linux_utils.get_resource_path("data", constants.__app_name__))
378 logPath = linux_utils.get_resource_path("cache", constants.__app_name__, "%s.log" % constants.__app_name__)
379 logFormat = '(%(relativeCreated)5d) %(levelname)-5s %(threadName)s.%(name)s.%(funcName)s: %(message)s'
380 logging.basicConfig(level=logging.DEBUG, format=logFormat)
381 rotating = logging.handlers.RotatingFileHandler(logPath, maxBytes=512*1024, backupCount=1)
382 rotating.setFormatter(logging.Formatter(logFormat))
383 root = logging.getLogger()
384 root.addHandler(rotating)
385 _moduleLogger.info("%s %s-%s" % (constants.__app_name__, constants.__version__, constants.__build__))
386 _moduleLogger.info("OS: %s" % (os.uname()[0], ))
387 _moduleLogger.info("Kernel: %s (%s) for %s" % os.uname()[2:])
388 _moduleLogger.info("Hostname: %s" % os.uname()[1])
390 app = QtGui.QApplication([])
391 handle = Calculator(app)
396 if __name__ == "__main__":