gtg team mailing list archive
-
gtg team
-
Mailing list archive
-
Message #03428
[Merge] lp:~gtg-contributors/gtg/notification-area-rewrite into lp:gtg
Izidor Matušov has proposed merging lp:~gtg-contributors/gtg/notification-area-rewrite into lp:gtg.
Requested reviews:
Gtg developers (gtg)
Related bugs:
Bug #591928 in Getting Things GNOME!: "GTG's panel applet list is bad at managing large numbers of tasks"
https://bugs.launchpad.net/gtg/+bug/591928
Bug #621333 in Getting Things GNOME!: "Indicator list should reflect sorting done in windowed view"
https://bugs.launchpad.net/gtg/+bug/621333
Bug #696250 in Getting Things GNOME!: "Status icon should use icon theme"
https://bugs.launchpad.net/gtg/+bug/696250
Bug #700751 in Getting Things GNOME!: "Make the list of tasks in the notification area smarter"
https://bugs.launchpad.net/gtg/+bug/700751
Bug #814851 in Getting Things GNOME!: "app indicator should close to tray"
https://bugs.launchpad.net/gtg/+bug/814851
Bug #897179 in Getting Things GNOME!: "New task button from Notification Area plugin works differently than gtg_new_task"
https://bugs.launchpad.net/gtg/+bug/897179
For more details, see:
https://code.launchpad.net/~gtg-contributors/gtg/notification-area-rewrite/+merge/86635
Rewriting notification area (almost from scratch :)
--
https://code.launchpad.net/~gtg-contributors/gtg/notification-area-rewrite/+merge/86635
Your team Gtg developers is requested to review the proposed merge of lp:~gtg-contributors/gtg/notification-area-rewrite into lp:gtg.
=== modified file 'GTG/core/plugins/engine.py'
--- GTG/core/plugins/engine.py 2011-11-19 21:41:08 +0000
+++ GTG/core/plugins/engine.py 2011-12-21 23:22:24 +0000
@@ -24,6 +24,7 @@
import dbus
from GTG.tools.borg import Borg
+from GTG.tools.logger import Log
class Plugin(object):
@@ -129,8 +130,9 @@
# no dependencies in info file; use the ImportError instead
self.missing_modules.append(str(e).split(" ")[3])
self.error = True
- except Exception:
+ except Exception, e:
# load_module() failed for some other reason
+ Log.error(e)
self.error = True
def reload(self, module_path):
=== modified file 'GTG/core/requester.py'
--- GTG/core/requester.py 2011-11-19 21:41:08 +0000
+++ GTG/core/requester.py 2011-12-21 23:22:24 +0000
@@ -124,6 +124,7 @@
task = self.ds.get_task(tid)
return task
+ # FIXME unused parameter newtask (maybe for compatibility?)
def new_task(self, tags=None, newtask=True):
"""Create a new task.
=== modified file 'GTG/gtk/browser/browser.py'
--- GTG/gtk/browser/browser.py 2011-11-23 20:30:54 +0000
+++ GTG/gtk/browser/browser.py 2011-12-21 23:22:24 +0000
@@ -428,12 +428,8 @@
}
self.builder.connect_signals(SIGNAL_CONNECTIONS_DIC)
- if (self.window):
- self.window.connect("destroy", self.quit)
- #The following is needed to let the Notification Area plugin to
- # minimize the window instead of closing the program
- self.delete_event_handle = \
- self.window.connect("delete-event", self.on_delete)
+ # When destroying this window, quit GTG
+ self.window.connect("destroy", self.quit)
# Active tasks TreeView
self.vtree_panes['active'].connect('row-activated',\
@@ -556,6 +552,7 @@
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:
@@ -565,6 +562,7 @@
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:
@@ -621,19 +619,9 @@
# Try it later
return True
- if self._start_gtg_maximized():
- odic = self.config.get("opened_tasks")
- #This should be removed. This is bad !
-# #odic can contain also "None" or "None,", so we skip them
-# if odic == "None" or (len(odic)> 0 and odic[0] == "None"):
-# return
- for t in odic:
- gobject.idle_add(open_task, self.req, t)
-
- def _start_gtg_maximized(self):
- #This is needed as a hook point to let the Notification are plugin
- #start gtg minimized
- return True
+ odic = self.config.get("opened_tasks")
+ for t in odic:
+ gobject.idle_add(open_task, self.req, t)
def do_toggle_workview(self):
""" Switch between default and work view
@@ -769,14 +757,11 @@
self.config.set('width',width)
self.config.set('height',height)
- #on_delete is called when the user close the window
- def on_delete(self, widget, user_data):
- # Cleanup collapsed row list
- #TODO: the cleanup should better be done on task deletion
- botpos = self.builder.get_object("vpaned1").get_position()
- self.config.set('bottom_pane_position',botpos)
- sidepos = self.builder.get_object("hpaned1").get_position()
- self.config.set('sidebar_width',sidepos)
+ 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):
"""
@@ -1480,7 +1465,6 @@
def on_close(self, widget=None):
"""Closing the window."""
#Saving is now done in main.py
- self.on_delete(None, None)
self.quit()
#using dummy parameters that are given by the signal
=== modified file 'GTG/gtk/dbuswrapper.py'
--- GTG/gtk/dbuswrapper.py 2011-09-05 13:49:46 +0000
+++ GTG/gtk/dbuswrapper.py 2011-12-21 23:22:24 +0000
@@ -218,12 +218,13 @@
This routine returns as soon as the GUI has launched.
"""
- nt = self.req.new_task(newtask=True)
- nt.set_title(title)
+ new_task = self.req.new_task(newtask=True)
+ if title != "":
+ new_task.set_title(title)
if description != "":
- nt.set_text(description)
- uid = nt.get_id()
- self.view_manager.open_task(uid,thisisnew=True)
+ new_task.set_text(description)
+ task_id = new_task.get_id()
+ self.view_manager.open_task(task_id, thisisnew=True)
@dbus.service.method(BUSNAME)
def HideTaskBrowser(self):
=== modified file 'GTG/gtk/manager.py'
--- GTG/gtk/manager.py 2011-11-12 21:48:20 +0000
+++ GTG/gtk/manager.py 2011-12-21 23:22:24 +0000
@@ -62,6 +62,10 @@
self.browser = None
self.__start_browser_hidden = False
self.gtk_terminate = False #if true, the gtk main is not started
+
+ # if true, closing the last window doesn't quit GTG
+ # (GTG lives somewhere else without GUI, e.g. notification area)
+ self.daemon_mode = False
#Shared clipboard
self.clipboard = clipboard.TaskClipboard(self.req)
@@ -138,6 +142,11 @@
def start_browser_hidden(self):
self.__start_browser_hidden = True
+ def set_daemon_mode(self, in_daemon_mode):
+ """ Used by notification area plugin to override the behavior:
+ last closed window quits GTG """
+ self.daemon_mode = in_daemon_mode
+
################# Task Editor ############################################
def get_opened_editors(self):
@@ -147,7 +156,7 @@
'''
return self.opened_task
- def open_task(self, uid,thisisnew = False):
+ def open_task(self, uid, thisisnew = False):
"""Open the task identified by 'uid'.
If a Task editor is already opened for a given task, we present it.
@@ -188,7 +197,7 @@
'''
checking if we need to shut down the whole GTG (if no window is open)
'''
- if not self.is_browser_visible() and not self.opened_task:
+ if not self.daemon_mode and not self.is_browser_visible() and not self.opened_task:
#no need to live"
self.quit()
=== modified file 'GTG/plugins/notification-area.gtg-plugin'
--- GTG/plugins/notification-area.gtg-plugin 2010-08-01 19:30:27 +0000
+++ GTG/plugins/notification-area.gtg-plugin 2011-12-21 23:22:24 +0000
@@ -6,6 +6,6 @@
that keeps the list of the currently workable tasks.
To start GTG minimized, click on the 'Configure Plugin' button
at the bottom of this window."""
-Authors="Paulo Cabido <paulo.cabido@xxxxxxxxx>, Luca Invernizzi <invernizzi.l@xxxxxxxxx>, Jono Bacon <jono@xxxxxxxxxx>"
-Version=0.9
+Authors="Paulo Cabido <paulo.cabido@xxxxxxxxx>, Luca Invernizzi <invernizzi.l@xxxxxxxxx>, Jono Bacon <jono@xxxxxxxxxx>, Izidor Matušov <izidor.matusov@xxxxxxxxx>"
+Version=0.95
Enabled=False
=== modified file 'GTG/plugins/notification_area/notification_area.py'
--- GTG/plugins/notification_area/notification_area.py 2011-08-07 08:57:59 +0000
+++ GTG/plugins/notification_area/notification_area.py 2011-12-21 23:22:24 +0000
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2009 - Paulo Cabido <paulo.cabido@xxxxxxxxx>
# - Luca Invernizzi <invernizzi.l@xxxxxxxxx>
+# - Izidor Matušov <izidor.matusov@xxxxxxxxx>
#
# 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
@@ -22,22 +23,19 @@
except:
pass
-from GTG import _, DATA_DIR
+from GTG import _
from GTG.tools.borg import Borg
-from GTG.tools.sorted_dict import SortedDict
-
-
class NotificationArea:
- '''
+ """
Plugin that display a notification area widget or an indicator
to quickly access tasks.
- '''
-
+ """
DEFAULT_PREFERENCES = {"start_minimized": False}
PLUGIN_NAME = "notification_area"
MAX_TITLE_LEN = 30
+ MAX_ITEMS = 10
class TheIndicator(Borg):
"""
@@ -64,43 +62,69 @@
def __init__(self):
self.__indicator = NotificationArea.TheIndicator().get_indicator()
- print "INDI", self.__indicator
+ self.__browser_handler = None
+ self.__liblarch_callbacks = []
def activate(self, plugin_api):
+ """ Set up the plugin, set callbacks, etc """
self.__plugin_api = plugin_api
self.__view_manager = plugin_api.get_view_manager()
self.__requester = plugin_api.get_requester()
- #Tasks_in_menu will hold the menu_items in the menu, to quickly access
- #them given the task id. Contains tuple of this format: (title, key,
- # gtk.MenuItem)
- self.__tasks_in_menu = SortedDict(key_position = 1, sort_position = 0)
+ # Tasks_in_menu will hold the menu_items in the menu, to quickly access
+ # them given the task id. Contains tuple of this format:
+ # (title, key, gtk.MenuItem)
self.__init_gtk()
self.__connect_to_tree()
+
#Load the preferences
self.preference_dialog_init()
self.preferences_load()
- self.preferences_apply(True)
+
+ # When no windows (browser or text editors) are shown, it tries to quit
+ # With hidden browser and closing the only single text editor,
+ # GTG would quit no matter what
+ self.__view_manager.set_daemon_mode(True)
+
+ # Don't quit GTG after closing browser
+ self.__set_browser_close_callback(self.__on_browser_minimize)
+
+ if self.preferences["start_minimized"]:
+ self.__view_manager.start_browser_hidden()
def deactivate(self, plugin_api):
+ """ Set everything back to normal """
if self.__indicator:
self.__indicator.set_status(appindicator.STATUS_PASSIVE)
else:
self.__status_icon.set_visible(False)
+ # Allow to close browser after deactivation
+ self.__set_browser_close_callback(None)
+
+ # Allow closing GTG after the last window
+ self.__view_manager.set_daemon_mode(True)
+
+ # Deactivate LibLarch callbacks
+ for key, event in self.__liblarch_callbacks:
+ self.__tree.deregister_cllbck(event, key)
+ self.__tree = None
+ self.__liblarch_callbacks = []
+
## Helper methods ##############################################################
def __init_gtk(self):
+ browser = self.__view_manager.get_browser()
+
self.__menu = gtk.Menu()
#view in main window checkbox
view_browser_checkbox = gtk.CheckMenuItem(_("_View Main Window"))
- view_browser_checkbox.set_active(self.__view_manager.get_browser( \
- ).is_shown())
+ view_browser_checkbox.set_active(browser.is_shown())
self.__signal_handler = view_browser_checkbox.connect('activate',
self.__toggle_browser)
- self.__view_manager.get_browser().connect('visibility-toggled',
- self.__on_browser_toggled,
+ browser.connect('visibility-toggled', self.__on_browser_toggled,
view_browser_checkbox)
self.__menu.append(view_browser_checkbox)
+ self.checkbox = view_browser_checkbox
#add "new task"
menuItem = gtk.ImageMenuItem(gtk.STOCK_ADD)
menuItem.get_children()[0].set_label(_('Add _New Task'))
@@ -112,18 +136,20 @@
self.__menu.append(menuItem)
self.__menu.show_all()
#separator (it's intended to be after show_all)
+ # separator should be shown only when having tasks
self.__task_separator = gtk.SeparatorMenuItem()
- self.__task_separator.show()
self.__menu.append(self.__task_separator)
self.__menu_top_length = len(self.__menu)
+
+ self.__tasks_menu = SortedLimitedMenu(self.MAX_ITEMS,
+ self.__menu, self.__menu_top_length)
+
if self.__indicator:
self.__indicator.set_menu(self.__menu)
self.__indicator.set_status(appindicator.STATUS_ACTIVE)
else:
- print "ELSE?"
- icon = gtk.gdk.pixbuf_new_from_file_at_size(DATA_DIR + \
- "/icons/hicolor/16x16/apps/gtg.png", 16, 16)
- self.status_icon = gtk.status_icon_new_from_pixbuf(icon)
+ self.status_icon = gtk.StatusIcon()
+ self.status_icon.set_from_icon_name("gtg")
self.status_icon.set_tooltip("Getting Things Gnome!")
self.status_icon.set_visible(True)
self.status_icon.connect('activate', self.__toggle_browser)
@@ -131,92 +157,63 @@
self.__on_icon_popup, \
self.__menu)
- def __toggle_browser(self, sender = None, data = None):
- if self.__plugin_api.get_ui().is_shown():
- self.__plugin_api.get_view_manager().hide_browser()
- else:
- self.__plugin_api.get_view_manager().show_browser()
-
- def __on_browser_toggled(self, sender, checkbox):
- checkbox.disconnect(self.__signal_handler)
- checkbox.set_active(self.__view_manager.get_browser().is_shown())
- self.__signal_handler = checkbox.connect('activate',
- self.__toggle_browser)
-
- def __open_task(self, widget, tid = None):
+ def __open_task(self, widget, task_id = None):
"""
Opens a task in the TaskEditor, if it's not currently opened.
- If tid is None, it creates a new task and opens it
+ If task_id is None, it creates a new task and opens it
"""
- if tid == None:
- tid = self.__requester.new_task().get_id()
- self.__view_manager.open_task(tid)
+ if task_id == None:
+ task_id = self.__requester.new_task().get_id()
+ new_task = True
+ else:
+ new_task = False
+
+ self.__view_manager.open_task(task_id, thisisnew=new_task)
def __connect_to_tree(self):
self.__tree = self.__requester.get_tasks_tree()
# Request a new view so we do not influence anybody
self.__tree = self.__tree.get_basetree().get_viewtree(refresh=False)
+
+ c1 = self.__tree.register_cllbck("node-added-inview", self.__on_task_added)
+ c2 = self.__tree.register_cllbck("node-modified-inview", self.__on_task_added)
+ c3 = self.__tree.register_cllbck("node-deleted-inview", self.__on_task_deleted)
+ self.__liblarch_callbacks = [(c1, "node-added-inview"),
+ (c2, "node-modified-inview"),
+ (c3, "node-deleted-inview")]
+
self.__tree.apply_filter('workview')
- self.__tree.register_cllbck("node-added-inview", self.__on_task_added)
- self.__tree.register_cllbck("node-deleted-inview", self.__on_task_deleted)
- self.__tree.register_cllbck("node-modified-inview", self.__on_task_added)
-
- #Flushing all tasks, as the plugin may have been started after GTG
- def visit_tree(tree, nodes, fun):
- for node in nodes:
- tid = node.get_id()
- if tree.is_displayed(tid):
- fun(tid)
- if node.has_child():
- children = [self.__tree.get_node(c) \
- for c in node.get_children()]
- visit_tree(tree, children, fun)
- virtual_root = self.__tree.get_root()
- visit_tree(self.__tree,
- [self.__tree.get_node(c) \
- for c in virtual_root.get_children()],
- lambda t: self.__on_task_added(t, None))
+ self.__tree.refresh_all()
def __on_task_added(self, tid, path):
self.__task_separator.show()
task = self.__requester.get_task(tid)
+
#ellipsis of the title
title = self.__create_short_title(task.get_title())
- try:
- #if it's already in the menu, remove it (to reinsert in a sorted
- # way)
- menu_item = self.__tasks_in_menu.pop_by_key(tid)[2]
- self.__menu.remove(menu_item)
- except:
- pass
+
#creating the menu item
- menu_item = gtk.MenuItem(title,False)
+ menu_item = gtk.MenuItem(title, False)
menu_item.connect('activate', self.__open_task, tid)
- menu_item.show()
- position = self.__tasks_in_menu.sorted_insert((title, tid, menu_item))
- self.__menu.insert(menu_item, position + self.__menu_top_length)
+ self.__tasks_menu.add(tid, (task.get_due_date(), title), menu_item)
+
if self.__indicator:
self.__indicator.set_menu(self.__menu)
+ def __on_task_deleted(self, tid, path):
+ self.__tasks_menu.remove(tid)
+ if self.__tasks_menu.empty():
+ self.__task_separator.hide()
+
def __create_short_title(self, title):
- # Underscores must be escaped to avoid to be ignored (or, optionally,
- # treated like accelerators ~~ Invernizzi
+ """ Make title short if it is long. Replace '_' by '__' so
+ it is not ignored or interpreted as an accelerator."""
title =title.replace("_", "__")
short_title = title[0 : self.MAX_TITLE_LEN]
if len(title) > self.MAX_TITLE_LEN:
short_title = short_title.strip() + "..."
return short_title
- def __on_task_deleted(self, tid, path):
- try:
- menu_item = self.__tasks_in_menu.pop_by_key(tid)[2]
- self.__menu.remove(menu_item)
- except:
- return
- #if the dynamic menu is empty, remove the separator
- if not self.__tasks_in_menu:
- self.__task_separator.hide()
-
def __on_icon_popup(self, icon, button, timestamp, menu=None):
if not self.__indicator:
menu.popup(None, None, gtk.status_icon_position_menu, \
@@ -224,31 +221,10 @@
### Preferences methods #######################################################
- def is_configurable(self):
- """A configurable plugin should have this method and return True"""
- return True
-
- def configure_dialog(self, manager_dialog):
- self.preference_dialog_init()
- self.preferences_load()
- self.chbox_minimized.set_active(self.preferences["start_minimized"])
- self.preferences_dialog.show_all()
- self.preferences_dialog.set_transient_for(manager_dialog)
-
- def on_preferences_cancel(self, widget = None, data = None):
- self.preferences_dialog.hide()
- return True
-
- def on_preferences_ok(self, widget = None, data = None):
- self.preferences["start_minimized"] = self.chbox_minimized.get_active()
- self.preferences_apply(False)
- self.preferences_store()
- self.preferences_dialog.hide()
-
def preferences_load(self):
data = self.__plugin_api.load_configuration_object(self.PLUGIN_NAME,
"preferences")
- if not data or isinstance(data, dict):
+ if not data or not isinstance(data, dict):
self.preferences = self.DEFAULT_PREFERENCES
else:
self.preferences = data
@@ -257,10 +233,9 @@
self.__plugin_api.save_configuration_object(self.PLUGIN_NAME,
"preferences",
self.preferences)
-
- def preferences_apply(self, first_start):
- if self.preferences["start_minimized"]:
- self.__view_manager.start_browser_hidden()
+ def is_configurable(self):
+ """A configurable plugin should have this method and return True"""
+ return True
def preference_dialog_init(self):
self.builder = gtk.Builder()
@@ -279,6 +254,103 @@
}
self.builder.connect_signals(SIGNAL_CONNECTIONS_DIC)
-
-
-
+ def configure_dialog(self, manager_dialog):
+ self.chbox_minimized.set_active(self.preferences["start_minimized"])
+ self.preferences_dialog.show_all()
+ self.preferences_dialog.set_transient_for(manager_dialog)
+
+ def on_preferences_cancel(self, widget = None, data = None):
+ self.preferences_dialog.hide()
+ return True
+
+ def on_preferences_ok(self, widget = None, data = None):
+ self.preferences["start_minimized"] = self.chbox_minimized.get_active()
+ self.preferences_store()
+ self.preferences_dialog.hide()
+
+### Browser methods ###########################################################
+
+ def __on_browser_toggled(self, sender, checkbox):
+ checkbox.disconnect(self.__signal_handler)
+ is_shown = self.__view_manager.get_browser().is_shown()
+ checkbox.set_active(is_shown)
+ self.__signal_handler = checkbox.connect('activate',
+ self.__toggle_browser)
+
+ def __on_browser_minimize(self, widget = None, plugin_api = None):
+ self.__view_manager.hide_browser()
+ return True
+
+ def __toggle_browser(self, sender = None, data = None):
+ if self.__plugin_api.get_ui().is_shown():
+ self.__plugin_api.get_view_manager().hide_browser()
+ else:
+ self.__plugin_api.get_view_manager().show_browser()
+
+ def __set_browser_close_callback(self, method):
+ """ Set a callback for browser's close event. If method is None,
+ unset the previous callback """
+
+ browser = self.__view_manager.get_browser()
+
+ if self.__browser_handler is not None:
+ browser.window.disconnect(self.__browser_handler)
+
+ if method is not None:
+ self.__browser_handler = browser.window.connect(
+ "delete-event", method)
+
+class SortedLimitedMenu:
+ """ Sorted GTK Menu which shows only first N elements """
+
+ def __init__(self, max_items, gtk_menu, offset):
+ """ max_items - how many items could be shown
+ gtk_menu - items are added to this menu
+ offset - add to position this offset
+ """
+ self.max_items = max_items
+ self.menu = gtk_menu
+ self.offset = offset
+
+ self.sorted_keys = []
+ self.elements = {}
+
+ def add(self, key, sort_elem, menu_item):
+ """ Add/modify item """
+ if key in self.elements:
+ self.remove(key)
+
+ item = (sort_elem, key)
+ self.sorted_keys.append(item)
+ self.sorted_keys.sort()
+ position = self.sorted_keys.index(item)
+ self.elements[key] = menu_item
+ self.menu.insert(menu_item, position + self.offset)
+
+ # Show/hide elements
+ if position < self.max_items:
+ menu_item.show()
+
+ if len(self.sorted_keys) > self.max_items:
+ hidden_key = self.sorted_keys[self.max_items][1]
+ self.elements[hidden_key].hide()
+
+ def remove(self, key):
+ """ Remove item """
+ menu_item = self.elements.pop(key)
+ self.menu.remove(menu_item)
+
+ for item in self.sorted_keys:
+ if item[1] == key:
+ position = self.sorted_keys.index(item)
+ self.sorted_keys.remove(item)
+ break
+
+ # show elemnt which takes the freed place
+ if position < self.max_items and len(self.sorted_keys) >= self.max_items:
+ shown_key = self.sorted_keys[self.max_items-1][1]
+ self.elements[shown_key].show()
+
+ def empty(self):
+ """ Menu is without items """
+ return self.sorted_keys == []
=== renamed file 'GTG/tools/sorted_dict.py' => 'GTG/tools/sorted_dict.py.THIS'