Fixing the phone type selector
[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, services, parentWindow = None):
126                 """
127                 @note UI Thread
128                 """
129                 if parentWindow is None:
130                         parentWindow = self._parentWindow
131
132                 self._serviceList.clear()
133                 for serviceIdserviceName in services.iteritems():
134                         self._serviceList.append(serviceIdserviceName)
135                 self._serviceCombo.set_active(0)
136                 self._serviceCombo.show()
137
138                 try:
139                         self._dialog.set_transient_for(parentWindow)
140                         self._dialog.set_default_response(gtk.RESPONSE_OK)
141                         response = self._dialog.run()
142                         if response != gtk.RESPONSE_OK:
143                                 raise RuntimeError("Login Cancelled")
144
145                         username = self._usernameEntry.get_text()
146                         password = self._passwordEntry.get_text()
147                         self._passwordEntry.set_text("")
148                 finally:
149                         self._dialog.hide()
150
151                 itr = self._serviceCombo.get_active_iter()
152                 serviceId = int(self._serviceList.get_value(itr, 0))
153                 self._serviceList.clear()
154                 return serviceId, username, password
155
156         def _on_loginbutton_clicked(self, *args):
157                 self._dialog.response(gtk.RESPONSE_OK)
158
159         def _on_loginclose_clicked(self, *args):
160                 self._dialog.response(gtk.RESPONSE_CANCEL)
161
162
163 class ErrorDisplay(object):
164
165         def __init__(self, widgetTree):
166                 super(ErrorDisplay, self).__init__()
167                 self.__errorBox = widgetTree.get_widget("errorEventBox")
168                 self.__errorDescription = widgetTree.get_widget("errorDescription")
169                 self.__errorClose = widgetTree.get_widget("errorClose")
170                 self.__parentBox = self.__errorBox.get_parent()
171
172                 self.__errorBox.connect("button_release_event", self._on_close)
173
174                 self.__messages = []
175                 self.__parentBox.remove(self.__errorBox)
176
177         def push_message_with_lock(self, message):
178                 with gtk_lock():
179                         self.push_message(message)
180
181         def push_message(self, message):
182                 if 0 < len(self.__messages):
183                         self.__messages.append(message)
184                 else:
185                         self.__show_message(message)
186
187         def push_exception_with_lock(self, exception = None):
188                 with gtk_lock():
189                         self.push_exception(exception)
190
191         def push_exception(self, exception = None):
192                 if exception is None:
193                         userMessage = str(sys.exc_value)
194                         warningMessage = str(traceback.format_exc())
195                 else:
196                         userMessage = str(exception)
197                         warningMessage = str(exception)
198                 self.push_message(userMessage)
199                 warnings.warn(warningMessage, stacklevel=3)
200
201         def pop_message(self):
202                 if 0 < len(self.__messages):
203                         self.__show_message(self.__messages[0])
204                         del self.__messages[0]
205                 else:
206                         self.__hide_message()
207
208         def _on_close(self, *args):
209                 self.pop_message()
210
211         def __show_message(self, message):
212                 self.__errorDescription.set_text(message)
213                 self.__parentBox.pack_start(self.__errorBox, False, False)
214                 self.__parentBox.reorder_child(self.__errorBox, 1)
215
216         def __hide_message(self):
217                 self.__errorDescription.set_text("")
218                 self.__parentBox.remove(self.__errorBox)
219
220
221 class DummyErrorDisplay(object):
222
223         def __init__(self):
224                 super(DummyErrorDisplay, self).__init__()
225
226                 self.__messages = []
227
228         def push_message_with_lock(self, message):
229                 self.push_message(message)
230
231         def push_message(self, message):
232                 if 0 < len(self.__messages):
233                         self.__messages.append(message)
234                 else:
235                         self.__show_message(message)
236
237         def push_exception(self, exception = None):
238                 if exception is None:
239                         warningMessage = traceback.format_exc()
240                 else:
241                         warningMessage = exception
242                 warnings.warn(warningMessage, stacklevel=3)
243
244         def pop_message(self):
245                 if 0 < len(self.__messages):
246                         self.__show_message(self.__messages[0])
247                         del self.__messages[0]
248
249         def __show_message(self, message):
250                 warnings.warn(message, stacklevel=2)
251
252
253 class MessageBox(gtk.MessageDialog):
254
255         def __init__(self, message):
256                 parent = None
257                 gtk.MessageDialog.__init__(
258                         self,
259                         parent,
260                         gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
261                         gtk.MESSAGE_ERROR,
262                         gtk.BUTTONS_OK,
263                         message,
264                 )
265                 self.set_default_response(gtk.RESPONSE_OK)
266                 self.connect('response', self._handle_clicked)
267
268         def _handle_clicked(self, *args):
269                 self.destroy()
270
271
272 class MessageBox2(gtk.MessageDialog):
273
274         def __init__(self, message):
275                 parent = None
276                 gtk.MessageDialog.__init__(
277                         self,
278                         parent,
279                         gtk.DIALOG_DESTROY_WITH_PARENT,
280                         gtk.MESSAGE_ERROR,
281                         gtk.BUTTONS_OK,
282                         message,
283                 )
284                 self.set_default_response(gtk.RESPONSE_OK)
285                 self.connect('response', self._handle_clicked)
286
287         def _handle_clicked(self, *args):
288                 self.destroy()
289
290
291 class PopupCalendar(object):
292
293         def __init__(self, parent, displayDate, title = ""):
294                 self._displayDate = displayDate
295
296                 self._calendar = gtk.Calendar()
297                 self._calendar.select_month(self._displayDate.month, self._displayDate.year)
298                 self._calendar.select_day(self._displayDate.day)
299                 self._calendar.set_display_options(
300                         gtk.CALENDAR_SHOW_HEADING |
301                         gtk.CALENDAR_SHOW_DAY_NAMES |
302                         gtk.CALENDAR_NO_MONTH_CHANGE |
303                         0
304                 )
305                 self._calendar.connect("day-selected", self._on_day_selected)
306
307                 self._popupWindow = gtk.Window()
308                 self._popupWindow.set_title(title)
309                 self._popupWindow.add(self._calendar)
310                 self._popupWindow.set_transient_for(parent)
311                 self._popupWindow.set_modal(True)
312                 self._popupWindow.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
313                 self._popupWindow.set_skip_pager_hint(True)
314                 self._popupWindow.set_skip_taskbar_hint(True)
315
316         def run(self):
317                 self._popupWindow.show_all()
318
319         def _on_day_selected(self, *args):
320                 try:
321                         self._calendar.select_month(self._displayDate.month, self._displayDate.year)
322                         self._calendar.select_day(self._displayDate.day)
323                 except StandardError, e:
324                         warnings.warn(e.message)
325
326
327 class QuickAddView(object):
328
329         def __init__(self, widgetTree, errorDisplay, signalSink, prefix):
330                 self._errorDisplay = errorDisplay
331                 self._manager = None
332                 self._signalSink = signalSink
333
334                 self._clipboard = gtk.clipboard_get()
335
336                 self._taskNameEntry = widgetTree.get_widget(prefix+"-nameEntry")
337                 self._addTaskButton = widgetTree.get_widget(prefix+"-addButton")
338                 self._pasteTaskNameButton = widgetTree.get_widget(prefix+"-pasteNameButton")
339                 self._clearTaskNameButton = widgetTree.get_widget(prefix+"-clearNameButton")
340                 self._onAddId = None
341                 self._onAddClickedId = None
342                 self._onAddReleasedId = None
343                 self._addToEditTimerId = None
344                 self._onClearId = None
345                 self._onPasteId = None
346
347         def enable(self, manager):
348                 self._manager = manager
349
350                 self._onAddId = self._addTaskButton.connect("clicked", self._on_add)
351                 self._onAddClickedId = self._addTaskButton.connect("pressed", self._on_add_pressed)
352                 self._onAddReleasedId = self._addTaskButton.connect("released", self._on_add_released)
353                 self._onPasteId = self._pasteTaskNameButton.connect("clicked", self._on_paste)
354                 self._onClearId = self._clearTaskNameButton.connect("clicked", self._on_clear)
355
356         def disable(self):
357                 self._manager = None
358
359                 self._addTaskButton.disconnect(self._onAddId)
360                 self._addTaskButton.disconnect(self._onAddClickedId)
361                 self._addTaskButton.disconnect(self._onAddReleasedId)
362                 self._pasteTaskNameButton.disconnect(self._onPasteId)
363                 self._clearTaskNameButton.disconnect(self._onClearId)
364
365         def set_addability(self, addability):
366                 self._addTaskButton.set_sensitive(addability)
367
368         def _on_add(self, *args):
369                 try:
370                         name = self._taskNameEntry.get_text()
371                         self._taskNameEntry.set_text("")
372
373                         self._signalSink.stage.send(("add", name))
374                 except StandardError, e:
375                         self._errorDisplay.push_exception()
376
377         def _on_add_edit(self, *args):
378                 try:
379                         name = self._taskNameEntry.get_text()
380                         self._taskNameEntry.set_text("")
381
382                         self._signalSink.stage.send(("add-edit", name))
383                 except StandardError, e:
384                         self._errorDisplay.push_exception()
385
386         def _on_add_pressed(self, widget):
387                 try:
388                         self._addToEditTimerId = gobject.timeout_add(1000, self._on_add_edit)
389                 except StandardError, e:
390                         self._errorDisplay.push_exception()
391
392         def _on_add_released(self, widget):
393                 try:
394                         if self._addToEditTimerId is not None:
395                                 gobject.source_remove(self._addToEditTimerId)
396                         self._addToEditTimerId = None
397                 except StandardError, e:
398                         self._errorDisplay.push_exception()
399
400         def _on_paste(self, *args):
401                 try:
402                         entry = self._taskNameEntry.get_text()
403                         addedText = self._clipboard.wait_for_text()
404                         if addedText:
405                                 entry += addedText
406                         self._taskNameEntry.set_text(entry)
407                 except StandardError, e:
408                         self._errorDisplay.push_exception()
409
410         def _on_clear(self, *args):
411                 try:
412                         self._taskNameEntry.set_text("")
413                 except StandardError, e:
414                         self._errorDisplay.push_exception()
415
416
417 if __name__ == "__main__":
418         if False:
419                 import datetime
420                 cal = PopupCalendar(None, datetime.datetime.now())
421                 cal._popupWindow.connect("destroy", lambda w: gtk.main_quit())
422                 cal.run()
423
424         gtk.main()