a3aee2c9ca7d48cb0799551b28852ed4ca0b58f0
[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 """
6
7 from __future__ import with_statement
8
9
10 import sys
11 import gc
12 import os
13 import threading
14 import warnings
15 import ConfigParser
16 import socket
17
18 import gtk
19 import gtk.glade
20
21 try:
22         import hildon
23 except ImportError:
24         hildon = None
25
26 import gtk_toolbox
27
28
29 socket.setdefaulttimeout(10)
30
31
32 class DoneIt(object):
33
34         __pretty_app_name__ = "DoneIt"
35         __app_name__ = "doneit"
36         __version__ = "0.3.1"
37         __app_magic__ = 0xdeadbeef
38
39         _glade_files = [
40                 '/usr/lib/doneit/doneit.glade',
41                 os.path.join(os.path.dirname(__file__), "doneit.glade"),
42                 os.path.join(os.path.dirname(__file__), "../lib/doneit.glade"),
43         ]
44
45         _user_data = os.path.expanduser("~/.%s/" % __app_name__)
46         _user_settings = "%s/settings.ini" % _user_data
47
48         def __init__(self):
49                 self._todoUIs = {}
50                 self._todoUI = None
51                 self._osso = None
52                 self._deviceIsOnline = True
53                 self._connection = None
54                 self._fallbackUIName = ""
55                 self._defaultUIName = ""
56
57                 for path in self._glade_files:
58                         if os.path.isfile(path):
59                                 self._widgetTree = gtk.glade.XML(path)
60                                 break
61                 else:
62                         self.display_error_message("Cannot find doneit.glade")
63                         gtk.main_quit()
64                 try:
65                         os.makedirs(self._user_data)
66                 except OSError, e:
67                         if e.errno != 17:
68                                 raise
69
70                 self._clipboard = gtk.clipboard_get()
71                 self.__window = self._widgetTree.get_widget("mainWindow")
72                 self.__errorDisplay = gtk_toolbox.ErrorDisplay(self._widgetTree)
73                 self._prefsDialog = gtk_toolbox.PreferencesDialog(self._widgetTree)
74
75                 self._app = None
76                 self._isFullScreen = False
77                 if hildon is not None:
78                         self._app = hildon.Program()
79                         self.__window = hildon.Window()
80                         self._widgetTree.get_widget("mainLayout").reparent(self.__window)
81                         self._app.add_window(self.__window)
82                         self._widgetTree.get_widget("usernameentry").set_property('hildon-input-mode', 7)
83                         self._widgetTree.get_widget("passwordentry").set_property('hildon-input-mode', 7|(1 << 29))
84
85                         gtkMenu = self._widgetTree.get_widget("mainMenubar")
86                         menu = gtk.Menu()
87                         for child in gtkMenu.get_children():
88                                 child.reparent(menu)
89                         self.__window.set_menu(menu)
90                         gtkMenu.destroy()
91
92                         self.__window.connect("key-press-event", self._on_key_press)
93                         self.__window.connect("window-state-event", self._on_window_state_change)
94                 else:
95                         pass # warnings.warn("No Hildon", UserWarning, 2)
96
97                 callbackMapping = {
98                         "on_doneit_quit": self._on_close,
99                         "on_about": self._on_about_activate,
100                 }
101                 self._widgetTree.signal_autoconnect(callbackMapping)
102
103                 if self.__window:
104                         if hildon is None:
105                                 self.__window.set_title("%s" % self.__pretty_app_name__)
106                         self.__window.connect("destroy", self._on_close)
107                         self.__window.show_all()
108
109                 backgroundSetup = threading.Thread(target=self._idle_setup)
110                 backgroundSetup.setDaemon(True)
111                 backgroundSetup.start()
112
113         def _idle_setup(self):
114                 # Barebones UI handlers
115                 import null_view
116                 with gtk_toolbox.gtk_lock():
117                         nullView = null_view.GtkNull(self._widgetTree)
118                         self._todoUIs[nullView.name()] = nullView
119                         self._todoUI = nullView
120                         self._todoUI.enable()
121                         self._fallbackUIName = nullView.name()
122
123                 # Setup maemo specifics
124                 try:
125                         import osso
126                 except ImportError:
127                         osso = None
128                 self._osso = None
129                 if osso is not None:
130                         self._osso = osso.Context(DoneIt.__app_name__, DoneIt.__version__, False)
131                         device = osso.DeviceState(self._osso)
132                         device.set_device_state_callback(self._on_device_state_change, 0)
133                 else:
134                         pass # warnings.warn("No OSSO", UserWarning, 2)
135
136                 try:
137                         import conic
138                 except ImportError:
139                         conic = None
140                 self._connection = None
141                 if conic is not None:
142                         self._connection = conic.Connection()
143                         self._connection.connect("connection-event", self._on_connection_change, self.__app_magic__)
144                         self._connection.request_connection(conic.CONNECT_FLAG_NONE)
145                 else:
146                         pass # warnings.warn("No Internet Connectivity API ", UserWarning)
147
148                 # Setup costly backends
149                 import rtm_view
150                 with gtk_toolbox.gtk_lock():
151                         rtmView = rtm_view.GtkRtMilk(self._widgetTree, self.__errorDisplay)
152                 self._todoUIs[rtmView.name()] = rtmView
153                 self._defaultUIName = rtmView.name()
154
155                 config = ConfigParser.SafeConfigParser()
156                 config.read(self._user_settings)
157                 with gtk_toolbox.gtk_lock():
158                         self.load_settings(config)
159                         self._widgetTree.get_widget("connectMenuItem").connect("activate", lambda *args: self.switch_ui(self._defaultUIName))
160                         self._widgetTree.get_widget("preferencesMenuItem").connect("activate", self._on_prefs)
161
162         def display_error_message(self, msg):
163                 """
164                 @note UI Thread
165                 """
166                 error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
167
168                 def close(dialog, response, editor):
169                         editor.about_dialog = None
170                         dialog.destroy()
171                 error_dialog.connect("response", close, self)
172                 error_dialog.run()
173
174         def load_settings(self, config):
175                 """
176                 @note UI Thread
177                 """
178                 for todoUI in self._todoUIs.itervalues():
179                         try:
180                                 todoUI.load_settings(config)
181                         except ConfigParser.NoSectionError, e:
182                                 warnings.warn(
183                                         "Settings file %s is missing section %s" % (
184                                                 self._user_settings,
185                                                 e.section,
186                                         ),
187                                         stacklevel=2
188                                 )
189
190                 try:
191                         activeUIName = config.get(self.__pretty_app_name__, "active")
192                 except ConfigParser.NoSectionError, e:
193                         activeUIName = ""
194                         warnings.warn(
195                                 "Settings file %s is missing section %s" % (
196                                         self._user_settings,
197                                         e.section,
198                                 ),
199                                 stacklevel=2
200                         )
201
202                 try:
203                         self.switch_ui(activeUIName)
204                 except KeyError, e:
205                         self.switch_ui(self._defaultUIName)
206
207         def save_settings(self, config):
208                 """
209                 @note Thread Agnostic
210                 """
211                 config.add_section(self.__pretty_app_name__)
212                 config.set(self.__pretty_app_name__, "active", self._todoUI.name())
213
214                 for todoUI in self._todoUIs.itervalues():
215                         todoUI.save_settings(config)
216
217         def get_uis(self):
218                 return (ui for ui in self._todoUIs.iteritems())
219
220         def get_default_ui(self):
221                 return self._defaultUIName
222
223         def switch_ui(self, uiName):
224                 """
225                 @note UI Thread
226                 """
227                 newActiveUI = self._todoUIs[uiName]
228                 try:
229                         newActiveUI.login()
230                 except RuntimeError:
231                         return # User cancelled the operation
232
233                 self._todoUI.disable()
234                 self._todoUI = newActiveUI
235                 self._todoUI.enable()
236
237                 if uiName != self._fallbackUIName:
238                         self._defaultUIName = uiName
239
240         def _save_settings(self):
241                 """
242                 @note Thread Agnostic
243                 """
244                 config = ConfigParser.SafeConfigParser()
245                 self.save_settings(config)
246                 with open(self._user_settings, "wb") as configFile:
247                         config.write(configFile)
248
249         def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
250                 """
251                 For system_inactivity, we have no background tasks to pause
252
253                 @note Hildon specific
254                 """
255                 if memory_low:
256                         gc.collect()
257
258                 if save_unsaved_data or shutdown:
259                         self._save_settings()
260
261         def _on_connection_change(self, connection, event, magicIdentifier):
262                 """
263                 @note Hildon specific
264                 """
265                 import conic
266
267                 status = event.get_status()
268                 error = event.get_error()
269                 iap_id = event.get_iap_id()
270                 bearer = event.get_bearer_type()
271
272                 if status == conic.STATUS_CONNECTED:
273                         self._deviceIsOnline = True
274                         self.switch_ui(self._defaultUIName)
275                 elif status == conic.STATUS_DISCONNECTED:
276                         self._deviceIsOnline = False
277                         self.switch_ui(self._fallbackUIName)
278
279         def _on_window_state_change(self, widget, event, *args):
280                 """
281                 @note Hildon specific
282                 """
283                 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
284                         self._isFullScreen = True
285                 else:
286                         self._isFullScreen = False
287
288         def _on_close(self, *args, **kwds):
289                 try:
290                         if self._osso is not None:
291                                 self._osso.close()
292
293                         self._save_settings()
294                 finally:
295                         gtk.main_quit()
296
297         def _on_paste(self, *args):
298                 pass
299
300         def _on_key_press(self, widget, event, *args):
301                 """
302                 @note Hildon specific
303                 """
304                 if event.keyval == gtk.keysyms.F6:
305                         if self._isFullScreen:
306                                 self.__window.unfullscreen()
307                         else:
308                                 self.__window.fullscreen()
309
310         def _on_logout(self, *args):
311                 self._todoUI.logout()
312                 self.switch_ui(self._fallbackUIName)
313
314         def _on_prefs(self, *args):
315                 self._prefsDialog.enable()
316                 try:
317                         self._prefsDialog.run(self)
318                 finally:
319                         self._prefsDialog.disable()
320
321         def _on_about_activate(self, *args):
322                 dlg = gtk.AboutDialog()
323                 dlg.set_name(self.__pretty_app_name__)
324                 dlg.set_version(self.__version__)
325                 dlg.set_copyright("Copyright 2008 - LGPL")
326                 dlg.set_comments("")
327                 dlg.set_website("http://doneit.garage.maemo.org")
328                 dlg.set_authors(["Ed Page"])
329                 dlg.run()
330                 dlg.destroy()
331
332
333 def run_doctest():
334         import doctest
335
336         failureCount, testCount = doctest.testmod()
337         if not failureCount:
338                 print "Tests Successful"
339                 sys.exit(0)
340         else:
341                 sys.exit(1)
342
343
344 def run_doneit():
345         gtk.gdk.threads_init()
346
347         if hildon is not None:
348                 gtk.set_application_name(DoneIt.__pretty_app_name__)
349         handle = DoneIt()
350         gtk.main()
351
352
353 class DummyOptions(object):
354
355         def __init__(self):
356                 self.test = False
357
358
359 if __name__ == "__main__":
360         if len(sys.argv) > 1:
361                 try:
362                         import optparse
363                 except ImportError:
364                         optparse = None
365
366                 if optparse is not None:
367                         parser = optparse.OptionParser()
368                         parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
369                         (commandOptions, commandArgs) = parser.parse_args()
370         else:
371                 commandOptions = DummyOptions()
372                 commandArgs = []
373
374         if commandOptions.test:
375                 run_doctest()
376         else:
377                 run_doneit()