4 @todo Add logging support to make debugging random user issues a lot easier
5 @todo See Tasque for UI ideas http://live.gnome.org/Tasque/Screenshots
8 from __future__ import with_statement
31 socket.setdefaulttimeout(10)
34 class PreferencesDialog(object):
36 def __init__(self, widgetTree):
37 self._backendList = gtk.ListStore(gobject.TYPE_STRING)
38 self._backendCell = gtk.CellRendererText()
40 self._dialog = widgetTree.get_widget("preferencesDialog")
41 self._backendSelector = widgetTree.get_widget("prefsBackendSelector")
42 self._applyButton = widgetTree.get_widget("applyPrefsButton")
43 self._cancelButton = widgetTree.get_widget("cancelPrefsButton")
45 self._onApplyId = None
46 self._onCancelId = None
49 self._dialog.set_default_size(800, 300)
50 self._onApplyId = self._applyButton.connect("clicked", self._on_apply_clicked)
51 self._onCancelId = self._cancelButton.connect("clicked", self._on_cancel_clicked)
53 cell = self._backendCell
54 self._backendSelector.pack_start(cell, True)
55 self._backendSelector.add_attribute(cell, 'text', 0)
56 self._backendSelector.set_model(self._backendList)
59 self._applyButton.disconnect(self._onApplyId)
60 self._cancelButton.disconnect(self._onCancelId)
62 self._backendList.clear()
63 self._backendSelector.set_model(None)
65 def run(self, app, parentWindow = None):
66 if parentWindow is not None:
67 self._dialog.set_transient_for(parentWindow)
69 self._backendList.clear()
71 for i, (uiName, ui) in enumerate(app.get_uis()):
72 self._backendList.append((uiName, ))
73 if uiName == app.get_default_ui():
75 self._backendSelector.set_active(activeIndex)
78 response = self._dialog.run()
79 if response != gtk.RESPONSE_OK:
80 raise RuntimeError("Edit Cancelled")
84 backendName = self._backendSelector.get_active_text()
85 app.switch_ui(backendName)
87 def _on_apply_clicked(self, *args):
88 self._dialog.response(gtk.RESPONSE_OK)
90 def _on_cancel_clicked(self, *args):
91 self._dialog.response(gtk.RESPONSE_CANCEL)
96 __pretty_app_name__ = "DoneIt"
97 __app_name__ = "doneit"
99 __app_magic__ = 0xdeadbeef
102 '/usr/lib/doneit/doneit.glade',
103 os.path.join(os.path.dirname(__file__), "doneit.glade"),
104 os.path.join(os.path.dirname(__file__), "../lib/doneit.glade"),
107 _user_data = os.path.expanduser("~/.%s/" % __app_name__)
108 _user_settings = "%s/settings.ini" % _user_data
111 self._initDone = False
115 self._deviceIsOnline = True
116 self._connection = None
117 self._fallbackUIName = ""
118 self._defaultUIName = ""
120 for path in self._glade_files:
121 if os.path.isfile(path):
122 self._widgetTree = gtk.glade.XML(path)
125 self.display_error_message("Cannot find doneit.glade")
128 os.makedirs(self._user_data)
133 self._clipboard = gtk.clipboard_get()
134 self.__window = self._widgetTree.get_widget("mainWindow")
135 self.__errorDisplay = gtk_toolbox.ErrorDisplay(self._widgetTree)
136 self._prefsDialog = PreferencesDialog(self._widgetTree)
139 self._isFullScreen = False
140 if hildon is not None:
141 self._app = hildon.Program()
142 self.__window = hildon.Window()
143 self._widgetTree.get_widget("mainLayout").reparent(self.__window)
144 self._app.add_window(self.__window)
145 self._widgetTree.get_widget("usernameentry").set_property('hildon-input-mode', 7)
146 self._widgetTree.get_widget("passwordentry").set_property('hildon-input-mode', 7|(1 << 29))
148 gtkMenu = self._widgetTree.get_widget("mainMenubar")
150 for child in gtkMenu.get_children():
152 self.__window.set_menu(menu)
155 self.__window.connect("key-press-event", self._on_key_press)
156 self.__window.connect("window-state-event", self._on_window_state_change)
158 pass # warnings.warn("No Hildon", UserWarning, 2)
161 "on_doneit_quit": self._on_close,
162 "on_about": self._on_about_activate,
164 self._widgetTree.signal_autoconnect(callbackMapping)
168 self.__window.set_title("%s" % self.__pretty_app_name__)
169 self.__window.connect("destroy", self._on_close)
170 self.__window.show_all()
172 backgroundSetup = threading.Thread(target=self._idle_setup)
173 backgroundSetup.setDaemon(True)
174 backgroundSetup.start()
176 def _idle_setup(self):
177 # Barebones UI handlers
179 with gtk_toolbox.gtk_lock():
180 nullView = null_view.GtkNull(self._widgetTree)
181 self._todoUIs[nullView.name()] = nullView
182 self._todoUI = nullView
183 self._todoUI.enable()
184 self._fallbackUIName = nullView.name()
186 # Setup maemo specifics
193 self._osso = osso.Context(DoneIt.__app_name__, DoneIt.__version__, False)
194 device = osso.DeviceState(self._osso)
195 device.set_device_state_callback(self._on_device_state_change, 0)
197 pass # warnings.warn("No OSSO", UserWarning, 2)
203 self._connection = None
204 if conic is not None:
205 self._connection = conic.Connection()
206 self._connection.connect("connection-event", self._on_connection_change, self.__app_magic__)
207 self._connection.request_connection(conic.CONNECT_FLAG_NONE)
209 pass # warnings.warn("No Internet Connectivity API ", UserWarning)
211 # Setup costly backends
213 with gtk_toolbox.gtk_lock():
214 rtmView = rtm_view.RtmView(self._widgetTree, self.__errorDisplay)
215 self._todoUIs[rtmView.name()] = rtmView
218 defaultStoragePath = "%s/data.txt" % self._user_data
219 with gtk_toolbox.gtk_lock():
220 fileView = file_view.FileView(self._widgetTree, self.__errorDisplay, defaultStoragePath)
221 self._todoUIs[fileView.name()] = fileView
223 self._defaultUIName = fileView.name()
225 config = ConfigParser.SafeConfigParser()
226 config.read(self._user_settings)
227 with gtk_toolbox.gtk_lock():
228 self.load_settings(config)
229 self._widgetTree.get_widget("connectMenuItem").connect("activate", lambda *args: self.switch_ui(self._defaultUIName))
230 self._widgetTree.get_widget("preferencesMenuItem").connect("activate", self._on_prefs)
232 self._initDone = True
234 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 load_settings(self, config):
250 for todoUI in self._todoUIs.itervalues():
252 todoUI.load_settings(config)
253 except ConfigParser.NoSectionError, e:
255 "Settings file %s is missing section %s" % (
263 activeUIName = config.get(self.__pretty_app_name__, "active")
264 except ConfigParser.NoSectionError, e:
267 "Settings file %s is missing section %s" % (
275 self.switch_ui(activeUIName)
277 self.switch_ui(self._defaultUIName)
279 def save_settings(self, config):
281 @note Thread Agnostic
283 config.add_section(self.__pretty_app_name__)
284 config.set(self.__pretty_app_name__, "active", self._todoUI.name())
286 for todoUI in self._todoUIs.itervalues():
287 todoUI.save_settings(config)
290 return (ui for ui in self._todoUIs.iteritems())
292 def get_default_ui(self):
293 return self._defaultUIName
295 def switch_ui(self, uiName):
299 newActiveUI = self._todoUIs[uiName]
303 return # User cancelled the operation
305 self._todoUI.disable()
306 self._todoUI = newActiveUI
307 self._todoUI.enable()
309 if uiName != self._fallbackUIName:
310 self._defaultUIName = uiName
312 def _save_settings(self):
314 @note Thread Agnostic
316 config = ConfigParser.SafeConfigParser()
317 self.save_settings(config)
318 with open(self._user_settings, "wb") as configFile:
319 config.write(configFile)
321 def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
323 For system_inactivity, we have no background tasks to pause
325 @note Hildon specific
330 if save_unsaved_data or shutdown:
331 self._save_settings()
333 def _on_connection_change(self, connection, event, magicIdentifier):
335 @note Hildon specific
339 status = event.get_status()
340 error = event.get_error()
341 iap_id = event.get_iap_id()
342 bearer = event.get_bearer_type()
344 if status == conic.STATUS_CONNECTED:
345 self._deviceIsOnline = True
347 self.switch_ui(self._defaultUIName)
348 elif status == conic.STATUS_DISCONNECTED:
349 self._deviceIsOnline = False
351 self.switch_ui(self._fallbackUIName)
353 def _on_window_state_change(self, widget, event, *args):
355 @note Hildon specific
357 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
358 self._isFullScreen = True
360 self._isFullScreen = False
362 def _on_close(self, *args, **kwds):
364 if self._osso is not None:
368 self._save_settings()
372 def _on_key_press(self, widget, event, *args):
374 @note Hildon specific
376 if event.keyval == gtk.keysyms.F6:
377 if self._isFullScreen:
378 self.__window.unfullscreen()
380 self.__window.fullscreen()
382 def _on_logout(self, *args):
383 if not self._initDone:
386 self._todoUI.logout()
387 self.switch_ui(self._fallbackUIName)
389 def _on_prefs(self, *args):
390 if not self._initDone:
393 self._prefsDialog.enable()
395 self._prefsDialog.run(self)
397 self._prefsDialog.disable()
399 def _on_about_activate(self, *args):
400 dlg = gtk.AboutDialog()
401 dlg.set_name(self.__pretty_app_name__)
402 dlg.set_version(self.__version__)
403 dlg.set_copyright("Copyright 2008 - LGPL")
405 dlg.set_website("http://doneit.garage.maemo.org")
406 dlg.set_authors(["Ed Page"])
414 failureCount, testCount = doctest.testmod()
416 print "Tests Successful"
423 gtk.gdk.threads_init()
425 if hildon is not None:
426 gtk.set_application_name(DoneIt.__pretty_app_name__)
431 class DummyOptions(object):
437 if __name__ == "__main__":
438 if len(sys.argv) > 1:
444 if optparse is not None:
445 parser = optparse.OptionParser()
446 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
447 (commandOptions, commandArgs) = parser.parse_args()
449 commandOptions = DummyOptions()
452 if commandOptions.test: