Removing useless doctest since its dependent on non-existent stuff
[doneit] / src / gtk_toolbox.py
1 #!/usr/bin/python
2
3 import warnings
4
5 import gobject
6 import gtk
7
8
9 class LoginWindow(object):
10
11         def __init__(self, widgetTree):
12                 """
13                 @note Thread agnostic
14                 """
15                 self._dialog = widgetTree.get_widget("loginDialog")
16                 self._parentWindow = widgetTree.get_widget("mainWindow")
17                 self._serviceCombo = widgetTree.get_widget("serviceCombo")
18                 self._usernameEntry = widgetTree.get_widget("usernameentry")
19                 self._passwordEntry = widgetTree.get_widget("passwordentry")
20
21                 self._serviceList = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_STRING)
22                 self._serviceCombo.set_model(self._serviceList)
23                 cell = gtk.CellRendererText()
24                 self._serviceCombo.pack_start(cell, True)
25                 self._serviceCombo.add_attribute(cell, 'text', 1)
26                 self._serviceCombo.set_active(0)
27
28                 callbackMapping = {
29                         "on_loginbutton_clicked": self._on_loginbutton_clicked,
30                         "on_loginclose_clicked": self._on_loginclose_clicked,
31                 }
32                 widgetTree.signal_autoconnect(callbackMapping)
33
34         def request_credentials(self, parentWindow = None):
35                 """
36                 @note UI Thread
37                 """
38                 if parentWindow is None:
39                         parentWindow = self._parentWindow
40
41                 self._serviceCombo.hide()
42                 self._serviceList.clear()
43
44                 try:
45                         self._dialog.set_transient_for(parentWindow)
46                         self._dialog.set_default_response(gtk.RESPONSE_OK)
47                         response = self._dialog.run()
48                         if response != gtk.RESPONSE_OK:
49                                 raise RuntimeError("Login Cancelled")
50
51                         username = self._usernameEntry.get_text()
52                         password = self._passwordEntry.get_text()
53                         self._passwordEntry.set_text("")
54                 finally:
55                         self._dialog.hide()
56
57                 return username, password
58
59         def request_credentials_from(self, services, parentWindow = None):
60                 """
61                 @note UI Thread
62                 """
63                 if parentWindow is None:
64                         parentWindow = self._parentWindow
65
66                 self._serviceList.clear()
67                 for serviceIdserviceName in services.iteritems():
68                         self._serviceList.append(serviceIdserviceName)
69                 self._serviceCombo.set_active(0)
70                 self._serviceCombo.show()
71
72                 try:
73                         self._dialog.set_transient_for(parentWindow)
74                         self._dialog.set_default_response(gtk.RESPONSE_OK)
75                         response = self._dialog.run()
76                         if response != gtk.RESPONSE_OK:
77                                 raise RuntimeError("Login Cancelled")
78
79                         username = self._usernameEntry.get_text()
80                         password = self._passwordEntry.get_text()
81                         self._passwordEntry.set_text("")
82                 finally:
83                         self._dialog.hide()
84
85                 itr = self._serviceCombo.get_active_iter()
86                 serviceId = int(self._serviceList.get_value(itr, 0))
87                 self._serviceList.clear()
88                 return serviceId, username, password
89
90         def _on_loginbutton_clicked(self, *args):
91                 self._dialog.response(gtk.RESPONSE_OK)
92
93         def _on_loginclose_clicked(self, *args):
94                 self._dialog.response(gtk.RESPONSE_CANCEL)
95
96
97 class ErrorDisplay(object):
98
99         def __init__(self, widgetTree):
100                 super(ErrorDisplay, self).__init__()
101                 self.__errorBox = widgetTree.get_widget("errorEventBox")
102                 self.__errorDescription = widgetTree.get_widget("errorDescription")
103                 self.__errorClose = widgetTree.get_widget("errorClose")
104                 self.__parentBox = self.__errorBox.get_parent()
105
106                 self.__errorBox.connect("button_release_event", self._on_close)
107
108                 self.__messages = []
109                 self.__parentBox.remove(self.__errorBox)
110
111         def push_message_with_lock(self, message):
112                 gtk.gdk.threads_enter()
113                 try:
114                         self.push_message(message)
115                 finally:
116                         gtk.gdk.threads_leave()
117
118         def push_message(self, message):
119                 if 0 < len(self.__messages):
120                         self.__messages.append(message)
121                 else:
122                         self.__show_message(message)
123
124         def push_exception(self, exception):
125                 self.push_message(exception.message)
126                 warnings.warn(exception, stacklevel=3)
127
128         def pop_message(self):
129                 if 0 < len(self.__messages):
130                         self.__show_message(self.__messages[0])
131                         del self.__messages[0]
132                 else:
133                         self.__hide_message()
134
135         def _on_close(self, *args):
136                 self.pop_message()
137
138         def __show_message(self, message):
139                 self.__errorDescription.set_text(message)
140                 self.__parentBox.pack_start(self.__errorBox, False, False)
141                 self.__parentBox.reorder_child(self.__errorBox, 1)
142
143         def __hide_message(self):
144                 self.__errorDescription.set_text("")
145                 self.__parentBox.remove(self.__errorBox)
146
147
148 class MessageBox(gtk.MessageDialog):
149
150         def __init__(self, message):
151                 parent = None
152                 gtk.MessageDialog.__init__(
153                         self,
154                         parent,
155                         gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
156                         gtk.MESSAGE_ERROR,
157                         gtk.BUTTONS_OK,
158                         message,
159                 )
160                 self.set_default_response(gtk.RESPONSE_OK)
161                 self.connect('response', self._handle_clicked)
162
163         def _handle_clicked(self, *args):
164                 self.destroy()
165
166
167 class MessageBox2(gtk.MessageDialog):
168
169         def __init__(self, message):
170                 parent = None
171                 gtk.MessageDialog.__init__(
172                         self,
173                         parent,
174                         gtk.DIALOG_DESTROY_WITH_PARENT,
175                         gtk.MESSAGE_ERROR,
176                         gtk.BUTTONS_OK,
177                         message,
178                 )
179                 self.set_default_response(gtk.RESPONSE_OK)
180                 self.connect('response', self._handle_clicked)
181
182         def _handle_clicked(self, *args):
183                 self.destroy()
184
185
186 class PopupCalendar(object):
187
188         def __init__(self, parent):
189                 self.__calendar = gtk.Calendar()
190                 self.__calendar.connect("day-selected-double-click", self._on_date_select)
191
192                 self.__popupWindow = gtk.Window(type = gtk.WINDOW_POPUP)
193                 self.__popupWindow.set_title("")
194                 self.__popupWindow.add(self.__calendar)
195                 self.__popupWindow.set_transient_for(parent)
196                 self.__popupWindow.set_modal(True)
197
198         def get_date(self):
199                 year, month, day = self.__calendar.get_date()
200                 month += 1 # Seems to be 0 indexed
201                 return year, month, day
202
203         def run(self):
204                 self.__popupWindow.show_all()
205
206         def _on_date_select(self, *args):
207                 self.__popupWindow.hide()
208                 self.callback()
209
210         def callback(self):
211                 pass
212
213
214 class EditTaskDialog(object):
215
216         def __init__(self, widgetTree):
217                 self._projectsList = gtk.ListStore(gobject.TYPE_STRING)
218
219                 self._dialog = widgetTree.get_widget("editTaskDialog")
220                 self._projectCombo = widgetTree.get_widget("edit-targetProjectCombo")
221                 self._taskName = widgetTree.get_widget("edit-taskNameEntry")
222                 self._pasteTaskNameButton = widgetTree.get_widget("edit-pasteTaskNameButton")
223                 self._priorityChoiceCombo = widgetTree.get_widget("edit-priorityChoiceCombo")
224                 self._dueDateDisplay = widgetTree.get_widget("edit-dueDateDisplay")
225                 self._dueDateProperties = widgetTree.get_widget("edit-dueDateProperties")
226                 self._clearDueDate = widgetTree.get_widget("edit-clearDueDate")
227
228                 self._addButton = widgetTree.get_widget("edit-commitEditTaskButton")
229                 self._cancelButton = widgetTree.get_widget("edit-cancelEditTaskButton")
230
231                 self._popupCalendar = PopupCalendar(self._dialog)
232
233         def enable(self, todoManager):
234                 self._populate_projects(todoManager)
235                 self._pasteTaskNameButton.connect("clicked", self._on_name_paste)
236                 self._dueDateProperties.connect("clicked", self._on_choose_duedate)
237                 self._clearDueDate.connect("clicked", self._on_clear_duedate)
238
239                 self._addButton.connect("clicked", self._on_add_clicked)
240                 self._cancelButton.connect("clicked", self._on_cancel_clicked)
241
242                 self._popupCalendar.callback = self._update_duedate
243
244         def disable(self):
245                 self._popupCalendar.callback = lambda: None
246
247                 self._pasteTaskNameButton.disconnect("clicked", self._on_name_paste)
248                 self._dueDateProperties.disconnect("clicked", self._on_choose_duedate)
249                 self._clearDueDate.disconnect("clicked", self._on_clear_duedate)
250                 self._projectsList.clear()
251                 self._projectCombo.set_model(None)
252
253         def request_task(self, todoManager, taskId, parentWindow = None):
254                 if parentWindow is not None:
255                         self._dialog.set_transient_for(parentWindow)
256
257                 taskDetails = todoManager.get_task_details(taskId)
258                 originalProjectId = taskDetails["projId"]
259                 originalProjectName = todoManager.get_project(originalProjectId)["name"]
260                 originalName = taskDetails["name"]
261                 try:
262                         originalPriority = int(taskDetails["priority"])
263                 except ValueError:
264                         originalPriority = 0
265                 originalDue = taskDetails["due"]
266
267                 self._dialog.set_default_response(gtk.RESPONSE_OK)
268                 self._taskName.set_text(originalName)
269                 self._set_active_proj(originalProjectName)
270                 self._priorityChoiceCombo.set_active(originalPriority)
271                 self._dueDateDisplay.set_text(originalDue)
272
273                 try:
274                         response = self._dialog.run()
275                         if response != gtk.RESPONSE_OK:
276                                 raise RuntimeError("Edit Cancelled")
277                 finally:
278                         self._dialog.hide()
279
280                 newProjectName = self._get_project(todoManager)
281                 newName = self._taskName.get_text()
282                 newPriority = self._get_priority()
283                 newDueDate = self._dueDateDisplay.get_text()
284
285                 isProjDifferent = newProjectName != originalProjectName
286                 isNameDifferent = newName != originalName
287                 isPriorityDifferent = newPriority != originalPriority
288                 isDueDifferent = newDueDate != originalDue
289
290                 if isProjDifferent:
291                         newProjectId = todoManager.lookup_project(newProjectName)
292                         todoManager.set_project(taskId, newProjectId)
293                         print "PROJ CHANGE"
294                 if isNameDifferent:
295                         todoManager.set_name(taskId, newName)
296                         print "NAME CHANGE"
297                 if isPriorityDifferent:
298                         todoManager.set_priority(taskId, newPriority)
299                         print "PRIO CHANGE"
300                 if isDueDifferent:
301                         todoManager.set_duedate(taskId, newDueDate)
302                         print "DUE CHANGE"
303
304                 return {
305                         "projId": isProjDifferent,
306                         "name": isNameDifferent,
307                         "priority": isPriorityDifferent,
308                         "due": isDueDifferent,
309                 }
310
311         def _populate_projects(self, todoManager):
312                 for projectName in todoManager.get_projects():
313                         row = (projectName["name"], )
314                         self._projectsList.append(row)
315                 self._projectCombo.set_model(self._projectsList)
316                 cell = gtk.CellRendererText()
317                 self._projectCombo.pack_start(cell, True)
318                 self._projectCombo.add_attribute(cell, 'text', 0)
319                 self._projectCombo.set_active(0)
320
321         def _set_active_proj(self, projName):
322                 for i, row in enumerate(self._projectsList):
323                         if row[0] == projName:
324                                 self._projectCombo.set_active(i)
325                                 break
326                 else:
327                         raise ValueError("%s not in list" % projName)
328
329         def _get_project(self, todoManager):
330                 name = self._projectCombo.get_active_text()
331                 return name
332
333         def _get_priority(self):
334                 index = self._priorityChoiceCombo.get_active()
335                 assert index != -1
336                 if index < 1:
337                         return ""
338                 else:
339                         return str(index)
340
341         def _get_date(self):
342                 # @bug The date is not used in a consistent manner causing ... issues
343                 dateParts = self._popupCalendar.get_date()
344                 dateParts = (str(part) for part in dateParts)
345                 return "-".join(dateParts)
346
347         def _update_duedate(self):
348                 self._dueDateDisplay.set_text(self._get_date())
349
350         def _on_name_paste(self, *args):
351                 clipboard = gtk.clipboard_get()
352                 contents = clipboard.wait_for_text()
353                 if contents is not None:
354                         self._taskName.set_text(contents)
355
356         def _on_choose_duedate(self, *args):
357                 self._popupCalendar.run()
358
359         def _on_clear_duedate(self, *args):
360                 self._dueDateDisplay.set_text("")
361
362         def _on_add_clicked(self, *args):
363                 self._dialog.response(gtk.RESPONSE_OK)
364
365         def _on_cancel_clicked(self, *args):
366                 self._dialog.response(gtk.RESPONSE_CANCEL)
367