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