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