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