52dcd5dd1d730b038f89a1235636ab008466743b
[ejpi] / src / gtk_toolbox.py
1 #!/usr/bin/python
2
3 import sys
4 import traceback
5 import functools
6 import contextlib
7 import warnings
8
9 import gobject
10 import gtk
11
12
13 @contextlib.contextmanager
14 def gtk_lock():
15         gtk.gdk.threads_enter()
16         try:
17                 yield
18         finally:
19                 gtk.gdk.threads_leave()
20
21
22 def make_idler(func):
23         """
24         Decorator that makes a generator-function into a function that will continue execution on next call
25         """
26         a = []
27
28         @functools.wraps(func)
29         def decorated_func(*args, **kwds):
30                 if not a:
31                         a.append(func(*args, **kwds))
32                 try:
33                         a[0].next()
34                         return True
35                 except StopIteration:
36                         del a[:]
37                         return False
38
39         return decorated_func
40
41
42 def asynchronous_gtk_message(original_func):
43         """
44         @note Idea came from http://www.aclevername.com/articles/python-webgui/
45         """
46
47         def execute(allArgs):
48                 args, kwargs = allArgs
49                 original_func(*args, **kwargs)
50
51         @functools.wraps(original_func)
52         def delayed_func(*args, **kwargs):
53                 gobject.idle_add(execute, (args, kwargs))
54
55         return delayed_func
56
57
58 def synchronous_gtk_message(original_func):
59         """
60         @note Idea came from http://www.aclevername.com/articles/python-webgui/
61         """
62
63         @functools.wraps(original_func)
64         def immediate_func(*args, **kwargs):
65                 with gtk_lock():
66                         return original_func(*args, **kwargs)
67
68         return immediate_func
69
70
71 class ErrorDisplay(object):
72
73         def __init__(self, widgetTree):
74                 super(ErrorDisplay, self).__init__()
75                 self.__errorBox = widgetTree.get_widget("errorEventBox")
76                 self.__errorDescription = widgetTree.get_widget("errorDescription")
77                 self.__errorClose = widgetTree.get_widget("errorClose")
78                 self.__parentBox = self.__errorBox.get_parent()
79
80                 self.__errorBox.connect("button_release_event", self._on_close)
81
82                 self.__messages = []
83                 self.__parentBox.remove(self.__errorBox)
84
85         def push_message_with_lock(self, message):
86                 gtk.gdk.threads_enter()
87                 try:
88                         self.push_message(message)
89                 finally:
90                         gtk.gdk.threads_leave()
91
92         def push_message(self, message):
93                 if 0 < len(self.__messages):
94                         self.__messages.append(message)
95                 else:
96                         self.__show_message(message)
97
98         def push_exception(self, exception = None):
99                 if exception is None:
100                         userMessage = str(sys.exc_value)
101                         warningMessage = str(traceback.format_exc())
102                 else:
103                         userMessage = str(exception)
104                         warningMessage = str(exception)
105                 self.push_message(userMessage)
106                 warnings.warn(warningMessage, stacklevel=3)
107
108         def pop_message(self):
109                 if 0 < len(self.__messages):
110                         self.__show_message(self.__messages[0])
111                         del self.__messages[0]
112                 else:
113                         self.__hide_message()
114
115         def _on_close(self, *args):
116                 self.pop_message()
117
118         def __show_message(self, message):
119                 self.__errorDescription.set_text(message)
120                 self.__parentBox.pack_start(self.__errorBox, False, False)
121                 self.__parentBox.reorder_child(self.__errorBox, 1)
122
123         def __hide_message(self):
124                 self.__errorDescription.set_text("")
125                 self.__parentBox.remove(self.__errorBox)
126
127
128 class DummyErrorDisplay(object):
129
130         def __init__(self):
131                 super(DummyErrorDisplay, self).__init__()
132
133                 self.__messages = []
134
135         def push_message_with_lock(self, message):
136                 self.push_message(message)
137
138         def push_message(self, message):
139                 if 0 < len(self.__messages):
140                         self.__messages.append(message)
141                 else:
142                         self.__show_message(message)
143
144         def push_exception(self, exception = None):
145                 if exception is None:
146                         warningMessage = traceback.format_exc()
147                 else:
148                         warningMessage = exception
149                 warnings.warn(warningMessage, stacklevel=3)
150
151         def pop_message(self):
152                 if 0 < len(self.__messages):
153                         self.__show_message(self.__messages[0])
154                         del self.__messages[0]
155
156         def __show_message(self, message):
157                 warnings.warn(message, stacklevel=2)
158
159
160 class MessageBox(gtk.MessageDialog):
161
162         def __init__(self, message):
163                 parent = None
164                 gtk.MessageDialog.__init__(
165                         self,
166                         parent,
167                         gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
168                         gtk.MESSAGE_ERROR,
169                         gtk.BUTTONS_OK,
170                         message,
171                 )
172                 self.set_default_response(gtk.RESPONSE_OK)
173                 self.connect('response', self._handle_clicked)
174
175         def _handle_clicked(self, *args):
176                 self.destroy()
177
178
179 class MessageBox2(gtk.MessageDialog):
180
181         def __init__(self, message):
182                 parent = None
183                 gtk.MessageDialog.__init__(
184                         self,
185                         parent,
186                         gtk.DIALOG_DESTROY_WITH_PARENT,
187                         gtk.MESSAGE_ERROR,
188                         gtk.BUTTONS_OK,
189                         message,
190                 )
191                 self.set_default_response(gtk.RESPONSE_OK)
192                 self.connect('response', self._handle_clicked)
193
194         def _handle_clicked(self, *args):
195                 self.destroy()
196
197
198 class PopupCalendar(object):
199
200         def __init__(self, parent, displayDate, title = ""):
201                 self._displayDate = displayDate
202
203                 self._calendar = gtk.Calendar()
204                 self._calendar.select_month(self._displayDate.month, self._displayDate.year)
205                 self._calendar.select_day(self._displayDate.day)
206                 self._calendar.set_display_options(
207                         gtk.CALENDAR_SHOW_HEADING |
208                         gtk.CALENDAR_SHOW_DAY_NAMES |
209                         gtk.CALENDAR_NO_MONTH_CHANGE |
210                         0
211                 )
212                 self._calendar.connect("day-selected", self._on_day_selected)
213
214                 self._popupWindow = gtk.Window()
215                 self._popupWindow.set_title(title)
216                 self._popupWindow.add(self._calendar)
217                 self._popupWindow.set_transient_for(parent)
218                 self._popupWindow.set_modal(True)
219                 self._popupWindow.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
220                 self._popupWindow.set_skip_pager_hint(True)
221                 self._popupWindow.set_skip_taskbar_hint(True)
222
223         def run(self):
224                 self._popupWindow.show_all()
225
226         def _on_day_selected(self, *args):
227                 try:
228                         self._calendar.select_month(self._displayDate.month, self._displayDate.year)
229                         self._calendar.select_day(self._displayDate.day)
230                 except StandardError, e:
231                         warnings.warn(e.message)
232
233
234 class QuickAddView(object):
235
236         def __init__(self, widgetTree, errorDisplay, signalSink, prefix):
237                 self._errorDisplay = errorDisplay
238                 self._manager = None
239                 self._signalSink = signalSink
240
241                 self._clipboard = gtk.clipboard_get()
242
243                 self._taskNameEntry = widgetTree.get_widget(prefix+"-nameEntry")
244                 self._addTaskButton = widgetTree.get_widget(prefix+"-addButton")
245                 self._pasteTaskNameButton = widgetTree.get_widget(prefix+"-pasteNameButton")
246                 self._clearTaskNameButton = widgetTree.get_widget(prefix+"-clearNameButton")
247                 self._onAddId = None
248                 self._onAddClickedId = None
249                 self._onAddReleasedId = None
250                 self._addToEditTimerId = None
251                 self._onClearId = None
252                 self._onPasteId = None
253
254         def enable(self, manager):
255                 self._manager = manager
256
257                 self._onAddId = self._addTaskButton.connect("clicked", self._on_add)
258                 self._onAddClickedId = self._addTaskButton.connect("pressed", self._on_add_pressed)
259                 self._onAddReleasedId = self._addTaskButton.connect("released", self._on_add_released)
260                 self._onPasteId = self._pasteTaskNameButton.connect("clicked", self._on_paste)
261                 self._onClearId = self._clearTaskNameButton.connect("clicked", self._on_clear)
262
263         def disable(self):
264                 self._manager = None
265
266                 self._addTaskButton.disconnect(self._onAddId)
267                 self._addTaskButton.disconnect(self._onAddClickedId)
268                 self._addTaskButton.disconnect(self._onAddReleasedId)
269                 self._pasteTaskNameButton.disconnect(self._onPasteId)
270                 self._clearTaskNameButton.disconnect(self._onClearId)
271
272         def set_addability(self, addability):
273                 self._addTaskButton.set_sensitive(addability)
274
275         def _on_add(self, *args):
276                 try:
277                         name = self._taskNameEntry.get_text()
278                         self._taskNameEntry.set_text("")
279
280                         self._signalSink.stage.send(("add", name))
281                 except StandardError, e:
282                         self._errorDisplay.push_exception()
283
284         def _on_add_edit(self, *args):
285                 try:
286                         name = self._taskNameEntry.get_text()
287                         self._taskNameEntry.set_text("")
288
289                         self._signalSink.stage.send(("add-edit", name))
290                 except StandardError, e:
291                         self._errorDisplay.push_exception()
292
293         def _on_add_pressed(self, widget):
294                 try:
295                         self._addToEditTimerId = gobject.timeout_add(1000, self._on_add_edit)
296                 except StandardError, e:
297                         self._errorDisplay.push_exception()
298
299         def _on_add_released(self, widget):
300                 try:
301                         if self._addToEditTimerId is not None:
302                                 gobject.source_remove(self._addToEditTimerId)
303                         self._addToEditTimerId = None
304                 except StandardError, e:
305                         self._errorDisplay.push_exception()
306
307         def _on_paste(self, *args):
308                 try:
309                         entry = self._taskNameEntry.get_text()
310                         addedText = self._clipboard.wait_for_text()
311                         if addedText:
312                                 entry += addedText
313                         self._taskNameEntry.set_text(entry)
314                 except StandardError, e:
315                         self._errorDisplay.push_exception()
316
317         def _on_clear(self, *args):
318                 try:
319                         self._taskNameEntry.set_text("")
320                 except StandardError, e:
321                         self._errorDisplay.push_exception()
322
323
324 if __name__ == "__main__":
325         if True:
326                 win = gtk.Window()
327                 win.set_title("Tap'N'Hold")
328                 eventBox = gtk.EventBox()
329                 win.add(eventBox)
330
331                 context = ContextHandler(eventBox, coroutines.printer_sink())
332                 context.enable()
333                 win.connect("destroy", lambda w: gtk.main_quit())
334
335                 win.show_all()
336
337         if False:
338                 import datetime
339                 cal = PopupCalendar(None, datetime.datetime.now())
340                 cal._popupWindow.connect("destroy", lambda w: gtk.main_quit())
341                 cal.run()
342
343         gtk.main()