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._widgetTree.get_widget("functionLayout").reorder_child(self.__builtinKeyboard, 0)
207 self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Trigonometry"))
208 self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Computer"))
209 self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Alphabet"))
212 "on_calculator_quit": self._on_close,
213 "on_paste": self._on_paste,
214 "on_clear_history": self._on_clear_all,
215 "on_about": self._on_about_activate,
217 self._widgetTree.signal_autoconnect(callbackMapping)
218 self._widgetTree.get_widget("copyMenuItem").connect("activate", self._on_copy)
219 self._widgetTree.get_widget("copyEquationMenuItem").connect("activate", self._on_copy_equation)
223 self.__window.set_title("%s" % self.__pretty_app_name__)
224 self.__window.connect("destroy", self._on_close)
225 self.__window.show_all()
234 self._osso = osso.Context(Calculator.__app_name__, Calculator.__version__, False)
235 device = osso.DeviceState(self._osso)
236 device.set_device_state_callback(self._on_device_state_change, 0)
238 pass # warnings.warn("No OSSO", UserWarning, 2)
240 def display_error_message(self, msg):
241 error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
243 def close(dialog, response, editor):
244 editor.about_dialog = None
246 error_dialog.connect("response", close, self)
249 def enable_plugin(self, pluginId):
250 self.__keyboardPlugins.enable_plugin(pluginId)
251 pluginData = self.__keyboardPlugins.plugin_info(pluginId)
252 pluginName = pluginData[0]
253 plugin = self.__keyboardPlugins.keyboards[pluginName].construct_keyboard()
254 pluginKeyboard = plugin.setup(self.__history, self.__sliceStyle, self.__handler)
256 keyboardTabs = self._widgetTree.get_widget("pluginKeyboards")
257 keyboardTabs.append_page(pluginKeyboard, gtk.Label(pluginName))
258 keyboardPageNum = keyboardTabs.page_num(pluginKeyboard)
259 assert keyboardPageNum not in self.__activeKeyboards
260 self.__activeKeyboards[keyboardPageNum] = {
261 "pluginName": pluginName,
263 "pluginKeyboard": pluginKeyboard,
266 def __load_history(self):
269 with open(self._user_history, "rU") as f:
271 (part.strip() for part in line.split(" "))
272 for line in f.readlines()
277 self.__history.deserialize_stack(serialized)
279 def __save_history(self):
280 serialized = self.__history.serialize_stack()
281 with open(self._user_history, "w") as f:
282 for lineData in serialized:
283 line = " ".join(data for data in lineData)
284 f.write("%s\n" % line)
286 def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
288 For system_inactivity, we have no background tasks to pause
290 @note Hildon specific
295 if save_unsaved_data or shutdown:
296 self.__save_history()
298 def _on_window_state_change(self, widget, event, *args):
300 @note Hildon specific
302 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
303 self._isFullScreen = True
305 self._isFullScreen = False
307 def _on_close(self, *args, **kwds):
308 if self._osso is not None:
312 self.__save_history()
316 def _on_copy(self, *args):
318 equationNode = self.__history.history.peek()
319 result = str(equationNode.evaluate())
320 self._clipboard.set_text(result)
321 except StandardError, e:
322 self.__errorDisplay.push_exception()
324 def _on_copy_equation(self, *args):
326 equationNode = self.__history.history.peek()
327 equation = str(equationNode)
328 self._clipboard.set_text(equation)
329 except StandardError, e:
330 self.__errorDisplay.push_exception()
332 def _on_paste(self, *args):
333 contents = self._clipboard.wait_for_text()
334 self.__userEntry.append(contents)
336 def _on_key_press(self, widget, event, *args):
338 @note Hildon specific
340 if event.keyval == gtk.keysyms.F6:
341 if self._isFullScreen:
342 self.__window.unfullscreen()
344 self.__window.fullscreen()
346 def _on_push(self, *args):
347 self.__history.push_entry()
349 def _on_unpush(self, *args):
350 self.__historyStore.unpush()
352 def _on_entry_direct(self, keys, modifiers):
353 if "shift" in modifiers:
355 self.__userEntry.append(keys)
357 def _on_entry_backspace(self, *args):
358 self.__userEntry.pop()
360 def _on_entry_clear(self, *args):
361 self.__userEntry.clear()
363 def _on_clear_all(self, *args):
364 self.__history.clear()
366 def _on_about_activate(self, *args):
367 dlg = gtk.AboutDialog()
368 dlg.set_name(self.__pretty_app_name__)
369 dlg.set_version(self.__version__)
370 dlg.set_copyright("Copyright 2008 - LGPL")
372 ejpi A Touch Screen Optimized RPN Calculator for Maemo and Linux.
375 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.
377 This is RPN, where are the swap, roll, etc operations?
378 This also uses a touch screen, go ahead and feel adventerous by dragging the stack items around.
380 dlg.set_website("http://ejpi.garage.maemo.org")
381 dlg.set_authors(["Ed Page"])
389 failureCount, testCount = doctest.testmod()
391 print "Tests Successful"
397 def run_calculator():
398 gtk.gdk.threads_init()
400 gtkpie.IMAGES.add_path(os.path.join(os.path.dirname(__file__), "libraries/images"), )
401 if hildon is not None:
402 gtk.set_application_name(Calculator.__pretty_app_name__)
403 handle = Calculator()
407 class DummyOptions(object):
413 if __name__ == "__main__":
414 if len(sys.argv) > 1:
420 if optparse is not None:
421 parser = optparse.OptionParser()
422 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
423 (commandOptions, commandArgs) = parser.parse_args()
425 commandOptions = DummyOptions()
428 if commandOptions.test: