Adding notes support
authorEd Page <eopage@byu.net>
Fri, 17 Apr 2009 03:28:01 +0000 (22:28 -0500)
committerEd Page <eopage@byu.net>
Fri, 17 Apr 2009 03:28:01 +0000 (22:28 -0500)
src/doneit.glade
src/gtk_toolbox.py
src/rtm_backend.py
src/rtm_view.py

index 2957e48..45a79e9 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
-<!--Generated with glade3 3.4.5 on Thu Apr 16 08:06:09 2009 -->
+<!--Generated with glade3 3.4.5 on Thu Apr 16 22:22:13 2009 -->
 <glade-interface>
   <widget class="GtkWindow" id="mainWindow">
     <property name="default_width">800</property>
         <property name="visible">True</property>
         <property name="spacing">2</property>
         <child>
+          <widget class="GtkComboBox" id="serviceCombo">
+            <property name="visible">True</property>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
           <widget class="GtkTable" id="table1">
             <property name="visible">True</property>
             <property name="n_rows">2</property>
             <property name="n_columns">2</property>
             <child>
-              <widget class="GtkEntry" id="passwordentry">
+              <widget class="GtkLabel" id="username_label">
                 <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="visibility">False</property>
+                <property name="label" translatable="yes">Username</property>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkLabel" id="password_label">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">Password</property>
               </widget>
               <packing>
-                <property name="left_attach">1</property>
-                <property name="right_attach">2</property>
                 <property name="top_attach">1</property>
                 <property name="bottom_attach">2</property>
               </packing>
               </packing>
             </child>
             <child>
-              <widget class="GtkLabel" id="password_label">
+              <widget class="GtkEntry" id="passwordentry">
                 <property name="visible">True</property>
-                <property name="label" translatable="yes">Password</property>
+                <property name="can_focus">True</property>
+                <property name="visibility">False</property>
               </widget>
               <packing>
+                <property name="left_attach">1</property>
+                <property name="right_attach">2</property>
                 <property name="top_attach">1</property>
                 <property name="bottom_attach">2</property>
               </packing>
             </child>
-            <child>
-              <widget class="GtkLabel" id="username_label">
-                <property name="visible">True</property>
-                <property name="label" translatable="yes">Username</property>
-              </widget>
-            </child>
-          </widget>
-          <packing>
-            <property name="position">1</property>
-          </packing>
-        </child>
-        <child>
-          <widget class="GtkComboBox" id="serviceCombo">
-            <property name="visible">True</property>
           </widget>
           <packing>
-            <property name="expand">False</property>
-            <property name="fill">False</property>
             <property name="position">1</property>
           </packing>
         </child>
                 <property name="n_rows">3</property>
                 <property name="n_columns">2</property>
                 <child>
-                  <widget class="GtkLabel" id="edit-nameLabel">
-                    <property name="visible">True</property>
-                    <property name="label" translatable="yes">Name</property>
-                  </widget>
-                </child>
-                <child>
-                  <widget class="GtkLabel" id="edit-priorityLabel">
-                    <property name="visible">True</property>
-                    <property name="label" translatable="yes">Priority</property>
-                  </widget>
-                  <packing>
-                    <property name="top_attach">1</property>
-                    <property name="bottom_attach">2</property>
-                  </packing>
-                </child>
-                <child>
-                  <widget class="GtkLabel" id="edit-dueDateLabel">
-                    <property name="visible">True</property>
-                    <property name="label" translatable="yes">Due Date</property>
-                  </widget>
-                  <packing>
-                    <property name="top_attach">2</property>
-                    <property name="bottom_attach">3</property>
-                  </packing>
-                </child>
-                <child>
-                  <widget class="GtkComboBox" id="edit-priorityChoiceCombo">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="items" translatable="yes">None
-1
-2
-3</property>
-                  </widget>
-                  <packing>
-                    <property name="left_attach">1</property>
-                    <property name="right_attach">2</property>
-                    <property name="top_attach">1</property>
-                    <property name="bottom_attach">2</property>
-                    <property name="y_options"></property>
-                  </packing>
-                </child>
-                <child>
-                  <widget class="GtkHBox" id="edit-dueDateDisplayControlBox">
+                  <widget class="GtkHBox" id="edit-hbox2">
                     <property name="visible">True</property>
                     <child>
-                      <widget class="GtkCalendar" id="edit-dueDateCalendar">
+                      <widget class="GtkEntry" id="edit-taskNameEntry">
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
-                        <property name="year">2009</property>
-                        <property name="month">3</property>
-                        <property name="day">16</property>
-                        <property name="show_details">False</property>
+                        <property name="has_focus">True</property>
+                        <property name="is_focus">True</property>
+                        <property name="can_default">True</property>
+                        <property name="has_default">True</property>
+                        <property name="receives_default">True</property>
                       </widget>
                     </child>
                     <child>
-                      <widget class="GtkHButtonBox" id="edit-dueDateActionBox">
+                      <widget class="GtkHButtonBox" id="edit-hbuttonbox2">
                         <property name="visible">True</property>
                         <child>
-                          <widget class="GtkButton" id="edit-clearDueDate">
+                          <widget class="GtkButton" id="edit-pasteTaskNameButton">
                             <property name="visible">True</property>
                             <property name="can_focus">True</property>
                             <property name="receives_default">True</property>
-                            <property name="label" translatable="yes">gtk-clear</property>
+                            <property name="label" translatable="yes">gtk-paste</property>
                             <property name="use_stock">True</property>
                             <property name="response_id">0</property>
                           </widget>
                       </widget>
                       <packing>
                         <property name="expand">False</property>
+                        <property name="fill">False</property>
                         <property name="position">1</property>
                       </packing>
                     </child>
                   <packing>
                     <property name="left_attach">1</property>
                     <property name="right_attach">2</property>
-                    <property name="top_attach">2</property>
-                    <property name="bottom_attach">3</property>
                   </packing>
                 </child>
                 <child>
-                  <widget class="GtkHBox" id="edit-hbox2">
+                  <widget class="GtkHBox" id="edit-dueDateDisplayControlBox">
                     <property name="visible">True</property>
                     <child>
-                      <widget class="GtkEntry" id="edit-taskNameEntry">
+                      <widget class="GtkCalendar" id="edit-dueDateCalendar">
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
-                        <property name="has_focus">True</property>
-                        <property name="is_focus">True</property>
-                        <property name="can_default">True</property>
-                        <property name="has_default">True</property>
-                        <property name="receives_default">True</property>
+                        <property name="year">2009</property>
+                        <property name="month">3</property>
+                        <property name="day">16</property>
+                        <property name="show_details">False</property>
                       </widget>
                     </child>
                     <child>
-                      <widget class="GtkHButtonBox" id="edit-hbuttonbox2">
+                      <widget class="GtkHButtonBox" id="edit-dueDateActionBox">
                         <property name="visible">True</property>
                         <child>
-                          <widget class="GtkButton" id="edit-pasteTaskNameButton">
+                          <widget class="GtkButton" id="edit-clearDueDate">
                             <property name="visible">True</property>
                             <property name="can_focus">True</property>
                             <property name="receives_default">True</property>
-                            <property name="label" translatable="yes">gtk-paste</property>
+                            <property name="label" translatable="yes">gtk-clear</property>
                             <property name="use_stock">True</property>
                             <property name="response_id">0</property>
                           </widget>
                       </widget>
                       <packing>
                         <property name="expand">False</property>
-                        <property name="fill">False</property>
                         <property name="position">1</property>
                       </packing>
                     </child>
                   <packing>
                     <property name="left_attach">1</property>
                     <property name="right_attach">2</property>
+                    <property name="top_attach">2</property>
+                    <property name="bottom_attach">3</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkComboBox" id="edit-priorityChoiceCombo">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="items" translatable="yes">None
+1
+2
+3</property>
+                  </widget>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                    <property name="top_attach">1</property>
+                    <property name="bottom_attach">2</property>
+                    <property name="y_options"></property>
                   </packing>
                 </child>
+                <child>
+                  <widget class="GtkLabel" id="edit-dueDateLabel">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Due Date</property>
+                  </widget>
+                  <packing>
+                    <property name="top_attach">2</property>
+                    <property name="bottom_attach">3</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="edit-priorityLabel">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Priority</property>
+                  </widget>
+                  <packing>
+                    <property name="top_attach">1</property>
+                    <property name="bottom_attach">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="edit-nameLabel">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Name</property>
+                  </widget>
+                </child>
               </widget>
               <packing>
                 <property name="position">1</property>
       </widget>
     </child>
   </widget>
+  <widget class="GtkDialog" id="notesDialog">
+    <property name="border_width">5</property>
+    <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+    <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+    <property name="transient_for">mainWindow</property>
+    <property name="has_separator">False</property>
+    <child internal-child="vbox">
+      <widget class="GtkVBox" id="dialog-vbox3">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <widget class="GtkScrolledWindow" id="scrolledwindow1">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+            <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+            <child>
+              <widget class="GtkViewport" id="viewport1">
+                <property name="visible">True</property>
+                <property name="resize_mode">GTK_RESIZE_QUEUE</property>
+                <child>
+                  <widget class="GtkVBox" id="notes-notesBox">
+                    <property name="visible">True</property>
+                    <property name="spacing">10</property>
+                    <child>
+                      <widget class="GtkHButtonBox" id="hbuttonbox1">
+                        <property name="visible">True</property>
+                        <property name="layout_style">GTK_BUTTONBOX_CENTER</property>
+                        <child>
+                          <widget class="GtkButton" id="notes-addButton">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">True</property>
+                            <property name="label" translatable="yes">gtk-add</property>
+                            <property name="use_stock">True</property>
+                            <property name="response_id">0</property>
+                          </widget>
+                        </child>
+                      </widget>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="pack_type">GTK_PACK_END</property>
+                      </packing>
+                    </child>
+                  </widget>
+                </child>
+              </widget>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <widget class="GtkHButtonBox" id="dialog-action_area3">
+            <property name="visible">True</property>
+            <property name="layout_style">GTK_BUTTONBOX_END</property>
+            <child>
+              <widget class="GtkButton" id="notes-saveButton">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="label" translatable="yes">gtk-save</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">0</property>
+              </widget>
+              <packing>
+                <property name="pack_type">GTK_PACK_END</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkButton" id="notes-cancelButton">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="label" translatable="yes">gtk-cancel</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">0</property>
+              </widget>
+              <packing>
+                <property name="pack_type">GTK_PACK_END</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">GTK_PACK_END</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
 </glade-interface>
index 02ea6a6..e7d108d 100644 (file)
@@ -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):
index be908e7..5a3f346 100644 (file)
@@ -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):
                """
index 60a4680..9061f5d 100644 (file)
@@ -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()