From a00dde472d293c43fc8d29e3bf166ee1f4edc9c5 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 16 Apr 2009 22:28:01 -0500 Subject: [PATCH] Adding notes support --- src/doneit.glade | 271 +++++++++++++++++++++++++++++++++++----------------- src/gtk_toolbox.py | 111 +++++++++++++++++++++ src/rtm_backend.py | 34 ++++++- src/rtm_view.py | 21 ++-- 4 files changed, 339 insertions(+), 98 deletions(-) diff --git a/src/doneit.glade b/src/doneit.glade index 2957e48..45a79e9 100644 --- a/src/doneit.glade +++ b/src/doneit.glade @@ -1,6 +1,6 @@ - + 800 @@ -276,19 +276,32 @@ True 2 + + True + + + False + False + 1 + + + True 2 2 - + True - True - False + Username + + + + + True + Password - 1 - 2 1 2 @@ -304,33 +317,20 @@ - + True - Password + True + False + 1 + 2 1 2 - - - True - Username - - - - - 1 - - - - - True - False - False 1 @@ -410,70 +410,28 @@ 3 2 - - True - Name - - - - - True - Priority - - - 1 - 2 - - - - - True - Due Date - - - 2 - 3 - - - - - True - True - None -1 -2 -3 - - - 1 - 2 - 1 - 2 - - - - - + True - + True True - 2009 - 3 - 16 - False + True + True + True + True + True - + True - + True True True - gtk-clear + gtk-paste True 0 @@ -481,6 +439,7 @@ False + False 1 @@ -488,33 +447,30 @@ 1 2 - 2 - 3 - + True - + True True - True - True - True - True - True + 2009 + 3 + 16 + False - + True - + True True True - gtk-paste + gtk-clear True 0 @@ -522,7 +478,6 @@ False - False 1 @@ -530,8 +485,53 @@ 1 2 + 2 + 3 + + + + + True + True + None +1 +2 +3 + + + 1 + 2 + 1 + 2 + + + + True + Due Date + + + 2 + 3 + + + + + True + Priority + + + 1 + 2 + + + + + True + Name + + 1 @@ -578,4 +578,97 @@ + + 5 + GTK_WIN_POS_CENTER_ON_PARENT + GDK_WINDOW_TYPE_HINT_DIALOG + mainWindow + False + + + True + 2 + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + GTK_RESIZE_QUEUE + + + True + 10 + + + True + GTK_BUTTONBOX_CENTER + + + True + True + True + gtk-add + True + 0 + + + + + False + GTK_PACK_END + + + + + + + + + 1 + + + + + True + GTK_BUTTONBOX_END + + + True + True + True + gtk-save + True + 0 + + + GTK_PACK_END + 1 + + + + + True + True + True + gtk-cancel + True + 0 + + + GTK_PACK_END + + + + + False + GTK_PACK_END + + + + + diff --git a/src/gtk_toolbox.py b/src/gtk_toolbox.py index 02ea6a6..e7d108d 100644 --- a/src/gtk_toolbox.py +++ b/src/gtk_toolbox.py @@ -343,6 +343,117 @@ class PopupCalendar(object): warnings.warn(e.message) +class NotesDialog(object): + + def __init__(self, widgetTree): + self._dialog = widgetTree.get_widget("notesDialog") + self._notesBox = widgetTree.get_widget("notes-notesBox") + self._addButton = widgetTree.get_widget("notes-addButton") + self._saveButton = widgetTree.get_widget("notes-saveButton") + self._cancelButton = widgetTree.get_widget("notes-cancelButton") + self._onAddId = None + self._onSaveId = None + self._onCancelId = None + + self._notes = [] + self._notesToDelete = [] + + def enable(self): + self._dialog.set_default_size(800, 300) + self._onAddId = self._addButton.connect("clicked", self._on_add_clicked) + self._onSaveId = self._saveButton.connect("clicked", self._on_save_clicked) + self._onCancelId = self._cancelButton.connect("clicked", self._on_cancel_clicked) + + def disable(self): + self._addButton.disconnect(self._onAddId) + self._saveButton.disconnect(self._onSaveId) + self._cancelButton.disconnect(self._onAddId) + + def run(self, todoManager, taskId, parentWindow = None): + if parentWindow is not None: + self._dialog.set_transient_for(parentWindow) + + taskDetails = todoManager.get_task_details(taskId) + + self._dialog.set_default_response(gtk.RESPONSE_OK) + for note in taskDetails["notes"]: + noteBox, titleEntry, noteDeleteButton, noteEntry = self._append_notebox(note) + noteDeleteButton.connect("clicked", self._on_delete_existing, note["id"], noteBox) + + try: + response = self._dialog.run() + if response != gtk.RESPONSE_OK: + raise RuntimeError("Edit Cancelled") + finally: + self._dialog.hide() + + for note in self._notes: + noteId = note[0] + noteTitle = note[2].get_text() + noteBody = note[4].get_buffer().get_text() + if noteId is None: + print "New note:", note + todoManager.add_note(taskId, noteTitle, noteBody) + else: + # @todo Provide way to only update on change + print "Updating note:", note + todoManager.update_note(noteId, noteTitle, noteBody) + + for deletedNoteId in self._notesToDelete: + print "Deleted note:", deletedNoteId + todoManager.delete_note(noteId) + + def _append_notebox(self, noteDetails = None): + if noteDetails is None: + noteDetails = {"id": None, "title": "", "body": ""} + + noteBox = gtk.VBox() + + titleBox = gtk.HBox() + titleEntry = gtk.Entry() + titleEntry.set_text(noteDetails["title"]) + titleBox.pack_start(titleEntry, True, True) + noteDeleteButton = gtk.Button(stock=gtk.STOCK_DELETE) + titleBox.pack_end(noteDeleteButton, False, False) + noteBox.pack_start(titleBox, False, True) + + noteEntryScroll = gtk.ScrolledWindow() + noteEntryScroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + noteEntry = gtk.TextView() + noteEntry.set_editable(True) + noteEntry.set_wrap_mode(gtk.WRAP_WORD) + noteEntry.get_buffer().set_text(noteDetails["body"]) + noteEntry.set_size_request(-1, 150) + noteEntryScroll.add(noteEntry) + noteBox.pack_start(noteEntryScroll, True, True) + + self._notesBox.pack_start(noteBox, True, True) + noteBox.show_all() + + note = noteDetails["id"], noteBox, titleEntry, noteDeleteButton, noteEntry + self._notes.append(note) + return note[1:] + + def _on_add_clicked(self, *args): + noteBox, titleEntry, noteDeleteButton, noteEntry = self._append_notebox() + noteDeleteButton.connect("clicked", self._on_delete_new, noteBox) + + def _on_save_clicked(self, *args): + self._dialog.response(gtk.RESPONSE_OK) + + def _on_cancel_clicked(self, *args): + self._dialog.response(gtk.RESPONSE_CANCEL) + + def _on_delete_new(self, widget, noteBox): + self._notesBox.remove(noteBox) + self._notes = [note for note in self._notes if note[1] is not noteBox] + + def _on_delete_existing(self, widget, noteId, noteBox): + self._notesBox.remove(noteBox) + self._notes = [note for note in self._notes if note[1] is not noteBox] + self._notesToDelete.append(noteId) + + class EditTaskDialog(object): def __init__(self, widgetTree): diff --git a/src/rtm_backend.py b/src/rtm_backend.py index be908e7..5a3f346 100644 --- a/src/rtm_backend.py +++ b/src/rtm_backend.py @@ -23,7 +23,6 @@ class RtMilkManager(object): @todo Add interface for task estimate @todo Add interface for task location @todo Add interface for task url - @todo Add interface for task notes @todo Add undo support """ API_KEY = '71f471f7c6ecdda6def341967686fe05' @@ -183,6 +182,39 @@ class RtMilkManager(object): ) assert rsp.stat == "ok", "Bad response: %r" % (rsp, ) + def add_note(self, taskId, noteTitle, noteBody): + projId, seriesId, taskId = self._unpack_ids(taskId) + + rsp = self._rtm.tasks.notes.add( + timeline=self._timeline, + list_id=projId, + taskseries_id=seriesId, + task_id=taskId, + note_title=noteTitle, + note_text=noteBody, + ) + assert rsp.stat == "ok", "Bad response: %r" % (rsp, ) + + def update_note(self, noteId, noteTitle, noteBody): + projId, seriesId, taskId, note = self._unpack_ids(noteId) + + rsp = self._rtm.tasks.notes.edit( + timeline=self._timeline, + note_id=noteId, + note_title=noteTitle, + note_text=noteBody, + ) + assert rsp.stat == "ok", "Bad response: %r" % (rsp, ) + + def delete_note(self, noteId): + projId, seriesId, taskId, noteId = self._unpack_ids(noteId) + + rsp = self._rtm.tasks.notes.delete( + timeline=self._timeline, + note_id=noteId, + ) + assert rsp.stat == "ok", "Bad response: %r" % (rsp, ) + @staticmethod def _pack_ids(*ids): """ diff --git a/src/rtm_view.py b/src/rtm_view.py index 60a4680..9061f5d 100644 --- a/src/rtm_view.py +++ b/src/rtm_view.py @@ -2,14 +2,14 @@ @todo Add an agenda view to the task list Tree of days, with each successive 7 days dropping the visibility of further lower priority items @todo Add a map view - Using new api widgets people are developing) - Integrate GPS w/ fallback to default location - Use locations for mapping + Using new api widgets people are developing) + Integrate GPS w/ fallback to default location + Use locations for mapping @todo Add a quick search (OR within a property type, and between property types) view - Drop down for multi selecting priority - Drop down for multi selecting tags - Drop down for multi selecting locations - Calendar selector for choosing due date range + Drop down for multi selecting priority + Drop down for multi selecting tags + Drop down for multi selecting locations + Calendar selector for choosing due date range @todo Remove blocking operations from UI thread """ @@ -146,6 +146,7 @@ class ItemListView(object): self._showIncomplete = True self._editDialog = gtk_toolbox.EditTaskDialog(widgetTree) + self._notesDialog = gtk_toolbox.NotesDialog(widgetTree) self._itemList = gtk.ListStore( gobject.TYPE_STRING, # id @@ -294,7 +295,11 @@ class ItemListView(object): elif viewColumn is self._linkColumn: webbrowser.open(self._manager.get_task_details(taskId)["url"]) elif viewColumn is self._notesColumn: - pass + self._notesDialog.enable() + try: + self._notesDialog.run(self._manager, taskId) + finally: + self._notesDialog.disable() except StandardError, e: self._errorDisplay.push_exception() -- 1.7.9.5