Reorging the code. Don't worry, no layoffs
authorEd Page <eopage@byu.net>
Sun, 26 Apr 2009 00:16:44 +0000 (19:16 -0500)
committerEd Page <eopage@byu.net>
Sun, 26 Apr 2009 00:16:44 +0000 (19:16 -0500)
src/common_view.py
src/doneit_glade.py
src/gtk_toolbox.py
src/rtm_view.py
src/toolbox.py

index 3270106..2c6dd25 100644 (file)
@@ -15,8 +15,6 @@
 
 import webbrowser
 import datetime
-import urlparse
-import base64
 
 import gobject
 import gtk
@@ -24,59 +22,6 @@ import gtk
 import coroutines
 import toolbox
 import gtk_toolbox
-import rtm_backend
-import cache_backend
-import rtm_api
-
-
-def abbreviate(text, expectedLen):
-       singleLine = " ".join(text.split("\n"))
-       lineLen = len(singleLine)
-       if lineLen <= expectedLen:
-               return singleLine
-
-       abbrev = "..."
-
-       leftLen = expectedLen // 2 - 1
-       rightLen = max(expectedLen - leftLen - len(abbrev) + 1, 1)
-
-       abbrevText =  singleLine[0:leftLen] + abbrev + singleLine[-rightLen:-1]
-       assert len(abbrevText) <= expectedLen, "Too long: '%s'" % abbrevText
-       return abbrevText
-
-
-def abbreviate_url(url, domainLength, pathLength):
-       urlParts = urlparse.urlparse(url)
-
-       netloc = urlParts.netloc
-       path = urlParts.path
-
-       pathLength += max(domainLength - len(netloc), 0)
-       domainLength += max(pathLength - len(path), 0)
-
-       netloc = abbreviate(netloc, domainLength)
-       path = abbreviate(path, pathLength)
-       return netloc + path
-
-
-def get_token(username, apiKey, secret):
-       token = None
-       rtm = rtm_api.RTMapi(username, apiKey, secret, token)
-
-       authURL = rtm.getAuthURL()
-       webbrowser.open(authURL)
-       mb = gtk_toolbox.MessageBox2("You need to authorize DoneIt with\nRemember The Milk.\nClick OK after you authorize.")
-       mb.run()
-
-       token = rtm.getToken()
-       return token
-
-
-def get_credentials(credentialsDialog):
-       # @todo Pass in parent window
-       username, password = credentialsDialog.request_credentials()
-       token = get_token(username, rtm_backend.RtmBackend.API_KEY, rtm_backend.RtmBackend.SECRET)
-       return username, password, token
 
 
 def project_sort_by_type(projects):
@@ -163,8 +108,8 @@ class ItemListView(object):
                self._showCompleted = False
                self._showIncomplete = True
 
-               self._editDialog = gtk_toolbox.EditTaskDialog(widgetTree)
-               self._notesDialog = gtk_toolbox.NotesDialog(widgetTree)
+               self._editDialog = EditTaskDialog(widgetTree)
+               self._notesDialog = NotesDialog(widgetTree)
 
                self._itemList = gtk.ListStore(
                        gobject.TYPE_STRING, # id
@@ -264,7 +209,7 @@ class ItemListView(object):
                for taskDetails in sortedTasks:
                        id = taskDetails["id"]
                        isCompleted = taskDetails["isCompleted"]
-                       name = abbreviate(taskDetails["name"], 100)
+                       name = toolbox.abbreviate(taskDetails["name"], 100)
                        priority = str(taskDetails["priority"].get_nothrow(""))
                        if taskDetails["dueDate"].is_good():
                                dueDate = taskDetails["dueDate"].get()
@@ -275,7 +220,7 @@ class ItemListView(object):
                                fuzzyDue = ""
 
                        linkDisplay = taskDetails["url"]
-                       linkDisplay = abbreviate_url(linkDisplay, 20, 10)
+                       linkDisplay = toolbox.abbreviate_url(linkDisplay, 20, 10)
 
                        notes = taskDetails["notes"]
                        notesDisplay = "%d Notes" % len(notes) if notes else ""
@@ -325,7 +270,369 @@ class ItemListView(object):
                        self._errorDisplay.push_exception()
 
 
-class GtkRtMilk(object):
+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._onCancelId)
+
+       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"].itervalues():
+                       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):
+               self._projectsList = gtk.ListStore(gobject.TYPE_STRING)
+
+               self._dialog = widgetTree.get_widget("editTaskDialog")
+               self._projectCombo = widgetTree.get_widget("edit-targetProjectCombo")
+               self._taskName = widgetTree.get_widget("edit-taskNameEntry")
+               self._pasteTaskNameButton = widgetTree.get_widget("edit-pasteTaskNameButton")
+               self._priorityChoiceCombo = widgetTree.get_widget("edit-priorityChoiceCombo")
+               self._dueDateDisplay = widgetTree.get_widget("edit-dueDateCalendar")
+               self._clearDueDate = widgetTree.get_widget("edit-clearDueDate")
+
+               self._addButton = widgetTree.get_widget("edit-commitEditTaskButton")
+               self._cancelButton = widgetTree.get_widget("edit-cancelEditTaskButton")
+
+               self._onPasteTaskId = None
+               self._onClearDueDateId = None
+               self._onAddId = None
+               self._onCancelId = None
+
+       def enable(self, todoManager):
+               self._populate_projects(todoManager)
+
+               self._onPasteTaskId = self._pasteTaskNameButton.connect("clicked", self._on_name_paste)
+               self._onClearDueDateId = self._clearDueDate.connect("clicked", self._on_clear_duedate)
+               self._onAddId = self._addButton.connect("clicked", self._on_add_clicked)
+               self._onCancelId = self._cancelButton.connect("clicked", self._on_cancel_clicked)
+
+       def disable(self):
+               self._pasteTaskNameButton.disconnect(self._onPasteTaskId)
+               self._clearDueDate.disconnect(self._onClearDueDateId)
+               self._addButton.disconnect(self._onAddId)
+               self._cancelButton.disconnect(self._onCancelId)
+
+               self._projectsList.clear()
+               self._projectCombo.set_model(None)
+
+       def request_task(self, todoManager, taskId, parentWindow = None):
+               if parentWindow is not None:
+                       self._dialog.set_transient_for(parentWindow)
+
+               taskDetails = todoManager.get_task_details(taskId)
+               originalProjectId = taskDetails["projId"]
+               originalProjectName = todoManager.get_project(originalProjectId)["name"]
+               originalName = taskDetails["name"]
+               originalPriority = str(taskDetails["priority"].get_nothrow(0))
+               if taskDetails["dueDate"].is_good():
+                       originalDue = taskDetails["dueDate"].get()
+               else:
+                       originalDue = None
+
+               self._dialog.set_default_response(gtk.RESPONSE_OK)
+               self._taskName.set_text(originalName)
+               self._set_active_proj(originalProjectName)
+               self._priorityChoiceCombo.set_active(int(originalPriority))
+               if originalDue is not None:
+                       # Months are 0 indexed
+                       self._dueDateDisplay.select_month(originalDue.month - 1, originalDue.year)
+                       self._dueDateDisplay.select_day(originalDue.day)
+               else:
+                       now = datetime.datetime.now()
+                       self._dueDateDisplay.select_month(now.month, now.year)
+                       self._dueDateDisplay.select_day(0)
+
+               try:
+                       response = self._dialog.run()
+                       if response != gtk.RESPONSE_OK:
+                               raise RuntimeError("Edit Cancelled")
+               finally:
+                       self._dialog.hide()
+
+               newProjectName = self._get_project(todoManager)
+               newName = self._taskName.get_text()
+               newPriority = self._get_priority()
+               year, month, day = self._dueDateDisplay.get_date()
+               if day != 0:
+                       # Months are 0 indexed
+                       date = datetime.date(year, month + 1, day)
+                       time = datetime.time()
+                       newDueDate = datetime.datetime.combine(date, time)
+               else:
+                       newDueDate = None
+
+               isProjDifferent = newProjectName != originalProjectName
+               isNameDifferent = newName != originalName
+               isPriorityDifferent = newPriority != originalPriority
+               isDueDifferent = newDueDate != originalDue
+
+               if isProjDifferent:
+                       newProjectId = todoManager.lookup_project(newProjectName)
+                       todoManager.set_project(taskId, newProjectId)
+                       print "PROJ CHANGE"
+               if isNameDifferent:
+                       todoManager.set_name(taskId, newName)
+                       print "NAME CHANGE"
+               if isPriorityDifferent:
+                       try:
+                               priority = toolbox.Optional(int(newPriority))
+                       except ValueError:
+                               priority = toolbox.Optional()
+                       todoManager.set_priority(taskId, priority)
+                       print "PRIO CHANGE"
+               if isDueDifferent:
+                       if newDueDate:
+                               due = toolbox.Optional(newDueDate)
+                       else:
+                               due = toolbox.Optional()
+
+                       todoManager.set_duedate(taskId, due)
+                       print "DUE CHANGE"
+
+               return {
+                       "projId": isProjDifferent,
+                       "name": isNameDifferent,
+                       "priority": isPriorityDifferent,
+                       "due": isDueDifferent,
+               }
+
+       def _populate_projects(self, todoManager):
+               for projectName in todoManager.get_projects():
+                       row = (projectName["name"], )
+                       self._projectsList.append(row)
+               self._projectCombo.set_model(self._projectsList)
+               cell = gtk.CellRendererText()
+               self._projectCombo.pack_start(cell, True)
+               self._projectCombo.add_attribute(cell, 'text', 0)
+               self._projectCombo.set_active(0)
+
+       def _set_active_proj(self, projName):
+               for i, row in enumerate(self._projectsList):
+                       if row[0] == projName:
+                               self._projectCombo.set_active(i)
+                               break
+               else:
+                       raise ValueError("%s not in list" % projName)
+
+       def _get_project(self, todoManager):
+               name = self._projectCombo.get_active_text()
+               return name
+
+       def _get_priority(self):
+               index = self._priorityChoiceCombo.get_active()
+               assert index != -1
+               if index < 1:
+                       return ""
+               else:
+                       return str(index)
+
+       def _on_name_paste(self, *args):
+               clipboard = gtk.clipboard_get()
+               contents = clipboard.wait_for_text()
+               if contents is not None:
+                       self._taskName.set_text(contents)
+
+       def _on_clear_duedate(self, *args):
+               self._dueDateDisplay.select_day(0)
+
+       def _on_add_clicked(self, *args):
+               self._dialog.response(gtk.RESPONSE_OK)
+
+       def _on_cancel_clicked(self, *args):
+               self._dialog.response(gtk.RESPONSE_CANCEL)
+
+
+class ProjectsDialog(object):
+
+       ID_IDX = 0
+       NAME_IDX = 1
+       VISIBILITY_IDX = 2
+
+       def __init__(self, widgetTree):
+               self._manager = None
+
+               self._dialog = widgetTree.get_widget("projectsDialog")
+               self._projView = widgetTree.get_widget("proj-projectView")
+
+               addSink = coroutines.CoSwitch(["add", "add-edit"])
+               addSink.register_sink("add", coroutines.func_sink(self._on_add))
+               addSink.register_sink("add-edit", coroutines.func_sink(self._on_add_edit))
+               self._addView = gtk_toolbox.QuickAddView(widgetTree, gtk_toolbox.DummyErrorDisplay(), addSink, "proj")
+
+               self._projList = gtk.ListStore(
+                       gobject.TYPE_STRING, # id
+                       gobject.TYPE_STRING, # name
+                       gobject.TYPE_BOOLEAN, # is visible
+               )
+               self._visibilityColumn = gtk.TreeViewColumn('') # Complete?
+               self._visibilityCell = gtk.CellRendererToggle()
+               self._visibilityCell.set_property("activatable", True)
+               self._visibilityCell.connect("toggled", self._on_toggle_visibility)
+               self._visibilityColumn.pack_start(self._visibilityCell, False)
+               self._visibilityColumn.set_attributes(self._visibilityCell, active=self.VISIBILITY_IDX)
+               self._nameColumn = gtk.TreeViewColumn('Name')
+               self._nameCell = gtk.CellRendererText()
+               self._nameColumn.pack_start(self._nameCell, True)
+               self._nameColumn.set_attributes(self._nameCell, text=self.NAME_IDX)
+               self._nameColumn.set_expand(True)
+
+               self._projView.append_column(self._visibilityColumn)
+               self._projView.append_column(self._nameColumn)
+               self._projView.connect("row-activated", self._on_proj_select)
+
+       def enable(self, manager):
+               self._manager = manager
+
+               self._populate_projects()
+               self._dialog.show_all()
+
+       def disable(self):
+               self._dialog.hide_all()
+               self._manager = None
+
+               self._projList.clear()
+               self._projView.set_model(None)
+
+       def _populate_projects(self):
+               self._projList.clear()
+
+               projects = self._manager.get_projects()
+               for project in projects:
+                       projectId = project["id"]
+                       projectName = project["name"]
+                       isVisible = project["isVisible"]
+                       row = (projectId, projectName, isVisible)
+                       self._projList.append(row)
+               self._projView.set_model(self._projList)
+
+       def _on_add(self, eventData):
+               eventName, projectName, = eventData
+               self._manager.add_project(projectName)
+               self._populate_projects()
+
+       def _on_add_edit(self, eventData):
+               self._on_add(eventData)
+
+       def _on_toggle_visibility(self, cell, path):
+               listIndex = path[0]
+               row = self._projList[listIndex]
+               projId = row[self.ID_IDX]
+               oldValue = row[self.VISIBILITY_IDX]
+               newValue = not oldValue
+               print oldValue, newValue
+               self._manager.set_project_visibility(projId, newValue)
+               row[self.VISIBILITY_IDX] = newValue
+
+       def _on_proj_select(self, *args):
+               # @todo Implement project renaming
+               pass
+
+
+class CommonView(object):
 
        def __init__(self, widgetTree, errorDisplay):
                """
@@ -333,10 +640,9 @@ class GtkRtMilk(object):
                """
                self._errorDisplay = errorDisplay
                self._manager = None
-               self._credentials = "", "", ""
 
-               self._editDialog = gtk_toolbox.EditTaskDialog(widgetTree)
-               self._projDialog = gtk_toolbox.ProjectsDialog(widgetTree)
+               self._editDialog = EditTaskDialog(widgetTree)
+               self._projDialog = ProjectsDialog(widgetTree)
                self._projectsList = gtk.ListStore(gobject.TYPE_STRING)
                self._projectsCombo = widgetTree.get_widget("projectsCombo")
                self._projectCell = gtk.CellRendererText()
@@ -350,59 +656,34 @@ class GtkRtMilk(object):
                addSink.register_sink("add", coroutines.func_sink(self._on_add))
                addSink.register_sink("add-edit", coroutines.func_sink(self._on_add_edit))
                self._addView = gtk_toolbox.QuickAddView(widgetTree, self._errorDisplay, addSink, "add")
-               self._credentialsDialog = gtk_toolbox.LoginWindow(widgetTree)
 
        @staticmethod
        def name():
-               return "Remember The Milk"
+               raise NotImplementedError
 
        def load_settings(self, config):
                """
                @note Thread Agnostic
                """
-               blobs = (
-                       config.get(self.name(), "bin_blob_%i" % i)
-                       for i in xrange(len(self._credentials))
-               )
-               creds = (
-                       base64.b64decode(blob)
-                       for blob in blobs
-               )
-               self._credentials = tuple(creds)
+               raise NotImplementedError
 
        def save_settings(self, config):
                """
                @note Thread Agnostic
                """
-               config.add_section(self.name())
-               for i, value in enumerate(self._credentials):
-                       blob = base64.b64encode(value)
-                       config.set(self.name(), "bin_blob_%i" % i, blob)
+               raise NotImplementedError
 
        def login(self):
                """
                @note UI Thread
                """
-               if self._manager is not None:
-                       return
-
-               credentials = self._credentials
-               while True:
-                       try:
-                               self._manager = rtm_backend.RtmBackend(*credentials)
-                               self._manager = cache_backend.LazyCacheBackend(self._manager)
-                               self._credentials = credentials
-                               return # Login succeeded
-                       except rtm_api.AuthStateMachine.NoData:
-                               # Login failed, grab new credentials
-                               credentials = get_credentials(self._credentialsDialog)
+               raise NotImplementedError
 
        def logout(self):
                """
                @note Thread Agnostic
                """
-               self._credentials = "", "", ""
-               self._manager = None
+               raise NotImplementedError
 
        def enable(self):
                """
index 2ec55cf..a699ae5 100755 (executable)
@@ -15,6 +15,7 @@ import warnings
 import ConfigParser
 import socket
 
+import gobject
 import gtk
 import gtk.glade
 
@@ -29,6 +30,66 @@ import gtk_toolbox
 socket.setdefaulttimeout(10)
 
 
+class PreferencesDialog(object):
+
+       def __init__(self, widgetTree):
+               self._backendList = gtk.ListStore(gobject.TYPE_STRING)
+               self._backendCell = gtk.CellRendererText()
+
+               self._dialog = widgetTree.get_widget("preferencesDialog")
+               self._backendSelector = widgetTree.get_widget("prefsBackendSelector")
+               self._applyButton = widgetTree.get_widget("applyPrefsButton")
+               self._cancelButton = widgetTree.get_widget("cancelPrefsButton")
+
+               self._onApplyId = None
+               self._onCancelId = None
+
+       def enable(self):
+               self._dialog.set_default_size(800, 300)
+               self._onApplyId = self._applyButton.connect("clicked", self._on_apply_clicked)
+               self._onCancelId = self._cancelButton.connect("clicked", self._on_cancel_clicked)
+
+               cell = self._backendCell
+               self._backendSelector.pack_start(cell, True)
+               self._backendSelector.add_attribute(cell, 'text', 0)
+               self._backendSelector.set_model(self._backendList)
+
+       def disable(self):
+               self._applyButton.disconnect(self._onApplyId)
+               self._cancelButton.disconnect(self._onCancelId)
+
+               self._backendList.clear()
+               self._backendSelector.set_model(None)
+
+       def run(self, app, parentWindow = None):
+               if parentWindow is not None:
+                       self._dialog.set_transient_for(parentWindow)
+
+               self._backendList.clear()
+               activeIndex = 0
+               for i, (uiName, ui) in enumerate(app.get_uis()):
+                       self._backendList.append((uiName, ))
+                       if uiName == app.get_default_ui():
+                               activeIndex = i
+               self._backendSelector.set_active(activeIndex)
+
+               try:
+                       response = self._dialog.run()
+                       if response != gtk.RESPONSE_OK:
+                               raise RuntimeError("Edit Cancelled")
+               finally:
+                       self._dialog.hide()
+
+               backendName = self._backendSelector.get_active_text()
+               app.switch_ui(backendName)
+
+       def _on_apply_clicked(self, *args):
+               self._dialog.response(gtk.RESPONSE_OK)
+
+       def _on_cancel_clicked(self, *args):
+               self._dialog.response(gtk.RESPONSE_CANCEL)
+
+
 class DoneIt(object):
 
        __pretty_app_name__ = "DoneIt"
@@ -71,7 +132,7 @@ class DoneIt(object):
                self._clipboard = gtk.clipboard_get()
                self.__window = self._widgetTree.get_widget("mainWindow")
                self.__errorDisplay = gtk_toolbox.ErrorDisplay(self._widgetTree)
-               self._prefsDialog = gtk_toolbox.PreferencesDialog(self._widgetTree)
+               self._prefsDialog = PreferencesDialog(self._widgetTree)
 
                self._app = None
                self._isFullScreen = False
@@ -149,7 +210,7 @@ class DoneIt(object):
                # Setup costly backends
                import rtm_view
                with gtk_toolbox.gtk_lock():
-                       rtmView = rtm_view.GtkRtMilk(self._widgetTree, self.__errorDisplay)
+                       rtmView = rtm_view.RtmView(self._widgetTree, self.__errorDisplay)
                self._todoUIs[rtmView.name()] = rtmView
                self._defaultUIName = rtmView.name()
 
index 4077473..c7738a3 100644 (file)
@@ -16,7 +16,6 @@ import warnings
 import gobject
 import gtk
 
-import toolbox
 import coroutines
 
 
@@ -468,426 +467,6 @@ class QuickAddView(object):
                        self._errorDisplay.push_exception()
 
 
-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._onCancelId)
-
-       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"].itervalues():
-                       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):
-               self._projectsList = gtk.ListStore(gobject.TYPE_STRING)
-
-               self._dialog = widgetTree.get_widget("editTaskDialog")
-               self._projectCombo = widgetTree.get_widget("edit-targetProjectCombo")
-               self._taskName = widgetTree.get_widget("edit-taskNameEntry")
-               self._pasteTaskNameButton = widgetTree.get_widget("edit-pasteTaskNameButton")
-               self._priorityChoiceCombo = widgetTree.get_widget("edit-priorityChoiceCombo")
-               self._dueDateDisplay = widgetTree.get_widget("edit-dueDateCalendar")
-               self._clearDueDate = widgetTree.get_widget("edit-clearDueDate")
-
-               self._addButton = widgetTree.get_widget("edit-commitEditTaskButton")
-               self._cancelButton = widgetTree.get_widget("edit-cancelEditTaskButton")
-
-               self._onPasteTaskId = None
-               self._onClearDueDateId = None
-               self._onAddId = None
-               self._onCancelId = None
-
-       def enable(self, todoManager):
-               self._populate_projects(todoManager)
-
-               self._onPasteTaskId = self._pasteTaskNameButton.connect("clicked", self._on_name_paste)
-               self._onClearDueDateId = self._clearDueDate.connect("clicked", self._on_clear_duedate)
-               self._onAddId = self._addButton.connect("clicked", self._on_add_clicked)
-               self._onCancelId = self._cancelButton.connect("clicked", self._on_cancel_clicked)
-
-       def disable(self):
-               self._pasteTaskNameButton.disconnect(self._onPasteTaskId)
-               self._clearDueDate.disconnect(self._onClearDueDateId)
-               self._addButton.disconnect(self._onAddId)
-               self._cancelButton.disconnect(self._onCancelId)
-
-               self._projectsList.clear()
-               self._projectCombo.set_model(None)
-
-       def request_task(self, todoManager, taskId, parentWindow = None):
-               if parentWindow is not None:
-                       self._dialog.set_transient_for(parentWindow)
-
-               taskDetails = todoManager.get_task_details(taskId)
-               originalProjectId = taskDetails["projId"]
-               originalProjectName = todoManager.get_project(originalProjectId)["name"]
-               originalName = taskDetails["name"]
-               originalPriority = str(taskDetails["priority"].get_nothrow(0))
-               if taskDetails["dueDate"].is_good():
-                       originalDue = taskDetails["dueDate"].get()
-               else:
-                       originalDue = None
-
-               self._dialog.set_default_response(gtk.RESPONSE_OK)
-               self._taskName.set_text(originalName)
-               self._set_active_proj(originalProjectName)
-               self._priorityChoiceCombo.set_active(int(originalPriority))
-               if originalDue is not None:
-                       # Months are 0 indexed
-                       self._dueDateDisplay.select_month(originalDue.month - 1, originalDue.year)
-                       self._dueDateDisplay.select_day(originalDue.day)
-               else:
-                       now = datetime.datetime.now()
-                       self._dueDateDisplay.select_month(now.month, now.year)
-                       self._dueDateDisplay.select_day(0)
-
-               try:
-                       response = self._dialog.run()
-                       if response != gtk.RESPONSE_OK:
-                               raise RuntimeError("Edit Cancelled")
-               finally:
-                       self._dialog.hide()
-
-               newProjectName = self._get_project(todoManager)
-               newName = self._taskName.get_text()
-               newPriority = self._get_priority()
-               year, month, day = self._dueDateDisplay.get_date()
-               if day != 0:
-                       # Months are 0 indexed
-                       date = datetime.date(year, month + 1, day)
-                       time = datetime.time()
-                       newDueDate = datetime.datetime.combine(date, time)
-               else:
-                       newDueDate = None
-
-               isProjDifferent = newProjectName != originalProjectName
-               isNameDifferent = newName != originalName
-               isPriorityDifferent = newPriority != originalPriority
-               isDueDifferent = newDueDate != originalDue
-
-               if isProjDifferent:
-                       newProjectId = todoManager.lookup_project(newProjectName)
-                       todoManager.set_project(taskId, newProjectId)
-                       print "PROJ CHANGE"
-               if isNameDifferent:
-                       todoManager.set_name(taskId, newName)
-                       print "NAME CHANGE"
-               if isPriorityDifferent:
-                       try:
-                               priority = toolbox.Optional(int(newPriority))
-                       except ValueError:
-                               priority = toolbox.Optional()
-                       todoManager.set_priority(taskId, priority)
-                       print "PRIO CHANGE"
-               if isDueDifferent:
-                       if newDueDate:
-                               due = toolbox.Optional(newDueDate)
-                       else:
-                               due = toolbox.Optional()
-
-                       todoManager.set_duedate(taskId, due)
-                       print "DUE CHANGE"
-
-               return {
-                       "projId": isProjDifferent,
-                       "name": isNameDifferent,
-                       "priority": isPriorityDifferent,
-                       "due": isDueDifferent,
-               }
-
-       def _populate_projects(self, todoManager):
-               for projectName in todoManager.get_projects():
-                       row = (projectName["name"], )
-                       self._projectsList.append(row)
-               self._projectCombo.set_model(self._projectsList)
-               cell = gtk.CellRendererText()
-               self._projectCombo.pack_start(cell, True)
-               self._projectCombo.add_attribute(cell, 'text', 0)
-               self._projectCombo.set_active(0)
-
-       def _set_active_proj(self, projName):
-               for i, row in enumerate(self._projectsList):
-                       if row[0] == projName:
-                               self._projectCombo.set_active(i)
-                               break
-               else:
-                       raise ValueError("%s not in list" % projName)
-
-       def _get_project(self, todoManager):
-               name = self._projectCombo.get_active_text()
-               return name
-
-       def _get_priority(self):
-               index = self._priorityChoiceCombo.get_active()
-               assert index != -1
-               if index < 1:
-                       return ""
-               else:
-                       return str(index)
-
-       def _on_name_paste(self, *args):
-               clipboard = gtk.clipboard_get()
-               contents = clipboard.wait_for_text()
-               if contents is not None:
-                       self._taskName.set_text(contents)
-
-       def _on_clear_duedate(self, *args):
-               self._dueDateDisplay.select_day(0)
-
-       def _on_add_clicked(self, *args):
-               self._dialog.response(gtk.RESPONSE_OK)
-
-       def _on_cancel_clicked(self, *args):
-               self._dialog.response(gtk.RESPONSE_CANCEL)
-
-
-class PreferencesDialog(object):
-
-       def __init__(self, widgetTree):
-               self._backendList = gtk.ListStore(gobject.TYPE_STRING)
-               self._backendCell = gtk.CellRendererText()
-
-               self._dialog = widgetTree.get_widget("preferencesDialog")
-               self._backendSelector = widgetTree.get_widget("prefsBackendSelector")
-               self._applyButton = widgetTree.get_widget("applyPrefsButton")
-               self._cancelButton = widgetTree.get_widget("cancelPrefsButton")
-
-               self._onApplyId = None
-               self._onCancelId = None
-
-       def enable(self):
-               self._dialog.set_default_size(800, 300)
-               self._onApplyId = self._applyButton.connect("clicked", self._on_apply_clicked)
-               self._onCancelId = self._cancelButton.connect("clicked", self._on_cancel_clicked)
-
-               cell = self._backendCell
-               self._backendSelector.pack_start(cell, True)
-               self._backendSelector.add_attribute(cell, 'text', 0)
-               self._backendSelector.set_model(self._backendList)
-
-       def disable(self):
-               self._applyButton.disconnect(self._onApplyId)
-               self._cancelButton.disconnect(self._onCancelId)
-
-               self._backendList.clear()
-               self._backendSelector.set_model(None)
-
-       def run(self, app, parentWindow = None):
-               if parentWindow is not None:
-                       self._dialog.set_transient_for(parentWindow)
-
-               self._backendList.clear()
-               activeIndex = 0
-               for i, (uiName, ui) in enumerate(app.get_uis()):
-                       self._backendList.append((uiName, ))
-                       if uiName == app.get_default_ui():
-                               activeIndex = i
-               self._backendSelector.set_active(activeIndex)
-
-               try:
-                       response = self._dialog.run()
-                       if response != gtk.RESPONSE_OK:
-                               raise RuntimeError("Edit Cancelled")
-               finally:
-                       self._dialog.hide()
-
-               backendName = self._backendSelector.get_active_text()
-               app.switch_ui(backendName)
-
-       def _on_apply_clicked(self, *args):
-               self._dialog.response(gtk.RESPONSE_OK)
-
-       def _on_cancel_clicked(self, *args):
-               self._dialog.response(gtk.RESPONSE_CANCEL)
-
-
-class ProjectsDialog(object):
-
-       ID_IDX = 0
-       NAME_IDX = 1
-       VISIBILITY_IDX = 2
-
-       def __init__(self, widgetTree):
-               self._manager = None
-
-               self._dialog = widgetTree.get_widget("projectsDialog")
-               self._projView = widgetTree.get_widget("proj-projectView")
-
-               addSink = coroutines.CoSwitch(["add", "add-edit"])
-               addSink.register_sink("add", coroutines.func_sink(self._on_add))
-               addSink.register_sink("add-edit", coroutines.func_sink(self._on_add_edit))
-               self._addView = QuickAddView(widgetTree, DummyErrorDisplay(), addSink, "proj")
-
-               self._projList = gtk.ListStore(
-                       gobject.TYPE_STRING, # id
-                       gobject.TYPE_STRING, # name
-                       gobject.TYPE_BOOLEAN, # is visible
-               )
-               self._visibilityColumn = gtk.TreeViewColumn('') # Complete?
-               self._visibilityCell = gtk.CellRendererToggle()
-               self._visibilityCell.set_property("activatable", True)
-               self._visibilityCell.connect("toggled", self._on_toggle_visibility)
-               self._visibilityColumn.pack_start(self._visibilityCell, False)
-               self._visibilityColumn.set_attributes(self._visibilityCell, active=self.VISIBILITY_IDX)
-               self._nameColumn = gtk.TreeViewColumn('Name')
-               self._nameCell = gtk.CellRendererText()
-               self._nameColumn.pack_start(self._nameCell, True)
-               self._nameColumn.set_attributes(self._nameCell, text=self.NAME_IDX)
-               self._nameColumn.set_expand(True)
-
-               self._projView.append_column(self._visibilityColumn)
-               self._projView.append_column(self._nameColumn)
-               self._projView.connect("row-activated", self._on_proj_select)
-
-       def enable(self, manager):
-               self._manager = manager
-
-               self._populate_projects()
-               self._dialog.show_all()
-
-       def disable(self):
-               self._dialog.hide_all()
-               self._manager = None
-
-               self._projList.clear()
-               self._projView.set_model(None)
-
-       def _populate_projects(self):
-               self._projList.clear()
-
-               projects = self._manager.get_projects()
-               for project in projects:
-                       projectId = project["id"]
-                       projectName = project["name"]
-                       isVisible = project["isVisible"]
-                       row = (projectId, projectName, isVisible)
-                       self._projList.append(row)
-               self._projView.set_model(self._projList)
-
-       def _on_add(self, eventData):
-               eventName, projectName, = eventData
-               self._manager.add_project(projectName)
-               self._populate_projects()
-
-       def _on_add_edit(self, eventData):
-               self._on_add(eventData)
-
-       def _on_toggle_visibility(self, cell, path):
-               listIndex = path[0]
-               row = self._projList[listIndex]
-               projId = row[self.ID_IDX]
-               oldValue = row[self.VISIBILITY_IDX]
-               self._manager.set_project_visibility(projId, not oldValue)
-               row[self.VISIBILITY_IDX] = not oldValue
-
-       def _on_proj_select(self, *args):
-               # @todo Implement project renaming
-               pass
-
-
 if __name__ == "__main__":
        if True:
                win = gtk.Window()
index 3270106..706fdf7 100644 (file)
@@ -1,64 +1,18 @@
 """
-@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
-@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
 @todo Remove blocking operations from UI thread
 """
 
 import webbrowser
-import datetime
-import urlparse
 import base64
 
-import gobject
-import gtk
+import common_view
 
-import coroutines
-import toolbox
 import gtk_toolbox
 import rtm_backend
 import cache_backend
 import rtm_api
 
 
-def abbreviate(text, expectedLen):
-       singleLine = " ".join(text.split("\n"))
-       lineLen = len(singleLine)
-       if lineLen <= expectedLen:
-               return singleLine
-
-       abbrev = "..."
-
-       leftLen = expectedLen // 2 - 1
-       rightLen = max(expectedLen - leftLen - len(abbrev) + 1, 1)
-
-       abbrevText =  singleLine[0:leftLen] + abbrev + singleLine[-rightLen:-1]
-       assert len(abbrevText) <= expectedLen, "Too long: '%s'" % abbrevText
-       return abbrevText
-
-
-def abbreviate_url(url, domainLength, pathLength):
-       urlParts = urlparse.urlparse(url)
-
-       netloc = urlParts.netloc
-       path = urlParts.path
-
-       pathLength += max(domainLength - len(netloc), 0)
-       domainLength += max(pathLength - len(path), 0)
-
-       netloc = abbreviate(netloc, domainLength)
-       path = abbreviate(path, pathLength)
-       return netloc + path
-
-
 def get_token(username, apiKey, secret):
        token = None
        rtm = rtm_api.RTMapi(username, apiKey, secret, token)
@@ -79,277 +33,11 @@ def get_credentials(credentialsDialog):
        return username, password, token
 
 
-def project_sort_by_type(projects):
-       sortedProjects = list(projects)
-       def advanced_key(proj):
-               if proj["name"] == "Inbox":
-                       type = 0
-               elif proj["name"] == "Sent":
-                       type = 1
-               elif not proj["isMeta"]:
-                       type = 2
-               else:
-                       type = 3
-               return type, proj["name"]
-       sortedProjects.sort(key=advanced_key)
-       return sortedProjects
-
-
-def item_sort_by_priority_then_date(items):
-       sortedTasks = list(items)
-       sortedTasks.sort(
-               key = lambda taskDetails: (
-                       taskDetails["priority"].get_nothrow(4),
-                       taskDetails["dueDate"].get_nothrow(datetime.datetime.max),
-               ),
-       )
-       return sortedTasks
-
-
-def item_sort_by_date_then_priority(items):
-       sortedTasks = list(items)
-       sortedTasks.sort(
-               key = lambda taskDetails: (
-                       taskDetails["dueDate"].get_nothrow(datetime.datetime.max),
-                       taskDetails["priority"].get_nothrow(4),
-               ),
-       )
-       return sortedTasks
-
-
-def item_in_agenda(item):
-       taskDate = item["dueDate"].get_nothrow(datetime.datetime.max)
-       today = datetime.datetime.now()
-       delta = taskDate - today
-       dayDelta = abs(delta.days)
-
-       priority = item["priority"].get_nothrow(4)
-       weeksVisible = 5 - priority
-
-       isVisible = not bool(dayDelta / (weeksVisible * 7))
-       return isVisible
-
-
-def item_sort_by_fuzzydate_then_priority(items):
-       sortedTasks = list(items)
-
-       def advanced_key(taskDetails):
-               dueDate = taskDetails["dueDate"].get_nothrow(datetime.datetime.max)
-               priority = taskDetails["priority"].get_nothrow(4)
-               isNotSameYear = not toolbox.is_same_year(dueDate)
-               isNotSameMonth = not toolbox.is_same_month(dueDate)
-               isNotSameDay = not toolbox.is_same_day(dueDate)
-               return isNotSameDay, isNotSameMonth, isNotSameYear, priority, dueDate
-
-       sortedTasks.sort(key=advanced_key)
-       return sortedTasks
-
-
-class ItemListView(object):
-
-       ID_IDX = 0
-       COMPLETION_IDX = 1
-       NAME_IDX = 2
-       PRIORITY_IDX = 3
-       DUE_IDX = 4
-       FUZZY_IDX = 5
-       LINK_IDX = 6
-       NOTES_IDX = 7
-
-       def __init__(self, widgetTree, errorDisplay):
-               self._errorDisplay = errorDisplay
-               self._manager = None
-               self._projId = None
-               self._showCompleted = False
-               self._showIncomplete = True
-
-               self._editDialog = gtk_toolbox.EditTaskDialog(widgetTree)
-               self._notesDialog = gtk_toolbox.NotesDialog(widgetTree)
-
-               self._itemList = gtk.ListStore(
-                       gobject.TYPE_STRING, # id
-                       gobject.TYPE_BOOLEAN, # is complete
-                       gobject.TYPE_STRING, # name
-                       gobject.TYPE_STRING, # priority
-                       gobject.TYPE_STRING, # due
-                       gobject.TYPE_STRING, # fuzzy due
-                       gobject.TYPE_STRING, # Link
-                       gobject.TYPE_STRING, # Notes
-               )
-               self._completionColumn = gtk.TreeViewColumn('') # Complete?
-               self._completionCell = gtk.CellRendererToggle()
-               self._completionCell.set_property("activatable", True)
-               self._completionCell.connect("toggled", self._on_completion_change)
-               self._completionColumn.pack_start(self._completionCell, False)
-               self._completionColumn.set_attributes(self._completionCell, active=self.COMPLETION_IDX)
-               self._priorityColumn = gtk.TreeViewColumn('') # Priority
-               self._priorityCell = gtk.CellRendererText()
-               self._priorityColumn.pack_start(self._priorityCell, False)
-               self._priorityColumn.set_attributes(self._priorityCell, text=self.PRIORITY_IDX)
-               self._nameColumn = gtk.TreeViewColumn('Name')
-               self._nameCell = gtk.CellRendererText()
-               self._nameColumn.pack_start(self._nameCell, True)
-               self._nameColumn.set_attributes(self._nameCell, text=self.NAME_IDX)
-               self._nameColumn.set_expand(True)
-               self._dueColumn = gtk.TreeViewColumn('Due')
-               self._dueCell = gtk.CellRendererText()
-               self._dueColumn.pack_start(self._nameCell, False)
-               self._dueColumn.set_attributes(self._nameCell, text=self.FUZZY_IDX)
-               self._linkColumn = gtk.TreeViewColumn('') # Link
-               self._linkCell = gtk.CellRendererText()
-               self._linkColumn.pack_start(self._nameCell, False)
-               self._linkColumn.set_attributes(self._nameCell, text=self.LINK_IDX)
-               self._notesColumn = gtk.TreeViewColumn('Notes') # Notes
-               self._notesCell = gtk.CellRendererText()
-               self._notesColumn.pack_start(self._nameCell, False)
-               self._notesColumn.set_attributes(self._nameCell, text=self.NOTES_IDX)
-
-               self._todoBox = widgetTree.get_widget("todoBox")
-               self._todoItemScroll = gtk.ScrolledWindow()
-               self._todoItemScroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
-               self._todoItemTree = gtk.TreeView()
-               self._todoItemTree.set_headers_visible(True)
-               self._todoItemTree.set_rules_hint(True)
-               self._todoItemTree.set_search_column(self.NAME_IDX)
-               self._todoItemTree.set_enable_search(True)
-               self._todoItemTree.append_column(self._completionColumn)
-               self._todoItemTree.append_column(self._priorityColumn)
-               self._todoItemTree.append_column(self._nameColumn)
-               self._todoItemTree.append_column(self._dueColumn)
-               self._todoItemTree.append_column(self._linkColumn)
-               self._todoItemTree.append_column(self._notesColumn)
-               self._todoItemTree.connect("row-activated", self._on_item_select)
-               self._todoItemScroll.add(self._todoItemTree)
-
-       def enable(self, manager, projId):
-               self._manager = manager
-               self._projId = projId
-
-               self._todoBox.pack_start(self._todoItemScroll)
-               self._todoItemScroll.show_all()
-
-               self._itemList.clear()
-               try:
-                       self.reset_task_list(self._projId)
-               except StandardError, e:
-                       self._errorDisplay.push_exception()
-
-       def disable(self):
-               self._manager = None
-               self._projId = None
-
-               self._todoBox.remove(self._todoItemScroll)
-               self._todoItemScroll.hide_all()
-
-               self._itemList.clear()
-               self._todoItemTree.set_model(None)
-
-       def reset_task_list(self, projId):
-               self._projId = projId
-               self._itemList.clear()
-               self._populate_items()
-
-       def _populate_items(self):
-               projId = self._projId
-               rawTasks = self._manager.get_tasks_with_details(projId)
-               filteredTasks = (
-                       taskDetails
-                       for taskDetails in rawTasks
-                               if self._showCompleted and taskDetails["isCompleted"] or self._showIncomplete and not taskDetails["isCompleted"]
-               )
-               # filteredTasks = (taskDetails for taskDetails in filteredTasks if item_in_agenda(taskDetails))
-               sortedTasks = item_sort_by_priority_then_date(filteredTasks)
-               # sortedTasks = item_sort_by_date_then_priority(filteredTasks)
-               # sortedTasks = item_sort_by_fuzzydate_then_priority(filteredTasks)
-               for taskDetails in sortedTasks:
-                       id = taskDetails["id"]
-                       isCompleted = taskDetails["isCompleted"]
-                       name = abbreviate(taskDetails["name"], 100)
-                       priority = str(taskDetails["priority"].get_nothrow(""))
-                       if taskDetails["dueDate"].is_good():
-                               dueDate = taskDetails["dueDate"].get()
-                               dueDescription = dueDate.strftime("%Y-%m-%d %H:%M:%S")
-                               fuzzyDue = toolbox.to_fuzzy_date(dueDate)
-                       else:
-                               dueDescription = ""
-                               fuzzyDue = ""
-
-                       linkDisplay = taskDetails["url"]
-                       linkDisplay = abbreviate_url(linkDisplay, 20, 10)
-
-                       notes = taskDetails["notes"]
-                       notesDisplay = "%d Notes" % len(notes) if notes else ""
-
-                       row = (id, isCompleted, name, priority, dueDescription, fuzzyDue, linkDisplay, notesDisplay)
-                       self._itemList.append(row)
-               self._todoItemTree.set_model(self._itemList)
-
-       def _on_item_select(self, treeView, path, viewColumn):
-               try:
-                       # @todo See if there is a way to use the new gtk_toolbox.ContextHandler
-                       taskId = self._itemList[path[0]][self.ID_IDX]
-
-                       if viewColumn is self._priorityColumn:
-                               pass
-                       elif viewColumn is self._nameColumn:
-                               self._editDialog.enable(self._manager)
-                               try:
-                                       self._editDialog.request_task(self._manager, taskId)
-                               finally:
-                                       self._editDialog.disable()
-                               self.reset_task_list(self._projId)
-                       elif viewColumn is self._dueColumn:
-                               due = self._manager.get_task_details(taskId)["dueDate"]
-                               if due.is_good():
-                                       # @todo Pass to calendar the parent widget
-                                       calendar = gtk_toolbox.PopupCalendar(None, due.get(), "Due Date")
-                                       calendar.run()
-                       elif viewColumn is self._linkColumn:
-                               webbrowser.open(self._manager.get_task_details(taskId)["url"])
-                       elif viewColumn is self._notesColumn:
-                               self._notesDialog.enable()
-                               try:
-                                       # @todo Need to pass in parent window
-                                       self._notesDialog.run(self._manager, taskId)
-                               finally:
-                                       self._notesDialog.disable()
-               except StandardError, e:
-                       self._errorDisplay.push_exception()
-
-       def _on_completion_change(self, cell, path):
-               try:
-                       taskId = self._itemList[path[0]][self.ID_IDX]
-                       self._manager.complete_task(taskId)
-                       self.reset_task_list(self._projId)
-               except StandardError, e:
-                       self._errorDisplay.push_exception()
-
-
-class GtkRtMilk(object):
+class RtmView(common_view.CommonView):
 
        def __init__(self, widgetTree, errorDisplay):
-               """
-               @note Thread agnostic
-               """
-               self._errorDisplay = errorDisplay
-               self._manager = None
+               super(RtmView, self).__init__(widgetTree, errorDisplay)
                self._credentials = "", "", ""
-
-               self._editDialog = gtk_toolbox.EditTaskDialog(widgetTree)
-               self._projDialog = gtk_toolbox.ProjectsDialog(widgetTree)
-               self._projectsList = gtk.ListStore(gobject.TYPE_STRING)
-               self._projectsCombo = widgetTree.get_widget("projectsCombo")
-               self._projectCell = gtk.CellRendererText()
-               self._onListActivateId = 0
-
-               self._projectMenuItem = widgetTree.get_widget("projectMenuItem")
-               self._onProjectMenuItemActivated = 0
-
-               self._itemView = ItemListView(widgetTree, self._errorDisplay)
-               addSink = coroutines.CoSwitch(["add", "add-edit"])
-               addSink.register_sink("add", coroutines.func_sink(self._on_add))
-               addSink.register_sink("add-edit", coroutines.func_sink(self._on_add_edit))
-               self._addView = gtk_toolbox.QuickAddView(widgetTree, self._errorDisplay, addSink, "add")
                self._credentialsDialog = gtk_toolbox.LoginWindow(widgetTree)
 
        @staticmethod
@@ -403,94 +91,3 @@ class GtkRtMilk(object):
                """
                self._credentials = "", "", ""
                self._manager = None
-
-       def enable(self):
-               """
-               @note UI Thread
-               """
-               self._projectsList.clear()
-               self._populate_projects()
-               cell = self._projectCell
-               self._projectsCombo.pack_start(cell, True)
-               self._projectsCombo.add_attribute(cell, 'text', 0)
-
-               currentProject = self._get_project()
-               projId = self._manager.lookup_project(currentProject)["id"]
-               self._addView.enable(self._manager)
-               self._itemView.enable(self._manager, projId)
-
-               self._onListActivateId = self._projectsCombo.connect("changed", self._on_list_activate)
-               self._onProjectMenuItemActivated = self._projectMenuItem.connect("activate", self._on_proj_activate)
-
-       def disable(self):
-               """
-               @note UI Thread
-               """
-               self._projectsCombo.disconnect(self._onListActivateId)
-               self._projectMenuItem.disconnect(self._onProjectMenuItemActivated)
-               self._onListActivateId = 0
-
-               self._addView.disable()
-               self._itemView.disable()
-
-               self._projectsList.clear()
-               self._projectsCombo.set_model(None)
-
-       def _populate_projects(self):
-               projects = self._manager.get_projects()
-               sortedProjects = project_sort_by_type(projects)
-               for project in sortedProjects:
-                       projectName = project["name"]
-                       isVisible = project["isVisible"]
-                       row = (projectName, )
-                       if isVisible:
-                               self._projectsList.append(row)
-               self._projectsCombo.set_model(self._projectsList)
-               self._projectsCombo.set_active(0)
-
-       def _reset_task_list(self):
-               projectName = self._get_project()
-               projId = self._manager.lookup_project(projectName)["id"]
-               self._itemView.reset_task_list(projId)
-
-               isMeta = self._manager.get_project(projId)["isMeta"]
-               # @todo RTM handles this by defaulting to a specific list
-               self._addView.set_addability(not isMeta)
-
-       def _get_project(self):
-               currentProjectName = self._projectsCombo.get_active_text()
-               return currentProjectName
-
-       def _on_list_activate(self, *args):
-               try:
-                       self._reset_task_list()
-               except StandardError, e:
-                       self._errorDisplay.push_exception()
-
-       def _on_add(self, eventData):
-               eventName, taskName, = eventData
-               projectName = self._get_project()
-               projId = self._manager.lookup_project(projectName)["id"]
-
-               taskId = self._manager.add_task(projId, taskName)
-
-               self._itemView.reset_task_list(projId)
-
-       def _on_add_edit(self, eventData):
-               eventName, taskName, = eventData
-               projectName = self._get_project()
-               projId = self._manager.lookup_project(projectName)["id"]
-
-               taskId = self._manager.add_task(projId, taskName)
-
-               self._editDialog.enable(self._manager)
-               try:
-                       # @todo Need to pass in parent
-                       self._editDialog.request_task(self._manager, taskId)
-               finally:
-                       self._editDialog.disable()
-               self._itemView.reset_task_list(projId)
-
-       def _on_proj_activate(self, *args):
-               # @todo Need to pass in parent
-               self._projDialog.enable(self._manager)
index fe20e00..e21ca82 100644 (file)
@@ -1,6 +1,7 @@
 import sys
 import StringIO
 import urllib
+import urlparse
 from xml.dom import minidom
 import datetime
 
@@ -155,6 +156,36 @@ def load_xml(source, alternative=None):
        return xmldoc
 
 
+def abbreviate(text, expectedLen):
+       singleLine = " ".join(text.split("\n"))
+       lineLen = len(singleLine)
+       if lineLen <= expectedLen:
+               return singleLine
+
+       abbrev = "..."
+
+       leftLen = expectedLen // 2 - 1
+       rightLen = max(expectedLen - leftLen - len(abbrev) + 1, 1)
+
+       abbrevText =  singleLine[0:leftLen] + abbrev + singleLine[-rightLen:-1]
+       assert len(abbrevText) <= expectedLen, "Too long: '%s'" % abbrevText
+       return abbrevText
+
+
+def abbreviate_url(url, domainLength, pathLength):
+       urlParts = urlparse.urlparse(url)
+
+       netloc = urlParts.netloc
+       path = urlParts.path
+
+       pathLength += max(domainLength - len(netloc), 0)
+       domainLength += max(pathLength - len(path), 0)
+
+       netloc = abbreviate(netloc, domainLength)
+       path = abbreviate(path, pathLength)
+       return netloc + path
+
+
 def is_same_year(targetDate, todaysDate = datetime.datetime.today()):
        return targetDate.year == todaysDate.year