|
0 |
Description: Add support for nested menus
|
|
1 |
Author: Emilio Pozuelo Monfort <[email protected]>
|
|
2 |
Bug-Kali: https://bugs.kali.org/view.php?id=2223
|
|
3 |
Bug: https://bugzilla.gnome.org/show_bug.cgi?id=739480
|
|
4 |
|
|
5 |
--- a/extensions/apps-menu/extension.js
|
|
6 |
+++ b/extensions/apps-menu/extension.js
|
|
7 |
@@ -97,10 +97,9 @@ const ApplicationMenuItem = new Lang.Cla
|
|
8 |
|
|
9 |
const CategoryMenuItem = new Lang.Class({
|
|
10 |
Name: 'CategoryMenuItem',
|
|
11 |
- Extends: PopupMenu.PopupBaseMenuItem,
|
|
12 |
+ Extends: PopupMenu.PopupMenuItem,
|
|
13 |
|
|
14 |
_init: function(button, category) {
|
|
15 |
- this.parent();
|
|
16 |
this._category = category;
|
|
17 |
this._button = button;
|
|
18 |
|
|
19 |
@@ -113,13 +112,126 @@ const CategoryMenuItem = new Lang.Class(
|
|
20 |
else
|
|
21 |
name = _("Favorites");
|
|
22 |
|
|
23 |
- this.actor.add_child(new St.Label({ text: name }));
|
|
24 |
- this.actor.connect('motion-event', Lang.bind(this, this._onMotionEvent));
|
|
25 |
+ this.parent(name);
|
|
26 |
+
|
|
27 |
+ //this.actor.connect('motion-event', Lang.bind(this, this._onMotionEvent));
|
|
28 |
+ },
|
|
29 |
+
|
|
30 |
+ activate: function(event) {
|
|
31 |
+ this._button.selectCategory(this._category, this);
|
|
32 |
+ //this._button.scrollToCatButton(this);
|
|
33 |
+ // we don't chain up here so that clicking on a category doesn't
|
|
34 |
+ // close the menu
|
|
35 |
+ },
|
|
36 |
+
|
|
37 |
+ _isNavigatingSubmenu: function([x, y]) {
|
|
38 |
+ let [posX, posY] = this.actor.get_transformed_position();
|
|
39 |
+
|
|
40 |
+ if (this._oldX == -1) {
|
|
41 |
+ this._oldX = x;
|
|
42 |
+ this._oldY = y;
|
|
43 |
+ return true;
|
|
44 |
+ }
|
|
45 |
+
|
|
46 |
+ let deltaX = Math.abs(x - this._oldX);
|
|
47 |
+ let deltaY = Math.abs(y - this._oldY);
|
|
48 |
+
|
|
49 |
+ this._oldX = x;
|
|
50 |
+ this._oldY = y;
|
|
51 |
+
|
|
52 |
+ // If it lies outside the x-coordinates then it is definitely outside.
|
|
53 |
+ if (posX > x || posX + this.actor.width < x)
|
|
54 |
+ return false;
|
|
55 |
+
|
|
56 |
+ // If it lies inside the menu item then it is definitely inside.
|
|
57 |
+ if (posY <= y && posY + this.actor.height >= y)
|
|
58 |
+ return true;
|
|
59 |
+
|
|
60 |
+ // We want the keep-up triangle only if the movement is more
|
|
61 |
+ // horizontal than vertical.
|
|
62 |
+ if (deltaX * HORIZ_FACTOR < deltaY)
|
|
63 |
+ return false;
|
|
64 |
+
|
|
65 |
+ // Check whether the point lies inside triangle ABC, and a similar
|
|
66 |
+ // triangle on the other side of the menu item.
|
|
67 |
+ //
|
|
68 |
+ // +---------------------+
|
|
69 |
+ // | menu item |
|
|
70 |
+ // A +---------------------+ C
|
|
71 |
+ // P |
|
|
72 |
+ // B
|
|
73 |
+
|
|
74 |
+ // Ensure that the point P always lies below line AC so that we can
|
|
75 |
+ // only check for triangle ABC.
|
|
76 |
+ if (posY > y) {
|
|
77 |
+ let offset = posY - y;
|
|
78 |
+ y = posY + this.actor.height + offset;
|
|
79 |
+ }
|
|
80 |
+
|
|
81 |
+ // Ensure that A is (0, 0).
|
|
82 |
+ x -= posX;
|
|
83 |
+ y -= posY + this.actor.height;
|
|
84 |
+
|
|
85 |
+ // Check which side of line AB the point P lies on by taking the
|
|
86 |
+ // cross-product of AB and AP. See:
|
|
87 |
+ // http://stackoverflow.com/questions/3461453/determine-which-side-of-a-line-a-point-lies
|
|
88 |
+ if (((this.actor.width * y) - (NAVIGATION_REGION_OVERSHOOT * x)) <= 0)
|
|
89 |
+ return true;
|
|
90 |
+
|
|
91 |
+ return false;
|
|
92 |
+ },
|
|
93 |
+
|
|
94 |
+ _onMotionEvent: function(actor, event) {
|
|
95 |
+ if (!Clutter.get_pointer_grab()) {
|
|
96 |
+ this._oldX = -1;
|
|
97 |
+ this._oldY = -1;
|
|
98 |
+ Clutter.grab_pointer(this.actor);
|
|
99 |
+ }
|
|
100 |
+ this.actor.hover = true;
|
|
101 |
+
|
|
102 |
+ if (this._isNavigatingSubmenu(event.get_coords()))
|
|
103 |
+ return true;
|
|
104 |
+
|
|
105 |
+ this._oldX = -1;
|
|
106 |
+ this._oldY = -1;
|
|
107 |
+ this.actor.hover = false;
|
|
108 |
+ Clutter.ungrab_pointer();
|
|
109 |
+ return false;
|
|
110 |
+ },
|
|
111 |
+
|
|
112 |
+ setActive: function(active, params) {
|
|
113 |
+ if (active) {
|
|
114 |
+ this._button.selectCategory(this._category, this);
|
|
115 |
+ //this._button.scrollToCatButton(this);
|
|
116 |
+ }
|
|
117 |
+ this.parent(active, params);
|
|
118 |
+ }
|
|
119 |
+});
|
|
120 |
+const ParentCategoryMenuItem = new Lang.Class({
|
|
121 |
+ Name: 'ParentCategoryMenuItem',
|
|
122 |
+ Extends: PopupMenu.PopupSubMenuMenuItem,
|
|
123 |
+
|
|
124 |
+ _init: function(button, category) {
|
|
125 |
+ this._category = category;
|
|
126 |
+ this._button = button;
|
|
127 |
+
|
|
128 |
+ this._oldX = -1;
|
|
129 |
+ this._oldY = -1;
|
|
130 |
+
|
|
131 |
+ let name;
|
|
132 |
+ if (this._category)
|
|
133 |
+ name = this._category.get_name();
|
|
134 |
+ else
|
|
135 |
+ name = _("Favorites");
|
|
136 |
+
|
|
137 |
+ this.parent(name, false);
|
|
138 |
+
|
|
139 |
+ //this.actor.connect('motion-event', Lang.bind(this, this._onMotionEvent));
|
|
140 |
},
|
|
141 |
|
|
142 |
activate: function(event) {
|
|
143 |
this._button.selectCategory(this._category, this);
|
|
144 |
- this._button.scrollToCatButton(this);
|
|
145 |
+ //this._button.scrollToCatButton(this);
|
|
146 |
this.parent(event);
|
|
147 |
},
|
|
148 |
|
|
149 |
@@ -201,12 +313,31 @@ const CategoryMenuItem = new Lang.Class(
|
|
150 |
setActive: function(active, params) {
|
|
151 |
if (active) {
|
|
152 |
this._button.selectCategory(this._category, this);
|
|
153 |
- this._button.scrollToCatButton(this);
|
|
154 |
+ //this._button.scrollToCatButton(this);
|
|
155 |
}
|
|
156 |
this.parent(active, params);
|
|
157 |
}
|
|
158 |
});
|
|
159 |
|
|
160 |
+const PopupMenuScrollView = new Lang.Class({
|
|
161 |
+ Name: 'PopupMenuScrollView',
|
|
162 |
+ Extends: PopupMenu.PopupMenuSection,
|
|
163 |
+
|
|
164 |
+ _init: function() {
|
|
165 |
+ this.parent();
|
|
166 |
+
|
|
167 |
+ this.actor = new St.ScrollView({ style_class: 'vfade',
|
|
168 |
+ hscrollbar_policy: Gtk.PolicyType.NEVER,
|
|
169 |
+ vscrollbar_policy: Gtk.PolicyType.AUTOMATIC });
|
|
170 |
+
|
|
171 |
+ this.container = new Shell.GenericContainer();
|
|
172 |
+ this.box.add_actor(this.container);
|
|
173 |
+ this.actor.add_actor(this.box);
|
|
174 |
+ this.actor._delegate = this;
|
|
175 |
+ this.actor.clip_to_allocation = true;
|
|
176 |
+ },
|
|
177 |
+});
|
|
178 |
+
|
|
179 |
const HotCorner = new Lang.Class({
|
|
180 |
Name: 'HotCorner',
|
|
181 |
Extends: Layout.HotCorner,
|
|
182 |
@@ -305,7 +436,7 @@ const ApplicationsButton = new Lang.Clas
|
|
183 |
_installedChangedId = appSys.connect('installed-changed', Lang.bind(this, function() {
|
|
184 |
if (this.menu.isOpen) {
|
|
185 |
this._redisplay();
|
|
186 |
- this.mainBox.show();
|
|
187 |
+ this.mainBox.actor.show();
|
|
188 |
} else {
|
|
189 |
this.reloadFlag = true;
|
|
190 |
}
|
|
191 |
@@ -370,18 +501,19 @@ const ApplicationsButton = new Lang.Clas
|
|
192 |
this._redisplay();
|
|
193 |
this.reloadFlag = false;
|
|
194 |
}
|
|
195 |
- this.mainBox.show();
|
|
196 |
+ //this.categoriesBox.box.width = this._menuContainerGetPreferredWidth(this.categoriesBox.box);
|
|
197 |
+ this.mainBox.actor.show();
|
|
198 |
}
|
|
199 |
this.parent(menu, open);
|
|
200 |
},
|
|
201 |
|
|
202 |
_redisplay: function() {
|
|
203 |
this.applicationsBox.destroy_all_children();
|
|
204 |
- this.categoriesBox.destroy_all_children();
|
|
205 |
+ this.categoriesBox.actor.destroy_all_children();
|
|
206 |
this._display();
|
|
207 |
},
|
|
208 |
|
|
209 |
- _loadCategory: function(categoryId, dir) {
|
|
210 |
+ _loadCategory: function(dir, parentCategory) {
|
|
211 |
let iter = dir.iter();
|
|
212 |
let nextType;
|
|
213 |
while ((nextType = iter.next()) != GMenu.TreeItemType.INVALID) {
|
|
214 |
@@ -391,12 +523,21 @@ const ApplicationsButton = new Lang.Clas
|
|
215 |
let app = appSys.lookup_app(entry.get_desktop_file_id());
|
|
216 |
if (appInfo.should_show()) {
|
|
217 |
let menu_id = dir.get_menu_id();
|
|
218 |
- this.applicationsByCategory[categoryId].push(app);
|
|
219 |
+ this.applicationsByCategory[menu_id].push(app);
|
|
220 |
}
|
|
221 |
} else if (nextType == GMenu.TreeItemType.DIRECTORY) {
|
|
222 |
let subdir = iter.get_directory();
|
|
223 |
- if (!subdir.get_is_nodisplay())
|
|
224 |
- this._loadCategory(categoryId, subdir);
|
|
225 |
+ if (!subdir.get_is_nodisplay()) {
|
|
226 |
+ let menu_id = subdir.get_menu_id();
|
|
227 |
+ this.applicationsByCategory[menu_id] = [];
|
|
228 |
+ let categoryMenuItem = new ParentCategoryMenuItem(this, subdir);
|
|
229 |
+ this._loadCategory(subdir, categoryMenuItem);
|
|
230 |
+ if (categoryMenuItem.menu.isEmpty())
|
|
231 |
+ categoryMenuItem = new CategoryMenuItem(this, subdir);
|
|
232 |
+ if (this.applicationsByCategory[menu_id].length > 0 || !categoryMenuItem.menu.isEmpty()) {
|
|
233 |
+ parentCategory.menu.addMenuItem(categoryMenuItem);
|
|
234 |
+ }
|
|
235 |
+ }
|
|
236 |
}
|
|
237 |
}
|
|
238 |
},
|
|
239 |
@@ -417,8 +558,8 @@ const ApplicationsButton = new Lang.Clas
|
|
240 |
},
|
|
241 |
|
|
242 |
scrollToCatButton: function(button) {
|
|
243 |
- let catsScrollBoxAdj = this.categoriesScrollBox.get_vscroll_bar().get_adjustment();
|
|
244 |
- let catsScrollBoxAlloc = this.categoriesScrollBox.get_allocation_box();
|
|
245 |
+ let catsScrollBoxAdj = this.categoriesBox.actor.get_vscroll_bar().get_adjustment();
|
|
246 |
+ let catsScrollBoxAlloc = this.categoriesBox.actor.get_allocation_box();
|
|
247 |
let currentScrollValue = catsScrollBoxAdj.get_value();
|
|
248 |
let boxHeight = catsScrollBoxAlloc.y2 - catsScrollBoxAlloc.y1;
|
|
249 |
let buttonAlloc = button.actor.get_allocation_box();
|
|
250 |
@@ -432,10 +573,16 @@ const ApplicationsButton = new Lang.Clas
|
|
251 |
},
|
|
252 |
|
|
253 |
_createLayout: function() {
|
|
254 |
+ // https://mail.gnome.org/archives/gnome-shell-list/2014-January/msg00010.html
|
|
255 |
+ this.menu._setOpenedSubMenu = Lang.bind(this, function(submenu) {
|
|
256 |
+ this._openedSubMenu = submenu;
|
|
257 |
+ });
|
|
258 |
+
|
|
259 |
let section = new PopupMenu.PopupMenuSection();
|
|
260 |
this.menu.addMenuItem(section);
|
|
261 |
- this.mainBox = new St.BoxLayout({ vertical: false });
|
|
262 |
- this.leftBox = new St.BoxLayout({ vertical: true });
|
|
263 |
+ this.mainBox = new PopupMenu.PopupMenuSection();
|
|
264 |
+ this.mainBox.actor.vertical = false;
|
|
265 |
+ this.leftBox = new PopupMenu.PopupMenuSection();
|
|
266 |
this.applicationsScrollBox = new St.ScrollView({ x_fill: true, y_fill: false,
|
|
267 |
y_align: St.Align.START,
|
|
268 |
style_class: 'apps-menu vfade' });
|
|
269 |
@@ -447,41 +594,52 @@ const ApplicationsButton = new Lang.Clas
|
|
270 |
vscroll.connect('scroll-stop', Lang.bind(this, function() {
|
|
271 |
this.menu.passEvents = false;
|
|
272 |
}));
|
|
273 |
- this.categoriesScrollBox = new St.ScrollView({ x_fill: true, y_fill: false,
|
|
274 |
- y_align: St.Align.START,
|
|
275 |
- style_class: 'vfade' });
|
|
276 |
- this.categoriesScrollBox.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
|
277 |
- vscroll = this.categoriesScrollBox.get_vscroll_bar();
|
|
278 |
+
|
|
279 |
+ let activities = new ActivitiesMenuItem(this);
|
|
280 |
+
|
|
281 |
+ this.applicationsBox = new St.BoxLayout({ vertical: true });
|
|
282 |
+ this.applicationsScrollBox.add_actor(this.applicationsBox);
|
|
283 |
+
|
|
284 |
+ this.categoriesBox = new PopupMenuScrollView();
|
|
285 |
+ vscroll = this.categoriesBox.actor.get_vscroll_bar();
|
|
286 |
vscroll.connect('scroll-start', Lang.bind(this, function() {
|
|
287 |
this.menu.passEvents = true;
|
|
288 |
}));
|
|
289 |
vscroll.connect('scroll-stop', Lang.bind(this, function() {
|
|
290 |
this.menu.passEvents = false;
|
|
291 |
}));
|
|
292 |
- this.leftBox.add(this.categoriesScrollBox, { expand: true,
|
|
293 |
- x_fill: true, y_fill: true,
|
|
294 |
- y_align: St.Align.START });
|
|
295 |
-
|
|
296 |
- let activities = new ActivitiesMenuItem(this);
|
|
297 |
- this.leftBox.add(activities.actor, { expand: false,
|
|
298 |
- x_fill: true, y_fill: false,
|
|
299 |
- y_align: St.Align.START });
|
|
300 |
+ this.leftBox.addMenuItem(this.categoriesBox);
|
|
301 |
+ // FIXME we re-add it to apply the right properties, but re-adding it causes a warning
|
|
302 |
+ this.leftBox.actor.add(this.categoriesBox.actor, { expand: true, x_fill: true, y_fill: true, y_align: St.Align.START });
|
|
303 |
+
|
|
304 |
+ this.leftBox.actor.add(activities.actor, { expand: false,
|
|
305 |
+ x_fill: true, y_fill: false,
|
|
306 |
+ y_align: St.Align.START });
|
|
307 |
+
|
|
308 |
+ this.mainBox.addMenuItem(this.leftBox);
|
|
309 |
+ this.mainBox.actor.add(this._createVertSeparator(), { expand: false, x_fill: false, y_fill: true});
|
|
310 |
+ this.mainBox.actor.add(this.applicationsScrollBox, { expand: true, x_fill: true, y_fill: true });
|
|
311 |
+ section.addMenuItem(this.mainBox);
|
|
312 |
+ },
|
|
313 |
+
|
|
314 |
+ _menuContainerGetPreferredWidth: function(container) {
|
|
315 |
+ let max_width = 0;
|
|
316 |
+ for (let child = container.get_first_child();
|
|
317 |
+ child;
|
|
318 |
+ child = child.get_next_sibling()) {
|
|
319 |
+ // recurse into submenus
|
|
320 |
+ if (child._delegate instanceof ParentCategoryMenuItem)
|
|
321 |
+ max_width = Math.max(max_width, this._menuContainerGetPreferredWidth(child._delegate.menu.box));
|
|
322 |
|
|
323 |
- this.applicationsBox = new St.BoxLayout({ vertical: true });
|
|
324 |
- this.applicationsScrollBox.add_actor(this.applicationsBox);
|
|
325 |
- this.categoriesBox = new St.BoxLayout({ vertical: true });
|
|
326 |
- this.categoriesScrollBox.add_actor(this.categoriesBox, { expand: true, x_fill: false });
|
|
327 |
-
|
|
328 |
- this.mainBox.add(this.leftBox);
|
|
329 |
- this.mainBox.add(this._createVertSeparator(), { expand: false, x_fill: false, y_fill: true});
|
|
330 |
- this.mainBox.add(this.applicationsScrollBox, { expand: true, x_fill: true, y_fill: true });
|
|
331 |
- section.actor.add_actor(this.mainBox);
|
|
332 |
+ max_width = Math.max(max_width, child.width);
|
|
333 |
+ }
|
|
334 |
+ return max_width;
|
|
335 |
},
|
|
336 |
|
|
337 |
_display: function() {
|
|
338 |
this._applicationsButtons = new Array();
|
|
339 |
- this.mainBox.style=('width: 640px;');
|
|
340 |
- this.mainBox.hide();
|
|
341 |
+ this.mainBox.actor.style=('width: 640px;');
|
|
342 |
+ this.mainBox.actor.hide();
|
|
343 |
|
|
344 |
//Load categories
|
|
345 |
this.applicationsByCategory = {};
|
|
346 |
@@ -489,7 +647,7 @@ const ApplicationsButton = new Lang.Clas
|
|
347 |
tree.load_sync();
|
|
348 |
let root = tree.get_root_directory();
|
|
349 |
let categoryMenuItem = new CategoryMenuItem(this, null);
|
|
350 |
- this.categoriesBox.add_actor(categoryMenuItem.actor);
|
|
351 |
+ this.categoriesBox.addMenuItem(categoryMenuItem);
|
|
352 |
let iter = root.iter();
|
|
353 |
let nextType;
|
|
354 |
while ((nextType = iter.next()) != GMenu.TreeItemType.INVALID) {
|
|
355 |
@@ -498,10 +656,12 @@ const ApplicationsButton = new Lang.Clas
|
|
356 |
if (!dir.get_is_nodisplay()) {
|
|
357 |
let categoryId = dir.get_menu_id();
|
|
358 |
this.applicationsByCategory[categoryId] = [];
|
|
359 |
- this._loadCategory(categoryId, dir);
|
|
360 |
- if (this.applicationsByCategory[categoryId].length > 0) {
|
|
361 |
- let categoryMenuItem = new CategoryMenuItem(this, dir);
|
|
362 |
- this.categoriesBox.add_actor(categoryMenuItem.actor);
|
|
363 |
+ let categoryMenuItem = new ParentCategoryMenuItem(this, dir);
|
|
364 |
+ this._loadCategory(dir, categoryMenuItem);
|
|
365 |
+ if (categoryMenuItem.menu.isEmpty())
|
|
366 |
+ categoryMenuItem = new CategoryMenuItem(this, dir);
|
|
367 |
+ if (this.applicationsByCategory[categoryId].length > 0 || !categoryMenuItem.menu.isEmpty()) {
|
|
368 |
+ this.categoriesBox.addMenuItem(categoryMenuItem);
|
|
369 |
}
|
|
370 |
}
|
|
371 |
}
|
|
372 |
@@ -510,8 +670,9 @@ const ApplicationsButton = new Lang.Clas
|
|
373 |
//Load applications
|
|
374 |
this._displayButtons(this._listApplications(null));
|
|
375 |
|
|
376 |
- let height = this.categoriesBox.height + MENU_HEIGHT_OFFSET + 'px';
|
|
377 |
- this.mainBox.style+=('height: ' + height);
|
|
378 |
+ let height = this.categoriesBox.actor.height + MENU_HEIGHT_OFFSET + 'px';
|
|
379 |
+ this.mainBox.actor.style+=('height: ' + height);
|
|
380 |
+ this.categoriesBox.box.width = 220;
|
|
381 |
},
|
|
382 |
|
|
383 |
_clearApplicationsBox: function(selectedActor) {
|