2121public class PantheonShell.WallpaperContainer : Granite .Bin {
2222 public signal void trash ();
2323
24+ // https://www.w3.org/WAI/WCAG21/Understanding/target-size.html
25+ private const int TOUCH_TARGET_WIDTH = 44 ;
26+
2427 protected const int THUMB_WIDTH = 256 ;
2528 protected const int THUMB_HEIGHT = 144 ;
2629 protected Gtk . Picture image;
2730
31+ private GLib . Menu menu_model;
32+ private GLib . SimpleAction remove_wallpaper_action;
2833 private Gtk . Revealer check_revealer;
2934
35+ private Gtk . EventControllerKey menu_key_controller;
36+ private Gtk . GestureClick ? click_controller;
37+ private Gtk . GestureLongPress ? long_press_controller;
38+ private Gtk . PopoverMenu ? context_menu;
3039 private string ? thumb_path = null ;
3140
32- public string ? uri { get ; construct ; default = null ; }
41+ public string ? uri { get ; set ; default = null ; }
3342 public uint64 creation_date = 0 ;
3443
3544 public bool checked {
@@ -48,10 +57,6 @@ public class PantheonShell.WallpaperContainer : Granite.Bin {
4857 }
4958 }
5059
51- public WallpaperContainer (string uri ) {
52- Object (uri: uri);
53- }
54-
5560 construct {
5661 image = new Gtk .Picture () {
5762 content_fit = COVER ,
@@ -80,58 +85,136 @@ public class PantheonShell.WallpaperContainer : Granite.Bin {
8085 halign = CENTER ;
8186 valign = CENTER ;
8287
88+ // So we can receive key events
89+ focusable = true ;
8390 child = overlay;
8491
85- if (uri != null ) {
86- var remove_wallpaper_action = new SimpleAction (" trash" , null );
87- remove_wallpaper_action. activate. connect (() = > trash ());
92+ remove_wallpaper_action = new SimpleAction (" trash" , null );
93+ remove_wallpaper_action. activate. connect (() = > trash ());
8894
89- var action_group = new SimpleActionGroup ();
90- action_group. add_action (remove_wallpaper_action);
95+ var action_group = new SimpleActionGroup ();
96+ action_group. add_action (remove_wallpaper_action);
9197
92- insert_action_group (" wallpaper" , action_group);
98+ insert_action_group (" wallpaper" , action_group);
9399
94- var file = File . new_for_uri (uri);
95- try {
96- var info = file. query_info (" *" , FileQueryInfoFlags . NONE );
100+ menu_model = new Menu ();
101+ menu_model. append (_(" Remove" ), " wallpaper.trash" );
97102
98- thumb_path = info. get_attribute_as_string (FileAttribute . THUMBNAIL_PATH );
103+ notify[" uri" ]. connect (construct_from_uri);
104+ }
99105
100- if (thumb_path != null && info . get_attribute_boolean ( FileAttribute . THUMBNAIL_IS_VALID ) ) {
101- update_thumb . begin ();
102- } else {
103- generate_and_load_thumb ( );
104- }
106+ private void construct_from_uri ( ) {
107+ if (uri == null ) {
108+ remove_controller (click_controller);
109+ remove_controller (long_press_controller );
110+ remove_controller (menu_key_controller);
105111
106- creation_date = info. get_attribute_uint64 (GLib . FileAttribute . TIME_CREATED );
107- remove_wallpaper_action. set_enabled (info. get_attribute_boolean (GLib . FileAttribute . ACCESS_CAN_DELETE ));
108- } catch (Error e) {
109- critical (e. message);
112+ click_controller = null ;
113+ long_press_controller = null ;
114+ menu_key_controller = null ;
115+
116+ context_menu. unparent ();
117+ context_menu = null ;
118+
119+ return ;
120+ }
121+
122+ var file = File . new_for_uri (uri);
123+ try {
124+ var info = file. query_info (" *" , FileQueryInfoFlags . NONE );
125+
126+ thumb_path = info. get_attribute_as_string (FileAttribute . THUMBNAIL_PATH );
127+
128+ if (thumb_path != null && info. get_attribute_boolean (FileAttribute . THUMBNAIL_IS_VALID )) {
129+ update_thumb. begin ();
130+ } else {
131+ generate_and_load_thumb ();
110132 }
111133
112- var menu_model = new Menu ();
113- menu_model. append (_(" Remove" ), " wallpaper.trash" );
114-
115- var context_menu = new Gtk .PopoverMenu .from_model (menu_model) {
116- halign = START ,
117- has_arrow = false
118- };
119- context_menu. set_parent (this );
120-
121- var secondary_click_gesture = new Gtk .GestureClick () {
122- button = Gdk . BUTTON_SECONDARY
123- };
124- secondary_click_gesture. released. connect ((n_press, x, y) = > {
125- secondary_click_gesture. set_state (CLAIMED );
126- context_menu. pointing_to = Gdk . Rectangle () {
127- x = (int ) x,
128- y = (int ) y
129- };
130- context_menu. popup ();
131- });
132-
133- add_controller (secondary_click_gesture);
134+ creation_date = info. get_attribute_uint64 (GLib . FileAttribute . TIME_CREATED );
135+ remove_wallpaper_action. set_enabled (info. get_attribute_boolean (GLib . FileAttribute . ACCESS_CAN_DELETE ));
136+ } catch (Error e) {
137+ critical (e. message);
134138 }
139+
140+ context_menu = new Gtk .PopoverMenu .from_model (menu_model) {
141+ halign = START ,
142+ has_arrow = false
143+ };
144+ context_menu. set_parent (this );
145+
146+ click_controller = new Gtk .GestureClick () {
147+ button = 0 ,
148+ exclusive = true
149+ };
150+ click_controller. pressed. connect ((n_press, x, y) = > {
151+ var sequence = click_controller. get_current_sequence ();
152+ var event = click_controller. get_last_event (sequence);
153+
154+ if (event. triggers_context_menu ()) {
155+ context_menu. halign = START ;
156+ menu_popup_at_pointer (context_menu, x, y);
157+
158+ click_controller. set_state (CLAIMED );
159+ click_controller. reset ();
160+ }
161+ });
162+
163+ long_press_controller = new Gtk .GestureLongPress () {
164+ touch_only = true
165+ };
166+ long_press_controller. pressed. connect ((x, y) = > {
167+ // Try to keep menu from under your hand
168+ if (x > get_root (). get_width () / 2 ) {
169+ context_menu. halign = END ;
170+ x - = TOUCH_TARGET_WIDTH ;
171+ } else {
172+ context_menu. halign = START ;
173+ x + = TOUCH_TARGET_WIDTH ;
174+ }
175+
176+ menu_popup_at_pointer (context_menu, x, y - (TOUCH_TARGET_WIDTH * 0.75 ));
177+ });
178+
179+ menu_key_controller = new Gtk .EventControllerKey ();
180+ menu_key_controller. key_released. connect ((keyval, keycode, state) = > {
181+ var mods = state & Gtk . accelerator_get_default_mod_mask ();
182+ switch (keyval) {
183+ case Gdk . Key . F10:
184+ if (mods == Gdk . ModifierType . SHIFT_MASK ) {
185+ menu_popup_on_keypress (context_menu);
186+ }
187+ break ;
188+ case Gdk . Key . Menu :
189+ case Gdk . Key . MenuKB :
190+ menu_popup_on_keypress (context_menu);
191+ break ;
192+ default:
193+ return ;
194+ }
195+ });
196+
197+ add_controller (click_controller);
198+ add_controller (long_press_controller);
199+ add_controller (menu_key_controller);
200+ }
201+
202+ private void menu_popup_on_keypress (Gtk .PopoverMenu popover ) {
203+ popover. halign = END ;
204+ popover. set_pointing_to (Gdk . Rectangle () {
205+ x = (int ) get_width (),
206+ y = (int ) get_height () / 2
207+ });
208+ popover. popup ();
209+ }
210+
211+ private void menu_popup_at_pointer (Gtk .PopoverMenu popover , double x , double y ) {
212+ var rect = Gdk . Rectangle () {
213+ x = (int ) x,
214+ y = (int ) y
215+ };
216+ popover. pointing_to = rect;
217+ popover. popup ();
135218 }
136219
137220 private void generate_and_load_thumb () {
0 commit comments