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
39 from libraries import gtkpie
40 from libraries import gtkpieboard
46 PLUGIN_SEARCH_PATHS = [
47 os.path.join(os.path.dirname(__file__), "plugins/"),
51 class ValueEntry(object):
53 def __init__(self, widget):
54 self.__widget = widget
55 self.__actualEntryDisplay = ""
58 value = self.__actualEntryDisplay.strip()
60 0 < value.find(whitespace)
61 for whitespace in string.whitespace
64 raise ValueError('Invalid input "%s"' % value)
67 def set_value(self, value):
70 0 < value.find(whitespace)
71 for whitespace in string.whitespace
73 raise ValueError('Invalid input "%s"' % value)
74 self.__actualEntryDisplay = value
75 self.__widget.set_text(value)
77 def append(self, value):
80 0 < value.find(whitespace)
81 for whitespace in string.whitespace
83 raise ValueError('Invalid input "%s"' % value)
84 self.set_value(self.get_value() + value)
87 value = self.get_value()[0:-1]
93 value = property(get_value, set_value, clear)
96 class ErrorDisplay(history.ErrorReporting):
98 def __init__(self, widgetTree):
99 super(ErrorDisplay, self).__init__()
100 self.__errorBox = widgetTree.get_widget("errorEventBox")
101 self.__errorDescription = widgetTree.get_widget("errorDescription")
102 self.__errorClose = widgetTree.get_widget("errorClose")
103 self.__parentBox = self.__errorBox.get_parent()
105 self.__errorBox.connect("button_release_event", self._on_close)
108 self.__parentBox.remove(self.__errorBox)
110 def push_message(self, message):
111 if 0 < len(self.__messages):
112 self.__messages.append(message)
114 self.__show_message(message)
116 def pop_message(self):
117 if 0 < len(self.__messages):
118 self.__show_message(self.__messages[0])
119 del self.__messages[0]
121 self.__hide_message()
123 def _on_close(self, *args):
126 def __show_message(self, message):
127 self.__errorDescription.set_text(message)
128 self.__parentBox.pack_start(self.__errorBox, False, False)
129 self.__parentBox.reorder_child(self.__errorBox, 1)
131 def __hide_message(self):
132 self.__errorDescription.set_text("")
133 self.__parentBox.remove(self.__errorBox)
136 class Calculator(object):
138 __pretty_app_name__ = "e^(j pi) + 1 = 0"
139 __app_name__ = "ejpi"
140 __version__ = "0.1.0"
141 __app_magic__ = 0xdeadbeef
144 '/usr/lib/ejpi/calc.glade',
145 os.path.join(os.path.dirname(__file__), "calc.glade"),
146 os.path.join(os.path.dirname(__file__), "../lib/calc.glade"),
149 _plugin_search_paths = [
150 os.path.join(os.path.dirname(__file__), "plugins/")
153 _user_data = os.path.expanduser("~/.%s/" % __app_name__)
154 _user_settings = "%s/settings.ini" % _user_data
155 _user_history = "%s/history.stack" % _user_data
158 self.__constantPlugins = plugin_utils.ConstantPluginManager()
159 self.__constantPlugins.add_path(*self._plugin_search_paths)
160 self.__constantPlugins.enable_plugin(self.__constantPlugins.lookup_plugin("Builtin"))
161 self.__constantPlugins.enable_plugin(self.__constantPlugins.lookup_plugin("Trigonometry"))
162 self.__constantPlugins.enable_plugin(self.__constantPlugins.lookup_plugin("Computer"))
163 self.__constantPlugins.enable_plugin(self.__constantPlugins.lookup_plugin("Alphabet"))
165 self.__operatorPlugins = plugin_utils.OperatorPluginManager()
166 self.__operatorPlugins.add_path(*self._plugin_search_paths)
167 self.__operatorPlugins.enable_plugin(self.__operatorPlugins.lookup_plugin("Builtin"))
168 self.__operatorPlugins.enable_plugin(self.__operatorPlugins.lookup_plugin("Trigonometry"))
169 self.__operatorPlugins.enable_plugin(self.__operatorPlugins.lookup_plugin("Computer"))
170 self.__operatorPlugins.enable_plugin(self.__operatorPlugins.lookup_plugin("Alphabet"))
172 self.__keyboardPlugins = plugin_utils.KeyboardPluginManager()
173 self.__keyboardPlugins.add_path(*self._plugin_search_paths)
174 self.__activeKeyboards = {}
176 for path in self._glade_files:
177 if os.path.isfile(path):
178 self._widgetTree = gtk.glade.XML(path)
181 self.display_error_message("Cannot find calc.glade")
184 os.makedirs(self._user_data)
189 self._clipboard = gtk.clipboard_get()
190 self.__window = self._widgetTree.get_widget("mainWindow")
194 self._isFullScreen = False
195 if hildon is not None and self.__window is gtk.Window:
196 warnings.warn("Hildon installed but glade file not updated to work with hildon", UserWarning, 2)
198 elif hildon is not None:
199 self._app = hildon.Program()
200 self.__window = hildon.Window()
201 self._widgetTree.get_widget("mainLayout").reparent(self.__window)
202 self._app.add_window(self.__window)
203 hildon.hildon_helper_set_thumb_scrollbar(self._widgetTree.get_widget('scrollingHistory'), True)
205 gtkMenu = self._widgetTree.get_widget("mainMenubar")
207 for child in gtkMenu.get_children():
209 self.__window.set_menu(menu)
212 self.__window.connect("key-press-event", self._on_key_press)
213 self.__window.connect("window-state-event", self._on_window_state_change)
215 warnings.warn("No Hildon", UserWarning, 2)
217 self.__errorDisplay = ErrorDisplay(self._widgetTree)
218 self.__userEntry = ValueEntry(self._widgetTree.get_widget("entryView"))
219 self.__stackView = self._widgetTree.get_widget("historyView")
221 self.__historyStore = gtkhistory.GtkCalcHistory(self.__stackView)
222 self.__history = history.RpnCalcHistory(
224 self.__userEntry, self.__errorDisplay,
225 self.__constantPlugins.constants, self.__operatorPlugins.operators
227 self.__load_history()
229 self.__sliceStyle = gtkpie.generate_pie_style(self.__window)
230 self.__handler = gtkpieboard.KeyboardHandler(self._on_entry_direct)
231 self.__handler.register_command_handler("push", self._on_push)
232 self.__handler.register_command_handler("unpush", self._on_unpush)
233 self.__handler.register_command_handler("backspace", self._on_entry_backspace)
234 self.__handler.register_command_handler("clear", self._on_entry_clear)
236 builtinKeyboardId = self.__keyboardPlugins.lookup_plugin("Builtin")
237 self.__keyboardPlugins.enable_plugin(builtinKeyboardId)
238 self.__builtinPlugin = self.__keyboardPlugins.keyboards["Builtin"].construct_keyboard()
239 self.__builtinKeyboard = self.__builtinPlugin.setup(self.__history, self.__sliceStyle, self.__handler)
240 self._widgetTree.get_widget("functionLayout").pack_start(self.__builtinKeyboard)
241 self._widgetTree.get_widget("functionLayout").reorder_child(self.__builtinKeyboard, 0)
242 self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Trigonometry"))
243 self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Computer"))
244 self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Alphabet"))
247 "on_calculator_quit": self._on_close,
248 "on_paste": self._on_paste,
249 "on_clear_entry": self._on_clear_all,
250 "on_about": self._on_about_activate,
252 self._widgetTree.signal_autoconnect(callbackMapping)
256 self.__window.set_title("%s" % self.__pretty_app_name__)
257 self.__window.connect("destroy", self._on_close)
258 self.__window.show_all()
267 self._osso = osso.Context(Calculator.__app_name__, Calculator.__version__, False)
268 device = osso.DeviceState(self._osso)
269 device.set_device_state_callback(self._on_device_state_change, 0)
271 warnings.warn("No OSSO", UserWarning, 2)
273 def display_error_message(self, msg):
274 error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
276 def close(dialog, response, editor):
277 editor.about_dialog = None
279 error_dialog.connect("response", close, self)
282 def enable_plugin(self, pluginId):
283 self.__keyboardPlugins.enable_plugin(pluginId)
284 pluginData = self.__keyboardPlugins.plugin_info(pluginId)
285 pluginName = pluginData[0]
286 plugin = self.__keyboardPlugins.keyboards[pluginName].construct_keyboard()
287 pluginKeyboard = plugin.setup(self.__history, self.__sliceStyle, self.__handler)
289 keyboardTabs = self._widgetTree.get_widget("pluginKeyboards")
290 keyboardTabs.append_page(pluginKeyboard, gtk.Label(pluginName))
291 keyboardPageNum = keyboardTabs.page_num(pluginKeyboard)
292 assert keyboardPageNum not in self.__activeKeyboards
293 self.__activeKeyboards[keyboardPageNum] = {
294 "pluginName": pluginName,
296 "pluginKeyboard": pluginKeyboard,
299 def __load_history(self):
302 with open(self._user_history, "rU") as f:
304 (part.strip() for part in line.split(" "))
305 for line in f.readlines()
310 self.__history.deserialize_stack(serialized)
312 def __save_history(self):
313 serialized = self.__history.serialize_stack()
314 with open(self._user_history, "w") as f:
315 for lineData in serialized:
316 line = " ".join(data for data in lineData)
317 f.write("%s\n" % line)
319 def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
321 For system_inactivity, we have no background tasks to pause
323 @note Hildon specific
328 if save_unsaved_data or shutdown:
329 self.__save_history()
331 def _on_window_state_change(self, widget, event, *args):
333 @note Hildon specific
335 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
336 self._isFullScreen = True
338 self._isFullScreen = False
340 def _on_close(self, *args, **kwds):
342 self.__save_history()
346 def _on_paste(self, *args):
347 contents = self._clipboard.wait_for_text()
348 self.__userEntry.append(contents)
350 def _on_key_press(self, widget, event, *args):
352 @note Hildon specific
354 if event.keyval == gtk.keysyms.F6:
355 if self._isFullScreen:
356 self.__window.unfullscreen()
358 self.__window.fullscreen()
360 def _on_push(self, *args):
361 self.__history.push_entry()
363 def _on_unpush(self, *args):
364 self.__historyStore.unpush()
366 def _on_entry_direct(self, keys, modifiers):
367 if "shift" in modifiers:
369 self.__userEntry.append(keys)
371 def _on_entry_backspace(self, *args):
372 self.__userEntry.pop()
374 def _on_entry_clear(self, *args):
375 self.__userEntry.clear()
377 def _on_clear_all(self, *args):
378 self.__history.clear()
380 def _on_about_activate(self, *args):
381 dlg = gtk.AboutDialog()
382 dlg.set_name(self.__pretty_app_name__)
383 dlg.set_version(self.__version__)
384 dlg.set_copyright("Copyright 2008 - LGPL")
387 dlg.set_authors([""])
395 failureCount, testCount = doctest.testmod()
397 print "Tests Successful"
403 def run_calculator():
404 gtk.gdk.threads_init()
406 if hildon is not None:
407 gtk.set_application_name(Calculator.__pretty_app_name__)
408 handle = Calculator()
412 class DummyOptions(object):
418 if __name__ == "__main__":
419 if len(sys.argv) > 1:
425 if optparse is not None:
426 parser = optparse.OptionParser()
427 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
428 (commandOptions, commandArgs) = parser.parse_args()
430 commandOptions = DummyOptions()
433 if commandOptions.test:
436 gtkpie.IMAGES.add_path(os.path.join(os.path.dirname(__file__), "libraries/images"), )