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?
16 Some useful things on Maemo
17 @li http://maemo.org/api_refs/4.1/libosso-2.16-1/group__Statesave.html
18 @li http://maemo.org/api_refs/4.1/libosso-2.16-1/group__Autosave.html
22 from __future__ import with_statement
37 from libraries import gtkpie
38 from libraries import gtkpieboard
46 _moduleLogger = logging.getLogger("ejpi_glade")
48 PLUGIN_SEARCH_PATHS = [
49 os.path.join(os.path.dirname(__file__), "plugins/"),
52 PROFILE_STARTUP = False
55 class ValueEntry(object):
57 def __init__(self, widget):
58 self.__widget = widget
59 self.__actualEntryDisplay = ""
62 value = self.__actualEntryDisplay.strip()
64 0 < value.find(whitespace)
65 for whitespace in string.whitespace
68 raise ValueError('Invalid input "%s"' % value)
71 def set_value(self, value):
74 0 < value.find(whitespace)
75 for whitespace in string.whitespace
77 raise ValueError('Invalid input "%s"' % value)
78 self.__actualEntryDisplay = value
79 self.__widget.set_text(value)
81 def append(self, value):
84 0 < value.find(whitespace)
85 for whitespace in string.whitespace
87 raise ValueError('Invalid input "%s"' % value)
88 self.set_value(self.get_value() + value)
91 value = self.get_value()[0:-1]
97 value = property(get_value, set_value, clear)
100 class Calculator(object):
103 '/usr/lib/ejpi/ejpi.glade',
104 os.path.join(os.path.dirname(__file__), "ejpi.glade"),
105 os.path.join(os.path.dirname(__file__), "../lib/ejpi.glade"),
108 _plugin_search_paths = [
109 "/usr/lib/ejpi/plugins/",
110 os.path.join(os.path.dirname(__file__), "plugins/"),
113 _user_data = constants._data_path_
114 _user_settings = "%s/settings.ini" % _user_data
115 _user_history = "%s/history.stack" % _user_data
117 MIN_BUTTON_SIZE = min(800, 480) // 6 - 20
120 self.__constantPlugins = plugin_utils.ConstantPluginManager()
121 self.__constantPlugins.add_path(*self._plugin_search_paths)
122 for pluginName in ["Builtin", "Trigonometry", "Computer", "Alphabet"]:
124 pluginId = self.__constantPlugins.lookup_plugin(pluginName)
125 self.__constantPlugins.enable_plugin(pluginId)
127 warnings.warn("Failed to load plugin %s" % pluginName)
129 self.__operatorPlugins = plugin_utils.OperatorPluginManager()
130 self.__operatorPlugins.add_path(*self._plugin_search_paths)
131 for pluginName in ["Builtin", "Trigonometry", "Computer", "Alphabet"]:
133 pluginId = self.__operatorPlugins.lookup_plugin(pluginName)
134 self.__operatorPlugins.enable_plugin(pluginId)
136 warnings.warn("Failed to load plugin %s" % pluginName)
138 self.__keyboardPlugins = plugin_utils.KeyboardPluginManager()
139 self.__keyboardPlugins.add_path(*self._plugin_search_paths)
140 self.__activeKeyboards = []
142 for path in self._glade_files:
143 if os.path.isfile(path):
144 self._widgetTree = gtk.glade.XML(path)
147 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")
160 self._isFullScreen = False
161 self._app = hildonize.get_app_class()()
162 self._window = hildonize.hildonize_window(self._app, self._window)
164 menu = hildonize.hildonize_menu(
166 self._widgetTree.get_widget("mainMenubar"),
170 for scrollingWidgetName in (
173 scrollingWidget = self._widgetTree.get_widget(scrollingWidgetName)
174 assert scrollingWidget is not None, scrollingWidgetName
175 hildonize.hildonize_scrollwindow_with_viewport(scrollingWidget)
177 self.__errorDisplay = gtk_toolbox.ErrorDisplay(self._widgetTree)
178 self.__userEntry = ValueEntry(self._widgetTree.get_widget("entryView"))
179 self.__stackView = self._widgetTree.get_widget("historyView")
180 self.__pluginButton = self._widgetTree.get_widget("keyboardSelectionButton")
182 self.__historyStore = gtkhistory.GtkCalcHistory(self.__stackView)
183 self.__history = history.RpnCalcHistory(
185 self.__userEntry, self.__errorDisplay,
186 self.__constantPlugins.constants, self.__operatorPlugins.operators
188 self.__load_history()
190 # Basic keyboard stuff
191 self.__sliceStyle = gtkpie.generate_pie_style(gtk.Button())
192 self.__handler = gtkpieboard.KeyboardHandler(self._on_entry_direct)
193 self.__handler.register_command_handler("push", self._on_push)
194 self.__handler.register_command_handler("unpush", self._on_unpush)
195 self.__handler.register_command_handler("backspace", self._on_entry_backspace)
196 self.__handler.register_command_handler("clear", self._on_entry_clear)
199 builtinKeyboardId = self.__keyboardPlugins.lookup_plugin("Builtin")
200 self.__keyboardPlugins.enable_plugin(builtinKeyboardId)
201 self.__builtinPlugin = self.__keyboardPlugins.keyboards["Builtin"].construct_keyboard()
202 self.__builtinKeyboard = self.__builtinPlugin.setup(self.__history, self.__sliceStyle, self.__handler)
203 self._widgetTree.get_widget("mainKeyboard").pack_start(self.__builtinKeyboard)
204 for child in self.__builtinKeyboard.get_children():
205 child.set_size_request(self.MIN_BUTTON_SIZE, self.MIN_BUTTON_SIZE)
208 self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Trigonometry"))
209 self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Computer"))
210 self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Alphabet"))
211 self._set_plugin_kb(0)
214 if not hildonize.IS_FREMANTLE_SUPPORTED:
215 # Menus aren't used in the Fremantle version
217 "on_calculator_quit": self._on_close,
218 "on_paste": self._on_paste,
219 "on_clear_history": self._on_clear_all,
220 "on_about": self._on_about_activate,
222 self._widgetTree.signal_autoconnect(callbackMapping)
223 self._widgetTree.get_widget("copyMenuItem").connect("activate", self._on_copy)
224 self._widgetTree.get_widget("copyEquationMenuItem").connect("activate", self._on_copy_equation)
225 self._window.connect("key-press-event", self._on_key_press)
226 self._window.connect("window-state-event", self._on_window_state_change)
227 self._widgetTree.get_widget("entryView").connect("activate", self._on_push)
228 self.__pluginButton.connect("clicked", self._on_kb_plugin_selection_button)
230 hildonize.set_application_title(self._window, "%s" % constants.__pretty_app_name__)
231 self._window.connect("destroy", self._on_close)
232 self._window.show_all()
234 if not hildonize.IS_HILDON_SUPPORTED:
235 _moduleLogger.warning("No hildonization support")
243 self._osso = osso.Context(constants.__app_name__, constants.__version__, False)
244 device = osso.DeviceState(self._osso)
245 device.set_device_state_callback(self._on_device_state_change, 0)
247 _moduleLogger.warning("No OSSO support")
249 def display_error_message(self, msg):
250 error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
252 def close(dialog, response, editor):
253 editor.about_dialog = None
255 error_dialog.connect("response", close, self)
258 def enable_plugin(self, pluginId):
259 self.__keyboardPlugins.enable_plugin(pluginId)
260 pluginData = self.__keyboardPlugins.plugin_info(pluginId)
261 pluginName = pluginData[0]
262 plugin = self.__keyboardPlugins.keyboards[pluginName].construct_keyboard()
263 pluginKeyboard = plugin.setup(self.__history, self.__sliceStyle, self.__handler)
264 for child in pluginKeyboard.get_children():
265 child.set_size_request(self.MIN_BUTTON_SIZE, self.MIN_BUTTON_SIZE)
267 self.__activeKeyboards.append({
268 "pluginName": pluginName,
270 "pluginKeyboard": pluginKeyboard,
273 def _on_kb_plugin_selection_button(self, *args):
275 pluginNames = [plugin["pluginName"] for plugin in self.__activeKeyboards]
276 oldIndex = pluginNames.index(self.__pluginButton.get_label())
277 newIndex = hildonize.touch_selector(self._window, "Keyboards", pluginNames, oldIndex)
278 self._set_plugin_kb(newIndex)
280 self.__errorDisplay.push_exception()
282 def _set_plugin_kb(self, pluginIndex):
283 plugin = self.__activeKeyboards[pluginIndex]
284 self.__pluginButton.set_label(plugin["pluginName"])
286 pluginParent = self._widgetTree.get_widget("pluginKeyboard")
287 oldPluginChildren = pluginParent.get_children()
288 if oldPluginChildren:
289 assert len(oldPluginChildren) == 1, "%r" % (oldPluginChildren, )
290 pluginParent.remove(oldPluginChildren[0])
291 oldPluginChildren[0].hide()
292 pluginKeyboard = plugin["pluginKeyboard"]
293 pluginParent.pack_start(pluginKeyboard)
295 pluginKeyboard.show_all()
297 def __load_history(self):
300 with open(self._user_history, "rU") as f:
302 (part.strip() for part in line.split(" "))
303 for line in f.readlines()
308 self.__history.deserialize_stack(serialized)
310 def __save_history(self):
311 serialized = self.__history.serialize_stack()
312 with open(self._user_history, "w") as f:
313 for lineData in serialized:
314 line = " ".join(data for data in lineData)
315 f.write("%s\n" % line)
317 def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
319 For system_inactivity, we have no background tasks to pause
321 @note Hildon specific
327 if save_unsaved_data or shutdown:
328 self.__save_history()
330 self.__errorDisplay.push_exception()
332 def _on_window_state_change(self, widget, event, *args):
334 @note Hildon specific
337 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
338 self._isFullScreen = True
340 self._isFullScreen = False
342 self.__errorDisplay.push_exception()
344 def _on_close(self, *args, **kwds):
346 if self._osso is not None:
350 self.__save_history()
354 self.__errorDisplay.push_exception()
356 def _on_copy(self, *args):
358 equationNode = self.__history.history.peek()
359 result = str(equationNode.evaluate())
360 self._clipboard.set_text(result)
362 self.__errorDisplay.push_exception()
364 def _on_copy_equation(self, *args):
366 equationNode = self.__history.history.peek()
367 equation = str(equationNode)
368 self._clipboard.set_text(equation)
370 self.__errorDisplay.push_exception()
372 def _on_paste(self, *args):
374 contents = self._clipboard.wait_for_text()
375 self.__userEntry.append(contents)
377 self.__errorDisplay.push_exception()
379 def _on_key_press(self, widget, event, *args):
381 @note Hildon specific
384 RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter)
386 event.keyval == gtk.keysyms.F6 or
387 event.keyval in RETURN_TYPES and event.get_state() & gtk.gdk.CONTROL_MASK
389 if self._isFullScreen:
390 self._window.unfullscreen()
392 self._window.fullscreen()
394 self.__errorDisplay.push_exception()
396 def _on_push(self, *args):
398 self.__history.push_entry()
400 self.__errorDisplay.push_exception()
402 def _on_unpush(self, *args):
404 self.__historyStore.unpush()
406 self.__errorDisplay.push_exception()
408 def _on_entry_direct(self, keys, modifiers):
410 if "shift" in modifiers:
412 self.__userEntry.append(keys)
414 self.__errorDisplay.push_exception()
416 def _on_entry_backspace(self, *args):
418 self.__userEntry.pop()
420 self.__errorDisplay.push_exception()
422 def _on_entry_clear(self, *args):
424 self.__userEntry.clear()
426 self.__errorDisplay.push_exception()
428 def _on_clear_all(self, *args):
430 self.__history.clear()
432 self.__errorDisplay.push_exception()
434 def _on_about_activate(self, *args):
435 dlg = gtk.AboutDialog()
436 dlg.set_name(constants.__pretty_app_name__)
437 dlg.set_version(constants.__version__)
438 dlg.set_copyright("Copyright 2008 - LGPL")
440 ejpi A Touch Screen Optimized RPN Calculator for Maemo and Linux.
442 RPN: Stack based math, its fun
443 Buttons: Try both pressing and hold/drag
444 History: Try dragging things around, deleting them, etc
446 dlg.set_website("http://ejpi.garage.maemo.org")
447 dlg.set_authors(["Ed Page"])
455 failureCount, testCount = doctest.testmod()
457 print "Tests Successful"
463 def run_calculator():
464 gtk.gdk.threads_init()
466 gtkpie.IMAGES.add_path(os.path.join(os.path.dirname(__file__), "libraries/images"), )
467 handle = Calculator()
468 if not PROFILE_STARTUP:
472 class DummyOptions(object):
478 if __name__ == "__main__":
479 logging.basicConfig(level=logging.DEBUG)
480 if len(sys.argv) > 1:
486 if optparse is not None:
487 parser = optparse.OptionParser()
488 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
489 (commandOptions, commandArgs) = parser.parse_args()
491 commandOptions = DummyOptions()
494 if commandOptions.test: