Merge lp:~meese/slingshot/fix-1294917 into lp:~elementary-pantheon/slingshot/trunk

Proposed by meese
Status: Merged
Approved by: Cody Garver
Approved revision: 456
Merged at revision: 467
Proposed branch: lp:~meese/slingshot/fix-1294917
Merge into: lp:~elementary-pantheon/slingshot/trunk
Diff against target: 490 lines (+332/-10)
7 files modified
CMakeLists.txt (+1/-0)
src/Backend/App.vala (+97/-1)
src/SlingshotView.vala (+21/-4)
src/Widgets/AppEntry.vala (+40/-2)
src/Widgets/CategoryView.vala (+1/-2)
src/Widgets/PopoverMenu.vala (+169/-0)
src/Widgets/Switcher.vala (+3/-1)
To merge this branch: bzr merge lp:~meese/slingshot/fix-1294917
Reviewer Review Type Date Requested Status
Cody Garver (community) Approve
Danielle Foré ux Approve
elementary Pantheon team code Pending
Review via email: mp+233304@code.launchpad.net

Commit message

adds a popover context menu used for showing quicklist items on right click (lp:1294917)

Description of the change

Adds a popover menu widget that avoids the focus issues of using a normal context menu. Used to show launcher quicklist items on right clicking an app.

To post a comment you must log in.
Revision history for this message
Cody Garver (codygarver) wrote :

Has merge conflicts

lp:~meese/slingshot/fix-1294917 updated
454. By meese

merge

Revision history for this message
meese (meese) wrote :

fixed

Revision history for this message
Danielle Foré (danrabbit) wrote :

Works completely as intended for me. Style issues were mine and have been fixed in trunk.

review: Approve (ux)
Revision history for this message
Cody Garver (codygarver) wrote :

All good aside from the background under the popover

review: Approve
lp:~meese/slingshot/fix-1294917 updated
455. By meese

remove shadow from popover menu css

Revision history for this message
Djax (parnold-x) wrote :

@meese
to fix the 1px shrink of the popover on hover over the longest entry you could solve it like this:

add @ PopOverMenu.vala:75
 button.enter_notify_event.connect ((evt) => {
            // fix width because there is a issue with resizing on hover
            button.width_request = button.get_allocated_width ();
            return false;
        });

Revision history for this message
Danielle Foré (danrabbit) wrote :

Djax, that shouldn't happen anymore. There was a theme bug that was causing it.

Revision history for this message
Cody Garver (codygarver) wrote :

Needs trunk merged in and conflicts resolved

lp:~meese/slingshot/fix-1294917 updated
456. By meese

merge from trunk

Revision history for this message
meese (meese) wrote :

done

Revision history for this message
Rico Tzschichholz (ricotz) wrote :

