Minor updates being taken in from Dialcentral
[doneit] / src / doneit_glade.py
1 #!/usr/bin/python
2
3 """
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
6 """
7
8
9 from __future__ import with_statement
10
11 import sys
12 import gc
13 import os
14 import threading
15 import ConfigParser
16 import socket
17 import warnings
18
19 import gobject
20 import gtk
21 import gtk.glade
22
23 try:
24         import hildon
25 except ImportError:
26         hildon = None
27
28 import gtk_toolbox
29
30
31 socket.setdefaulttimeout(10)
32
33
34 class PreferencesDialog(object):
35
36         def __init__(self, widgetTree):
37                 self._backendList = gtk.ListStore(gobject.TYPE_STRING)
38                 self._backendCell = gtk.CellRendererText()
39
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")
44
45                 self._onApplyId = None
46                 self._onCancelId = None
47
48         def enable(self):
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)
52
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)
57
58         def disable(self):
59                 self._applyButton.disconnect(self._onApplyId)
60                 self._cancelButton.disconnect(self._onCancelId)
61
62                 self._backendList.clear()
63                 self._backendSelector.set_model(None)
64
65         def run(self, app, parentWindow = None):
66                 if parentWindow is not None:
67                         self._dialog.set_transient_for(parentWindow)
68
69                 self._backendList.clear()
70                 activeIndex = 0
71                 for i, (uiName, ui) in enumerate(app.get_uis()):
72                         self._backendList.append((uiName, ))
73                         if uiName == app.get_default_ui():
74                                 activeIndex = i
75                 self._backendSelector.set_active(activeIndex)
76
77                 try:
78                         response = self._dialog.run()
79                         if response != gtk.RESPONSE_OK:
80                                 raise RuntimeError("Edit Cancelled")
81                 finally:
82                         self._dialog.hide()
83
84                 backendName = self._backendSelector.get_active_text()
85                 app.switch_ui(backendName)
86
87         def _on_apply_clicked(self, *args):
88                 self._dialog.response(gtk.RESPONSE_OK)
89
90         def _on_cancel_clicked(self, *args):
91                 self._dialog.response(gtk.RESPONSE_CANCEL)
92
93
94 class DoneIt(object):
95
96         __pretty_app_name__ = "DoneIt"
97         __app_name__ = "doneit"
98         __version__ = "0.3.1"
99         __app_magic__ = 0xdeadbeef
100
101         _glade_files = [
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"),
105         ]
106
107         _user_data = os.path.join(os.path.expanduser("~"), ".%s" % __app_name__)
108         _user_settings = "%s/settings.ini" % _user_data
109
110         def __init__(self):
111                 self._initDone = False
112                 self._todoUIs = {}
113                 self._todoUI = None
114                 self._osso = None
115                 self._deviceIsOnline = True
116                 self._connection = None
117                 self._fallbackUIName = ""
118                 self._defaultUIName = ""
119
120                 for path in self._glade_files:
121                         if os.path.isfile(path):
122                                 self._widgetTree = gtk.glade.XML(path)
123                                 break
124                 else:
125                         self.display_error_message("Cannot find doneit.glade")
126                         gtk.main_quit()
127                 try:
128                         os.makedirs(self._user_data)
129                 except OSError, e:
130                         if e.errno != 17:
131                                 raise
132
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)
137
138                 self._app = None
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)
146
147                         try:
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))
150                         except TypeError, e:
151                                 warnings.warn(e.message)
152
153                         gtkMenu = self._widgetTree.get_widget("mainMenubar")
154                         menu = gtk.Menu()
155                         for child in gtkMenu.get_children():
156                                 child.reparent(menu)
157                         self.__window.set_menu(menu)
158                         gtkMenu.destroy()
159
160                         self.__window.connect("key-press-event", self._on_key_press)
161                         self.__window.connect("window-state-event", self._on_window_state_change)
162                 else:
163                         pass # warnings.warn("No Hildon", UserWarning, 2)
164
165                 if hildon is None:
166                         self.__window.set_title("%s" % self.__pretty_app_name__)
167
168                 callbackMapping = {
169                         "on_doneit_quit": self._on_close,
170                         "on_about": self._on_about_activate,
171                 }
172                 self._widgetTree.signal_autoconnect(callbackMapping)
173
174                 self.__window.connect("destroy", self._on_close)
175                 self.__window.show_all()
176
177                 backgroundSetup = threading.Thread(target=self._idle_setup)
178                 backgroundSetup.setDaemon(True)
179                 backgroundSetup.start()
180
181         def _idle_setup(self):
182                 # Barebones UI handlers
183                 import null_view
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()
190
191                 # Setup maemo specifics
192                 try:
193                         import osso
194                 except ImportError:
195                         osso = None
196                 self._osso = None
197                 if osso is not None:
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)
201                 else:
202                         pass # warnings.warn("No OSSO", UserWarning, 2)
203
204                 try:
205                         import conic
206                 except ImportError:
207                         conic = None
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)
213                 else:
214                         pass # warnings.warn("No Internet Connectivity API ", UserWarning)
215
216                 # Setup costly backends
217                 import rtm_view
218                 with gtk_toolbox.gtk_lock():
219                         rtmView = rtm_view.RtmView(self._widgetTree, self.__errorDisplay)
220                 self._todoUIs[rtmView.name()] = rtmView
221
222                 import file_view
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
227
228                 self._defaultUIName = fileView.name()
229
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)
236
237                 self._initDone = True
238
239         def display_error_message(self, msg):
240                 """
241                 @note UI Thread
242                 """
243                 error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
244
245                 def close(dialog, response, editor):
246                         editor.about_dialog = None
247                         dialog.destroy()
248                 error_dialog.connect("response", close, self)
249                 error_dialog.run()
250
251         def load_settings(self, config):
252                 """
253                 @note UI Thread
254                 """
255                 for todoUI in self._todoUIs.itervalues():
256                         try:
257                                 todoUI.load_settings(config)
258                         except ConfigParser.NoSectionError, e:
259                                 warnings.warn(
260                                         "Settings file %s is missing section %s" % (
261                                                 self._user_settings,
262                                                 e.section,
263                                         ),
264                                         stacklevel=2
265                                 )
266
267                 try:
268                         activeUIName = config.get(self.__pretty_app_name__, "active")
269                 except ConfigParser.NoSectionError, e:
270                         activeUIName = ""
271                         warnings.warn(
272                                 "Settings file %s is missing section %s" % (
273                                         self._user_settings,
274                                         e.section,
275                                 ),
276                                 stacklevel=2
277                         )
278
279                 try:
280                         self.switch_ui(activeUIName)
281                 except KeyError, e:
282                         self.switch_ui(self._defaultUIName)
283
284         def save_settings(self, config):
285                 """
286                 @note Thread Agnostic
287                 """
288                 config.add_section(self.__pretty_app_name__)
289                 config.set(self.__pretty_app_name__, "active", self._todoUI.name())
290
291                 for todoUI in self._todoUIs.itervalues():
292                         todoUI.save_settings(config)
293
294         def get_uis(self):
295                 return (ui for ui in self._todoUIs.iteritems())
296
297         def get_default_ui(self):
298                 return self._defaultUIName
299
300         def switch_ui(self, uiName):
301                 """
302                 @note UI Thread
303                 """
304                 newActiveUI = self._todoUIs[uiName]
305                 try:
306                         newActiveUI.login()
307                 except RuntimeError:
308                         return # User cancelled the operation
309
310                 self._todoUI.disable()
311                 self._todoUI = newActiveUI
312                 self._todoUI.enable()
313
314                 if uiName != self._fallbackUIName:
315                         self._defaultUIName = uiName
316
317         def _save_settings(self):
318                 """
319                 @note Thread Agnostic
320                 """
321                 config = ConfigParser.SafeConfigParser()
322                 self.save_settings(config)
323                 with open(self._user_settings, "wb") as configFile:
324                         config.write(configFile)
325
326         def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
327                 """
328                 For system_inactivity, we have no background tasks to pause
329
330                 @note Hildon specific
331                 """
332                 if memory_low:
333                         gc.collect()
334
335                 if save_unsaved_data or shutdown:
336                         self._save_settings()
337
338         def _on_connection_change(self, connection, event, magicIdentifier):
339                 """
340                 @note Hildon specific
341                 """
342                 import conic
343
344                 status = event.get_status()
345                 error = event.get_error()
346                 iap_id = event.get_iap_id()
347                 bearer = event.get_bearer_type()
348
349                 if status == conic.STATUS_CONNECTED:
350                         self._deviceIsOnline = True
351                         if self._initDone:
352                                 self.switch_ui(self._defaultUIName)
353                 elif status == conic.STATUS_DISCONNECTED:
354                         self._deviceIsOnline = False
355                         if self._initDone:
356                                 self.switch_ui(self._fallbackUIName)
357
358         def _on_window_state_change(self, widget, event, *args):
359                 """
360                 @note Hildon specific
361                 """
362                 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
363                         self._isFullScreen = True
364                 else:
365                         self._isFullScreen = False
366
367         def _on_close(self, *args, **kwds):
368                 try:
369                         if self._osso is not None:
370                                 self._osso.close()
371
372                         if self._initDone:
373                                 self._save_settings()
374                 finally:
375                         gtk.main_quit()
376
377         def _on_key_press(self, widget, event, *args):
378                 """
379                 @note Hildon specific
380                 """
381                 if event.keyval == gtk.keysyms.F6:
382                         if self._isFullScreen:
383                                 self.__window.unfullscreen()
384                         else:
385                                 self.__window.fullscreen()
386
387         def _on_logout(self, *args):
388                 if not self._initDone:
389                         return
390
391                 self._todoUI.logout()
392                 self.switch_ui(self._fallbackUIName)
393
394         def _on_prefs(self, *args):
395                 if not self._initDone:
396                         return
397
398                 self._prefsDialog.enable()
399                 try:
400                         self._prefsDialog.run(self)
401                 finally:
402                         self._prefsDialog.disable()
403
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")
409                 dlg.set_comments("")
410                 dlg.set_website("http://doneit.garage.maemo.org")
411                 dlg.set_authors(["Ed Page"])
412                 dlg.run()
413                 dlg.destroy()
414
415
416 def run_doctest():
417         import doctest
418
419         failureCount, testCount = doctest.testmod()
420         if not failureCount:
421                 print "Tests Successful"
422                 sys.exit(0)
423         else:
424                 sys.exit(1)
425
426
427 def run_doneit():
428         gtk.gdk.threads_init()
429
430         if hildon is not None:
431                 gtk.set_application_name(DoneIt.__pretty_app_name__)
432         handle = DoneIt()
433         gtk.main()
434
435
436 class DummyOptions(object):
437
438         def __init__(self):
439                 self.test = False
440
441
442 if __name__ == "__main__":
443         if len(sys.argv) > 1:
444                 try:
445                         import optparse
446                 except ImportError:
447                         optparse = None
448
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()
453         else:
454                 commandOptions = DummyOptions()
455                 commandArgs = []
456
457         if commandOptions.test:
458                 run_doctest()
459         else:
460                 run_doneit()