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