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
9 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.join(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 oldWindow = self.__window
143 self.__window = hildon.Window()
144 oldWindow.get_child().reparent(self.__window)
145 self._app.add_window(self.__window)
148 self._widgetTree.get_widget("usernameentry").set_property('hildon-input-mode', 7)
149 self._widgetTree.get_widget("passwordentry").set_property('hildon-input-mode', 7|(1 << 29))
151 warnings.warn(e.message)
153 gtkMenu = self._widgetTree.get_widget("mainMenubar")
155 for child in gtkMenu.get_children():
157 self.__window.set_menu(menu)
160 self.__window.connect("key-press-event", self._on_key_press)
161 self.__window.connect("window-state-event", self._on_window_state_change)
163 pass # warnings.warn("No Hildon", UserWarning, 2)
166 self.__window.set_title("%s" % self.__pretty_app_name__)
169 "on_doneit_quit": self._on_close,
170 "on_about": self._on_about_activate,
172 self._widgetTree.signal_autoconnect(callbackMapping)
174 self.__window.connect("destroy", self._on_close)
175 self.__window.show_all()
177 backgroundSetup = threading.Thread(target=self._idle_setup)
178 backgroundSetup.setDaemon(True)
179 backgroundSetup.start()
181 def _idle_setup(self):
182 # Barebones UI handlers
184 with gtk_toolbox.gtk_lock():
185 nullView = null_view.GtkNull(self._widgetTree)
186 self._todoUIs[nullView.name()] = nullView
187 self._todoUI = nullView
188 self._todoUI.enable()
189 self._fallbackUIName = nullView.name()
191 # Setup maemo specifics
198 self._osso = osso.Context(DoneIt.__app_name__, DoneIt.__version__, False)
199 device = osso.DeviceState(self._osso)
200 device.set_device_state_callback(self._on_device_state_change, 0)
202 pass # warnings.warn("No OSSO", UserWarning, 2)
208 self._connection = None
209 if conic is not None:
210 self._connection = conic.Connection()
211 self._connection.connect("connection-event", self._on_connection_change, self.__app_magic__)
212 self._connection.request_connection(conic.CONNECT_FLAG_NONE)
214 pass # warnings.warn("No Internet Connectivity API ", UserWarning)
216 # Setup costly backends
218 with gtk_toolbox.gtk_lock():
219 rtmView = rtm_view.RtmView(self._widgetTree, self.__errorDisplay)
220 self._todoUIs[rtmView.name()] = rtmView
223 defaultStoragePath = "%s/data.txt" % self._user_data
224 with gtk_toolbox.gtk_lock():
225 fileView = file_view.FileView(self._widgetTree, self.__errorDisplay, defaultStoragePath)
226 self._todoUIs[fileView.name()] = fileView
228 self._defaultUIName = fileView.name()
230 config = ConfigParser.SafeConfigParser()
231 config.read(self._user_settings)
232 with gtk_toolbox.gtk_lock():
233 self.load_settings(config)
234 self._widgetTree.get_widget("connectMenuItem").connect("activate", lambda *args: self.switch_ui(self._defaultUIName))
235 self._widgetTree.get_widget("preferencesMenuItem").connect("activate", self._on_prefs)
237 self._initDone = True
239 def display_error_message(self, msg):
243 error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
245 def close(dialog, response, editor):
246 editor.about_dialog = None
248 error_dialog.connect("response", close, self)
251 def load_settings(self, config):
255 for todoUI in self._todoUIs.itervalues():
257 todoUI.load_settings(config)
258 except ConfigParser.NoSectionError, e:
260 "Settings file %s is missing section %s" % (
268 activeUIName = config.get(self.__pretty_app_name__, "active")
269 except ConfigParser.NoSectionError, e:
272 "Settings file %s is missing section %s" % (
280 self.switch_ui(activeUIName)
282 self.switch_ui(self._defaultUIName)
284 def save_settings(self, config):
286 @note Thread Agnostic
288 config.add_section(self.__pretty_app_name__)
289 config.set(self.__pretty_app_name__, "active", self._todoUI.name())
291 for todoUI in self._todoUIs.itervalues():
292 todoUI.save_settings(config)
295 return (ui for ui in self._todoUIs.iteritems())
297 def get_default_ui(self):
298 return self._defaultUIName
300 def switch_ui(self, uiName):
304 newActiveUI = self._todoUIs[uiName]
308 return # User cancelled the operation
310 self._todoUI.disable()
311 self._todoUI = newActiveUI
312 self._todoUI.enable()
314 if uiName != self._fallbackUIName:
315 self._defaultUIName = uiName
317 def _save_settings(self):
319 @note Thread Agnostic
321 config = ConfigParser.SafeConfigParser()
322 self.save_settings(config)
323 with open(self._user_settings, "wb") as configFile:
324 config.write(configFile)
326 def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
328 For system_inactivity, we have no background tasks to pause
330 @note Hildon specific
335 if save_unsaved_data or shutdown:
336 self._save_settings()
338 def _on_connection_change(self, connection, event, magicIdentifier):
340 @note Hildon specific
344 status = event.get_status()
345 error = event.get_error()
346 iap_id = event.get_iap_id()
347 bearer = event.get_bearer_type()
349 if status == conic.STATUS_CONNECTED:
350 self._deviceIsOnline = True
352 self.switch_ui(self._defaultUIName)
353 elif status == conic.STATUS_DISCONNECTED:
354 self._deviceIsOnline = False
356 self.switch_ui(self._fallbackUIName)
358 def _on_window_state_change(self, widget, event, *args):
360 @note Hildon specific
362 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
363 self._isFullScreen = True
365 self._isFullScreen = False
367 def _on_close(self, *args, **kwds):
369 if self._osso is not None:
373 self._save_settings()
377 def _on_key_press(self, widget, event, *args):
379 @note Hildon specific
381 if event.keyval == gtk.keysyms.F6:
382 if self._isFullScreen:
383 self.__window.unfullscreen()
385 self.__window.fullscreen()
387 def _on_logout(self, *args):
388 if not self._initDone:
391 self._todoUI.logout()
392 self.switch_ui(self._fallbackUIName)
394 def _on_prefs(self, *args):
395 if not self._initDone:
398 self._prefsDialog.enable()
400 self._prefsDialog.run(self)
402 self._prefsDialog.disable()
404 def _on_about_activate(self, *args):
405 dlg = gtk.AboutDialog()
406 dlg.set_name(self.__pretty_app_name__)
407 dlg.set_version(self.__version__)
408 dlg.set_copyright("Copyright 2008 - LGPL")
410 dlg.set_website("http://doneit.garage.maemo.org")
411 dlg.set_authors(["Ed Page"])
419 failureCount, testCount = doctest.testmod()
421 print "Tests Successful"
428 gtk.gdk.threads_init()
430 if hildon is not None:
431 gtk.set_application_name(DoneIt.__pretty_app_name__)
436 class DummyOptions(object):
442 if __name__ == "__main__":
443 if len(sys.argv) > 1:
449 if optparse is not None:
450 parser = optparse.OptionParser()
451 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
452 (commandOptions, commandArgs) = parser.parse_args()
454 commandOptions = DummyOptions()
457 if commandOptions.test: