4 @todo Implement my own tap-n-hold
7 Callback function with default to a "callback widget" that places the widget in the appropriate place
8 Change of system default (class variable) for hold time
9 Follows enable/disable pattern
10 Cares about mouse move for disqualifying a tap
11 @todo Implement a custom label that encodes random info (Not set in stone, just throwing these out there)
12 Background color based on Location
13 Text intensity based on time estimate
14 Short/long version with long including tags colored specially
24 @contextlib.contextmanager
26 gtk.gdk.threads_enter()
30 gtk.gdk.threads_leave()
33 class LoginWindow(object):
35 def __init__(self, widgetTree):
39 self._dialog = widgetTree.get_widget("loginDialog")
40 self._parentWindow = widgetTree.get_widget("mainWindow")
41 self._serviceCombo = widgetTree.get_widget("serviceCombo")
42 self._usernameEntry = widgetTree.get_widget("usernameentry")
43 self._passwordEntry = widgetTree.get_widget("passwordentry")
45 self._serviceList = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_STRING)
46 self._serviceCombo.set_model(self._serviceList)
47 cell = gtk.CellRendererText()
48 self._serviceCombo.pack_start(cell, True)
49 self._serviceCombo.add_attribute(cell, 'text', 1)
50 self._serviceCombo.set_active(0)
53 "on_loginbutton_clicked": self._on_loginbutton_clicked,
54 "on_loginclose_clicked": self._on_loginclose_clicked,
56 widgetTree.signal_autoconnect(callbackMapping)
58 def request_credentials(self, parentWindow = None):
62 if parentWindow is None:
63 parentWindow = self._parentWindow
65 self._serviceCombo.hide()
66 self._serviceList.clear()
69 self._dialog.set_transient_for(parentWindow)
70 self._dialog.set_default_response(gtk.RESPONSE_OK)
71 response = self._dialog.run()
72 if response != gtk.RESPONSE_OK:
73 raise RuntimeError("Login Cancelled")
75 username = self._usernameEntry.get_text()
76 password = self._passwordEntry.get_text()
77 self._passwordEntry.set_text("")
81 return username, password
83 def request_credentials_from(self, services, parentWindow = None):
87 if parentWindow is None:
88 parentWindow = self._parentWindow
90 self._serviceList.clear()
91 for serviceIdserviceName in services.iteritems():
92 self._serviceList.append(serviceIdserviceName)
93 self._serviceCombo.set_active(0)
94 self._serviceCombo.show()
97 self._dialog.set_transient_for(parentWindow)
98 self._dialog.set_default_response(gtk.RESPONSE_OK)
99 response = self._dialog.run()
100 if response != gtk.RESPONSE_OK:
101 raise RuntimeError("Login Cancelled")
103 username = self._usernameEntry.get_text()
104 password = self._passwordEntry.get_text()
105 self._passwordEntry.set_text("")
109 itr = self._serviceCombo.get_active_iter()
110 serviceId = int(self._serviceList.get_value(itr, 0))
111 self._serviceList.clear()
112 return serviceId, username, password
114 def _on_loginbutton_clicked(self, *args):
115 self._dialog.response(gtk.RESPONSE_OK)
117 def _on_loginclose_clicked(self, *args):
118 self._dialog.response(gtk.RESPONSE_CANCEL)
121 class ErrorDisplay(object):
123 def __init__(self, widgetTree):
124 super(ErrorDisplay, self).__init__()
125 self.__errorBox = widgetTree.get_widget("errorEventBox")
126 self.__errorDescription = widgetTree.get_widget("errorDescription")
127 self.__errorClose = widgetTree.get_widget("errorClose")
128 self.__parentBox = self.__errorBox.get_parent()
130 self.__errorBox.connect("button_release_event", self._on_close)
133 self.__parentBox.remove(self.__errorBox)
135 def push_message_with_lock(self, message):
136 gtk.gdk.threads_enter()
138 self.push_message(message)
140 gtk.gdk.threads_leave()
142 def push_message(self, message):
143 if 0 < len(self.__messages):
144 self.__messages.append(message)
146 self.__show_message(message)
148 def push_exception(self, exception):
149 self.push_message(exception.message)
150 warnings.warn(exception, stacklevel=3)
152 def pop_message(self):
153 if 0 < len(self.__messages):
154 self.__show_message(self.__messages[0])
155 del self.__messages[0]
157 self.__hide_message()
159 def _on_close(self, *args):
162 def __show_message(self, message):
163 self.__errorDescription.set_text(message)
164 self.__parentBox.pack_start(self.__errorBox, False, False)
165 self.__parentBox.reorder_child(self.__errorBox, 1)
167 def __hide_message(self):
168 self.__errorDescription.set_text("")
169 self.__parentBox.remove(self.__errorBox)
172 class MessageBox(gtk.MessageDialog):
174 def __init__(self, message):
176 gtk.MessageDialog.__init__(
179 gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
184 self.set_default_response(gtk.RESPONSE_OK)
185 self.connect('response', self._handle_clicked)
187 def _handle_clicked(self, *args):
191 class MessageBox2(gtk.MessageDialog):
193 def __init__(self, message):
195 gtk.MessageDialog.__init__(
198 gtk.DIALOG_DESTROY_WITH_PARENT,
203 self.set_default_response(gtk.RESPONSE_OK)
204 self.connect('response', self._handle_clicked)
206 def _handle_clicked(self, *args):
210 class PopupCalendar(object):
212 def __init__(self, parent):
213 self.__calendar = gtk.Calendar()
214 self.__calendar.connect("day-selected-double-click", self._on_date_select)
216 self.__popupWindow = gtk.Window(type = gtk.WINDOW_POPUP)
217 self.__popupWindow.set_title("")
218 self.__popupWindow.add(self.__calendar)
219 self.__popupWindow.set_transient_for(parent)
220 self.__popupWindow.set_modal(True)
223 year, month, day = self.__calendar.get_date()
224 month += 1 # Seems to be 0 indexed
225 return year, month, day
228 self.__popupWindow.show_all()
230 def _on_date_select(self, *args):
231 self.__popupWindow.hide()
238 class EditTaskDialog(object):
240 def __init__(self, widgetTree):
241 self._projectsList = gtk.ListStore(gobject.TYPE_STRING)
243 self._dialog = widgetTree.get_widget("editTaskDialog")
244 self._projectCombo = widgetTree.get_widget("edit-targetProjectCombo")
245 self._taskName = widgetTree.get_widget("edit-taskNameEntry")
246 self._pasteTaskNameButton = widgetTree.get_widget("edit-pasteTaskNameButton")
247 self._priorityChoiceCombo = widgetTree.get_widget("edit-priorityChoiceCombo")
248 self._dueDateDisplay = widgetTree.get_widget("edit-dueDateDisplay")
249 self._dueDateProperties = widgetTree.get_widget("edit-dueDateProperties")
250 self._clearDueDate = widgetTree.get_widget("edit-clearDueDate")
252 self._addButton = widgetTree.get_widget("edit-commitEditTaskButton")
253 self._cancelButton = widgetTree.get_widget("edit-cancelEditTaskButton")
255 self._popupCalendar = PopupCalendar(self._dialog)
257 def enable(self, todoManager):
258 self._populate_projects(todoManager)
259 self._pasteTaskNameButton.connect("clicked", self._on_name_paste)
260 self._dueDateProperties.connect("clicked", self._on_choose_duedate)
261 self._clearDueDate.connect("clicked", self._on_clear_duedate)
263 self._addButton.connect("clicked", self._on_add_clicked)
264 self._cancelButton.connect("clicked", self._on_cancel_clicked)
266 self._popupCalendar.callback = self._update_duedate
269 self._popupCalendar.callback = lambda: None
271 self._pasteTaskNameButton.disconnect("clicked", self._on_name_paste)
272 self._dueDateProperties.disconnect("clicked", self._on_choose_duedate)
273 self._clearDueDate.disconnect("clicked", self._on_clear_duedate)
274 self._projectsList.clear()
275 self._projectCombo.set_model(None)
277 def request_task(self, todoManager, taskId, parentWindow = None):
278 if parentWindow is not None:
279 self._dialog.set_transient_for(parentWindow)
281 taskDetails = todoManager.get_task_details(taskId)
282 originalProjectId = taskDetails["projId"]
283 originalProjectName = todoManager.get_project(originalProjectId)["name"]
284 originalName = taskDetails["name"]
286 originalPriority = int(taskDetails["priority"])
289 originalDue = taskDetails["due"]
291 self._dialog.set_default_response(gtk.RESPONSE_OK)
292 self._taskName.set_text(originalName)
293 self._set_active_proj(originalProjectName)
294 self._priorityChoiceCombo.set_active(originalPriority)
295 self._dueDateDisplay.set_text(originalDue)
298 response = self._dialog.run()
299 if response != gtk.RESPONSE_OK:
300 raise RuntimeError("Edit Cancelled")
304 newProjectName = self._get_project(todoManager)
305 newName = self._taskName.get_text()
306 newPriority = self._get_priority()
307 newDueDate = self._dueDateDisplay.get_text()
309 isProjDifferent = newProjectName != originalProjectName
310 isNameDifferent = newName != originalName
311 isPriorityDifferent = newPriority != originalPriority
312 isDueDifferent = newDueDate != originalDue
315 newProjectId = todoManager.lookup_project(newProjectName)
316 todoManager.set_project(taskId, newProjectId)
319 todoManager.set_name(taskId, newName)
321 if isPriorityDifferent:
322 todoManager.set_priority(taskId, newPriority)
325 todoManager.set_duedate(taskId, newDueDate)
329 "projId": isProjDifferent,
330 "name": isNameDifferent,
331 "priority": isPriorityDifferent,
332 "due": isDueDifferent,
335 def _populate_projects(self, todoManager):
336 for projectName in todoManager.get_projects():
337 row = (projectName["name"], )
338 self._projectsList.append(row)
339 self._projectCombo.set_model(self._projectsList)
340 cell = gtk.CellRendererText()
341 self._projectCombo.pack_start(cell, True)
342 self._projectCombo.add_attribute(cell, 'text', 0)
343 self._projectCombo.set_active(0)
345 def _set_active_proj(self, projName):
346 for i, row in enumerate(self._projectsList):
347 if row[0] == projName:
348 self._projectCombo.set_active(i)
351 raise ValueError("%s not in list" % projName)
353 def _get_project(self, todoManager):
354 name = self._projectCombo.get_active_text()
357 def _get_priority(self):
358 index = self._priorityChoiceCombo.get_active()
366 # @bug The date is not used in a consistent manner causing ... issues
367 dateParts = self._popupCalendar.get_date()
368 dateParts = (str(part) for part in dateParts)
369 return "-".join(dateParts)
371 def _update_duedate(self):
372 self._dueDateDisplay.set_text(self._get_date())
374 def _on_name_paste(self, *args):
375 clipboard = gtk.clipboard_get()
376 contents = clipboard.wait_for_text()
377 if contents is not None:
378 self._taskName.set_text(contents)
380 def _on_choose_duedate(self, *args):
381 self._popupCalendar.run()
383 def _on_clear_duedate(self, *args):
384 self._dueDateDisplay.set_text("")
386 def _on_add_clicked(self, *args):
387 self._dialog.response(gtk.RESPONSE_OK)
389 def _on_cancel_clicked(self, *args):
390 self._dialog.response(gtk.RESPONSE_CANCEL)