4 @todo Add preference file
5 @li enable/disable plugins
9 @todo Expand operations to support
10 @li mathml then to cairo?
12 @todo Expanded copy/paste (Unusure how far to go)
13 @li Copy formula, value, serialized, mathml, latex?
14 @li Paste serialized, value?
15 @bug Has the same Maemo tab color bug as DialCentral
17 Some useful things on Maemo
18 @li http://maemo.org/api_refs/4.1/libosso-2.16-1/group__Statesave.html
19 @li http://maemo.org/api_refs/4.1/libosso-2.16-1/group__Autosave.html
23 from __future__ import with_statement
40 from libraries import gtkpie
41 from libraries import gtkpieboard
48 PLUGIN_SEARCH_PATHS = [
49 os.path.join(os.path.dirname(__file__), "plugins/"),
53 class ValueEntry(object):
55 def __init__(self, widget):
56 self.__widget = widget
57 self.__actualEntryDisplay = ""
60 value = self.__actualEntryDisplay.strip()
62 0 < value.find(whitespace)
63 for whitespace in string.whitespace
66 raise ValueError('Invalid input "%s"' % value)
69 def set_value(self, value):
72 0 < value.find(whitespace)
73 for whitespace in string.whitespace
75 raise ValueError('Invalid input "%s"' % value)
76 self.__actualEntryDisplay = value
77 self.__widget.set_text(value)
79 def append(self, value):
82 0 < value.find(whitespace)
83 for whitespace in string.whitespace
85 raise ValueError('Invalid input "%s"' % value)
86 self.set_value(self.get_value() + value)
89 value = self.get_value()[0:-1]
95 value = property(get_value, set_value, clear)
98 class Calculator(object):
100 __pretty_app_name__ = "e**(j pi) + 1 = 0"
101 __app_name__ = "ejpi"
102 __version__ = "0.9.4"
103 __app_magic__ = 0xdeadbeef
106 '/usr/lib/ejpi/ejpi.glade',
107 os.path.join(os.path.dirname(__file__), "ejpi.glade"),
108 os.path.join(os.path.dirname(__file__), "../lib/ejpi.glade"),
111 _plugin_search_paths = [
112 "/usr/lib/ejpi/plugins/",
113 os.path.join(os.path.dirname(__file__), "plugins/"),
116 _user_data = os.path.expanduser("~/.%s/" % __app_name__)
117 _user_settings = "%s/settings.ini" % _user_data
118 _user_history = "%s/history.stack" % _user_data
121 self.__constantPlugins = plugin_utils.ConstantPluginManager()
122 self.__constantPlugins.add_path(*self._plugin_search_paths)
123 for pluginName in ["Builtin", "Trigonometry", "Computer", "Alphabet"]:
125 pluginId = self.__constantPlugins.lookup_plugin(pluginName)
126 self.__constantPlugins.enable_plugin(pluginId)
128 warnings.warn("Failed to load plugin %s" % pluginName)
130 self.__operatorPlugins = plugin_utils.OperatorPluginManager()
131 self.__operatorPlugins.add_path(*self._plugin_search_paths)
132 for pluginName in ["Builtin", "Trigonometry", "Computer", "Alphabet"]:
134 pluginId = self.__operatorPlugins.lookup_plugin(pluginName)
135 self.__operatorPlugins.enable_plugin(pluginId)
137 warnings.warn("Failed to load plugin %s" % pluginName)
139 self.__keyboardPlugins = plugin_utils.KeyboardPluginManager()
140 self.__keyboardPlugins.add_path(*self._plugin_search_paths)
141 self.__activeKeyboards = {}
143 for path in self._glade_files:
144 if os.path.isfile(path):
145 self._widgetTree = gtk.glade.XML(path)
148 self.display_error_message("Cannot find ejpi.glade")
151 os.makedirs(self._user_data)
156 self._clipboard = gtk.clipboard_get()
157 self.__window = self._widgetTree.get_widget("mainWindow")
161 self._isFullScreen = False
162 if hildon is not None:
163 self._app = hildon.Program()
164 oldWindow = self._window
165 self.__window = hildon.Window()
166 oldWindow.get_child().reparent(self.__window)
167 self._app.add_window(self.__window)
168 hildon.hildon_helper_set_thumb_scrollbar(self._widgetTree.get_widget('scrollingHistory'), True)
170 gtkMenu = self._widgetTree.get_widget("mainMenubar")
172 for child in gtkMenu.get_children():
174 self.__window.set_menu(menu)
177 self.__window.connect("key-press-event", self._on_key_press)
178 self.__window.connect("window-state-event", self._on_window_state_change)
180 pass # warnings.warn("No Hildon", UserWarning, 2)
182 self.__errorDisplay = gtk_toolbox.ErrorDisplay(self._widgetTree)
183 self.__userEntry = ValueEntry(self._widgetTree.get_widget("entryView"))
184 self.__stackView = self._widgetTree.get_widget("historyView")
186 self.__historyStore = gtkhistory.GtkCalcHistory(self.__stackView)
187 self.__history = history.RpnCalcHistory(
189 self.__userEntry, self.__errorDisplay,
190 self.__constantPlugins.constants, self.__operatorPlugins.operators
192 self.__load_history()
194 self.__sliceStyle = gtkpie.generate_pie_style(gtk.Button())
195 self.__handler = gtkpieboard.KeyboardHandler(self._on_entry_direct)
196 self.__handler.register_command_handler("push", self._on_push)
197 self.__handler.register_command_handler("unpush", self._on_unpush)
198 self.__handler.register_command_handler("backspace", self._on_entry_backspace)
199 self.__handler.register_command_handler("clear", self._on_entry_clear)
201 builtinKeyboardId = self.__keyboardPlugins.lookup_plugin("Builtin")
202 self.__keyboardPlugins.enable_plugin(builtinKeyboardId)
203 self.__builtinPlugin = self.__keyboardPlugins.keyboards["Builtin"].construct_keyboard()
204 self.__builtinKeyboard = self.__builtinPlugin.setup(self.__history, self.__sliceStyle, self.__handler)
205 self._widgetTree.get_widget("functionLayout").pack_start(self.__builtinKeyboard)
206 self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Trigonometry"))
207 self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Computer"))
208 self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Alphabet"))
211 "on_calculator_quit": self._on_close,
212 "on_paste": self._on_paste,
213 "on_clear_history": self._on_clear_all,
214 "on_about": self._on_about_activate,
216 self._widgetTree.signal_autoconnect(callbackMapping)
217 self._widgetTree.get_widget("copyMenuItem").connect("activate", self._on_copy)
218 self._widgetTree.get_widget("copyEquationMenuItem").connect("activate", self._on_copy_equation)
222 self.__window.set_title("%s" % self.__pretty_app_name__)
223 self.__window.connect("destroy", self._on_close)
224 self.__window.show_all()
233 self._osso = osso.Context(Calculator.__app_name__, Calculator.__version__, False)
234 device = osso.DeviceState(self._osso)
235 device.set_device_state_callback(self._on_device_state_change, 0)
237 pass # warnings.warn("No OSSO", UserWarning, 2)
239 def display_error_message(self, msg):
240 error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
242 def close(dialog, response, editor):
243 editor.about_dialog = None
245 error_dialog.connect("response", close, self)
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 pluginKeyboard = plugin.setup(self.__history, self.__sliceStyle, self.__handler)
255 keyboardTabs = self._widgetTree.get_widget("pluginKeyboards")
256 keyboardTabs.append_page(pluginKeyboard, gtk.Label(pluginName))
257 keyboardPageNum = keyboardTabs.page_num(pluginKeyboard)
258 assert keyboardPageNum not in self.__activeKeyboards
259 self.__activeKeyboards[keyboardPageNum] = {
260 "pluginName": pluginName,
262 "pluginKeyboard": pluginKeyboard,
265 def __load_history(self):
268 with open(self._user_history, "rU") as f:
270 (part.strip() for part in line.split(" "))
271 for line in f.readlines()
276 self.__history.deserialize_stack(serialized)
278 def __save_history(self):
279 serialized = self.__history.serialize_stack()
280 with open(self._user_history, "w") as f:
281 for lineData in serialized:
282 line = " ".join(data for data in lineData)
283 f.write("%s\n" % line)
285 def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
287 For system_inactivity, we have no background tasks to pause
289 @note Hildon specific
294 if save_unsaved_data or shutdown:
295 self.__save_history()
297 def _on_window_state_change(self, widget, event, *args):
299 @note Hildon specific
301 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
302 self._isFullScreen = True
304 self._isFullScreen = False
306 def _on_close(self, *args, **kwds):
307 if self._osso is not None:
311 self.__save_history()
315 def _on_copy(self, *args):
317 equationNode = self.__history.history.peek()
318 result = str(equationNode.evaluate())
319 self._clipboard.set_text(result)
320 except StandardError, e:
321 self.__errorDisplay.push_exception()
323 def _on_copy_equation(self, *args):
325 equationNode = self.__history.history.peek()
326 equation = str(equationNode)
327 self._clipboard.set_text(equation)
328 except StandardError, e:
329 self.__errorDisplay.push_exception()
331 def _on_paste(self, *args):
332 contents = self._clipboard.wait_for_text()
333 self.__userEntry.append(contents)
335 def _on_key_press(self, widget, event, *args):
337 @note Hildon specific
339 if event.keyval == gtk.keysyms.F6:
340 if self._isFullScreen:
341 self.__window.unfullscreen()
343 self.__window.fullscreen()
345 def _on_push(self, *args):
346 self.__history.push_entry()
348 def _on_unpush(self, *args):
349 self.__historyStore.unpush()
351 def _on_entry_direct(self, keys, modifiers):
352 if "shift" in modifiers:
354 self.__userEntry.append(keys)
356 def _on_entry_backspace(self, *args):
357 self.__userEntry.pop()
359 def _on_entry_clear(self, *args):
360 self.__userEntry.clear()
362 def _on_clear_all(self, *args):
363 self.__history.clear()
365 def _on_about_activate(self, *args):
366 dlg = gtk.AboutDialog()
367 dlg.set_name(self.__pretty_app_name__)
368 dlg.set_version(self.__version__)
369 dlg.set_copyright("Copyright 2008 - LGPL")
371 ejpi A Touch Screen Optimized RPN Calculator for Maemo and Linux.
374 The buttons are all pie-menus. Clicking on them will give you the default (center) behavior. If you click and hold, the menu gets displayed showing what other actions you can then perform. While still holding, just drag in the direction of one of these actions.
376 This is RPN, where are the swap, roll, etc operations?
377 This also uses a touch screen, go ahead and feel adventerous by dragging the stack items around.
379 dlg.set_website("http://ejpi.garage.maemo.org")
380 dlg.set_authors(["Ed Page"])
388 failureCount, testCount = doctest.testmod()
390 print "Tests Successful"
396 def run_calculator():
397 gtk.gdk.threads_init()
399 gtkpie.IMAGES.add_path(os.path.join(os.path.dirname(__file__), "libraries/images"), )
400 if hildon is not None:
401 gtk.set_application_name(Calculator.__pretty_app_name__)
402 handle = Calculator()
406 class DummyOptions(object):
412 if __name__ == "__main__":
413 if len(sys.argv) > 1:
419 if optparse is not None:
420 parser = optparse.OptionParser()
421 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
422 (commandOptions, commandArgs) = parser.parse_args()
424 commandOptions = DummyOptions()
427 if commandOptions.test: