4 @todo Add logging support to make debugging random user issues a lot easier
7 from __future__ import with_statement
30 socket.setdefaulttimeout(10)
33 class PreferencesDialog(object):
35 def __init__(self, widgetTree):
36 self._backendList = gtk.ListStore(gobject.TYPE_STRING)
37 self._backendCell = gtk.CellRendererText()
39 self._dialog = widgetTree.get_widget("preferencesDialog")
40 self._backendSelector = widgetTree.get_widget("prefsBackendSelector")
41 self._applyButton = widgetTree.get_widget("applyPrefsButton")
42 self._cancelButton = widgetTree.get_widget("cancelPrefsButton")
44 self._onApplyId = None
45 self._onCancelId = None
48 self._dialog.set_default_size(800, 300)
49 self._onApplyId = self._applyButton.connect("clicked", self._on_apply_clicked)
50 self._onCancelId = self._cancelButton.connect("clicked", self._on_cancel_clicked)
52 cell = self._backendCell
53 self._backendSelector.pack_start(cell, True)
54 self._backendSelector.add_attribute(cell, 'text', 0)
55 self._backendSelector.set_model(self._backendList)
58 self._applyButton.disconnect(self._onApplyId)
59 self._cancelButton.disconnect(self._onCancelId)
61 self._backendList.clear()
62 self._backendSelector.set_model(None)
64 def run(self, app, parentWindow = None):
65 if parentWindow is not None:
66 self._dialog.set_transient_for(parentWindow)
68 self._backendList.clear()
70 for i, (uiName, ui) in enumerate(app.get_uis()):
71 self._backendList.append((uiName, ))
72 if uiName == app.get_default_ui():
74 self._backendSelector.set_active(activeIndex)
77 response = self._dialog.run()
78 if response != gtk.RESPONSE_OK:
79 raise RuntimeError("Edit Cancelled")
83 backendName = self._backendSelector.get_active_text()
84 app.switch_ui(backendName)
86 def _on_apply_clicked(self, *args):
87 self._dialog.response(gtk.RESPONSE_OK)
89 def _on_cancel_clicked(self, *args):
90 self._dialog.response(gtk.RESPONSE_CANCEL)
95 __pretty_app_name__ = "DoneIt"
96 __app_name__ = "doneit"
98 __app_magic__ = 0xdeadbeef
101 '/usr/lib/doneit/doneit.glade',
102 os.path.join(os.path.dirname(__file__), "doneit.glade"),
103 os.path.join(os.path.dirname(__file__), "../lib/doneit.glade"),
106 _user_data = os.path.expanduser("~/.%s/" % __app_name__)
107 _user_settings = "%s/settings.ini" % _user_data
110 self._initDone = False
114 self._deviceIsOnline = True
115 self._connection = None
116 self._fallbackUIName = ""
117 self._defaultUIName = ""
119 for path in self._glade_files:
120 if os.path.isfile(path):
121 self._widgetTree = gtk.glade.XML(path)
124 self.display_error_message("Cannot find doneit.glade")
127 os.makedirs(self._user_data)
132 self._clipboard = gtk.clipboard_get()
133 self.__window = self._widgetTree.get_widget("mainWindow")
134 self.__errorDisplay = gtk_toolbox.ErrorDisplay(self._widgetTree)
135 self._prefsDialog = PreferencesDialog(self._widgetTree)
138 self._isFullScreen = False
139 if hildon is not None:
140 self._app = hildon.Program()
141 self.__window = hildon.Window()
142 self._widgetTree.get_widget("mainLayout").reparent(self.__window)
143 self._app.add_window(self.__window)
144 self._widgetTree.get_widget("usernameentry").set_property('hildon-input-mode', 7)
145 self._widgetTree.get_widget("passwordentry").set_property('hildon-input-mode', 7|(1 << 29))
147 gtkMenu = self._widgetTree.get_widget("mainMenubar")
149 for child in gtkMenu.get_children():
151 self.__window.set_menu(menu)
154 self.__window.connect("key-press-event", self._on_key_press)
155 self.__window.connect("window-state-event", self._on_window_state_change)
157 pass # warnings.warn("No Hildon", UserWarning, 2)
160 "on_doneit_quit": self._on_close,
161 "on_about": self._on_about_activate,
163 self._widgetTree.signal_autoconnect(callbackMapping)
167 self.__window.set_title("%s" % self.__pretty_app_name__)
168 self.__window.connect("destroy", self._on_close)
169 self.__window.show_all()
171 backgroundSetup = threading.Thread(target=self._idle_setup)
172 backgroundSetup.setDaemon(True)
173 backgroundSetup.start()
175 def _idle_setup(self):
176 # Barebones UI handlers
178 with gtk_toolbox.gtk_lock():
179 nullView = null_view.GtkNull(self._widgetTree)
180 self._todoUIs[nullView.name()] = nullView
181 self._todoUI = nullView
182 self._todoUI.enable()
183 self._fallbackUIName = nullView.name()
185 # Setup maemo specifics
192 self._osso = osso.Context(DoneIt.__app_name__, DoneIt.__version__, False)
193 device = osso.DeviceState(self._osso)
194 device.set_device_state_callback(self._on_device_state_change, 0)
196 pass # warnings.warn("No OSSO", UserWarning, 2)
202 self._connection = None
203 if conic is not None:
204 self._connection = conic.Connection()
205 self._connection.connect("connection-event", self._on_connection_change, self.__app_magic__)
206 self._connection.request_connection(conic.CONNECT_FLAG_NONE)
208 pass # warnings.warn("No Internet Connectivity API ", UserWarning)
210 # Setup costly backends
212 with gtk_toolbox.gtk_lock():
213 rtmView = rtm_view.RtmView(self._widgetTree, self.__errorDisplay)
214 self._todoUIs[rtmView.name()] = rtmView
217 defaultStoragePath = "%s/data.txt" % self._user_data
218 with gtk_toolbox.gtk_lock():
219 fileView = file_view.FileView(self._widgetTree, self.__errorDisplay, defaultStoragePath)
220 self._todoUIs[fileView.name()] = fileView
222 self._defaultUIName = fileView.name()
224 config = ConfigParser.SafeConfigParser()
225 config.read(self._user_settings)
226 with gtk_toolbox.gtk_lock():
227 self.load_settings(config)
228 self._widgetTree.get_widget("connectMenuItem").connect("activate", lambda *args: self.switch_ui(self._defaultUIName))
229 self._widgetTree.get_widget("preferencesMenuItem").connect("activate", self._on_prefs)
231 self._initDone = True
233 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 load_settings(self, config):
249 for todoUI in self._todoUIs.itervalues():
251 todoUI.load_settings(config)
252 except ConfigParser.NoSectionError, e:
254 "Settings file %s is missing section %s" % (
262 activeUIName = config.get(self.__pretty_app_name__, "active")
263 except ConfigParser.NoSectionError, e:
266 "Settings file %s is missing section %s" % (
274 self.switch_ui(activeUIName)
276 self.switch_ui(self._defaultUIName)
278 def save_settings(self, config):
280 @note Thread Agnostic
282 config.add_section(self.__pretty_app_name__)
283 config.set(self.__pretty_app_name__, "active", self._todoUI.name())
285 for todoUI in self._todoUIs.itervalues():
286 todoUI.save_settings(config)
289 return (ui for ui in self._todoUIs.iteritems())
291 def get_default_ui(self):
292 return self._defaultUIName
294 def switch_ui(self, uiName):
298 newActiveUI = self._todoUIs[uiName]
302 return # User cancelled the operation
304 self._todoUI.disable()
305 self._todoUI = newActiveUI
306 self._todoUI.enable()
308 if uiName != self._fallbackUIName:
309 self._defaultUIName = uiName
311 def _save_settings(self):
313 @note Thread Agnostic
315 config = ConfigParser.SafeConfigParser()
316 self.save_settings(config)
317 with open(self._user_settings, "wb") as configFile:
318 config.write(configFile)
320 def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
322 For system_inactivity, we have no background tasks to pause
324 @note Hildon specific
329 if save_unsaved_data or shutdown:
330 self._save_settings()
332 def _on_connection_change(self, connection, event, magicIdentifier):
334 @note Hildon specific
338 status = event.get_status()
339 error = event.get_error()
340 iap_id = event.get_iap_id()
341 bearer = event.get_bearer_type()
343 if status == conic.STATUS_CONNECTED:
344 self._deviceIsOnline = True
346 self.switch_ui(self._defaultUIName)
347 elif status == conic.STATUS_DISCONNECTED:
348 self._deviceIsOnline = False
350 self.switch_ui(self._fallbackUIName)
352 def _on_window_state_change(self, widget, event, *args):
354 @note Hildon specific
356 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
357 self._isFullScreen = True
359 self._isFullScreen = False
361 def _on_close(self, *args, **kwds):
363 if self._osso is not None:
367 self._save_settings()
371 def _on_key_press(self, widget, event, *args):
373 @note Hildon specific
375 if event.keyval == gtk.keysyms.F6:
376 if self._isFullScreen:
377 self.__window.unfullscreen()
379 self.__window.fullscreen()
381 def _on_logout(self, *args):
382 if not self._initDone:
385 self._todoUI.logout()
386 self.switch_ui(self._fallbackUIName)
388 def _on_prefs(self, *args):
389 if not self._initDone:
392 self._prefsDialog.enable()
394 self._prefsDialog.run(self)
396 self._prefsDialog.disable()
398 def _on_about_activate(self, *args):
399 dlg = gtk.AboutDialog()
400 dlg.set_name(self.__pretty_app_name__)
401 dlg.set_version(self.__version__)
402 dlg.set_copyright("Copyright 2008 - LGPL")
404 dlg.set_website("http://doneit.garage.maemo.org")
405 dlg.set_authors(["Ed Page"])
413 failureCount, testCount = doctest.testmod()
415 print "Tests Successful"
422 gtk.gdk.threads_init()
424 if hildon is not None:
425 gtk.set_application_name(DoneIt.__pretty_app_name__)
430 class DummyOptions(object):
436 if __name__ == "__main__":
437 if len(sys.argv) > 1:
443 if optparse is not None:
444 parser = optparse.OptionParser()
445 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
446 (commandOptions, commandArgs) = parser.parse_args()
448 commandOptions = DummyOptions()
451 if commandOptions.test: