gtg team mailing list archive
-
gtg team
-
Mailing list archive
-
Message #03650
[Merge] lp:~nimit-svnit/gtg/bug-1067738 into lp:gtg
Nimit Shah has proposed merging lp:~nimit-svnit/gtg/bug-1067738 into lp:gtg.
Requested reviews:
Gtg developers (gtg)
For more details, see:
https://code.launchpad.net/~nimit-svnit/gtg/bug-1067738/+merge/130662
Emptied the text on quick-add bar when a user clicked on tagtree item or task tree item
--
https://code.launchpad.net/~nimit-svnit/gtg/bug-1067738/+merge/130662
Your team Gtg developers is requested to review the proposed merge of lp:~nimit-svnit/gtg/bug-1067738 into lp:gtg.
=== modified file 'CHANGELOG'
--- CHANGELOG 2012-09-08 08:10:51 +0000
+++ CHANGELOG 2012-10-20 05:46:21 +0000
@@ -53,6 +53,7 @@
* Fix for bug-1037051 (Due date is not set for a new subtask), by Nimit Shah
* Fix for bug #1036955: Due date is not preselected when start date is filled, by Steve Scheel
* Fix for bug #1045036: Slovak Translation Updated by Slavko
+ * Fix for bug #1067738: Quick-add bar search query not cleared, by Nimit Shah
2012-02-13 Getting Things GNOME! 0.2.9
* Big refractorization of code, now using liblarch
=== modified file 'GTG/gtk/browser/browser.py'
--- GTG/gtk/browser/browser.py 2012-08-12 23:01:10 +0000
+++ GTG/gtk/browser/browser.py 2012-10-20 05:46:21 +0000
@@ -5,6 +5,13 @@
# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
#
# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by th# -*- coding: utf-8 -*-
+# pylint: disable-msg=W0201
+# -----------------------------------------------------------------------------
+# Getting Things Gnome! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
+#
+# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
@@ -825,6 +832,1641 @@
"""
deals with mouse click event on the tag tree
"""
+ self.quickadd_entry.set_text('')
+ Log.debug("Received button event #%d at %d, %d" % (
+ event.button, event.x, event.y))
+ if event.button == 3:
+ x = int(event.x)
+ y = int(event.y)
+ time = event.time
+ pthinfo = treeview.get_path_at_pos(x, y)
+ if pthinfo is not None:
+ path, col, cellx, celly = pthinfo #pylint: disable-msg=W0612
+ treeview.grab_focus()
+ # The location we want the cursor to return to
+ # after we're done.
+ self.previous_cursor = treeview.get_cursor()
+ # For use in is_task_visible
+ self.previous_tag = self.get_selected_tags()
+ # Let's us know that we're working on a tag.
+ self.tag_active = True
+
+ # This location is stored in case we need to work with it
+ # later on.
+ self.target_cursor = path, col
+ treeview.set_cursor(path, col, 0)
+ #the nospecial=True disable right clicking for special tags
+ selected_tags = self.get_selected_tags(nospecial=True)
+ selected_search = self.get_selected_search()
+ #popup menu for searches
+ if selected_search is not None:
+ my_tag = self.req.get_tag(selected_search)
+ self.tagpopup.set_tag(my_tag)
+ self.tagpopup.popup(None, None, None, event.button, time)
+ elif len(selected_tags) > 0:
+ # Then we are looking at single, normal tag rather than
+ # the special 'All tags' or 'Tasks without tags'. We only
+ # want to popup the menu for normal tags.
+ my_tag = self.req.get_tag(selected_tags[0])
+ self.tagpopup.set_tag(my_tag)
+ self.tagpopup.popup(None, None, None, event.button, time)
+ else:
+ self.reset_cursor()
+ return True
+
+ def on_tag_treeview_key_press_event(self, treeview, event):
+ keyname = gtk.gdk.keyval_name(event.keyval)
+ is_shift_f10 = (keyname == "F10" and
+ event.get_state() & gtk.gdk.SHIFT_MASK)
+ if is_shift_f10 or keyname == "Menu":
+ selected_tags = self.get_selected_tags(nospecial=True)
+ selected_search = self.get_selected_search()
+ #popup menu for searches
+ if selected_search is not None:
+ self.searchpopup.popup(None, None, None, 0, event.time)
+ elif len(selected_tags) > 0:
+ # Then we are looking at single, normal tag rather than
+ # the special 'All tags' or 'Tasks without tags'. We only
+ # want to popup the menu for normal tags.
+ selected_tag = self.req.get_tag(selected_tags[0])
+ self.tagpopup.set_tag(selected_tag)
+ self.tagpopup.popup(None, None, None, 0, event.time)
+ else:
+ self.reset_cursor()
+ return True
+
+ def on_task_treeview_button_press_event(self, treeview, event):
+ """ Pop up context menu on right mouse click in the main
+ task tree view """
+ self.quickadd_entry.set_text('')
+ Log.debug("Received button event #%d at %d,%d" % (
+ event.button, event.x, event.y))
+ if event.button == 3:
+ x = int(event.x)
+ y = int(event.y)
+ time = event.time
+ pthinfo = treeview.get_path_at_pos(x, y)
+ if pthinfo is not None:
+ path, col, cellx, celly = pthinfo
+ selection = treeview.get_selection()
+ if selection.count_selected_rows() > 0:
+ if not selection.path_is_selected(path):
+ treeview.set_cursor(path, col, 0)
+ else:
+ treeview.set_cursor(path, col, 0)
+ treeview.grab_focus()
+ self.taskpopup.popup(None, None, None, event.button, time)
+ return True
+
+ def on_task_treeview_key_press_event(self, treeview, event):
+ keyname = gtk.gdk.keyval_name(event.keyval)
+ is_shift_f10 = (keyname == "F10" and
+ event.get_state() & gtk.gdk.SHIFT_MASK)
+
+ if keyname == "Delete":
+ self.on_delete_tasks()
+ return True
+ elif is_shift_f10 or keyname == "Menu":
+ self.taskpopup.popup(None, None, None, 0, event.time)
+ return True
+
+ def on_closed_task_treeview_button_press_event(self, treeview, event):
+ if event.button == 3:
+ x = int(event.x)
+ y = int(event.y)
+ time = event.time
+ pthinfo = treeview.get_path_at_pos(x, y)
+ if pthinfo is not None:
+ path, col, cellx, celly = pthinfo
+ treeview.grab_focus()
+ treeview.set_cursor(path, col, 0)
+ self.ctaskpopup.popup(None, None, None, event.button, time)
+ return True
+
+ def on_closed_task_treeview_key_press_event(self, treeview, event):
+ keyname = gtk.gdk.keyval_name(event.keyval)
+ is_shift_f10 = (keyname == "F10" and
+ event.get_state() & gtk.gdk.SHIFT_MASK)
+
+ if keyname == "Delete":
+ self.on_delete_tasks()
+ return True
+ elif is_shift_f10 or keyname == "Menu":
+ self.ctaskpopup.popup(None, None, None, 0, event.time)
+ return True
+
+ def on_add_task(self, widget, status=None):
+ tags = [tag for tag in self.get_selected_tags() if tag.startswith('@')]
+ task = self.req.new_task(tags=tags, newtask=True)
+ uid = task.get_id()
+ if status:
+ task.set_status(status)
+ self.vmanager.open_task(uid, thisisnew=True)
+
+ def on_add_subtask(self, widget):
+ uid = self.get_selected_task()
+ if uid:
+ zetask = self.req.get_task(uid)
+ tags = [t.get_name() for t in zetask.get_tags()]
+ task = self.req.new_task(tags=tags, newtask=True)
+ #task.add_parent(uid)
+ zetask.add_child(task.get_id())
+ self.vmanager.open_task(task.get_id(), thisisnew=True)
+
+ def on_edit_active_task(self, widget, row=None, col=None):
+ tid = self.get_selected_task()
+ if tid:
+ self.vmanager.open_task(tid)
+
+ def on_edit_done_task(self, widget, row=None, col=None):
+ tid = self.get_selected_task('closed')
+ if tid:
+ self.vmanager.open_task(tid)
+
+ def on_delete_tasks(self, widget=None, tid=None):
+ #If we don't have a parameter, then take the selection in the treeview
+ if not tid:
+ #tid_to_delete is a [project,task] tuple
+ tids_todelete = self.get_selected_tasks()
+ if not tids_todelete:
+ return
+ else:
+ tids_todelete = [tid]
+ Log.debug("going to delete %s" % tids_todelete)
+ self.vmanager.ask_delete_tasks(tids_todelete)
+
+ def update_start_date(self, widget, new_start_date):
+ tasks = [self.req.get_task(uid)
+ for uid in self.get_selected_tasks()
+ if uid is not None]
+
+ start_date = Date.parse(new_start_date)
+
+ #FIXME: If the task dialog is displayed, refresh its start_date widget
+ for task in tasks:
+ task.set_start_date(start_date)
+
+ def on_mark_as_started(self, widget):
+ self.update_start_date(widget, "today")
+
+ def on_start_for_tomorrow(self, widget):
+ self.update_start_date(widget, "tomorrow")
+
+ def on_start_for_next_week(self, widget):
+ self.update_start_date(widget, "next week")
+
+ def on_start_for_next_month(self, widget):
+ self.update_start_date(widget, "next month")
+
+ def on_start_for_next_year(self, widget):
+ self.update_start_date(widget, "next year")
+
+ def on_start_clear(self, widget):
+ self.update_start_date(widget, None)
+
+ def update_due_date(self, widget, new_due_date):
+ tasks = [self.req.get_task(uid)
+ for uid in self.get_selected_tasks()
+ if uid is not None]
+
+ due_date = Date.parse(new_due_date)
+
+ #FIXME: If the task dialog is displayed, refresh its due_date widget
+ for task in tasks:
+ task.set_due_date(due_date)
+
+ def on_set_due_today(self, widget):
+ self.update_due_date(widget, "today")
+
+ def on_set_due_tomorrow(self, widget):
+ self.update_due_date(widget, "tomorrow")
+
+ def on_set_due_next_week(self, widget):
+ self.update_due_date(widget, "next week")
+
+ def on_set_due_next_month(self, widget):
+ self.update_due_date(widget, "next month")
+
+ def on_set_due_next_year(self, widget):
+ self.update_due_date(widget, "next year")
+
+ def on_set_due_now(self, widget):
+ self.update_due_date(widget, "now")
+
+ def on_set_due_soon(self, widget):
+ self.update_due_date(widget, "soon")
+
+ def on_set_due_someday(self, widget):
+ self.update_due_date(widget, "someday")
+
+ def on_set_due_clear(self, widget):
+ self.update_due_date(widget, None)
+
+ def on_modify_tags(self, widget):
+ """ Run Modify Tags dialog on selected tasks """
+ tasks = self.get_selected_tasks()
+ self.modifytags_dialog.modify_tags(tasks)
+
+ def close_all_task_editors(self, task_id):
+ """ Including editors of subtasks """
+ all_subtasks = []
+
+ def trace_subtasks(root):
+ all_subtasks.append(root)
+ for i in root.get_subtasks():
+ if i not in all_subtasks:
+ trace_subtasks(i)
+
+ trace_subtasks(self.req.get_task(task_id))
+
+ for task in all_subtasks:
+ self.vmanager.close_task(task.get_id())
+
+ def on_mark_as_done(self, widget):
+ tasks_uid = [uid for uid in self.get_selected_tasks()
+ if uid is not None]
+ if len(tasks_uid) == 0:
+ return
+ tasks = [self.req.get_task(uid) for uid in tasks_uid]
+ tasks_status = [task.get_status() for task in tasks]
+ for uid, task, status in zip(tasks_uid, tasks, tasks_status):
+ if status == Task.STA_DONE:
+ # Marking as undone
+ task.set_status(Task.STA_ACTIVE)
+ # Parents of that task must be updated - not to be shown
+ # in workview, update children count, etc.
+ for parent_id in task.get_parents():
+ parent = self.req.get_task(parent_id)
+ parent.modified()
+ else:
+ task.set_status(Task.STA_DONE)
+ self.close_all_task_editors(uid)
+
+ def on_dismiss_task(self, widget):
+ tasks_uid = [uid for uid in self.get_selected_tasks()
+ if uid is not None]
+ if len(tasks_uid) == 0:
+ return
+ tasks = [self.req.get_task(uid) for uid in tasks_uid]
+ tasks_status = [task.get_status() for task in tasks]
+ for uid, task, status in zip(tasks_uid, tasks, tasks_status):
+ if status == Task.STA_DISMISSED:
+ task.set_status(Task.STA_ACTIVE)
+ else:
+ task.set_status(Task.STA_DISMISSED)
+ self.close_all_task_editors(uid)
+
+ def apply_filter_on_panes(self, filter_name):
+ """ Apply filters for every pane: active tasks, closed tasks """
+ for pane in self.vtree_panes:
+ vtree = self.req.get_tasks_tree(name=pane, refresh=False)
+ vtree.reset_filters(refresh=False, transparent_only=True)
+ vtree.apply_filter(filter_name, refresh=True)
+
+ def on_select_tag(self, widget=None, row=None, col=None):
+ """
+ callback for when selecting an element of the tagtree (left sidebar)
+ """
+ # FIXME add support for multiple selection of tags in future
+
+ # When enable_update_tags we should update all tags to match
+ # the current state. However, applying tag filter does not influence
+ # other tags, because of transparent filter. Therefore there is no
+ # self.tagree.refresh_all() => a significant optimization!
+ # See do_toggle_workview()
+ self.tv_factory.disable_update_tags()
+
+ #When you click on a tag, you want to unselect the tasks
+ taglist = self.get_selected_tags()
+ if len(taglist) > 0:
+ tagname = taglist[0]
+ self.apply_filter_on_panes(tagname)
+
+ # In case of search tag, set query in quickadd for
+ # refining search query
+ tag = self.req.get_tag(tagname)
+ if tag.is_search_tag():
+ self.quickadd_entry.set_text(tag.get_attribute("query"))
+
+ self.tv_factory.enable_update_tags()
+
+ def on_taskdone_cursor_changed(self, selection=None):
+ """Called when selection changes in closed task view.
+
+ Changes the way the selected task is displayed.
+ """
+ settings_done = {"label": GnomeConfig.MARK_DONE,
+ "tooltip": GnomeConfig.MARK_DONE_TOOLTIP,
+ "icon-name": "gtg-task-done"}
+ settings_undone = {"label": GnomeConfig.MARK_UNDONE,
+ "tooltip": GnomeConfig.MARK_UNDONE_TOOLTIP,
+ "icon-name": "gtg-task-undone"}
+ settings_dismiss = {"label": GnomeConfig.MARK_DISMISS,
+ "tooltip": GnomeConfig.MARK_DISMISS_TOOLTIP,
+ "icon-name": "gtg-task-dismiss"}
+ settings_undismiss = {"label": GnomeConfig.MARK_UNDISMISS,
+ "tooltip": GnomeConfig.MARK_UNDISMISS_TOOLTIP,
+ "icon-name": "gtg-task-undismiss"}
+
+ def update_button(button, settings):
+ button.set_icon_name(settings["icon-name"])
+ button.set_label(settings["label"])
+
+ def update_menu_item(menu_item, settings):
+ image = gtk.image_new_from_icon_name(settings["icon-name"], 16)
+ image.set_pixel_size(16)
+ image.show()
+ menu_item.set_image(image)
+ menu_item.set_label(settings["label"])
+
+ #We unselect all in the active task view
+ #Only if something is selected in the closed task list
+ #And we change the status of the Done/dismiss button
+ update_button(self.donebutton, settings_done)
+ update_menu_item(self.done_mi, settings_done)
+ update_button(self.dismissbutton, settings_dismiss)
+ update_menu_item(self.dismiss_mi, settings_dismiss)
+ if selection.count_selected_rows() > 0:
+ tid = self.get_selected_task('closed')
+ task = self.req.get_task(tid)
+ self.vtree_panes['active'].get_selection().unselect_all()
+ if task.get_status() == "Dismiss":
+ self.builder.get_object(
+ "ctcm_mark_as_not_done").set_sensitive(False)
+ self.builder.get_object("ctcm_undismiss").set_sensitive(True)
+ update_button(self.dismissbutton, settings_undismiss)
+ update_menu_item(self.dismiss_mi, settings_undismiss)
+ else:
+ self.builder.get_object(
+ "ctcm_mark_as_not_done").set_sensitive(True)
+ self.builder.get_object(
+ "ctcm_undismiss").set_sensitive(False)
+ update_button(self.donebutton, settings_undone)
+ update_menu_item(self.done_mi, settings_undone)
+ self.update_buttons_sensitivity()
+
+ def on_task_cursor_changed(self, selection=None):
+ """Called when selection changes in the active task view.
+
+ Changes the way the selected task is displayed.
+ """
+ #We unselect all in the closed task view
+ #Only if something is selected in the active task list
+ self.donebutton.set_icon_name("gtg-task-done")
+ self.dismissbutton.set_icon_name("gtg-task-dismiss")
+ if selection.count_selected_rows() > 0:
+ if 'closed' in self.vtree_panes:
+ self.vtree_panes['closed'].get_selection().unselect_all()
+ self.donebutton.set_label(GnomeConfig.MARK_DONE)
+ self.donebutton.set_tooltip_text(GnomeConfig.MARK_DONE_TOOLTIP)
+ self.dismissbutton.set_label(GnomeConfig.MARK_DISMISS)
+ self.update_buttons_sensitivity()
+
+ def on_close(self, widget=None):
+ """Closing the window."""
+ #Saving is now done in main.py
+ self.quit()
+
+ #using dummy parameters that are given by the signal
+ def update_buttons_sensitivity(self, a=None, b=None, c=None):
+ enable = self.selection.count_selected_rows()
+ if 'closed' in self.vtree_panes:
+ enable += self.closed_selection.count_selected_rows() > 0
+ self.edit_mi.set_sensitive(enable)
+ self.new_subtask_mi.set_sensitive(enable)
+ self.done_mi.set_sensitive(enable)
+ self.dismiss_mi.set_sensitive(enable)
+ self.delete_mi.set_sensitive(enable)
+ self.donebutton.set_sensitive(enable)
+ self.dismissbutton.set_sensitive(enable)
+ self.deletebutton.set_sensitive(enable)
+
+### PUBLIC METHODS #########################################################
+ def get_selected_task(self, tv=None):
+ """
+ Returns the'uid' of the selected task, if any.
+ If multiple tasks are selected, returns only the first and
+ takes care of selecting only that (unselecting the others)
+
+ @param tv: The tree view to find the selected task in. Defaults to
+ the task_tview.
+ """
+ ids = self.get_selected_tasks(tv)
+ if len(ids) > 0:
+ #FIXME: we should also unselect all the others
+ return ids[0]
+ else:
+ return None
+
+ def get_selected_tasks(self, tv=None):
+ """
+ Returns a list of 'uids' of the selected tasks, and the corresponding
+ iters
+
+ @param tv: The tree view to find the selected task in. Defaults to
+ the task_tview.
+ """
+ #FIXME Why we have active as back case? is that so? Study this code
+ selected = []
+ if tv:
+ selected = self.vtree_panes[tv].get_selected_nodes()
+ else:
+ if 'active' in self.vtree_panes:
+ selected = self.vtree_panes['active'].get_selected_nodes()
+ for i in self.vtree_panes:
+ if len(selected) == 0:
+ selected = self.vtree_panes[i].get_selected_nodes()
+ return selected
+
+ #If nospecial=True, only normal @tag are considered
+ def get_selected_tags(self, nospecial=False):
+ """
+ Returns the selected nodes from the tagtree
+
+ @param nospecial: doesn't return tags that do not stat with
+ """
+ taglist = []
+ if self.tagtreeview:
+ taglist = self.tagtreeview.get_selected_nodes()
+ #If no selection, we display all
+ if not nospecial and (not taglist or len(taglist) < 0):
+ taglist = ['gtg-tags-all']
+ if nospecial:
+ for t in list(taglist):
+ if not t.startswith('@'):
+ taglist.remove(t)
+ return taglist
+
+ def reset_cursor(self):
+ """ Returns the cursor to the tag that was selected prior
+ to any right click action. Should be used whenever we're done
+ working with any tag through a right click menu action.
+ """
+ if self.tag_active:
+ self.tag_active = False
+ path, col = self.previous_cursor
+ if self.tagtreeview:
+ self.tagtreeview.set_cursor(path, col, 0)
+
+ def set_target_cursor(self):
+ """ Selects the last tag to be right clicked.
+
+ We need this because the context menu will deactivate
+ (and in turn, call reset_cursor()) before, for example, the color
+ picker dialog begins. Should be used at the beginning of any tag
+ editing function to remind the user which tag they're working with.
+ """
+ if not self.tag_active:
+ self.tag_active = True
+ path, col = self.target_cursor
+ if self.tagtreeview:
+ self.tagtreeview.set_cursor(path, col, 0)
+
+ def add_page_to_sidebar_notebook(self, icon, page):
+ """Adds a new page tab to the left panel. The tab will
+ be added as the last tab. Also causes the tabs to be
+ shown if they're not.
+ @param icon: a gtk.Image picture to display on the tab
+ @param page: gtk.Frame-based panel to be added
+ """
+ return self._add_page(self.sidebar_notebook, icon, page)
+
+ def add_page_to_main_notebook(self, title, page):
+ """Adds a new page tab to the top right main panel. The tab
+ will be added as the last tab. Also causes the tabs to be
+ shown.
+ @param title: Short text to use for the tab label
+ @param page: gtk.Frame-based panel to be added
+ """
+ return self._add_page(self.main_notebook, gtk.Label(title), page)
+
+ def add_page_to_accessory_notebook(self, title, page):
+ """Adds a new page tab to the lower right accessory panel. The
+ tab will be added as the last tab. Also causes the tabs to be
+ shown.
+ @param title: Short text to use for the tab label
+ @param page: gtk.Frame-based panel to be added
+ """
+ return self._add_page(self.accessory_notebook, gtk.Label(title), page)
+
+ def remove_page_from_sidebar_notebook(self, page):
+ """Removes a new page tab from the left panel. If this leaves
+ only one tab in the notebook, the tab selector will be hidden.
+ @param page: gtk.Frame-based panel to be removed
+ """
+ return self._remove_page(self.sidebar_notebook, page)
+
+ def remove_page_from_main_notebook(self, page):
+ """Removes a new page tab from the top right main panel. If
+ this leaves only one tab in the notebook, the tab selector will
+ be hidden.
+ @param page: gtk.Frame-based panel to be removed
+ """
+ return self._remove_page(self.main_notebook, page)
+
+ def remove_page_from_accessory_notebook(self, page):
+ """Removes a new page tab from the lower right accessory panel.
+ If this leaves only one tab in the notebook, the tab selector
+ will be hidden.
+ @param page: gtk.Frame-based panel to be removed
+ """
+ return self._remove_page(self.accessory_notebook, page)
+
+ def hide(self):
+ """ Hides the task browser """
+ self.browser_shown = False
+ self.window.hide()
+ gobject.idle_add(self.emit, "visibility-toggled")
+
+ def show(self):
+ """ Unhides the TaskBrowser """
+ self.browser_shown = True
+ #redraws the GDK window, bringing it to front
+ self.window.show()
+ self.window.present()
+ self.window.grab_focus()
+ self.quickadd_entry.grab_focus()
+ gobject.idle_add(self.emit, "visibility-toggled")
+
+ def iconify(self):
+ """ Minimizes the TaskBrowser """
+ self.window.iconify()
+
+ def is_visible(self):
+ """ Returns true if window is shown or false if hidden. """
+ return self.window.get_property("visible")
+
+ def is_active(self):
+ """ Returns true if window is the currently active window """
+ return self.window.get_property("is-active")
+
+ def get_builder(self):
+ return self.builder
+
+ def get_window(self):
+ return self.window
+
+ def get_active_tree(self):
+ '''
+ Returns the browser tree with all the filters applied. The tasks in
+ the tree are the same as the ones shown in the browser current view
+ '''
+ return self.activetree
+
+ def get_closed_tree(self):
+ '''
+ Returns the browser tree with all the filters applied. The tasks in
+ the tree are the same as the ones shown in the browser current closed
+ view.
+ '''
+ return self.__create_closed_tree()
+
+ def is_shown(self):
+ return self.browser_shown
+
+## BACKENDS RELATED METHODS ##################################################
+ def on_backend_failed(self, sender, backend_id, error_code):
+ """
+ Signal callback.
+ When a backend fails to work, loads a gtk.Infobar to alert the user
+
+ @param sender: not used, only here for signal compatibility
+ @param backend_id: the id of the failing backend
+ @param error_code: a backend error code, as specified
+ in BackendsSignals
+ """
+ infobar = self._new_infobar(backend_id)
+ infobar.set_error_code(error_code)
+
+ def on_backend_needing_interaction(self, sender, backend_id, description, \
+ interaction_type, callback):
+ '''
+ Signal callback.
+ When a backend needs some kind of feedback from the user,
+ loads a gtk.Infobar to alert the user.
+ This is used, for example, to request confirmation after authenticating
+ via OAuth.
+
+ @param sender: not used, only here for signal compatibility
+ @param backend_id: the id of the failing backend
+ @param description: a string describing the interaction needed
+ @param interaction_type: a string describing the type of interaction
+ (yes/no, only confirm, ok/cancel...)
+ @param callback: the function to call when the user provides the
+ feedback
+ '''
+ infobar = self._new_infobar(backend_id)
+ infobar.set_interaction_request(description, interaction_type,
+ callback)
+
+ def __remove_backend_infobar(self, child, backend_id):
+ '''
+ Helper function to remove an gtk.Infobar related to a backend
+
+ @param child: a gtk.Infobar
+ @param backend_id: the id of the backend which gtk.Infobar should be
+ removed.
+ '''
+ if isinstance(child, CustomInfoBar) and\
+ child.get_backend_id() == backend_id:
+ if self.vbox_toolbars:
+ self.vbox_toolbars.remove(child)
+
+ def remove_backend_infobar(self, sender, backend_id):
+ '''
+ Signal callback.
+ Deletes the gtk.Infobars related to a backend
+
+ @param sender: not used, only here for signal compatibility
+ @param backend_id: the id of the backend which gtk.Infobar should be
+ removed.
+ '''
+ backend = self.req.get_backend(backend_id)
+ if not backend or (backend and backend.is_enabled()):
+ #remove old infobar related to backend_id, if any
+ if self.vbox_toolbars:
+ self.vbox_toolbars.foreach(self.__remove_backend_infobar, \
+ backend_id)
+
+ def _new_infobar(self, backend_id):
+ '''
+ Helper function to create a new infobar for a backend
+
+ @param backend_id: the backend for which we're creating the infobar
+ @returns gtk.Infobar: the created infobar
+ '''
+ #remove old infobar related to backend_id, if any
+ if not self.vbox_toolbars:
+ return
+ self.vbox_toolbars.foreach(self.__remove_backend_infobar, backend_id)
+ #add a new one
+ infobar = CustomInfoBar(self.req, self, self.vmanager, backend_id)
+ self.vbox_toolbars.pack_start(infobar, True)
+ return infobar
+
+#### SEARCH RELATED STUFF #####################################################
+ def get_selected_search(self):
+ """ return just one selected view """
+ if self.tagtreeview:
+ tags = self.tagtreeview.get_selected_nodes()
+ if len(tags)>0:
+ tag = self.tagtree.get_node(tags[0])
+ if tag.is_search_tag():
+ return tags[0]
+ return None
+
+ def _init_search_completion(self):
+ """ Initialize search completion """
+ self.search_completion = self.builder.get_object(
+ "quickadd_entrycompletion")
+ self.quickadd_entry.set_completion(self.search_completion)
+
+ self.search_possible_actions = {
+ 'add': _("Add Task"),
+ 'open': _("Open Task"),
+ 'search': _("Search"),
+ }
+
+ self.search_actions = []
+
+ self.search_complete_store = gtk.ListStore(str)
+ for tagname in self.req.get_all_tags():
+ # only for regular tags
+ if tagname.startswith("@"):
+ self.search_complete_store.append([tagname])
+
+ for command in SEARCH_COMMANDS:
+ self.search_complete_store.append([command])
+
+ self.search_completion.set_model(self.search_complete_store)
+ self.search_completion.set_text_column(0)
+
+ def on_quickadd_changed(self, editable):
+ """ Decide which actions are allowed with the current query """
+ # delete old actions
+ for i in range(len(self.search_actions)):
+ self.search_completion.delete_action(0)
+
+ self.search_actions = []
+ new_actions = []
+ query = self.quickadd_entry.get_text()
+ query = query.strip()
+
+ # If the tag pane is hidden, reset search filter when query is empty
+ if query == '' and not self.config.get("tag_pane"):
+ tree = self.req.get_tasks_tree(refresh=False)
+ filters = tree.list_applied_filters()
+ for tag_id in self.req.get_all_tags():
+ tag = self.req.get_tag(tag_id)
+ if tag.is_search_tag() and tag_id in filters:
+ self.req.remove_tag(tag_id)
+ self.apply_filter_on_panes(CoreConfig.ALLTASKS_TAG)
+ return
+
+ if query:
+ if self.req.get_task_id(query) is not None:
+ new_actions.append('open')
+ else:
+ new_actions.append('add')
+
+ # Is query parsable?
+ try:
+ parse_search_query(query)
+ new_actions.append('search')
+ except InvalidQuery:
+ pass
+
+ # Add new order of actions
+ for aid, name in enumerate(new_actions):
+ action = self.search_possible_actions[name]
+ self.search_completion.insert_action_markup(aid, action)
+ self.search_actions.append(name)
+
+ def expand_search_tag(self):
+ """ For some unknown reason, search tag is not expanded correctly and
+ it must be done manually """
+ if self.tagtreeview is not None:
+ model = self.tagtreeview.get_model()
+ search_iter = model.my_get_iter((CoreConfig.SEARCH_TAG, ))
+ search_path = model.get_path(search_iter)
+ self.tagtreeview.expand_row(search_path, False)
+
+ def on_entrycompletion_action_activated(self, completion, index):
+ """ Executes action from completition of quickadd toolbar """
+ action = self.search_actions[index]
+ if action == 'add':
+ self.on_quickadd_activate(None)
+ elif action == 'open':
+ task_title = self.quickadd_entry.get_text()
+ task_id = self.req.get_task_id(task_title)
+ self.vmanager.open_task(task_id)
+ self.quickadd_entry.set_text('')
+ elif action == 'search':
+ query = self.quickadd_entry.get_text()
+ # ! at the beginning is reserved keyword for liblarch
+ if query.startswith('!'):
+ label = '_' + query
+ else:
+ label = query
+
+ # find possible name collisions
+ name, number = label, 1
+ already_search = False
+ while True:
+ tag = self.req.get_tag(name)
+ if tag is None:
+ break
+
+ if tag.is_search_tag() and tag.get_attribute("query") == query:
+ already_search = True
+ break
+
+ # this name is used, adding number
+ number += 1
+ name = label + ' ' + str(number)
+
+ if not already_search:
+ tag = self.req.new_search_tag(name, query)
+
+ # Apply new search right now
+ if self.tagtreeview is not None:
+ # Select new search in tagsidebar and apply it
+
+ # Make sure search tag parent is expanded
+ # (otherwise selection does not work)
+ self.expand_search_tag()
+
+ # Get iterator for new search tag
+ model = self.tagtreeview.get_model()
+ path = self.tagtree.get_paths_for_node(tag.get_id())[0]
+ tag_iter = model.my_get_iter(path)
+
+ # Select only it and apply filters on top of that
+ selection = self.tagtreeview.get_selection()
+ selection.unselect_all()
+ selection.select_iter(tag_iter)
+ self.on_select_tag()
+ else:
+ self.apply_filter_on_panes(name)e Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+""" The main window for GTG, listing tags, and open and closed tasks """
+
+#=== IMPORT ===================================================================
+#system imports
+import time
+import threading
+from webbrowser import open as openurl
+
+import pygtk
+pygtk.require('2.0')
+import gobject
+import gtk
+
+#our own imports
+from GTG import _, info, ngettext
+from GTG.backends.backendsignals import BackendSignals
+from GTG.core import CoreConfig
+from GTG.core.search import parse_search_query, SEARCH_COMMANDS, InvalidQuery
+from GTG.core.task import Task
+from GTG.gtk.tag_completion import TagCompletion
+from GTG.gtk.browser import GnomeConfig
+from GTG.gtk.browser.custominfobar import CustomInfoBar
+from GTG.gtk.browser.modifytags_dialog import ModifyTagsDialog
+from GTG.gtk.browser.tag_context_menu import TagContextMenu
+from GTG.gtk.browser.treeview_factory import TreeviewFactory
+from GTG.tools.dates import Date
+from GTG.tools.logger import Log
+
+#=== MAIN CLASS ===============================================================
+
+
+
+class Timer:
+
+ def __init__(self, name):
+ self.name = name
+
+ def __enter__(self):
+ self.start = time.time()
+
+ def __exit__(self, *args):
+ print "%s : %s" % (self.name, time.time() - self.start)
+
+
+class TaskBrowser(gobject.GObject):
+ """ The UI for browsing open and closed tasks,
+ and listing tags in a tree """
+
+ __string_signal__ = (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (str, ))
+ __none_signal__ = (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, tuple())
+ __gsignals__ = {'task-added-via-quick-add': __string_signal__,
+ 'visibility-toggled': __none_signal__,
+ }
+
+ def __init__(self, requester, vmanager):
+ gobject.GObject.__init__(self)
+ # Object prime variables
+ self.req = requester
+ self.vmanager = vmanager
+ self.config = self.req.get_config('browser')
+ self.tag_active = False
+
+ #treeviews handlers
+ self.vtree_panes = {}
+ self.tv_factory = TreeviewFactory(self.req, self.config)
+ self.activetree = self.req.get_tasks_tree(name='active', refresh=False)
+ self.vtree_panes['active'] = \
+ self.tv_factory.active_tasks_treeview(self.activetree)
+
+ ### YOU CAN DEFINE YOUR INTERNAL MECHANICS VARIABLES BELOW
+ self.in_toggle_workview = False
+
+ # Setup GTG icon theme
+ self._init_icon_theme()
+
+ # Set up models
+ # Active Tasks
+ self.activetree.apply_filter('active')
+ # Tags
+ self.tagtree = None
+ self.tagtreeview = None
+
+ # Load window tree
+ self.builder = gtk.Builder()
+ self.builder.add_from_file(GnomeConfig.GLADE_FILE)
+
+ # Define aliases for specific widgets
+ self._init_widget_aliases()
+
+ # Init non-glade widgets
+ self._init_ui_widget()
+
+ #Set the tooltip for the toolbar buttons
+ self._init_toolbar_tooltips()
+
+ # Initialize "About" dialog
+ self._init_about_dialog()
+
+ #Create our dictionary and connect it
+ self._init_signal_connections()
+
+ # Define accelerator keys
+ self._init_accelerators()
+
+ # Initialize search completion
+ self._init_search_completion()
+
+ self.restore_state_from_conf()
+
+ self.on_select_tag()
+ self.browser_shown = False
+
+ #Update the title when a task change
+ self.activetree.register_cllbck('node-added-inview',
+ self._update_window_title)
+ self.activetree.register_cllbck('node-deleted-inview',
+ self._update_window_title)
+ self._update_window_title()
+
+### INIT HELPER FUNCTIONS #####################################################
+#
+ def _init_icon_theme(self):
+ """
+ sets the deafault theme for icon and its directory
+ """
+ icon_dirs = CoreConfig().get_icons_directories()
+ for i in icon_dirs:
+ gtk.icon_theme_get_default().prepend_search_path(i)
+ gtk.window_set_default_icon_name("gtg")
+
+ def _init_widget_aliases(self):
+ """
+ defines aliases for UI elements found in the glide file
+ """
+ self.window = self.builder.get_object("MainWindow")
+ self.taskpopup = self.builder.get_object("task_context_menu")
+ self.defertopopup = self.builder.get_object("defer_to_context_menu")
+ self.ctaskpopup = self.builder.get_object("closed_task_context_menu")
+ self.editbutton = self.builder.get_object("edit_b")
+ self.edit_mi = self.builder.get_object("edit_mi")
+ self.donebutton = self.builder.get_object("done_b")
+ self.done_mi = self.builder.get_object("done_mi")
+ self.deletebutton = self.builder.get_object("delete_b")
+ self.delete_mi = self.builder.get_object("delete_mi")
+ self.newtask = self.builder.get_object("new_task_b")
+ self.newsubtask = self.builder.get_object("new_subtask_b")
+ self.new_subtask_mi = self.builder.get_object("new_subtask_mi")
+ self.dismissbutton = self.builder.get_object("dismiss_b")
+ self.dismiss_mi = self.builder.get_object("dismiss_mi")
+ self.about = self.builder.get_object("about_dialog")
+ self.main_pane = self.builder.get_object("main_pane")
+ self.menu_view_workview = self.builder.get_object("view_workview")
+ self.toggle_workview = self.builder.get_object("workview_toggle")
+ self.quickadd_entry = self.builder.get_object("quickadd_field")
+ self.toolbar = self.builder.get_object("task_toolbar")
+ self.quickadd_pane = self.builder.get_object("quickadd_pane")
+ self.sidebar = self.builder.get_object("sidebar_vbox")
+ self.sidebar_container = self.builder.get_object("sidebar-scroll")
+ self.sidebar_notebook = self.builder.get_object("sidebar_notebook")
+ self.main_notebook = self.builder.get_object("main_notebook")
+ self.accessory_notebook = self.builder.get_object("accessory_notebook")
+ self.vbox_toolbars = self.builder.get_object("vbox_toolbars")
+
+ self.closed_pane = None
+ self.tagpopup = TagContextMenu(self.req, self.vmanager)
+
+ def _init_ui_widget(self):
+ """ Sets the main pane with the tree with active tasks and
+ create ModifyTagsDialog """
+ # The Active tasks treeview
+ self.main_pane.add(self.vtree_panes['active'])
+
+ tag_completion = TagCompletion(self.req.get_tag_tree())
+ self.modifytags_dialog = ModifyTagsDialog(tag_completion, self.req)
+
+ def init_tags_sidebar(self):
+ """
+ initializes the tagtree (left area with tags and searches)
+ """
+ # The tags treeview
+ self.tagtree = self.req.get_tag_tree()
+ self.tagtreeview = self.tv_factory.tags_treeview(self.tagtree)
+ #Tags treeview
+ self.tagtreeview.connect('cursor-changed', \
+ self.on_select_tag)
+ self.tagtreeview.connect('row-activated', \
+ self.on_select_tag)
+ self.tagtreeview.connect('button-press-event', \
+ self.on_tag_treeview_button_press_event)
+ self.tagtreeview.connect('key-press-event', \
+ self.on_tag_treeview_key_press_event)
+ self.sidebar_container.add(self.tagtreeview)
+
+ # Refresh tree
+ self.tagtree.reset_filters(transparent_only=True)
+
+ # expanding search tag does not work automatically, request it
+ self.expand_search_tag()
+
+ def _init_toolbar_tooltips(self):
+ """
+ sets tooltips for widgets on toolbars
+ """
+ self.donebutton.set_tooltip_text(GnomeConfig.MARK_DONE_TOOLTIP)
+ self.editbutton.set_tooltip_text(GnomeConfig.EDIT_TOOLTIP)
+ self.dismissbutton.set_tooltip_text(GnomeConfig.MARK_DISMISS_TOOLTIP)
+ self.newtask.set_tooltip_text(GnomeConfig.NEW_TASK_TOOLTIP)
+ self.newsubtask.set_tooltip_text(GnomeConfig.NEW_SUBTASK_TOOLTIP)
+ self.toggle_workview.set_tooltip_text(
+ GnomeConfig.WORKVIEW_TOGGLE_TOOLTIP)
+ self.quickadd_entry.set_tooltip_text(
+ GnomeConfig.QUICKADD_ENTRY_TOOLTIP)
+ self.quickadd_entry.set_icon_tooltip_text(1,
+ GnomeConfig.QUICKADD_ICON_TOOLTIP)
+
+ def _init_about_dialog(self):
+ """
+ Show the about dialog
+ """
+ gtk.about_dialog_set_url_hook(lambda dialog, url: openurl(url))
+ self.about.set_website(info.URL)
+ self.about.set_website_label(info.URL)
+ self.about.set_version(info.VERSION)
+ self.about.set_authors(info.AUTHORS)
+ self.about.set_artists(info.ARTISTS)
+ self.about.set_documenters(info.DOCUMENTERS)
+ self.about.set_translator_credits(info.TRANSLATORS)
+
+ def _init_signal_connections(self):
+ """
+ connects signals on UI elements
+ """
+ SIGNAL_CONNECTIONS_DIC = {
+ "on_add_task":
+ self.on_add_task,
+ "on_edit_active_task":
+ self.on_edit_active_task,
+ "on_edit_done_task":
+ self.on_edit_done_task,
+ "on_delete_task":
+ self.on_delete_tasks,
+ "on_modify_tags":
+ self.on_modify_tags,
+ "on_mark_as_done":
+ self.on_mark_as_done,
+ "on_mark_as_started":
+ self.on_mark_as_started,
+ "on_start_for_tomorrow":
+ self.on_start_for_tomorrow,
+ "on_start_for_next_week":
+ self.on_start_for_next_week,
+ "on_start_for_next_month":
+ self.on_start_for_next_month,
+ "on_start_for_next_year":
+ self.on_start_for_next_year,
+ "on_start_clear":
+ self.on_start_clear,
+ "on_set_due_today":
+ self.on_set_due_today,
+ "on_set_due_tomorrow":
+ self.on_set_due_tomorrow,
+ "on_set_due_next_week":
+ self.on_set_due_next_week,
+ "on_set_due_next_month":
+ self.on_set_due_next_month,
+ "on_set_due_next_year":
+ self.on_set_due_next_year,
+ "on_set_due_now":
+ self.on_set_due_now,
+ "on_set_due_soon":
+ self.on_set_due_soon,
+ "on_set_due_someday":
+ self.on_set_due_someday,
+ "on_set_due_clear":
+ self.on_set_due_clear,
+ "on_dismiss_task":
+ self.on_dismiss_task,
+ "on_move":
+ self.on_move,
+ "on_size_allocate":
+ self.on_size_allocate,
+ "gtk_main_quit":
+ self.on_close,
+ "on_add_subtask":
+ self.on_add_subtask,
+ "on_tagcontext_deactivate":
+ self.on_tagcontext_deactivate,
+ "on_workview_toggled":
+ self.on_workview_toggled,
+ "on_view_workview_toggled":
+ self.on_workview_toggled,
+ "on_view_closed_toggled":
+ self.on_closed_toggled,
+ "on_view_sidebar_toggled":
+ self.on_sidebar_toggled,
+ "on_quickadd_field_activate":
+ self.on_quickadd_activate,
+ "on_quickadd_field_icon_press":
+ self.on_quickadd_iconpress,
+ "on_quickadd_field_changed":
+ self.on_quickadd_changed,
+ "on_quickadd_entrycompletion_action_activated":
+ self.on_entrycompletion_action_activated,
+ "on_view_quickadd_toggled":
+ self.on_toggle_quickadd,
+ "on_view_toolbar_toggled":
+ self.on_toolbar_toggled,
+ "on_about_clicked":
+ self.on_about_clicked,
+ "on_about_delete":
+ self.on_about_close,
+ "on_about_close":
+ self.on_about_close,
+ "on_documentation_clicked":
+ lambda w: openurl(info.HELP_URI),
+ "on_translate_clicked":
+ lambda w: openurl(info.TRANSLATE_URL),
+ "on_report_bug_clicked":
+ lambda w: openurl(info.REPORT_BUG_URL),
+ "on_preferences_activate":
+ self.open_preferences,
+ "on_edit_plugins_activate":
+ self.open_plugins,
+ "on_edit_backends_activate":
+ self.open_edit_backends,
+ }
+ self.builder.connect_signals(SIGNAL_CONNECTIONS_DIC)
+
+ # When destroying this window, quit GTG
+ self.window.connect("destroy", self.quit)
+
+ # Active tasks TreeView
+ self.vtree_panes['active'].connect('row-activated', \
+ self.on_edit_active_task)
+ self.vtree_panes['active'].connect('button-press-event', \
+ self.on_task_treeview_button_press_event)
+ self.vtree_panes['active'].connect('key-press-event', \
+ self.on_task_treeview_key_press_event)
+ self.vtree_panes['active'].connect('node-expanded', \
+ self.on_task_expanded)
+ self.vtree_panes['active'].connect('node-collapsed', \
+ self.on_task_collapsed)
+
+ b_signals = BackendSignals()
+ b_signals.connect(b_signals.BACKEND_FAILED, self.on_backend_failed)
+ b_signals.connect(b_signals.BACKEND_STATE_TOGGLED, \
+ self.remove_backend_infobar)
+ b_signals.connect(b_signals.INTERACTION_REQUESTED, \
+ self.on_backend_needing_interaction)
+ # Selection changes
+ self.selection = self.vtree_panes['active'].get_selection()
+ self.selection.connect("changed", self.on_task_cursor_changed)
+
+ def _add_accelerator_for_widget(self, agr, name, accel):
+ widget = self.builder.get_object(name)
+ key, mod = gtk.accelerator_parse(accel)
+ widget.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE)
+
+ def _init_accelerators(self):
+ """
+ initialize gtk accelerators for different interface elements
+ """
+ agr = gtk.AccelGroup()
+ self.builder.get_object("MainWindow").add_accel_group(agr)
+
+ self._add_accelerator_for_widget(agr, "view_sidebar", "F9")
+ self._add_accelerator_for_widget(agr, "file_quit", "<Control>q")
+ self._add_accelerator_for_widget(agr, "edit_undo", "<Control>z")
+ self._add_accelerator_for_widget(agr, "edit_redo", "<Control>y")
+ self._add_accelerator_for_widget(agr, "new_task_mi", "<Control>n")
+ self._add_accelerator_for_widget(agr, "new_subtask_mi",
+ "<Control><Shift>n")
+ self._add_accelerator_for_widget(agr, "edit_mi", "Return")
+ self._add_accelerator_for_widget(agr, "done_mi", "<Control>d")
+ self._add_accelerator_for_widget(agr, "dismiss_mi", "<Control>i")
+ self._add_accelerator_for_widget(agr, "tcm_modifytags", "<Control>t")
+ self._add_accelerator_for_widget(agr, "view_closed", "<Control>F9")
+ self._add_accelerator_for_widget(agr, "help_contents", "F1")
+
+ quickadd_field = self.builder.get_object("quickadd_field")
+ key, mod = gtk.accelerator_parse("<Control>l")
+ quickadd_field.add_accelerator("grab-focus", agr, key, mod,
+ gtk.ACCEL_VISIBLE)
+
+### HELPER FUNCTIONS ########################################################
+ def open_preferences(self, widget):
+ self.vmanager.open_preferences(self.config)
+
+ def open_plugins(self, widget):
+ self.vmanager.configure_plugins()
+
+ def open_edit_backends(self, widget):
+ self.vmanager.open_edit_backends()
+
+ def quit(self, widget=None):
+ self.vmanager.close_browser()
+
+ def on_window_state_event(self, widget, event, data=None):
+ """ This event checks for the window state: maximized?
+ and stores the state in self.config.max
+ This is used to check the window state afterwards
+ and maximize it if needed """
+ mask = gtk.gdk.WINDOW_STATE_MAXIMIZED
+ if widget.get_window().get_state() & mask == mask:
+ self.config.set("max", True)
+ else:
+ self.config.set("max", False)
+
+ def restore_state_from_conf(self):
+
+# # Extract state from configuration dictionary
+# if not "browser" in self.config:
+# #necessary to have the minimum width of the tag pane
+# # inferior to the "first run" width
+# self.builder.get_object("hpaned1").set_position(250)
+# return
+
+ width = self.config.get('width')
+ height = self.config.get('height')
+ if width and height:
+ self.window.resize(width, height)
+
+ # checks for maximum size of window
+ self.window.connect('window-state-event', self.on_window_state_event)
+ if self.config.get("max"):
+ self.window.maximize()
+
+ xpos = self.config.get("x_pos")
+ ypos = self.config.get("y_pos")
+ if ypos and xpos:
+ self.window.move(xpos, ypos)
+
+ tag_pane = self.config.get("tag_pane")
+ if not tag_pane:
+ self.builder.get_object("view_sidebar").set_active(False)
+ self.sidebar.hide()
+ else:
+ self.builder.get_object("view_sidebar").set_active(True)
+ if not self.tagtreeview:
+ self.init_tags_sidebar()
+ self.sidebar.show()
+
+ sidebar_width = self.config.get("sidebar_width")
+ self.builder.get_object("hpaned1").set_position(sidebar_width)
+ self.builder.get_object("hpaned1").connect('notify::position',
+ self.on_sidebar_width)
+
+ closed_task_pane = self.config.get("closed_task_pane")
+ if not closed_task_pane:
+ self.hide_closed_pane()
+ else:
+ self.show_closed_pane()
+
+ botpos = self.config.get("bottom_pane_position")
+ self.builder.get_object("vpaned1").set_position(botpos)
+ self.builder.get_object("vpaned1").connect('notify::position',
+ self.on_bottom_pane_position)
+
+ toolbar = self.config.get("toolbar")
+ if toolbar:
+ self.builder.get_object("view_toolbar").set_active(1)
+ else:
+ self.toolbar.hide()
+ self.builder.get_object("view_toolbar").set_active(False)
+
+ quickadd_pane = self.config.get("quick_add")
+ if quickadd_pane:
+ self.builder.get_object("view_quickadd").set_active(True)
+ else:
+ self.quickadd_pane.hide()
+ self.builder.get_object("view_quickadd").set_active(False)
+
+ # Callbacks for sorting and restoring previous state
+ model = self.vtree_panes['active'].get_model()
+ model.connect('sort-column-changed', self.on_sort_column_changed)
+ sort_column = self.config.get('tasklist_sort_column')
+ sort_order = self.config.get('tasklist_sort_order')
+
+ if sort_column and sort_order:
+ sort_column, sort_order = int(sort_column), int(sort_order)
+ model.set_sort_column_id(sort_column, sort_order)
+
+ for path_s in self.config.get("collapsed_tasks"):
+ #the tuple was stored as a string. we have to reconstruct it
+ path = ()
+ for p in path_s[1:-1].split(","):
+ p = p.strip(" '")
+ path += (p, )
+ if path[-1] == '':
+ path = path[:-1]
+ self.vtree_panes['active'].collapse_node(path)
+
+ for t in self.config.get("collapsed_tags"):
+ #FIXME
+ print "Collapsing tag %s not implememted in browser.py" %t
+# self.tagtreeview.set_collapsed_tags(toset)
+
+ self.set_view(self.config.get("view"))
+
+ def open_task(req, t):
+ """ Open the task if loaded. Otherwise ask for next iteration """
+ if req.has_task(t):
+ self.vmanager.open_task(t)
+ return False
+ else:
+ return True
+
+ for t in self.config.get("opened_tasks"):
+ gobject.idle_add(open_task, self.req, t)
+
+ def do_toggle_workview(self):
+ """ Switch between default and work view
+
+ Updating tags is disabled while changing view. It consumes
+ a lot of CPU cycles and the user does not see it. Afterwards,
+ updating of tags is re-enabled and all tags are refreshed.
+
+ Because workview can be switched from more than one button
+ (currently toggle button and check menu item), we need to change
+ status of others also. It invokes again this method =>
+ a loop of signals.
+
+ It is more flexible to have a dedicated variable
+ (self.in_toggle_workview) which prevents that recursion. The other way
+ how to solve this is to checking state of those two buttons and check
+ if they are not the same. Adding another way may complicate things...
+ """
+
+ if self.in_toggle_workview:
+ return
+
+ self.in_toggle_workview = True
+ self.tv_factory.disable_update_tags()
+
+ if self.config.get('view') == 'workview':
+ self.set_view('default')
+ else:
+ self.set_view('workview')
+
+ if self.tagtree is not None:
+ self.tv_factory.enable_update_tags()
+ self.tagtree.refresh_all()
+
+ self.in_toggle_workview = False
+
+ def set_view(self, viewname):
+ if viewname == 'default':
+ self.activetree.unapply_filter('workview')
+ workview = False
+ elif viewname == 'workview':
+ self.activetree.apply_filter('workview')
+ workview = True
+ else:
+ raise Exception('Cannot set the view %s' %viewname)
+ self.menu_view_workview.set_active(workview)
+ self.toggle_workview.set_active(workview)
+ #The config_set has to be after the toggle, else you will have a loop
+ self.config.set('view', viewname)
+ self.vtree_panes['active'].set_col_visible('startdate', not workview)
+
+ def _update_window_title(self, nid=None, path=None, state_id=None):
+ count = self.activetree.get_n_nodes()
+ #Set the title of the window:
+ parenthesis = ""
+ if count == 0:
+ parenthesis = _("no active tasks")
+ else:
+ parenthesis = ngettext("%(tasks)d active task", \
+ "%(tasks)d active tasks", \
+ count) % {'tasks': count}
+ self.window.set_title("%s - "%parenthesis + info.NAME)
+
+ def _add_page(self, notebook, label, page):
+ notebook.append_page(page, label)
+ if notebook.get_n_pages() > 1:
+ notebook.set_show_tabs(True)
+ page_num = notebook.page_num(page)
+ notebook.set_tab_detachable(page, True)
+ notebook.set_tab_reorderable(page, True)
+ notebook.set_current_page(page_num)
+ notebook.show_all()
+ return page_num
+
+ def _remove_page(self, notebook, page):
+ if page:
+ page.hide()
+ notebook.remove(page)
+ if notebook.get_n_pages() == 1:
+ notebook.set_show_tabs(False)
+ elif notebook.get_n_pages() == 0:
+ notebook.hide()
+
+### SIGNAL CALLBACKS ##########################################################
+# Typically, reaction to user input & interactions with the GUI
+#
+ def register_filter_callback(self, cb):
+ print "DEPRECATED function register_filter_callback."
+ print "It is only dummy funnction now, ready for removing"
+
+ def unregister_filter_callback(self, cb):
+ print "DEPRECATED function unregister_filter_callback."
+ print "It is only dummy funnction now, ready for removing"
+
+ def on_sort_column_changed(self, model):
+ sort_column, sort_order = model.get_sort_column_id()
+
+ if sort_order == gtk.SORT_ASCENDING:
+ sort_order = 0
+ else:
+ sort_order = 1
+
+ self.config.set('tasklist_sort_column', sort_column)
+ self.config.set('tasklist_sort_order', sort_order)
+
+ def on_move(self, widget = None, data = None):
+ xpos, ypos = self.window.get_position()
+ self.config.set('x_pos', xpos)
+ self.config.set('y_pos', ypos)
+
+ def on_size_allocate(self, widget = None, data = None):
+ width, height = self.window.get_size()
+ self.config.set('width', width)
+ self.config.set('height', height)
+
+ def on_bottom_pane_position(self, widget, data = None):
+ self.config.set('bottom_pane_position', widget.get_position())
+
+ def on_sidebar_width(self, widget, data = None):
+ self.config.set('sidebar_width', widget.get_position())
+
+ def on_about_clicked(self, widget):
+ """
+ show the about dialog
+ """
+ self.about.show()
+
+ def on_about_close(self, widget, response):
+ """
+ close the about dialog
+ """
+ self.about.hide()
+ return True
+
+ def on_tagcontext_deactivate(self, menushell):
+ self.reset_cursor()
+
+ def on_workview_toggled(self, widget):
+ self.do_toggle_workview()
+
+ def on_sidebar_toggled(self, widget):
+ view_sidebar = self.builder.get_object("view_sidebar")
+ if self.sidebar.get_property("visible"):
+ view_sidebar.set_active(False)
+ self.config.set("tag_pane", False)
+ self.sidebar.hide()
+ else:
+ view_sidebar.set_active(True)
+ if not self.tagtreeview:
+ self.init_tags_sidebar()
+ self.sidebar.show()
+ self.config.set("tag_pane", True)
+
+ def on_closed_toggled(self, widget):
+ if widget.get_active():
+ self.show_closed_pane()
+ else:
+ self.hide_closed_pane()
+
+ def __create_closed_tree(self):
+ closedtree = self.req.get_tasks_tree(name='closed', refresh=False)
+ closedtree.apply_filter('closed', refresh=False)
+ return closedtree
+
+ def show_closed_pane(self):
+ # The done/dismissed tasks treeview
+ if not 'closed' in self.vtree_panes:
+ ctree = self.__create_closed_tree()
+ self.vtree_panes['closed'] = \
+ self.tv_factory.closed_tasks_treeview(ctree)
+ # Closed tasks TreeView
+ self.vtree_panes['closed'].connect('row-activated', \
+ self.on_edit_done_task)
+ self.vtree_panes['closed'].connect('button-press-event', \
+ self.on_closed_task_treeview_button_press_event)
+ self.vtree_panes['closed'].connect('key-press-event', \
+ self.on_closed_task_treeview_key_press_event)
+
+ self.closed_selection = self.vtree_panes['closed'].get_selection()
+ self.closed_selection.connect("changed",
+ self.on_taskdone_cursor_changed)
+ ctree.apply_filter(self.get_selected_tags()[0], refresh=True)
+ if not self.closed_pane:
+ self.closed_pane = gtk.ScrolledWindow()
+ self.closed_pane.set_size_request(-1, 100)
+ self.closed_pane.set_policy(gtk.POLICY_AUTOMATIC,
+ gtk.POLICY_AUTOMATIC)
+ self.closed_pane.add(self.vtree_panes['closed'])
+
+ elif self.accessory_notebook.page_num(self.closed_pane) != -1:
+ # Already contains the closed pane
+ return
+
+ self.add_page_to_accessory_notebook("Closed", self.closed_pane)
+ self.builder.get_object("view_closed").set_active(True)
+ self.config.set('closed_task_pane', True)
+
+ def hide_closed_pane(self):
+ #If we destroy completely the vtree, we cannot display it anymore
+ #Check is to hide/show the closed task pane multiple times.
+ #I let this code commented for now because it might be useful
+ #for performance reason, to really destroy the view when we don't
+ #display it. (Lionel, 17092010)
+# if self.vtree_panes.has_key('closed'):
+# self.vtree_panes['closed'].set_model(None)
+# del self.vtree_panes['closed']
+ self.remove_page_from_accessory_notebook(self.closed_pane)
+ self.builder.get_object("view_closed").set_active(False)
+ self.config.set('closed_task_pane', False)
+
+ def on_toolbar_toggled(self, widget):
+ if widget.get_active():
+ self.toolbar.show()
+ self.config.set('toolbar', True)
+ else:
+ self.toolbar.hide()
+ self.config.set('toolbar', False)
+
+ def on_toggle_quickadd(self, widget):
+ if widget.get_active():
+ self.quickadd_pane.show()
+ self.config.set('quick_add', True)
+ else:
+ self.quickadd_pane.hide()
+ self.config.set('quick_add', False)
+
+ def on_task_expanded(self, sender, tid):
+ colt = self.config.get("collapsed_tasks")
+ if tid in colt:
+ colt.remove(tid)
+
+ def on_task_collapsed(self, sender, tid):
+ colt = self.config.get("collapsed_tasks")
+ if tid not in colt:
+ colt.append(str(tid))
+
+ def on_quickadd_activate(self, widget):
+ """ Add a new task from quickadd toolbar """
+ text = unicode(self.quickadd_entry.get_text())
+ text = text.strip()
+ if text:
+ tags = self.get_selected_tags(nospecial=True)
+
+ # We will select quick-added task in browser.
+ # This has proven to be quite complex and deserves an explanation.
+ # We register a callback on the sorted treemodel that we're
+ # displaying, which is a TreeModelSort. When a row gets added,
+ # we're notified of it.
+ # We have to verify that that row belongs to the task we should
+ # select. So, we have to wait for the task to be created, and then
+ # wait for its tid to show up (invernizzi)
+ def select_next_added_task_in_browser(treemodelsort, path,
+ iter, self):
+
+ def selecter(treemodelsort, path, iter, self):
+ self.__last_quick_added_tid_event.wait()
+ treeview = self.vtree_panes['active']
+ tid = self.activetree.get_node_for_path(path)
+ if self.__last_quick_added_tid == tid:
+ #this is the correct task
+ treemodelsort.disconnect(
+ self.__quick_add_select_handle)
+ selection = treeview.get_selection()
+ selection.unselect_all()
+ selection.select_path(path)
+
+ #It cannot be another thread than the main gtk thread !
+ gobject.idle_add(selecter, treemodelsort, path, iter, self)
+ #event that is set when the new task is created
+ self.__last_quick_added_tid_event = threading.Event()
+ self.__quick_add_select_handle = \
+ self.vtree_panes['active'].get_model().connect(
+ "row-inserted", select_next_added_task_in_browser,
+ self)
+ task = self.req.new_task(newtask=True)
+ self.__last_quick_added_tid = task.get_id()
+ self.__last_quick_added_tid_event.set()
+ task.set_complex_title(text, tags=tags)
+ self.quickadd_entry.set_text('')
+
+ #signal the event for the plugins to catch
+ gobject.idle_add(self.emit, "task-added-via-quick-add",
+ task.get_id())
+ else:
+ #if no text is selected, we open the currently selected task
+ nids = self.vtree_panes['active'].get_selected_nodes()
+ for nid in nids:
+ self.vmanager.open_task(nid)
+
+ def on_quickadd_iconpress(self, widget, icon, event):
+ """ Clear the text in quickadd field by clicking on 'clear' icon """
+ if icon == gtk.ENTRY_ICON_SECONDARY:
+ self.quickadd_entry.set_text('')
+
+ def on_tag_treeview_button_press_event(self, treeview, event):
+ """
+ deals with mouse click event on the tag tree
+ """
Log.debug("Received button event #%d at %d, %d" % (
event.button, event.x, event.y))
if event.button == 3: