diff --git a/debian/changelog b/debian/changelog index 518684f..406c8e8 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +gnome-shell-extensions (3.14.2-1kali1) kali-dev; urgency=medium + + * Add apps-menu-with-multiple-levels.patch to support nested menus. + + -- Raphaƫl Hertzog Mon, 18 May 2015 17:41:17 +0200 + gnome-shell-extensions (3.14.2-1) unstable; urgency=medium * New upstream bugfix release. diff --git a/debian/patches/apps-menu-with-multiple-levels.patch b/debian/patches/apps-menu-with-multiple-levels.patch new file mode 100644 index 0000000..e678ef7 --- /dev/null +++ b/debian/patches/apps-menu-with-multiple-levels.patch @@ -0,0 +1,384 @@ +Description: Add support for nested menus +Author: Emilio Pozuelo Monfort +Bug-Kali: https://bugs.kali.org/view.php?id=2223 +Bug: https://bugzilla.gnome.org/show_bug.cgi?id=739480 + +--- a/extensions/apps-menu/extension.js ++++ b/extensions/apps-menu/extension.js +@@ -97,10 +97,9 @@ const ApplicationMenuItem = new Lang.Cla + + const CategoryMenuItem = new Lang.Class({ + Name: 'CategoryMenuItem', +- Extends: PopupMenu.PopupBaseMenuItem, ++ Extends: PopupMenu.PopupMenuItem, + + _init: function(button, category) { +- this.parent(); + this._category = category; + this._button = button; + +@@ -113,13 +112,126 @@ const CategoryMenuItem = new Lang.Class( + else + name = _("Favorites"); + +- this.actor.add_child(new St.Label({ text: name })); +- this.actor.connect('motion-event', Lang.bind(this, this._onMotionEvent)); ++ this.parent(name); ++ ++ //this.actor.connect('motion-event', Lang.bind(this, this._onMotionEvent)); ++ }, ++ ++ activate: function(event) { ++ this._button.selectCategory(this._category, this); ++ //this._button.scrollToCatButton(this); ++ // we don't chain up here so that clicking on a category doesn't ++ // close the menu ++ }, ++ ++ _isNavigatingSubmenu: function([x, y]) { ++ let [posX, posY] = this.actor.get_transformed_position(); ++ ++ if (this._oldX == -1) { ++ this._oldX = x; ++ this._oldY = y; ++ return true; ++ } ++ ++ let deltaX = Math.abs(x - this._oldX); ++ let deltaY = Math.abs(y - this._oldY); ++ ++ this._oldX = x; ++ this._oldY = y; ++ ++ // If it lies outside the x-coordinates then it is definitely outside. ++ if (posX > x || posX + this.actor.width < x) ++ return false; ++ ++ // If it lies inside the menu item then it is definitely inside. ++ if (posY <= y && posY + this.actor.height >= y) ++ return true; ++ ++ // We want the keep-up triangle only if the movement is more ++ // horizontal than vertical. ++ if (deltaX * HORIZ_FACTOR < deltaY) ++ return false; ++ ++ // Check whether the point lies inside triangle ABC, and a similar ++ // triangle on the other side of the menu item. ++ // ++ // +---------------------+ ++ // | menu item | ++ // A +---------------------+ C ++ // P | ++ // B ++ ++ // Ensure that the point P always lies below line AC so that we can ++ // only check for triangle ABC. ++ if (posY > y) { ++ let offset = posY - y; ++ y = posY + this.actor.height + offset; ++ } ++ ++ // Ensure that A is (0, 0). ++ x -= posX; ++ y -= posY + this.actor.height; ++ ++ // Check which side of line AB the point P lies on by taking the ++ // cross-product of AB and AP. See: ++ // http://stackoverflow.com/questions/3461453/determine-which-side-of-a-line-a-point-lies ++ if (((this.actor.width * y) - (NAVIGATION_REGION_OVERSHOOT * x)) <= 0) ++ return true; ++ ++ return false; ++ }, ++ ++ _onMotionEvent: function(actor, event) { ++ if (!Clutter.get_pointer_grab()) { ++ this._oldX = -1; ++ this._oldY = -1; ++ Clutter.grab_pointer(this.actor); ++ } ++ this.actor.hover = true; ++ ++ if (this._isNavigatingSubmenu(event.get_coords())) ++ return true; ++ ++ this._oldX = -1; ++ this._oldY = -1; ++ this.actor.hover = false; ++ Clutter.ungrab_pointer(); ++ return false; ++ }, ++ ++ setActive: function(active, params) { ++ if (active) { ++ this._button.selectCategory(this._category, this); ++ //this._button.scrollToCatButton(this); ++ } ++ this.parent(active, params); ++ } ++}); ++const ParentCategoryMenuItem = new Lang.Class({ ++ Name: 'ParentCategoryMenuItem', ++ Extends: PopupMenu.PopupSubMenuMenuItem, ++ ++ _init: function(button, category) { ++ this._category = category; ++ this._button = button; ++ ++ this._oldX = -1; ++ this._oldY = -1; ++ ++ let name; ++ if (this._category) ++ name = this._category.get_name(); ++ else ++ name = _("Favorites"); ++ ++ this.parent(name, false); ++ ++ //this.actor.connect('motion-event', Lang.bind(this, this._onMotionEvent)); + }, + + activate: function(event) { + this._button.selectCategory(this._category, this); +- this._button.scrollToCatButton(this); ++ //this._button.scrollToCatButton(this); + this.parent(event); + }, + +@@ -201,12 +313,31 @@ const CategoryMenuItem = new Lang.Class( + setActive: function(active, params) { + if (active) { + this._button.selectCategory(this._category, this); +- this._button.scrollToCatButton(this); ++ //this._button.scrollToCatButton(this); + } + this.parent(active, params); + } + }); + ++const PopupMenuScrollView = new Lang.Class({ ++ Name: 'PopupMenuScrollView', ++ Extends: PopupMenu.PopupMenuSection, ++ ++ _init: function() { ++ this.parent(); ++ ++ this.actor = new St.ScrollView({ style_class: 'vfade', ++ hscrollbar_policy: Gtk.PolicyType.NEVER, ++ vscrollbar_policy: Gtk.PolicyType.AUTOMATIC }); ++ ++ this.container = new Shell.GenericContainer(); ++ this.box.add_actor(this.container); ++ this.actor.add_actor(this.box); ++ this.actor._delegate = this; ++ this.actor.clip_to_allocation = true; ++ }, ++}); ++ + const HotCorner = new Lang.Class({ + Name: 'HotCorner', + Extends: Layout.HotCorner, +@@ -305,7 +436,7 @@ const ApplicationsButton = new Lang.Clas + _installedChangedId = appSys.connect('installed-changed', Lang.bind(this, function() { + if (this.menu.isOpen) { + this._redisplay(); +- this.mainBox.show(); ++ this.mainBox.actor.show(); + } else { + this.reloadFlag = true; + } +@@ -370,18 +501,19 @@ const ApplicationsButton = new Lang.Clas + this._redisplay(); + this.reloadFlag = false; + } +- this.mainBox.show(); ++ //this.categoriesBox.box.width = this._menuContainerGetPreferredWidth(this.categoriesBox.box); ++ this.mainBox.actor.show(); + } + this.parent(menu, open); + }, + + _redisplay: function() { + this.applicationsBox.destroy_all_children(); +- this.categoriesBox.destroy_all_children(); ++ this.categoriesBox.actor.destroy_all_children(); + this._display(); + }, + +- _loadCategory: function(categoryId, dir) { ++ _loadCategory: function(dir, parentCategory) { + let iter = dir.iter(); + let nextType; + while ((nextType = iter.next()) != GMenu.TreeItemType.INVALID) { +@@ -391,12 +523,21 @@ const ApplicationsButton = new Lang.Clas + let app = appSys.lookup_app(entry.get_desktop_file_id()); + if (appInfo.should_show()) { + let menu_id = dir.get_menu_id(); +- this.applicationsByCategory[categoryId].push(app); ++ this.applicationsByCategory[menu_id].push(app); + } + } else if (nextType == GMenu.TreeItemType.DIRECTORY) { + let subdir = iter.get_directory(); +- if (!subdir.get_is_nodisplay()) +- this._loadCategory(categoryId, subdir); ++ if (!subdir.get_is_nodisplay()) { ++ let menu_id = subdir.get_menu_id(); ++ this.applicationsByCategory[menu_id] = []; ++ let categoryMenuItem = new ParentCategoryMenuItem(this, subdir); ++ this._loadCategory(subdir, categoryMenuItem); ++ if (categoryMenuItem.menu.isEmpty()) ++ categoryMenuItem = new CategoryMenuItem(this, subdir); ++ if (this.applicationsByCategory[menu_id].length > 0 || !categoryMenuItem.menu.isEmpty()) { ++ parentCategory.menu.addMenuItem(categoryMenuItem); ++ } ++ } + } + } + }, +@@ -417,8 +558,8 @@ const ApplicationsButton = new Lang.Clas + }, + + scrollToCatButton: function(button) { +- let catsScrollBoxAdj = this.categoriesScrollBox.get_vscroll_bar().get_adjustment(); +- let catsScrollBoxAlloc = this.categoriesScrollBox.get_allocation_box(); ++ let catsScrollBoxAdj = this.categoriesBox.actor.get_vscroll_bar().get_adjustment(); ++ let catsScrollBoxAlloc = this.categoriesBox.actor.get_allocation_box(); + let currentScrollValue = catsScrollBoxAdj.get_value(); + let boxHeight = catsScrollBoxAlloc.y2 - catsScrollBoxAlloc.y1; + let buttonAlloc = button.actor.get_allocation_box(); +@@ -432,10 +573,16 @@ const ApplicationsButton = new Lang.Clas + }, + + _createLayout: function() { ++ // https://mail.gnome.org/archives/gnome-shell-list/2014-January/msg00010.html ++ this.menu._setOpenedSubMenu = Lang.bind(this, function(submenu) { ++ this._openedSubMenu = submenu; ++ }); ++ + let section = new PopupMenu.PopupMenuSection(); + this.menu.addMenuItem(section); +- this.mainBox = new St.BoxLayout({ vertical: false }); +- this.leftBox = new St.BoxLayout({ vertical: true }); ++ this.mainBox = new PopupMenu.PopupMenuSection(); ++ this.mainBox.actor.vertical = false; ++ this.leftBox = new PopupMenu.PopupMenuSection(); + this.applicationsScrollBox = new St.ScrollView({ x_fill: true, y_fill: false, + y_align: St.Align.START, + style_class: 'apps-menu vfade' }); +@@ -447,41 +594,52 @@ const ApplicationsButton = new Lang.Clas + vscroll.connect('scroll-stop', Lang.bind(this, function() { + this.menu.passEvents = false; + })); +- this.categoriesScrollBox = new St.ScrollView({ x_fill: true, y_fill: false, +- y_align: St.Align.START, +- style_class: 'vfade' }); +- this.categoriesScrollBox.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); +- vscroll = this.categoriesScrollBox.get_vscroll_bar(); ++ ++ let activities = new ActivitiesMenuItem(this); ++ ++ this.applicationsBox = new St.BoxLayout({ vertical: true }); ++ this.applicationsScrollBox.add_actor(this.applicationsBox); ++ ++ this.categoriesBox = new PopupMenuScrollView(); ++ vscroll = this.categoriesBox.actor.get_vscroll_bar(); + vscroll.connect('scroll-start', Lang.bind(this, function() { + this.menu.passEvents = true; + })); + vscroll.connect('scroll-stop', Lang.bind(this, function() { + this.menu.passEvents = false; + })); +- this.leftBox.add(this.categoriesScrollBox, { expand: true, +- x_fill: true, y_fill: true, +- y_align: St.Align.START }); +- +- let activities = new ActivitiesMenuItem(this); +- this.leftBox.add(activities.actor, { expand: false, +- x_fill: true, y_fill: false, +- y_align: St.Align.START }); ++ this.leftBox.addMenuItem(this.categoriesBox); ++ // FIXME we re-add it to apply the right properties, but re-adding it causes a warning ++ this.leftBox.actor.add(this.categoriesBox.actor, { expand: true, x_fill: true, y_fill: true, y_align: St.Align.START }); ++ ++ this.leftBox.actor.add(activities.actor, { expand: false, ++ x_fill: true, y_fill: false, ++ y_align: St.Align.START }); ++ ++ this.mainBox.addMenuItem(this.leftBox); ++ this.mainBox.actor.add(this._createVertSeparator(), { expand: false, x_fill: false, y_fill: true}); ++ this.mainBox.actor.add(this.applicationsScrollBox, { expand: true, x_fill: true, y_fill: true }); ++ section.addMenuItem(this.mainBox); ++ }, ++ ++ _menuContainerGetPreferredWidth: function(container) { ++ let max_width = 0; ++ for (let child = container.get_first_child(); ++ child; ++ child = child.get_next_sibling()) { ++ // recurse into submenus ++ if (child._delegate instanceof ParentCategoryMenuItem) ++ max_width = Math.max(max_width, this._menuContainerGetPreferredWidth(child._delegate.menu.box)); + +- this.applicationsBox = new St.BoxLayout({ vertical: true }); +- this.applicationsScrollBox.add_actor(this.applicationsBox); +- this.categoriesBox = new St.BoxLayout({ vertical: true }); +- this.categoriesScrollBox.add_actor(this.categoriesBox, { expand: true, x_fill: false }); +- +- this.mainBox.add(this.leftBox); +- this.mainBox.add(this._createVertSeparator(), { expand: false, x_fill: false, y_fill: true}); +- this.mainBox.add(this.applicationsScrollBox, { expand: true, x_fill: true, y_fill: true }); +- section.actor.add_actor(this.mainBox); ++ max_width = Math.max(max_width, child.width); ++ } ++ return max_width; + }, + + _display: function() { + this._applicationsButtons = new Array(); +- this.mainBox.style=('width: 640px;'); +- this.mainBox.hide(); ++ this.mainBox.actor.style=('width: 640px;'); ++ this.mainBox.actor.hide(); + + //Load categories + this.applicationsByCategory = {}; +@@ -489,7 +647,7 @@ const ApplicationsButton = new Lang.Clas + tree.load_sync(); + let root = tree.get_root_directory(); + let categoryMenuItem = new CategoryMenuItem(this, null); +- this.categoriesBox.add_actor(categoryMenuItem.actor); ++ this.categoriesBox.addMenuItem(categoryMenuItem); + let iter = root.iter(); + let nextType; + while ((nextType = iter.next()) != GMenu.TreeItemType.INVALID) { +@@ -498,10 +656,12 @@ const ApplicationsButton = new Lang.Clas + if (!dir.get_is_nodisplay()) { + let categoryId = dir.get_menu_id(); + this.applicationsByCategory[categoryId] = []; +- this._loadCategory(categoryId, dir); +- if (this.applicationsByCategory[categoryId].length > 0) { +- let categoryMenuItem = new CategoryMenuItem(this, dir); +- this.categoriesBox.add_actor(categoryMenuItem.actor); ++ let categoryMenuItem = new ParentCategoryMenuItem(this, dir); ++ this._loadCategory(dir, categoryMenuItem); ++ if (categoryMenuItem.menu.isEmpty()) ++ categoryMenuItem = new CategoryMenuItem(this, dir); ++ if (this.applicationsByCategory[categoryId].length > 0 || !categoryMenuItem.menu.isEmpty()) { ++ this.categoriesBox.addMenuItem(categoryMenuItem); + } + } + } +@@ -510,8 +670,9 @@ const ApplicationsButton = new Lang.Clas + //Load applications + this._displayButtons(this._listApplications(null)); + +- let height = this.categoriesBox.height + MENU_HEIGHT_OFFSET + 'px'; +- this.mainBox.style+=('height: ' + height); ++ let height = this.categoriesBox.actor.height + MENU_HEIGHT_OFFSET + 'px'; ++ this.mainBox.actor.style+=('height: ' + height); ++ this.categoriesBox.box.width = 220; + }, + + _clearApplicationsBox: function(selectedActor) { diff --git a/debian/patches/series b/debian/patches/series index c3746d7..f0b5e38 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -3,3 +3,4 @@ menu-arrows-icons.patch apps-center-labels.patch window-list-pointerInNotification.patch +apps-menu-with-multiple-levels.patch