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
38 from libraries import gtkpie
39 from libraries import gtkpieboard
47 _moduleLogger = logging.getLogger("ejpi_glade")
49 PLUGIN_SEARCH_PATHS = [
50 os.path.join(os.path.dirname(__file__), "plugins/"),
54 class ValueEntry(object):
56 def __init__(self, widget):
57 self.__widget = widget
58 self.__actualEntryDisplay = ""
61 value = self.__actualEntryDisplay.strip()
63 0 < value.find(whitespace)
64 for whitespace in string.whitespace
67 raise ValueError('Invalid input "%s"' % value)
70 def set_value(self, value):
73 0 < value.find(whitespace)
74 for whitespace in string.whitespace
76 raise ValueError('Invalid input "%s"' % value)
77 self.__actualEntryDisplay = value
78 self.__widget.set_text(value)
80 def append(self, value):
83 0 < value.find(whitespace)
84 for whitespace in string.whitespace
86 raise ValueError('Invalid input "%s"' % value)
87 self.set_value(self.get_value() + value)
90 value = self.get_value()[0:-1]
96 value = property(get_value, set_value, clear)
99 class Calculator(object):
102 '/usr/lib/ejpi/ejpi.glade',
103 os.path.join(os.path.dirname(__file__), "ejpi.glade"),
104 os.path.join(os.path.dirname(__file__), "../lib/ejpi.glade"),
107 _plugin_search_paths = [
108 "/usr/lib/ejpi/plugins/",
109 os.path.join(os.path.dirname(__file__), "plugins/"),
112 _user_data = os.path.expanduser("~/.%s/" % constants.__app_name__)
113 _user_settings = "%s/settings.ini" % _user_data
114 _user_history = "%s/history.stack" % _user_data
117 self.__constantPlugins = plugin_utils.ConstantPluginManager()
118 self.__constantPlugins.add_path(*self._plugin_search_paths)
119 for pluginName in ["Builtin", "Trigonometry", "Computer", "Alphabet"]:
121 pluginId = self.__constantPlugins.lookup_plugin(pluginName)
122 self.__constantPlugins.enable_plugin(pluginId)
124 warnings.warn("Failed to load plugin %s" % pluginName)
126 self.__operatorPlugins = plugin_utils.OperatorPluginManager()
127 self.__operatorPlugins.add_path(*self._plugin_search_paths)
128 for pluginName in ["Builtin", "Trigonometry", "Computer", "Alphabet"]:
130 pluginId = self.__operatorPlugins.lookup_plugin(pluginName)
131 self.__operatorPlugins.enable_plugin(pluginId)
133 warnings.warn("Failed to load plugin %s" % pluginName)
135 self.__keyboardPlugins = plugin_utils.KeyboardPluginManager()
136 self.__keyboardPlugins.add_path(*self._plugin_search_paths)
137 self.__activeKeyboards = {}
139 for path in self._glade_files:
140 if os.path.isfile(path):
141 self._widgetTree = gtk.glade.XML(path)
144 self.display_error_message("Cannot find ejpi.glade")
148 os.makedirs(self._user_data)
153 self._clipboard = gtk.clipboard_get()
154 self._window = self._widgetTree.get_widget("mainWindow")
157 self._isFullScreen = False
158 self._app = hildonize.get_app_class()()
159 self._window = hildonize.hildonize_window(self._app, self._window)
161 menu = hildonize.hildonize_menu(
163 self._widgetTree.get_widget("mainMenubar"),
167 for scrollingWidgetName in (
170 scrollingWidget = self._widgetTree.get_widget(scrollingWidgetName)
171 assert scrollingWidget is not None, scrollingWidgetName
172 hildonize.hildonize_scrollwindow_with_viewport(scrollingWidget)
174 self.__errorDisplay = gtk_toolbox.ErrorDisplay(self._widgetTree)
175 self.__userEntry = ValueEntry(self._widgetTree.get_widget("entryView"))
176 self.__stackView = self._widgetTree.get_widget("historyView")
178 self.__historyStore = gtkhistory.GtkCalcHistory(self.__stackView)
179 self.__history = history.RpnCalcHistory(
181 self.__userEntry, self.__errorDisplay,
182 self.__constantPlugins.constants, self.__operatorPlugins.operators
184 self.__load_history()
186 self.__sliceStyle = gtkpie.generate_pie_style(gtk.Button())
187 self.__handler = gtkpieboard.KeyboardHandler(self._on_entry_direct)
188 self.__handler.register_command_handler("push", self._on_push)
189 self.__handler.register_command_handler("unpush", self._on_unpush)
190 self.__handler.register_command_handler("backspace", self._on_entry_backspace)
191 self.__handler.register_command_handler("clear", self._on_entry_clear)
193 builtinKeyboardId = self.__keyboardPlugins.lookup_plugin("Builtin")
194 self.__keyboardPlugins.enable_plugin(builtinKeyboardId)
195 self.__builtinPlugin = self.__keyboardPlugins.keyboards["Builtin"].construct_keyboard()
196 self.__builtinKeyboard = self.__builtinPlugin.setup(self.__history, self.__sliceStyle, self.__handler)
197 self._widgetTree.get_widget("functionLayout").pack_start(self.__builtinKeyboard)
198 self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Trigonometry"))
199 self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Computer"))
200 self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Alphabet"))
203 "on_calculator_quit": self._on_close,
204 "on_paste": self._on_paste,
205 "on_clear_history": self._on_clear_all,
206 "on_about": self._on_about_activate,
208 self._widgetTree.signal_autoconnect(callbackMapping)
209 self._widgetTree.get_widget("copyMenuItem").connect("activate", self._on_copy)
210 self._widgetTree.get_widget("copyEquationMenuItem").connect("activate", self._on_copy_equation)
211 self._window.connect("key-press-event", self._on_key_press)
212 self._window.connect("window-state-event", self._on_window_state_change)
213 self._widgetTree.get_widget("entryView").connect("activate", self._on_push)
215 hildonize.set_application_title(self._window, "%s" % constants.__pretty_app_name__)
216 self._window.connect("destroy", self._on_close)
217 self._window.show_all()
219 if not hildonize.IS_HILDON_SUPPORTED:
220 _moduleLogger.warning("No hildonization support")
228 self._osso = osso.Context(constants.__app_name__, constants.__version__, False)
229 device = osso.DeviceState(self._osso)
230 device.set_device_state_callback(self._on_device_state_change, 0)
232 _moduleLogger.warning("No OSSO support")
234 def display_error_message(self, msg):
235 error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
237 def close(dialog, response, editor):
238 editor.about_dialog = None
240 error_dialog.connect("response", close, self)
243 def enable_plugin(self, pluginId):
244 self.__keyboardPlugins.enable_plugin(pluginId)
245 pluginData = self.__keyboardPlugins.plugin_info(pluginId)
246 pluginName = pluginData[0]
247 plugin = self.__keyboardPlugins.keyboards[pluginName].construct_keyboard()
248 pluginKeyboard = plugin.setup(self.__history, self.__sliceStyle, self.__handler)
250 keyboardTabs = self._widgetTree.get_widget("pluginKeyboards")
251 keyboardTabs.append_page(pluginKeyboard, gtk.Label(pluginName))
252 keyboardPageNum = keyboardTabs.page_num(pluginKeyboard)
253 assert keyboardPageNum not in self.__activeKeyboards
254 self.__activeKeyboards[keyboardPageNum] = {
255 "pluginName": pluginName,
257 "pluginKeyboard": pluginKeyboard,
260 def __load_history(self):
263 with open(self._user_history, "rU") as f:
265 (part.strip() for part in line.split(" "))
266 for line in f.readlines()
271 self.__history.deserialize_stack(serialized)
273 def __save_history(self):
274 serialized = self.__history.serialize_stack()
275 with open(self._user_history, "w") as f:
276 for lineData in serialized:
277 line = " ".join(data for data in lineData)
278 f.write("%s\n" % line)
280 def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
282 For system_inactivity, we have no background tasks to pause
284 @note Hildon specific
289 if save_unsaved_data or shutdown:
290 self.__save_history()
292 def _on_window_state_change(self, widget, event, *args):
294 @note Hildon specific
296 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
297 self._isFullScreen = True
299 self._isFullScreen = False
301 def _on_close(self, *args, **kwds):
302 if self._osso is not None:
306 self.__save_history()
310 def _on_copy(self, *args):
312 equationNode = self.__history.history.peek()
313 result = str(equationNode.evaluate())
314 self._clipboard.set_text(result)
315 except StandardError, e:
316 self.__errorDisplay.push_exception()
318 def _on_copy_equation(self, *args):
320 equationNode = self.__history.history.peek()
321 equation = str(equationNode)
322 self._clipboard.set_text(equation)
323 except StandardError, e:
324 self.__errorDisplay.push_exception()
326 def _on_paste(self, *args):
327 contents = self._clipboard.wait_for_text()
328 self.__userEntry.append(contents)
330 def _on_key_press(self, widget, event, *args):
332 @note Hildon specific
334 RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter)
336 event.keyval == gtk.keysyms.F6 or
337 event.keyval in RETURN_TYPES and event.get_state() & gtk.gdk.CONTROL_MASK
339 if self._isFullScreen:
340 self._window.unfullscreen()
342 self._window.fullscreen()
344 def _on_push(self, *args):
345 self.__history.push_entry()
347 def _on_unpush(self, *args):
348 self.__historyStore.unpush()
350 def _on_entry_direct(self, keys, modifiers):
351 if "shift" in modifiers:
353 self.__userEntry.append(keys)
355 def _on_entry_backspace(self, *args):
356 self.__userEntry.pop()
358 def _on_entry_clear(self, *args):
359 self.__userEntry.clear()
361 def _on_clear_all(self, *args):
362 self.__history.clear()
364 def _on_about_activate(self, *args):
365 dlg = gtk.AboutDialog()
366 dlg.set_name(constants.__pretty_app_name__)
367 dlg.set_version(constants.__version__)
368 dlg.set_copyright("Copyright 2008 - LGPL")
370 ejpi A Touch Screen Optimized RPN Calculator for Maemo and Linux.
372 RPN: Stack based math, its fun
373 Buttons: Try both pressing and hold/drag
374 History: Try dragging things around, deleting them, etc
376 dlg.set_website("http://ejpi.garage.maemo.org")
377 dlg.set_authors(["Ed Page"])
385 failureCount, testCount = doctest.testmod()
387 print "Tests Successful"
393 def run_calculator():
394 gtk.gdk.threads_init()
396 gtkpie.IMAGES.add_path(os.path.join(os.path.dirname(__file__), "libraries/images"), )
397 handle = Calculator()
401 class DummyOptions(object):
407 if __name__ == "__main__":
408 logging.basicConfig(level=logging.DEBUG)
409 if len(sys.argv) > 1:
415 if optparse is not None:
416 parser = optparse.OptionParser()
417 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
418 (commandOptions, commandArgs) = parser.parse_args()
420 commandOptions = DummyOptions()
423 if commandOptions.test: