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
47 PLUGIN_SEARCH_PATHS = [
48 os.path.join(os.path.dirname(__file__), "plugins/"),
52 class ValueEntry(object):
54 def __init__(self, widget):
55 self.__widget = widget
56 self.__actualEntryDisplay = ""
59 value = self.__actualEntryDisplay.strip()
61 0 < value.find(whitespace)
62 for whitespace in string.whitespace
65 raise ValueError('Invalid input "%s"' % value)
68 def set_value(self, value):
71 0 < value.find(whitespace)
72 for whitespace in string.whitespace
74 raise ValueError('Invalid input "%s"' % value)
75 self.__actualEntryDisplay = value
76 self.__widget.set_text(value)
78 def append(self, value):
81 0 < value.find(whitespace)
82 for whitespace in string.whitespace
84 raise ValueError('Invalid input "%s"' % value)
85 self.set_value(self.get_value() + value)
88 value = self.get_value()[0:-1]
94 value = property(get_value, set_value, clear)
97 class Calculator(object):
99 __pretty_app_name__ = "e**(j pi) + 1 = 0"
100 __app_name__ = "ejpi"
101 __version__ = "0.9.3"
102 __app_magic__ = 0xdeadbeef
105 '/usr/lib/ejpi/ejpi.glade',
106 os.path.join(os.path.dirname(__file__), "ejpi.glade"),
107 os.path.join(os.path.dirname(__file__), "../lib/ejpi.glade"),
110 _plugin_search_paths = [
111 "/usr/lib/ejpi/plugins/",
112 os.path.join(os.path.dirname(__file__), "plugins/"),
115 _user_data = os.path.expanduser("~/.%s/" % __app_name__)
116 _user_settings = "%s/settings.ini" % _user_data
117 _user_history = "%s/history.stack" % _user_data
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")
150 os.makedirs(self._user_data)
155 self._clipboard = gtk.clipboard_get()
156 self.__window = self._widgetTree.get_widget("mainWindow")
160 self._isFullScreen = False
161 if hildon is not None:
162 self._app = hildon.Program()
163 self.__window = hildon.Window()
164 self._widgetTree.get_widget("mainLayout").reparent(self.__window)
165 self._app.add_window(self.__window)
166 hildon.hildon_helper_set_thumb_scrollbar(self._widgetTree.get_widget('scrollingHistory'), True)
168 gtkMenu = self._widgetTree.get_widget("mainMenubar")
170 for child in gtkMenu.get_children():
172 self.__window.set_menu(menu)
175 self.__window.connect("key-press-event", self._on_key_press)
176 self.__window.connect("window-state-event", self._on_window_state_change)
178 pass # warnings.warn("No Hildon", UserWarning, 2)
180 self.__errorDisplay = gtk_toolbox.ErrorDisplay(self._widgetTree)
181 self.__userEntry = ValueEntry(self._widgetTree.get_widget("entryView"))
182 self.__stackView = self._widgetTree.get_widget("historyView")
184 self.__historyStore = gtkhistory.GtkCalcHistory(self.__stackView)
185 self.__history = history.RpnCalcHistory(
187 self.__userEntry, self.__errorDisplay,
188 self.__constantPlugins.constants, self.__operatorPlugins.operators
190 self.__load_history()
192 self.__sliceStyle = gtkpie.generate_pie_style(gtk.Button())
193 self.__handler = gtkpieboard.KeyboardHandler(self._on_entry_direct)
194 self.__handler.register_command_handler("push", self._on_push)
195 self.__handler.register_command_handler("unpush", self._on_unpush)
196 self.__handler.register_command_handler("backspace", self._on_entry_backspace)
197 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("functionLayout").pack_start(self.__builtinKeyboard)
204 self._widgetTree.get_widget("functionLayout").reorder_child(self.__builtinKeyboard, 0)
205 self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Trigonometry"))
206 self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Computer"))
207 self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Alphabet"))
210 "on_calculator_quit": self._on_close,
211 "on_paste": self._on_paste,
212 "on_clear_history": self._on_clear_all,
213 "on_about": self._on_about_activate,
215 self._widgetTree.signal_autoconnect(callbackMapping)
219 self.__window.set_title("%s" % self.__pretty_app_name__)
220 self.__window.connect("destroy", self._on_close)
221 self.__window.show_all()
230 self._osso = osso.Context(Calculator.__app_name__, Calculator.__version__, False)
231 device = osso.DeviceState(self._osso)
232 device.set_device_state_callback(self._on_device_state_change, 0)
234 pass # warnings.warn("No OSSO", UserWarning, 2)
236 def display_error_message(self, msg):
237 error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
239 def close(dialog, response, editor):
240 editor.about_dialog = None
242 error_dialog.connect("response", close, self)
245 def enable_plugin(self, pluginId):
246 self.__keyboardPlugins.enable_plugin(pluginId)
247 pluginData = self.__keyboardPlugins.plugin_info(pluginId)
248 pluginName = pluginData[0]
249 plugin = self.__keyboardPlugins.keyboards[pluginName].construct_keyboard()
250 pluginKeyboard = plugin.setup(self.__history, self.__sliceStyle, self.__handler)
252 keyboardTabs = self._widgetTree.get_widget("pluginKeyboards")
253 keyboardTabs.append_page(pluginKeyboard, gtk.Label(pluginName))
254 keyboardPageNum = keyboardTabs.page_num(pluginKeyboard)
255 assert keyboardPageNum not in self.__activeKeyboards
256 self.__activeKeyboards[keyboardPageNum] = {
257 "pluginName": pluginName,
259 "pluginKeyboard": pluginKeyboard,
262 def __load_history(self):
265 with open(self._user_history, "rU") as f:
267 (part.strip() for part in line.split(" "))
268 for line in f.readlines()
273 self.__history.deserialize_stack(serialized)
275 def __save_history(self):
276 serialized = self.__history.serialize_stack()
277 with open(self._user_history, "w") as f:
278 for lineData in serialized:
279 line = " ".join(data for data in lineData)
280 f.write("%s\n" % line)
282 def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
284 For system_inactivity, we have no background tasks to pause
286 @note Hildon specific
291 if save_unsaved_data or shutdown:
292 self.__save_history()
294 def _on_window_state_change(self, widget, event, *args):
296 @note Hildon specific
298 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
299 self._isFullScreen = True
301 self._isFullScreen = False
303 def _on_close(self, *args, **kwds):
304 if self._osso is not None:
308 self.__save_history()
312 def _on_paste(self, *args):
313 contents = self._clipboard.wait_for_text()
314 self.__userEntry.append(contents)
316 def _on_key_press(self, widget, event, *args):
318 @note Hildon specific
320 if event.keyval == gtk.keysyms.F6:
321 if self._isFullScreen:
322 self.__window.unfullscreen()
324 self.__window.fullscreen()
326 def _on_push(self, *args):
327 self.__history.push_entry()
329 def _on_unpush(self, *args):
330 self.__historyStore.unpush()
332 def _on_entry_direct(self, keys, modifiers):
333 if "shift" in modifiers:
335 self.__userEntry.append(keys)
337 def _on_entry_backspace(self, *args):
338 self.__userEntry.pop()
340 def _on_entry_clear(self, *args):
341 self.__userEntry.clear()
343 def _on_clear_all(self, *args):
344 self.__history.clear()
346 def _on_about_activate(self, *args):
347 dlg = gtk.AboutDialog()
348 dlg.set_name(self.__pretty_app_name__)
349 dlg.set_version(self.__version__)
350 dlg.set_copyright("Copyright 2008 - LGPL")
353 dlg.set_authors([""])
361 failureCount, testCount = doctest.testmod()
363 print "Tests Successful"
369 def run_calculator():
370 gtk.gdk.threads_init()
372 gtkpie.IMAGES.add_path(os.path.join(os.path.dirname(__file__), "libraries/images"), )
373 if hildon is not None:
374 gtk.set_application_name(Calculator.__pretty_app_name__)
375 handle = Calculator()
379 class DummyOptions(object):
385 if __name__ == "__main__":
386 if len(sys.argv) > 1:
392 if optparse is not None:
393 parser = optparse.OptionParser()
394 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
395 (commandOptions, commandArgs) = parser.parse_args()
397 commandOptions = DummyOptions()
400 if commandOptions.test: