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