More work to get the backend tests to work
[gc-dialer] / src / gtk_toolbox.py
1 #!/usr/bin/python
2
3 from __future__ import with_statement
4
5 import sys
6 import traceback
7 import functools
8 import contextlib
9 import warnings
10
11 import gobject
12 import gtk
13
14
15 @contextlib.contextmanager
16 def gtk_lock():
17         gtk.gdk.threads_enter()
18         try:
19                 yield
20         finally:
21                 gtk.gdk.threads_leave()
22
23
24 def find_parent_window(widget):
25         while True:
26                 parent = widget.get_parent()
27                 if isinstance(parent, gtk.Window):
28                         return parent
29                 widget = parent
30
31
32 def make_idler(func):
33         """
34         Decorator that makes a generator-function into a function that will continue execution on next call
35         """
36         a = []
37
38         @functools.wraps(func)
39         def decorated_func(*args, **kwds):
40                 if not a:
41                         a.append(func(*args, **kwds))
42                 try:
43                         a[0].next()
44                         return True
45                 except StopIteration:
46                         del a[:]
47                         return False
48
49         return decorated_func
50
51
52 def asynchronous_gtk_message(original_func):
53         """
54         @note Idea came from http://www.aclevername.com/articles/python-webgui/
55         """
56
57         def execute(allArgs):
58                 args, kwargs = allArgs
59                 with gtk_lock():
60                         original_func(*args, **kwargs)
61                 return False
62
63         @functools.wraps(original_func)
64         def delayed_func(*args, **kwargs):
65                 gobject.idle_add(execute, (args, kwargs))
66
67         return delayed_func
68
69
70 def synchronous_gtk_message(original_func):
71         """
72         @note Idea came from http://www.aclevername.com/articles/python-webgui/
73         """
74
75         @functools.wraps(original_func)
76         def immediate_func(*args, **kwargs):
77                 with gtk_lock():
78                         return original_func(*args, **kwargs)
79
80         return immediate_func
81
82
83 class LoginWindow(object):
84
85         def __init__(self, widgetTree):
86                 """
87                 @note Thread agnostic
88                 """
89                 self._dialog = widgetTree.get_widget("loginDialog")
90                 self._parentWindow = widgetTree.get_widget("mainWindow")
91                 self._serviceCombo = widgetTree.get_widget("serviceCombo")
92                 self._usernameEntry = widgetTree.get_widget("usernameentry")
93                 self._passwordEntry = widgetTree.get_widget("passwordentry")
94
95                 self._serviceList = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_STRING)
96                 self._serviceCombo.set_model(self._serviceList)
97                 cell = gtk.CellRendererText()
98                 self._serviceCombo.pack_start(cell, True)
99                 self._serviceCombo.add_attribute(cell, 'text', 1)
100                 self._serviceCombo.set_active(0)
101
102                 callbackMapping = {
103                         "on_loginbutton_clicked": self._on_loginbutton_clicked,
104                         "on_loginclose_clicked": self._on_loginclose_clicked,
105                 }
106                 widgetTree.signal_autoconnect(callbackMapping)
107
108         def request_credentials(self, parentWindow = None):
109                 """
110                 @note UI Thread
111                 """
112                 if parentWindow is None:
113                         parentWindow = self._parentWindow
114
115                 self._serviceCombo.hide()
116                 self._serviceList.clear()
117
118                 try:
119                         self._dialog.set_transient_for(parentWindow)
120                         self._dialog.set_default_response(gtk.RESPONSE_OK)
121                         response = self._dialog.run()
122                         if response != gtk.RESPONSE_OK:
123                                 raise RuntimeError("Login Cancelled")
124
125                         username = self._usernameEntry.get_text()
126                         password = self._passwordEntry.get_text()
127                         self._passwordEntry.set_text("")
128                 finally:
129                         self._dialog.hide()
130
131                 return username, password
132
133         def request_credentials_from(self,
134                 services,
135                 parentWindow = None,
136                 defaultCredentials = ("", "")
137         ):
138                 """
139                 @note UI Thread
140                 """
141                 if parentWindow is None:
142                         parentWindow = self._parentWindow
143
144                 self._serviceList.clear()
145                 for serviceIdserviceName in services.iteritems():
146                         self._serviceList.append(serviceIdserviceName)
147                 self._serviceCombo.set_active(0)
148                 self._serviceCombo.show()
149
150                 self._usernameEntry.set_text(defaultCredentials[0])
151                 self._passwordEntry.set_text(defaultCredentials[1])
152
153                 try:
154                         self._dialog.set_transient_for(parentWindow)
155                         self._dialog.set_default_response(gtk.RESPONSE_OK)
156                         response = self._dialog.run()
157                         if response != gtk.RESPONSE_OK:
158                                 raise RuntimeError("Login Cancelled")
159
160                         username = self._usernameEntry.get_text()
161                         password = self._passwordEntry.get_text()
162                 finally:
163                         self._dialog.hide()
164
165                 itr = self._serviceCombo.get_active_iter()
166                 serviceId = int(self._serviceList.get_value(itr, 0))
167                 self._serviceList.clear()
168                 return serviceId, username, password
169
170         def _on_loginbutton_clicked(self, *args):
171                 self._dialog.response(gtk.RESPONSE_OK)
172
173         def _on_loginclose_clicked(self, *args):
174                 self._dialog.response(gtk.RESPONSE_CANCEL)
175
176
177 class ErrorDisplay(object):
178
179         def __init__(self, widgetTree):
180                 super(ErrorDisplay, self).__init__()
181                 self.__errorBox = widgetTree.get_widget("errorEventBox")
182                 self.__errorDescription = widgetTree.get_widget("errorDescription")
183                 self.__errorClose = widgetTree.get_widget("errorClose")
184                 self.__parentBox = self.__errorBox.get_parent()
185
186                 self.__errorBox.connect("button_release_event", self._on_close)
187
188                 self.__messages = []
189                 self.__parentBox.remove(self.__errorBox)
190
191         def push_message_with_lock(self, message):
192                 with gtk_lock():
193                         self.push_message(message)
194
195         def push_message(self, message):
196                 if 0 < len(self.__messages):
197                         self.__messages.append(message)
198                 else:
199                         self.__show_message(message)
200
201         def push_exception_with_lock(self, exception = None):
202                 with gtk_lock():
203                         self.push_exception(exception)
204
205         def push_exception(self, exception = None):
206                 if exception is None:
207                         userMessage = str(sys.exc_value)
208                         warningMessage = str(traceback.format_exc())
209                 else:
210                         userMessage = str(exception)
211                         warningMessage = str(exception)
212                 self.push_message(userMessage)
213                 warnings.warn(warningMessage, stacklevel=3)
214
215         def pop_message(self):
216                 if 0 < len(self.__messages):
217                         self.__show_message(self.__messages[0])
218                         del self.__messages[0]
219                 else:
220                         self.__hide_message()
221
222         def _on_close(self, *args):
223                 self.pop_message()
224
225         def __show_message(self, message):
226                 self.__errorDescription.set_text(message)
227                 self.__parentBox.pack_start(self.__errorBox, False, False)
228                 self.__parentBox.reorder_child(self.__errorBox, 1)
229
230         def __hide_message(self):
231                 self.__errorDescription.set_text("")
232                 self.__parentBox.remove(self.__errorBox)
233
234
235 class DummyErrorDisplay(object):
236
237         def __init__(self):
238                 super(DummyErrorDisplay, self).__init__()
239
240                 self.__messages = []
241
242         def push_message_with_lock(self, message):
243                 self.push_message(message)
244
245         def push_message(self, message):
246                 if 0 < len(self.__messages):
247                         self.__messages.append(message)
248                 else:
249                         self.__show_message(message)
250
251         def push_exception(self, exception = None):
252                 if exception is None:
253                         warningMessage = traceback.format_exc()
254                 else:
255                         warningMessage = exception
256                 warnings.warn(warningMessage, stacklevel=3)
257
258         def pop_message(self):
259                 if 0 < len(self.__messages):
260                         self.__show_message(self.__messages[0])
261                         del self.__messages[0]
262
263         def __show_message(self, message):
264                 warnings.warn(message, stacklevel=2)
265
266
267 class MessageBox(gtk.MessageDialog):
268
269         def __init__(self, message):
270                 parent = None
271                 gtk.MessageDialog.__init__(
272                         self,
273                         parent,
274                         gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
275                         gtk.MESSAGE_ERROR,
276                         gtk.BUTTONS_OK,
277                         message,
278                 )
279                 self.set_default_response(gtk.RESPONSE_OK)
280                 self.connect('response', self._handle_clicked)
281
282         def _handle_clicked(self, *args):
283                 self.destroy()
284
285
286 class MessageBox2(gtk.MessageDialog):
287
288         def __init__(self, message):
289                 parent = None
290                 gtk.MessageDialog.__init__(
291                         self,
292                         parent,
293                         gtk.DIALOG_DESTROY_WITH_PARENT,
294                         gtk.MESSAGE_ERROR,
295                         gtk.BUTTONS_OK,
296                         message,
297                 )
298                 self.set_default_response(gtk.RESPONSE_OK)
299                 self.connect('response', self._handle_clicked)
300
301         def _handle_clicked(self, *args):
302                 self.destroy()
303
304
305 class PopupCalendar(object):
306
307         def __init__(self, parent, displayDate, title = ""):
308                 self._displayDate = displayDate
309
310                 self._calendar = gtk.Calendar()
311                 self._calendar.select_month(self._displayDate.month, self._displayDate.year)
312                 self._calendar.select_day(self._displayDate.day)
313                 self._calendar.set_display_options(
314                         gtk.CALENDAR_SHOW_HEADING |
315                         gtk.CALENDAR_SHOW_DAY_NAMES |
316                         gtk.CALENDAR_NO_MONTH_CHANGE |
317                         0
318                 )
319                 self._calendar.connect("day-selected", self._on_day_selected)
320
321                 self._popupWindow = gtk.Window()
322                 self._popupWindow.set_title(title)
323                 self._popupWindow.add(self._calendar)
324                 self._popupWindow.set_transient_for(parent)
325                 self._popupWindow.set_modal(True)
326                 self._popupWindow.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
327                 self._popupWindow.set_skip_pager_hint(True)
328                 self._popupWindow.set_skip_taskbar_hint(True)
329
330         def run(self):
331                 self._popupWindow.show_all()
332
333         def _on_day_selected(self, *args):
334                 try:
335                         self._calendar.select_month(self._displayDate.month, self._displayDate.year)
336                         self._calendar.select_day(self._displayDate.day)
337                 except StandardError, e:
338                         warnings.warn(e.message)
339
340
341 class QuickAddView(object):
342
343         def __init__(self, widgetTree, errorDisplay, signalSink, prefix):
344                 self._errorDisplay = errorDisplay
345                 self._manager = None
346                 self._signalSink = signalSink
347
348                 self._clipboard = gtk.clipboard_get()
349
350                 self._taskNameEntry = widgetTree.get_widget(prefix+"-nameEntry")
351                 self._addTaskButton = widgetTree.get_widget(prefix+"-addButton")
352                 self._pasteTaskNameButton = widgetTree.get_widget(prefix+"-pasteNameButton")
353                 self._clearTaskNameButton = widgetTree.get_widget(prefix+"-clearNameButton")
354                 self._onAddId = None
355                 self._onAddClickedId = None
356                 self._onAddReleasedId = None
357                 self._addToEditTimerId = None
358                 self._onClearId = None
359                 self._onPasteId = None
360
361         def enable(self, manager):
362                 self._manager = manager
363
364                 self._onAddId = self._addTaskButton.connect("clicked", self._on_add)
365                 self._onAddClickedId = self._addTaskButton.connect("pressed", self._on_add_pressed)
366                 self._onAddReleasedId = self._addTaskButton.connect("released", self._on_add_released)
367                 self._onPasteId = self._pasteTaskNameButton.connect("clicked", self._on_paste)
368                 self._onClearId = self._clearTaskNameButton.connect("clicked", self._on_clear)
369
370         def disable(self):
371                 self._manager = None
372
373                 self._addTaskButton.disconnect(self._onAddId)
374                 self._addTaskButton.disconnect(self._onAddClickedId)
375                 self._addTaskButton.disconnect(self._onAddReleasedId)
376                 self._pasteTaskNameButton.disconnect(self._onPasteId)
377                 self._clearTaskNameButton.disconnect(self._onClearId)
378
379         def set_addability(self, addability):
380                 self._addTaskButton.set_sensitive(addability)
381
382         def _on_add(self, *args):
383                 try:
384                         name = self._taskNameEntry.get_text()
385                         self._taskNameEntry.set_text("")
386
387                         self._signalSink.stage.send(("add", name))
388                 except StandardError, e:
389                         self._errorDisplay.push_exception()
390
391         def _on_add_edit(self, *args):
392                 try:
393                         name = self._taskNameEntry.get_text()
394                         self._taskNameEntry.set_text("")
395
396                         self._signalSink.stage.send(("add-edit", name))
397                 except StandardError, e:
398                         self._errorDisplay.push_exception()
399
400         def _on_add_pressed(self, widget):
401                 try:
402                         self._addToEditTimerId = gobject.timeout_add(1000, self._on_add_edit)
403                 except StandardError, e:
404                         self._errorDisplay.push_exception()
405
406         def _on_add_released(self, widget):
407                 try:
408                         if self._addToEditTimerId is not None:
409                                 gobject.source_remove(self._addToEditTimerId)
410                         self._addToEditTimerId = None
411                 except StandardError, e:
412                         self._errorDisplay.push_exception()
413
414         def _on_paste(self, *args):
415                 try:
416                         entry = self._taskNameEntry.get_text()
417                         addedText = self._clipboard.wait_for_text()
418                         if addedText:
419                                 entry += addedText
420                         self._taskNameEntry.set_text(entry)
421                 except StandardError, e:
422                         self._errorDisplay.push_exception()
423
424         def _on_clear(self, *args):
425                 try:
426                         self._taskNameEntry.set_text("")
427                 except StandardError, e:
428                         self._errorDisplay.push_exception()
429
430
431 if __name__ == "__main__":
432         if False:
433                 import datetime
434                 cal = PopupCalendar(None, datetime.datetime.now())
435                 cal._popupWindow.connect("destroy", lambda w: gtk.main_quit())
436                 cal.run()
437
438         gtk.main()