← Back to team overview

gtg-contributors team mailing list archive

[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 contributors is subscribed to branch lp:~gtg-contributors/gtg/notification-area-rewrite.
=== 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'

Follow ups