4 Some useful things on Maemo
5 @li http://maemo.org/api_refs/4.1/libosso-2.16-1/group__Statesave.html
6 @li http://maemo.org/api_refs/4.1/libosso-2.16-1/group__Autosave.html
10 from __future__ import with_statement
25 from libraries import gtkpie
26 from libraries import gtkpieboard
34 _moduleLogger = logging.getLogger("ejpi_glade")
36 PLUGIN_SEARCH_PATHS = [
37 os.path.join(os.path.dirname(__file__), "plugins/"),
40 PROFILE_STARTUP = False
43 class ValueEntry(object):
45 def __init__(self, widget):
46 self.__widget = widget
47 self.__actualEntryDisplay = ""
50 value = self.__actualEntryDisplay.strip()
52 0 < value.find(whitespace)
53 for whitespace in string.whitespace
56 raise ValueError('Invalid input "%s"' % value)
59 def set_value(self, value):
62 0 < value.find(whitespace)
63 for whitespace in string.whitespace
65 raise ValueError('Invalid input "%s"' % value)
66 self.__actualEntryDisplay = value
67 self.__widget.set_text(value)
69 def append(self, value):
72 0 < value.find(whitespace)
73 for whitespace in string.whitespace
75 raise ValueError('Invalid input "%s"' % value)
76 self.set_value(self.get_value() + value)
79 value = self.get_value()[0:-1]
85 value = property(get_value, set_value, clear)
88 class Calculator(object):
91 '/usr/lib/ejpi/ejpi.glade',
92 os.path.join(os.path.dirname(__file__), "ejpi.glade"),
93 os.path.join(os.path.dirname(__file__), "../lib/ejpi.glade"),
96 _plugin_search_paths = [
97 "/usr/lib/ejpi/plugins/",
98 os.path.join(os.path.dirname(__file__), "plugins/"),
101 _user_data = constants._data_path_
102 _user_settings = "%s/settings.ini" % _user_data
103 _user_history = "%s/history.stack" % _user_data
105 MIN_BUTTON_SIZE = min(800, 480) // 6 - 20
108 self.__constantPlugins = plugin_utils.ConstantPluginManager()
109 self.__constantPlugins.add_path(*self._plugin_search_paths)
110 for pluginName in ["Builtin", "Trigonometry", "Computer", "Alphabet"]:
112 pluginId = self.__constantPlugins.lookup_plugin(pluginName)
113 self.__constantPlugins.enable_plugin(pluginId)
115 warnings.warn("Failed to load plugin %s" % pluginName)
117 self.__operatorPlugins = plugin_utils.OperatorPluginManager()
118 self.__operatorPlugins.add_path(*self._plugin_search_paths)
119 for pluginName in ["Builtin", "Trigonometry", "Computer", "Alphabet"]:
121 pluginId = self.__operatorPlugins.lookup_plugin(pluginName)
122 self.__operatorPlugins.enable_plugin(pluginId)
124 warnings.warn("Failed to load plugin %s" % pluginName)
126 self.__keyboardPlugins = plugin_utils.KeyboardPluginManager()
127 self.__keyboardPlugins.add_path(*self._plugin_search_paths)
128 self.__activeKeyboards = []
130 for path in self._glade_files:
131 if os.path.isfile(path):
132 self._widgetTree = gtk.glade.XML(path)
135 self.display_error_message("Cannot find ejpi.glade")
139 os.makedirs(self._user_data)
144 self._clipboard = gtk.clipboard_get()
145 self._window = self._widgetTree.get_widget("mainWindow")
148 self._isFullScreen = False
149 self._app = hildonize.get_app_class()()
150 self._window = hildonize.hildonize_window(self._app, self._window)
152 menu = hildonize.hildonize_menu(
154 self._widgetTree.get_widget("mainMenubar"),
157 for scrollingWidgetName in (
160 scrollingWidget = self._widgetTree.get_widget(scrollingWidgetName)
161 assert scrollingWidget is not None, scrollingWidgetName
162 hildonize.hildonize_scrollwindow_with_viewport(scrollingWidget)
164 self.__errorDisplay = gtk_toolbox.ErrorDisplay(self._widgetTree)
165 self.__userEntry = ValueEntry(self._widgetTree.get_widget("entryView"))
166 self.__stackView = self._widgetTree.get_widget("historyView")
167 self.__pluginButton = self._widgetTree.get_widget("keyboardSelectionButton")
169 self.__historyStore = gtkhistory.GtkCalcHistory(self.__stackView)
170 self.__history = history.RpnCalcHistory(
172 self.__userEntry, self.__errorDisplay,
173 self.__constantPlugins.constants, self.__operatorPlugins.operators
175 self.__load_history()
177 # Basic keyboard stuff
178 self.__sliceStyle = gtkpie.generate_pie_style(gtk.Button())
179 self.__handler = gtkpieboard.KeyboardHandler(self._on_entry_direct)
180 self.__handler.register_command_handler("push", self._on_push)
181 self.__handler.register_command_handler("unpush", self._on_unpush)
182 self.__handler.register_command_handler("backspace", self._on_entry_backspace)
183 self.__handler.register_command_handler("clear", self._on_entry_clear)
186 builtinKeyboardId = self.__keyboardPlugins.lookup_plugin("Builtin")
187 self.__keyboardPlugins.enable_plugin(builtinKeyboardId)
188 self.__builtinPlugin = self.__keyboardPlugins.keyboards["Builtin"].construct_keyboard()
189 self.__builtinKeyboard = self.__builtinPlugin.setup(self.__history, self.__sliceStyle, self.__handler)
190 self._widgetTree.get_widget("mainKeyboard").pack_start(self.__builtinKeyboard)
191 for child in self.__builtinKeyboard.get_children():
192 child.set_size_request(self.MIN_BUTTON_SIZE, self.MIN_BUTTON_SIZE)
195 self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Trigonometry"))
196 self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Computer"))
197 self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Alphabet"))
198 self._set_plugin_kb(0)
201 if not hildonize.IS_FREMANTLE_SUPPORTED:
202 # Menus aren't used in the Fremantle version
204 "on_calculator_quit": self._on_close,
205 "on_paste": self._on_paste,
206 "on_clear_history": self._on_clear_all,
207 "on_about": self._on_about_activate,
209 self._widgetTree.signal_autoconnect(callbackMapping)
210 self._widgetTree.get_widget("copyMenuItem").connect("activate", self._on_copy)
211 self._widgetTree.get_widget("copyEquationMenuItem").connect("activate", self._on_copy_equation)
212 self._window.connect("key-press-event", self._on_key_press)
213 self._window.connect("window-state-event", self._on_window_state_change)
214 self._widgetTree.get_widget("entryView").connect("activate", self._on_push)
215 self.__pluginButton.connect("clicked", self._on_kb_plugin_selection_button)
217 hildonize.set_application_title(self._window, "%s" % constants.__pretty_app_name__)
218 self._window.connect("destroy", self._on_close)
219 self._window.show_all()
221 if not hildonize.IS_HILDON_SUPPORTED:
222 _moduleLogger.warning("No hildonization support")
229 self._deviceState = None
231 self._osso = osso.Context(constants.__app_name__, constants.__version__, False)
232 self._deviceState = osso.DeviceState(self._osso)
233 self._deviceState.set_device_state_callback(self._on_device_state_change, 0)
235 _moduleLogger.warning("No OSSO support")
237 def display_error_message(self, msg):
238 error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
240 def close(dialog, response, editor):
241 editor.about_dialog = None
243 error_dialog.connect("response", close, self)
246 def enable_plugin(self, pluginId):
247 self.__keyboardPlugins.enable_plugin(pluginId)
248 pluginData = self.__keyboardPlugins.plugin_info(pluginId)
249 pluginName = pluginData[0]
250 plugin = self.__keyboardPlugins.keyboards[pluginName].construct_keyboard()
251 pluginKeyboard = plugin.setup(self.__history, self.__sliceStyle, self.__handler)
252 for child in pluginKeyboard.get_children():
253 child.set_size_request(self.MIN_BUTTON_SIZE, self.MIN_BUTTON_SIZE)
255 self.__activeKeyboards.append({
256 "pluginName": pluginName,
258 "pluginKeyboard": pluginKeyboard,
261 @gtk_toolbox.log_exception(_moduleLogger)
262 def _on_kb_plugin_selection_button(self, *args):
263 pluginNames = [plugin["pluginName"] for plugin in self.__activeKeyboards]
264 oldIndex = pluginNames.index(self.__pluginButton.get_label())
265 newIndex = hildonize.touch_selector(self._window, "Keyboards", pluginNames, oldIndex)
266 self._set_plugin_kb(newIndex)
268 def _set_plugin_kb(self, pluginIndex):
269 plugin = self.__activeKeyboards[pluginIndex]
270 self.__pluginButton.set_label(plugin["pluginName"])
272 pluginParent = self._widgetTree.get_widget("pluginKeyboard")
273 oldPluginChildren = pluginParent.get_children()
274 if oldPluginChildren:
275 assert len(oldPluginChildren) == 1, "%r" % (oldPluginChildren, )
276 pluginParent.remove(oldPluginChildren[0])
277 oldPluginChildren[0].hide()
278 pluginKeyboard = plugin["pluginKeyboard"]
279 pluginParent.pack_start(pluginKeyboard)
281 pluginKeyboard.show_all()
283 def __load_history(self):
286 with open(self._user_history, "rU") as f:
288 (part.strip() for part in line.split(" "))
289 for line in f.readlines()
294 self.__history.deserialize_stack(serialized)
296 def __save_history(self):
297 serialized = self.__history.serialize_stack()
298 with open(self._user_history, "w") as f:
299 for lineData in serialized:
300 line = " ".join(data for data in lineData)
301 f.write("%s\n" % line)
303 @gtk_toolbox.log_exception(_moduleLogger)
304 def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
306 For system_inactivity, we have no background tasks to pause
308 @note Hildon specific
313 if save_unsaved_data or shutdown:
314 self.__save_history()
316 @gtk_toolbox.log_exception(_moduleLogger)
317 def _on_window_state_change(self, widget, event, *args):
318 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
319 self._isFullScreen = True
321 self._isFullScreen = False
323 @gtk_toolbox.log_exception(_moduleLogger)
324 def _on_close(self, *args, **kwds):
326 self.__save_history()
329 self._deviceState.close()
330 except AttributeError:
331 pass # Either None or close was removed (in Fremantle)
334 except AttributeError:
335 pass # Either None or close was removed (in Fremantle)
339 @gtk_toolbox.log_exception(_moduleLogger)
340 def _on_copy(self, *args):
341 equationNode = self.__history.history.peek()
342 result = str(equationNode.evaluate())
343 self._clipboard.set_text(result)
345 @gtk_toolbox.log_exception(_moduleLogger)
346 def _on_copy_equation(self, *args):
347 equationNode = self.__history.history.peek()
348 equation = str(equationNode)
349 self._clipboard.set_text(equation)
351 @gtk_toolbox.log_exception(_moduleLogger)
352 def _on_paste(self, *args):
353 contents = self._clipboard.wait_for_text()
354 self.__userEntry.append(contents)
356 @gtk_toolbox.log_exception(_moduleLogger)
357 def _on_key_press(self, widget, event, *args):
358 RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter)
360 event.keyval == gtk.keysyms.F6 or
361 event.keyval in RETURN_TYPES and event.get_state() & gtk.gdk.CONTROL_MASK
363 if self._isFullScreen:
364 self._window.unfullscreen()
366 self._window.fullscreen()
367 elif event.keyval == ord("c") and event.get_state() & gtk.gdk.CONTROL_MASK:
368 equationNode = self.__history.history.peek()
369 result = str(equationNode.evaluate())
370 self._clipboard.set_text(result)
371 elif event.keyval == ord("l") and event.get_state() & gtk.gdk.CONTROL_MASK:
372 with open(constants._user_logpath_, "r") as f:
373 logLines = f.xreadlines()
374 log = "".join(logLines)
375 self._clipboard.set_text(str(log))
376 elif event.keyval == gtk.keysyms.BackSpace and event.get_state() & gtk.gdk.CONTROL_MASK:
377 self.__historyStore.unpush()
378 elif event.keyval == gtk.keysyms.BackSpace:
379 self.__userEntry.pop()
380 elif event.keyval in RETURN_TYPES:
381 self.__history.push_entry()
383 @gtk_toolbox.log_exception(_moduleLogger)
384 def _on_push(self, *args):
385 self.__history.push_entry()
387 @gtk_toolbox.log_exception(_moduleLogger)
388 def _on_unpush(self, *args):
389 self.__historyStore.unpush()
391 @gtk_toolbox.log_exception(_moduleLogger)
392 def _on_entry_direct(self, keys, modifiers):
393 if "shift" in modifiers:
395 self.__userEntry.append(keys)
397 @gtk_toolbox.log_exception(_moduleLogger)
398 def _on_entry_backspace(self, *args):
399 self.__userEntry.pop()
401 @gtk_toolbox.log_exception(_moduleLogger)
402 def _on_entry_clear(self, *args):
403 self.__userEntry.clear()
405 @gtk_toolbox.log_exception(_moduleLogger)
406 def _on_clear_all(self, *args):
407 self.__history.clear()
409 @gtk_toolbox.log_exception(_moduleLogger)
410 def _on_about_activate(self, *args):
411 dlg = gtk.AboutDialog()
412 dlg.set_name(constants.__pretty_app_name__)
413 dlg.set_version(constants.__version__)
414 dlg.set_copyright("Copyright 2008 - LGPL")
416 ejpi A Touch Screen Optimized RPN Calculator for Maemo and Linux.
418 RPN: Stack based math, its fun
419 Buttons: Try both pressing and hold/drag
420 History: Try dragging things around, deleting them, etc
422 dlg.set_website("http://ejpi.garage.maemo.org")
423 dlg.set_authors(["Ed Page <eopage@byu.net>"])
431 failureCount, testCount = doctest.testmod()
433 print "Tests Successful"
439 def run_calculator():
440 gtk.gdk.threads_init()
442 gtkpie.IMAGES.add_path(os.path.join(os.path.dirname(__file__), "libraries/images"), )
443 if hildonize.IS_HILDON_SUPPORTED:
444 gtk.set_application_name(constants.__pretty_app_name__)
445 handle = Calculator()
446 if not PROFILE_STARTUP:
450 class DummyOptions(object):
456 if __name__ == "__main__":
457 logging.basicConfig(level=logging.DEBUG)
458 if len(sys.argv) > 1:
464 if optparse is not None:
465 parser = optparse.OptionParser()
466 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
467 (commandOptions, commandArgs) = parser.parse_args()
469 commandOptions = DummyOptions()
472 if commandOptions.test: