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
19 from util import qui_utils
20 from util import qwrappers
21 from util import qtpie
22 from util import qtpieboard
28 _moduleLogger = logging.getLogger(__name__)
31 class Calculator(qwrappers.ApplicationWrapper):
33 def __init__(self, app):
35 self._hiddenCategories = set()
36 self._hiddenUnits = {}
37 qwrappers.ApplicationWrapper.__init__(self, app, constants)
39 def load_settings(self):
41 with open(constants._user_settings_, "r") as settingsFile:
42 settings = simplejson.load(settingsFile)
44 _moduleLogger.info("No settings")
47 _moduleLogger.info("Settings were corrupt")
50 isPortraitDefault = qui_utils.screen_orientation() == QtCore.Qt.Vertical
51 self._fullscreenAction.setChecked(settings.get("isFullScreen", False))
52 self._orientationAction.setChecked(settings.get("isPortrait", isPortraitDefault))
54 def save_settings(self):
56 "isFullScreen": self._fullscreenAction.isChecked(),
57 "isPortrait": self._orientationAction.isChecked(),
59 with open(constants._user_settings_, "w") as settingsFile:
60 simplejson.dump(settings, settingsFile)
66 def _new_main_window(self):
67 return MainWindow(None, self)
69 @misc_utils.log_exception(_moduleLogger)
70 def _on_about(self, checked = True):
71 raise NotImplementedError("Booh")
74 class QValueEntry(object):
77 self._widget = QtGui.QLineEdit("")
78 qui_utils.mark_numbers_preferred(self._widget)
89 value = str(self._widget.text()).strip()
91 0 < value.find(whitespace)
92 for whitespace in string.whitespace
95 raise ValueError('Invalid input "%s"' % value)
98 def set_value(self, value):
101 0 < value.find(whitespace)
102 for whitespace in string.whitespace
104 raise ValueError('Invalid input "%s"' % value)
105 self._widget.setText(value)
107 def append(self, value):
108 value = value.strip()
110 0 < value.find(whitespace)
111 for whitespace in string.whitespace
113 raise ValueError('Invalid input "%s"' % value)
114 self.set_value(self.get_value() + value)
117 value = self.get_value()[0:-1]
118 self.set_value(value)
123 value = property(get_value, set_value, clear)
126 class MainWindow(qwrappers.WindowWrapper):
128 _plugin_search_paths = [
129 os.path.join(os.path.dirname(__file__), "plugins/"),
132 _user_history = "%s/history.stack" % constants._data_path_
134 def __init__(self, parent, app):
135 qwrappers.WindowWrapper.__init__(self, parent, app)
136 self._window.setWindowTitle("%s" % constants.__pretty_app_name__)
137 #self._freezer = qwrappers.AutoFreezeWindowFeature(self._app, self._window)
139 self._historyView = qhistory.QCalcHistory(self._app.errorLog)
140 self._userEntry = QValueEntry()
141 self._userEntry.entry.returnPressed.connect(self._on_push)
142 self._userEntryLayout = QtGui.QHBoxLayout()
143 self._userEntryLayout.setContentsMargins(0, 0, 0, 0)
144 self._userEntryLayout.addWidget(self._userEntry.toplevel, 10)
146 self._controlLayout = QtGui.QVBoxLayout()
147 self._controlLayout.setContentsMargins(0, 0, 0, 0)
148 self._controlLayout.addWidget(self._historyView.toplevel, 1000)
149 self._controlLayout.addLayout(self._userEntryLayout, 0)
151 self._keyboardTabs = QtGui.QTabWidget()
153 self._layout.addLayout(self._controlLayout)
154 self._layout.addWidget(self._keyboardTabs)
156 self._copyItemAction = QtGui.QAction(None)
157 self._copyItemAction.setText("Copy")
158 self._copyItemAction.setShortcut(QtGui.QKeySequence("CTRL+c"))
159 self._copyItemAction.triggered.connect(self._on_copy)
161 self._pasteItemAction = QtGui.QAction(None)
162 self._pasteItemAction.setText("Paste")
163 self._pasteItemAction.setShortcut(QtGui.QKeySequence("CTRL+v"))
164 self._pasteItemAction.triggered.connect(self._on_paste)
166 self._closeWindowAction = QtGui.QAction(None)
167 self._closeWindowAction.setText("Close")
168 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
169 self._closeWindowAction.triggered.connect(self._on_close_window)
171 self._window.addAction(self._copyItemAction)
172 self._window.addAction(self._pasteItemAction)
174 self._constantPlugins = plugin_utils.ConstantPluginManager()
175 self._constantPlugins.add_path(*self._plugin_search_paths)
176 for pluginName in ["Builtins", "Trigonometry", "Computer", "Alphabet"]:
178 pluginId = self._constantPlugins.lookup_plugin(pluginName)
179 self._constantPlugins.enable_plugin(pluginId)
181 _moduleLogger.exception("Failed to load plugin %s" % pluginName)
183 self._operatorPlugins = plugin_utils.OperatorPluginManager()
184 self._operatorPlugins.add_path(*self._plugin_search_paths)
185 for pluginName in ["Builtins", "Trigonometry", "Computer", "Alphabet"]:
187 pluginId = self._operatorPlugins.lookup_plugin(pluginName)
188 self._operatorPlugins.enable_plugin(pluginId)
190 _moduleLogger.exception("Failed to load plugin %s" % pluginName)
192 self._keyboardPlugins = plugin_utils.KeyboardPluginManager()
193 self._keyboardPlugins.add_path(*self._plugin_search_paths)
194 self._activeKeyboards = []
196 self._history = history.RpnCalcHistory(
198 self._userEntry, self._app.errorLog,
199 self._constantPlugins.constants, self._operatorPlugins.operators
203 # Basic keyboard stuff
204 self._handler = qtpieboard.KeyboardHandler(self._on_entry_direct)
205 self._handler.register_command_handler("push", self._on_push)
206 self._handler.register_command_handler("unpush", self._on_unpush)
207 self._handler.register_command_handler("backspace", self._on_entry_backspace)
208 self._handler.register_command_handler("clear", self._on_entry_clear)
211 entryKeyboardId = self._keyboardPlugins.lookup_plugin("Entry")
212 self._keyboardPlugins.enable_plugin(entryKeyboardId)
213 entryPlugin = self._keyboardPlugins.keyboards["Entry"].construct_keyboard()
214 entryKeyboard = entryPlugin.setup(self._history, self._handler)
215 self._userEntryLayout.addWidget(entryKeyboard.toplevel)
218 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Builtins"))
219 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Trigonometry"))
220 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Computer"))
221 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Alphabet"))
223 self._scrollTimer = QtCore.QTimer()
224 self._scrollTimer.setInterval(0)
225 self._scrollTimer.setSingleShot(True)
226 self._scrollTimer.timeout.connect(self._on_delayed_scroll_to_bottom)
227 self._scrollTimer.start()
229 self.set_fullscreen(self._app.fullscreenAction.isChecked())
230 self.update_orientation(self._app.orientation)
232 def walk_children(self):
235 def update_orientation(self, orientation):
236 qwrappers.WindowWrapper.update_orientation(self, orientation)
237 windowOrientation = self.idealWindowOrientation
238 if windowOrientation == QtCore.Qt.Horizontal:
239 defaultLayoutOrientation = QtGui.QBoxLayout.LeftToRight
240 tabPosition = QtGui.QTabWidget.North
242 defaultLayoutOrientation = QtGui.QBoxLayout.TopToBottom
243 #tabPosition = QtGui.QTabWidget.South
244 tabPosition = QtGui.QTabWidget.West
245 self._layout.setDirection(defaultLayoutOrientation)
246 self._keyboardTabs.setTabPosition(tabPosition)
248 def enable_plugin(self, pluginId):
249 self._keyboardPlugins.enable_plugin(pluginId)
250 pluginData = self._keyboardPlugins.plugin_info(pluginId)
251 pluginName = pluginData[0]
252 plugin = self._keyboardPlugins.keyboards[pluginName].construct_keyboard()
253 relIcon = self._keyboardPlugins.keyboards[pluginName].icon
254 for iconPath in self._keyboardPlugins.keyboards[pluginName].iconPaths:
255 absIconPath = os.path.join(iconPath, relIcon)
256 if os.path.exists(absIconPath):
257 icon = QtGui.QIcon(absIconPath)
261 pluginKeyboard = plugin.setup(self._history, self._handler)
263 self._activeKeyboards.append({
264 "pluginName": pluginName,
266 "pluginKeyboard": pluginKeyboard,
269 self._keyboardTabs.addTab(pluginKeyboard.toplevel, pluginName)
271 self._keyboardTabs.addTab(pluginKeyboard.toplevel, icon, "")
274 qwrappers.WindowWrapper.close(self)
277 def _load_history(self):
280 with open(self._user_history, "rU") as f:
282 (part.strip() for part in line.split(" "))
283 for line in f.readlines()
288 self._history.deserialize_stack(serialized)
290 def _save_history(self):
291 serialized = self._history.serialize_stack()
292 with open(self._user_history, "w") as f:
293 for lineData in serialized:
294 line = " ".join(data for data in lineData)
295 f.write("%s\n" % line)
297 @misc_utils.log_exception(_moduleLogger)
298 def _on_delayed_scroll_to_bottom(self):
299 with qui_utils.notify_error(self._app.errorLog):
300 self._historyView.scroll_to_bottom()
302 @misc_utils.log_exception(_moduleLogger)
303 def _on_child_close(self, something = None):
304 with qui_utils.notify_error(self._app.errorLog):
307 @misc_utils.log_exception(_moduleLogger)
308 def _on_copy(self, *args):
309 with qui_utils.notify_error(self._app.errorLog):
310 eqNode = self._historyView.peek()
311 resultNode = eqNode.simplify()
312 self._app._clipboard.setText(str(resultNode))
314 @misc_utils.log_exception(_moduleLogger)
315 def _on_paste(self, *args):
316 with qui_utils.notify_error(self._app.errorLog):
317 result = str(self._app._clipboard.text())
318 self._userEntry.append(result)
320 @misc_utils.log_exception(_moduleLogger)
321 def _on_entry_direct(self, keys, modifiers):
322 with qui_utils.notify_error(self._app.errorLog):
323 if "shift" in modifiers:
325 self._userEntry.append(keys)
327 @misc_utils.log_exception(_moduleLogger)
328 def _on_push(self, *args):
329 with qui_utils.notify_error(self._app.errorLog):
330 self._history.push_entry()
332 @misc_utils.log_exception(_moduleLogger)
333 def _on_unpush(self, *args):
334 with qui_utils.notify_error(self._app.errorLog):
335 self._historyView.unpush()
337 @misc_utils.log_exception(_moduleLogger)
338 def _on_entry_backspace(self, *args):
339 with qui_utils.notify_error(self._app.errorLog):
340 self._userEntry.pop()
342 @misc_utils.log_exception(_moduleLogger)
343 def _on_entry_clear(self, *args):
344 with qui_utils.notify_error(self._app.errorLog):
345 self._userEntry.clear()
347 @misc_utils.log_exception(_moduleLogger)
348 def _on_clear_all(self, *args):
349 with qui_utils.notify_error(self._app.errorLog):
350 self._history.clear()
355 os.makedirs(constants._data_path_)
360 logFormat = '(%(relativeCreated)5d) %(levelname)-5s %(threadName)s.%(name)s.%(funcName)s: %(message)s'
361 logging.basicConfig(level=logging.DEBUG, format=logFormat)
362 rotating = logging.handlers.RotatingFileHandler(constants._user_logpath_, maxBytes=512*1024, backupCount=1)
363 rotating.setFormatter(logging.Formatter(logFormat))
364 root = logging.getLogger()
365 root.addHandler(rotating)
366 _moduleLogger.info("%s %s-%s" % (constants.__app_name__, constants.__version__, constants.__build__))
367 _moduleLogger.info("OS: %s" % (os.uname()[0], ))
368 _moduleLogger.info("Kernel: %s (%s) for %s" % os.uname()[2:])
369 _moduleLogger.info("Hostname: %s" % os.uname()[1])
371 app = QtGui.QApplication([])
372 handle = Calculator(app)
377 if __name__ == "__main__":