@messe: while copy&paste from plank, it would have been nice to mention this!
(Sharing code by linking against libplank is also an option if this is reasonable.)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'CMakeLists.txt'
--- CMakeLists.txt 2014-10-14 17:02:40 +0000
+++ CMakeLists.txt 2014-10-17 01:18:18 +0000
@@ -83,6 +83,7 @@
83 src/Widgets/SearchItem.vala83 src/Widgets/SearchItem.vala
84 src/Widgets/Sidebar.vala84 src/Widgets/Sidebar.vala
85 src/Widgets/CategoryView.vala85 src/Widgets/CategoryView.vala
86 src/Widgets/PopoverMenu.vala
86PACKAGES87PACKAGES
87 ${CORE_DEPS}88 ${CORE_DEPS}
88 ${UI_DEPS}89 ${UI_DEPS}
8990
=== modified file 'src/Backend/App.vala'
--- src/Backend/App.vala 2014-10-15 22:16:39 +0000
+++ src/Backend/App.vala 2014-10-17 01:18:18 +0000
@@ -47,7 +47,10 @@
4747
48 public Synapse.Match? match { get; private set; default = null; }48 public Synapse.Match? match { get; private set; default = null; }
49 public Synapse.Match? target { get; private set; default = null; }49 public Synapse.Match? target { get; private set; default = null; }
5050 public Gee.ArrayList<string> actions { get; private set; default = null; }
51 public Gee.HashMap<string, string> actions_map { get; private set; default = null; }
52 public Gdk.Pixbuf? quicklist_icon { get; private set; default = null; }
53
51 public signal void icon_changed ();54 public signal void icon_changed ();
52 public signal void launched (App app);55 public signal void launched (App app);
5356
@@ -57,6 +60,18 @@
5760
58 private LoadableIcon loadable_icon = null;61 private LoadableIcon loadable_icon = null;
5962
63 // for FDO Desktop Actions
64 // see http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#extra-actions
65 private const string DESKTOP_ACTION_KEY = "Actions";
66 private const string DESKTOP_ACTION_GROUP_NAME = "Desktop Action %s";
67 // for the Unity static quicklists
68 // see https://wiki.edubuntu.org/Unity/LauncherAPI#Static_Quicklist_entries
69 private const string UNITY_QUICKLISTS_KEY = "X-Ayatana-Desktop-Shortcuts";
70 private const string UNITY_QUICKLISTS_SHORTCUT_GROUP_NAME = "%s Shortcut Group";
71 private const string UNITY_QUICKLISTS_TARGET_KEY = "TargetEnvironment";
72 private const string UNITY_QUICKLISTS_TARGET_VALUE = "Unity";
73 private const string[] SUPPORTED_GETTEXT_DOMAINS_KEYS = {"X-Ubuntu-Gettext-Domain", "X-GNOME-Gettext-Domain"};
74
60 public App (GMenu.TreeEntry entry) {75 public App (GMenu.TreeEntry entry) {
61 app_type = AppType.APP;76 app_type = AppType.APP;
6277
@@ -280,4 +295,85 @@
280 return true;295 return true;
281 }296 }
282297
298 public void init_actions () throws KeyFileError {
299 actions = new Gee.ArrayList<string> ();
300 actions_map = new Gee.HashMap<string, string> ();
301 quicklist_icon = load_icon (16);
302
303 // get FDO Desktop Actions
304 // see http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#extra-actions
305 // get the Unity static quicklists
306 // see https://wiki.edubuntu.org/Unity/LauncherAPI#Static Quicklist entries
307 KeyFile file;
308 try {
309 file = new KeyFile ();
310 file.load_from_file (desktop_path, 0);
311 } catch (Error e) {
312 critical ("%s: %s", desktop_path, e.message);
313 }
314
315 string? textdomain = null;
316 foreach (var domain_key in SUPPORTED_GETTEXT_DOMAINS_KEYS)
317 if (file.has_key (KeyFileDesktop.GROUP, domain_key)) {
318 textdomain = file.get_string (KeyFileDesktop.GROUP, domain_key);
319 break;
320 }
321 if (actions != null && actions_map != null) {
322 actions.clear ();
323 actions_map.clear ();
324 string[] keys = {DESKTOP_ACTION_KEY, UNITY_QUICKLISTS_KEY};
325
326 foreach (var key in keys) {
327 if (!file.has_key (KeyFileDesktop.GROUP, key))
328 continue;
329
330 foreach (var action in file.get_string_list (KeyFileDesktop.GROUP, key)) {
331 var group = DESKTOP_ACTION_GROUP_NAME.printf (action);
332 if (!file.has_group (group)) {
333 group = UNITY_QUICKLISTS_SHORTCUT_GROUP_NAME.printf (action);
334 if (!file.has_group (group))
335 continue;
336 }
337
338 // check for TargetEnvironment
339 if (file.has_key (group, UNITY_QUICKLISTS_TARGET_KEY)) {
340 var target = file.get_string (group, UNITY_QUICKLISTS_TARGET_KEY);
341 if (target != UNITY_QUICKLISTS_TARGET_VALUE)
342 continue;
343 }
344
345 // check for OnlyShowIn
346 if (file.has_key (group, KeyFileDesktop.KEY_ONLY_SHOW_IN)) {
347 var found = false;
348
349 foreach (var s in file.get_string_list (group, KeyFileDesktop.KEY_ONLY_SHOW_IN))
350 if (s == UNITY_QUICKLISTS_TARGET_VALUE) {
351 found = true;
352 break;
353 }
354
355 if (!found)
356 continue;
357 }
358
359 var action_name = file.get_locale_string (group, KeyFileDesktop.KEY_NAME);
360
361 var action_icon = "";
362 if (file.has_key (group, KeyFileDesktop.KEY_ICON))
363 action_icon = file.get_locale_string (group, KeyFileDesktop.KEY_ICON);
364
365 var action_exec = "";
366 if (file.has_key (group, KeyFileDesktop.KEY_EXEC))
367 action_exec = file.get_string (group, KeyFileDesktop.KEY_EXEC);
368
369 // apply given gettext-domain if available
370 if (textdomain != null)
371 action_name = GLib.dgettext (textdomain, action_name).dup ();
372
373 actions.add (action_name);
374 actions_map.set (action_name, "%s;;%s".printf (action_exec, action_icon));
375 }
376 }
377 }
378 }
283}379}
284\ No newline at end of file380\ No newline at end of file
285381
=== modified file 'src/SlingshotView.vala' (properties changed: +x to -x)
--- src/SlingshotView.vala 2014-10-15 21:57:37 +0000
+++ src/SlingshotView.vala 2014-10-17 01:18:18 +0000
@@ -31,6 +31,8 @@
31 public Gtk.Stack stack;31 public Gtk.Stack stack;
32 public Granite.Widgets.ModeButton view_selector;32 public Granite.Widgets.ModeButton view_selector;
33 private Gtk.Revealer view_selector_revealer;33 private Gtk.Revealer view_selector_revealer;
34 // Single popover to use for all of app as context menu
35 private Widgets.NofocusPopover context_popover;
3436
35 // Views37 // Views
36 private Widgets.Grid grid_view;38 private Widgets.Grid grid_view;
@@ -97,8 +99,9 @@
9799
98 height_request = calculate_grid_height () + Pixels.BOTTOM_SPACE;100 height_request = calculate_grid_height () + Pixels.BOTTOM_SPACE;
99 setup_ui ();101 setup_ui ();
102 context_popover = new Widgets.NofocusPopover (this, event_box);
103
100 connect_signals ();104 connect_signals ();
101
102 debug ("Apps loaded");105 debug ("Apps loaded");
103 }106 }
104107
@@ -311,6 +314,7 @@
311314
312 categories = app_system.get_categories ();315 categories = app_system.get_categories ();
313 apps = app_system.get_apps ();316 apps = app_system.get_apps ();
317
314 populate_grid_view ();318 populate_grid_view ();
315 category_view.setup_sidebar ();319 category_view.setup_sidebar ();
316 });320 });
@@ -336,6 +340,19 @@
336 motion_notify_event.connect (hotcorner_trigger);340 motion_notify_event.connect (hotcorner_trigger);
337 }341 }
338342
343 public void show_popover_menu (Gtk.Widget menu, Gtk.Widget relative) {
344 context_popover = new Widgets.NofocusPopover (this, event_box);
345 context_popover.set_relative_to (relative);
346 context_popover.add (menu);
347 context_popover.show_all ();
348
349 var entry = relative as Widgets.AppEntry;
350 if (entry != null)
351 entry.app_launched.connect (() => {context_popover.hide ();});
352
353 relative.grab_focus ();
354 }
355
339 private void gala_settings_changed () {356 private void gala_settings_changed () {
340 if (Slingshot.settings.gala_settings.hotcorner_topleft == "open-launcher") {357 if (Slingshot.settings.gala_settings.hotcorner_topleft == "open-launcher") {
341 can_trigger_hotcorner = true;358 can_trigger_hotcorner = true;
@@ -650,7 +667,7 @@
650 }667 }
651668
652 public override bool scroll_event (Gdk.EventScroll event) {669 public override bool scroll_event (Gdk.EventScroll event) {
653670 context_popover.hide ();
654 switch (event.direction.to_string ()) {671 switch (event.direction.to_string ()) {
655 case "GDK_SCROLL_UP":672 case "GDK_SCROLL_UP":
656 case "GDK_SCROLL_LEFT":673 case "GDK_SCROLL_LEFT":
@@ -674,7 +691,7 @@
674 }691 }
675692
676 public void show_slingshot () {693 public void show_slingshot () {
677694 context_popover.hide ();
678 search_entry.text = "";695 search_entry.text = "";
679696
680 reposition ();697 reposition ();
@@ -768,7 +785,7 @@
768785
769 foreach (Backend.App app in app_system.get_apps_by_name ()) {786 foreach (Backend.App app in app_system.get_apps_by_name ()) {
770787
771 var app_entry = new Widgets.AppEntry (app);788 var app_entry = new Widgets.AppEntry (app, this);
772 app_entry.app_launched.connect (() => hide ());789 app_entry.app_launched.connect (() => hide ());
773 grid_view.append (app_entry);790 grid_view.append (app_entry);
774 app_entry.show_all ();791 app_entry.show_all ();
775792
=== modified file 'src/Widgets/AppEntry.vala' (properties changed: +x to -x)
--- src/Widgets/AppEntry.vala 2014-10-15 21:57:37 +0000
+++ src/Widgets/AppEntry.vala 2014-10-17 01:18:18 +0000
@@ -32,8 +32,10 @@
32 private bool dragging = false; //prevent launching32 private bool dragging = false; //prevent launching
3333
34 private Backend.App application;34 private Backend.App application;
35 private unowned SlingshotView view;
3536
36 public AppEntry (Backend.App app) {37 public AppEntry (Backend.App app, SlingshotView view) {
38 this.view = view;
37 Gtk.TargetEntry dnd = {"text/uri-list", 0, 0};39 Gtk.TargetEntry dnd = {"text/uri-list", 0, 0};
38 Gtk.drag_source_set (this, Gdk.ModifierType.BUTTON1_MASK, {dnd},40 Gtk.drag_source_set (this, Gdk.ModifierType.BUTTON1_MASK, {dnd},
39 Gdk.DragAction.COPY);41 Gdk.DragAction.COPY);
@@ -75,7 +77,13 @@
7577
76 this.clicked.connect (launch_app);78 this.clicked.connect (launch_app);
7779
78 this.button_press_event.connect ((e) => {return e.button == 3;});80 this.button_release_event.connect ((e) => {
81 if (e.button == Gdk.BUTTON_SECONDARY) {
82 show_menu ();
83 return true;
84 }
85 return false;
86 });
7987
80 this.drag_begin.connect ( (ctx) => {88 this.drag_begin.connect ( (ctx) => {
81 this.dragging = true;89 this.dragging = true;
@@ -111,4 +119,34 @@
111 application.launch ();119 application.launch ();
112 app_launched ();120 app_launched ();
113 }121 }
122
123 private void show_menu () {
124 // Display the apps static quicklist items in a popover menu
125 if (application.actions == null) {
126 try {
127 application.init_actions ();
128 } catch (KeyFileError e) {
129 critical ("%s: %s", desktop_path, e.message);
130 }
131 }
132
133 var menu = new PopoverMenu ();
134 foreach (var action in application.actions) {
135 var values = application.actions_map.get (action).split (";;");
136 var menuitem = new Widgets.PopoverMenuItem (action, application.quicklist_icon);
137 menu.add_menu_item (menuitem);
138 menuitem.activated.connect (() => {
139 try {
140 AppInfo.create_from_commandline (values[0], null, AppInfoCreateFlags.NONE).launch (null, null);
141 app_launched ();
142 } catch (Error e) {
143 critical ("%s: %s", desktop_path, e.message);
144 }
145 });
146 }
147
148 if (menu.get_size () > 0)
149 view.show_popover_menu (menu, this);
150 }
151
114}152}
115\ No newline at end of file153\ No newline at end of file
116154
=== modified file 'src/Widgets/CategoryView.vala'
--- src/Widgets/CategoryView.vala 2014-10-15 21:44:44 +0000
+++ src/Widgets/CategoryView.vala 2014-10-17 01:18:18 +0000
@@ -96,8 +96,7 @@
96 }96 }
9797
98 private void add_app (Backend.App app) {98 private void add_app (Backend.App app) {
9999 var app_entry = new AppEntry (app, view);
100 var app_entry = new AppEntry (app);
101 app_entry.app_launched.connect (() => view.hide ());100 app_entry.app_launched.connect (() => view.hide ());
102 app_view.append (app_entry);101 app_view.append (app_entry);
103 app_view.show_all ();102 app_view.show_all ();
104103
=== added file 'src/Widgets/PopoverMenu.vala'
--- src/Widgets/PopoverMenu.vala 1970-01-01 00:00:00 +0000
+++ src/Widgets/PopoverMenu.vala 2014-10-17 01:18:18 +0000
@@ -0,0 +1,169 @@
1// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*-
2//
3// Copyright (C) 2011-2012 Giulio Collura
4// Copyright (C) 2014 Maddie May <madelynn@madelynnmay.com>
5//
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10//
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15//
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <http://www.gnu.org/licenses/>.
18//
19
20// A menu to be used inside of popovers for when a normal context
21// menu can't be used
22namespace Slingshot.Widgets {
23
24public class PopoverMenu : Gtk.Grid {
25 private int line_count = 0;
26 private Gee.ArrayList<PopoverMenuItem> items = new Gee.ArrayList<PopoverMenuItem> ();
27
28 public PopoverMenu () {
29 row_homogeneous = true;
30 column_homogeneous = true;
31 }
32
33 public void add_menu_item (PopoverMenuItem item) {
34 line_count++;
35 this.items.add (item);
36 this.attach (item.child, 0, line_count, 1 , 1);
37 }
38
39 public int get_size () {
40 return items.size;
41 }
42}
43
44public class PopoverMenuItem : Object {
45 public Gtk.Widget child = null;
46 public signal void activated ();
47
48 public PopoverMenuItem (string label, Gdk.Pixbuf? icon) {
49 var button = new Gtk.Button ();
50 button.get_style_context ().add_class (Gtk.STYLE_CLASS_MENUITEM);
51
52 var grid = new Gtk.Grid ();
53 if (icon != null) {
54 var image = new Gtk.Image.from_pixbuf (icon);
55 image.margin_left = 2;
56 image.margin_right = 2;
57 image.halign = Gtk.Align.START;
58 grid.attach (image, 0, 0, 1, 1);
59 } else {
60 var space_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
61 space_box.set_size_request (16, 16);
62 grid.attach (space_box, 0, 0, 1, 1);
63 }
64
65 var label_widget = new Gtk.Label.with_mnemonic (label);
66 label_widget.margin_right = 16;
67 label_widget.justify = Gtk.Justification.LEFT;
68 label_widget.set_alignment (0, 0);
69 grid.attach (label_widget, 1, 0, 1, 1);
70
71 button.add (grid);
72 button.relief = Gtk.ReliefStyle.NONE;
73 button.clicked.connect (on_activate);
74 child = button;
75 }
76
77 private void on_activate () {
78 activated ();
79 }
80}
81
82// A popover that doesn't grab focus
83public class NofocusPopover : Gtk.Popover {
84 private Gtk.Container parent_container;
85 private unowned SlingshotView view;
86
87 private const string POPOVER_STYLESHEET = """
88 .popover,
89 .popover.osd,
90 GtkPopover {
91 border-radius: 0px;
92 margin: 0px;
93 text-shadow: none;
94 }
95 """;
96
97 public NofocusPopover (SlingshotView view, Gtk.Container parent) {
98 this.view = view;
99 this.parent_container = parent;
100 connect_popover_signals (parent);
101 modal = false;
102 parent.button_release_event.connect (hide_popover_menu);
103 parent.button_press_event.connect (hide_popover_menu);
104 get_style_context ().add_class (Gtk.STYLE_CLASS_MENU);
105 Granite.Widgets.Utils.set_theming_for_screen (get_screen (), POPOVER_STYLESHEET,
106 Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
107 }
108
109 public void connect_popover_signals (Gtk.Container parent) {
110 foreach (Gtk.Widget child in parent.get_children ()) {
111 if (child is Gtk.Container) {
112 Gtk.Container container = child as Gtk.Container;
113 connect_popover_signals (container);
114 }
115
116 if (child is Widgets.Switcher) {
117 var switcher = child as Widgets.Switcher;
118 switcher.on_stack_changed.connect (switched);
119 }
120
121 child.button_release_event.connect (hide_popover_menu);
122 child.button_press_event.connect (hide_popover_menu);
123 }
124 }
125
126 private void disconnect_popover_signals (Gtk.Container parent) {
127 foreach (Gtk.Widget child in parent.get_children ()) {
128 if (child is Gtk.Container) {
129 Gtk.Container container = child as Gtk.Container;
130 disconnect_popover_signals (container);
131 }
132
133 if (child is Widgets.Switcher) {
134 var switcher = child as Widgets.Switcher;
135 switcher.on_stack_changed.disconnect (switched);
136 }
137
138 child.button_release_event.disconnect (hide_popover_menu);
139 child.button_press_event.disconnect (hide_popover_menu);
140 }
141 }
142
143 public void switched () {
144 this.hide ();
145 }
146
147 public bool hide_popover_menu (Gdk.EventButton event) {
148 if (this.visible) {
149 view.set_focus (null);
150 view.search_entry.grab_focus ();
151
152 this.hide ();
153 // Block here to replicate context menu behavior
154 return true;
155 }
156 // Visible can be false but the popover still thinks it's up
157 // and will show when it's view is switched back to
158 // in that case don't block forwarding but make sure it is
159 // really hidden
160 this.hide ();
161 return false;
162 }
163
164 ~NofocusPopover () {
165 disconnect_popover_signals (parent_container);
166 }
167}
168
169} // End namespace
0\ No newline at end of file170\ No newline at end of file
1171
=== modified file 'src/Widgets/Switcher.vala'
--- src/Widgets/Switcher.vala 2014-05-26 06:26:17 +0000
+++ src/Widgets/Switcher.vala 2014-10-17 01:18:18 +0000
@@ -27,7 +27,8 @@
2727
28 private Gtk.Stack stack;28 private Gtk.Stack stack;
29 private Gee.HashMap<Gtk.Widget, Gtk.ToggleButton> buttons;29 private Gee.HashMap<Gtk.Widget, Gtk.ToggleButton> buttons;
3030 public signal void on_stack_changed ();
31
31 public Switcher () {32 public Switcher () {
32 orientation = Gtk.Orientation.HORIZONTAL;33 orientation = Gtk.Orientation.HORIZONTAL;
33 spacing = 2;34 spacing = 2;
@@ -86,6 +87,7 @@
8687
87 private void on_button_clicked (Gtk.Widget widget) {88 private void on_button_clicked (Gtk.Widget widget) {
88 stack.set_visible_child (widget);89 stack.set_visible_child (widget);
90 on_stack_changed ();
89 }91 }
9092
91 private void populate_switcher () {93 private void populate_switcher () {

Subscribers

People subscribed via source and target branches