Merge lp:~artem-anufrij/audience/library-context-menu into lp:~audience-members/audience/trunk

Proposed by Artem Anufrij
Status: Merged
Approved by: Danielle Foré
Approved revision: 679
Merged at revision: 666
Proposed branch: lp:~artem-anufrij/audience/library-context-menu
Merge into: lp:~audience-members/audience/trunk
Diff against target: 623 lines (+254/-90)
5 files modified
src/Objects/Video.vala (+51/-30)
src/Services/LibraryManager.vala (+54/-36)
src/Widgets/LibraryItem.vala (+72/-12)
src/Widgets/LibraryPage.vala (+19/-6)
src/Window.vala (+58/-6)
To merge this branch: bzr merge lp:~artem-anufrij/audience/library-context-menu
Reviewer Review Type Date Requested Status
Danielle Foré ux Approve
Review via email: mp+306675@code.launchpad.net

Commit message

Add right click menu to set artwork and move library items to trash

To post a comment you must log in.
Revision history for this message
Danielle Foré (danrabbit) wrote :

Menu items are Title Case ;)

"Rename video title" is pretty excessive. In Files we just use "Rename"

review: Needs Fixing
663. By Artem Anufrij

renamed context menu items

664. By Artem Anufrij

renamed context menu items

665. By Artem Anufrij

left click fixed

666. By Artem Anufrij

removed 'restore artwork'

667. By Artem Anufrij

context menu: renaming and setting artwork

668. By Artem Anufrij

added menu item 'move to trash

669. By Artem Anufrij

added menu item 'move to trash

670. By Artem Anufrij

added a separator

671. By Artem Anufrij

improved renaming videos

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

For the rename stack, you should probably use a crossfade instead of a slide since we're changing the item state and not really navigating or moving a thing.

I think "Move to Trash" needs to come with an undo pattern. Specifically both ctrl + z and I would like to make use of undo via in-app notifications like in Online Accounts.

In this latest revision, I seem to be missing the "Set Artwork" item. Is that intentional?

Revision history for this message
Artem Anufrij (artem-anufrij) wrote :

"Set Artwork" is hidden for videos with a cover in same folder:
videos/myvideo.mpg
videos/cover.jpg

Artworks can be set only for videos without a cover file.

what do you think about that?

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

I think that ideally people don't have to open the file manager to change the artwork. The library view should be self sufficient.

672. By Artem Anufrij

ctrl+z for undo trashed files & set artwork for all items

673. By Artem Anufrij

improved thumbnails

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

* I can't confirm that Set Artwork works anymore

* Having a separator between rename and set artwork seems excessive since both of these items are about editing metadata

* Just making a note about using in-app notifications for undo like we talked in Slack

* If I move both items to trash, I think it should go back to the welcome screen instead of showing an empty Library

* Changing the name seems to change the artwork. I think that's probably not desired

674. By Artem Anufrij

added a notification box

675. By Artem Anufrij

back to welcomescreen after deleting last item

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

* I think when you've removed all items it should return to the welcome immediately and not wait until after you've clicked the close button. See online accounts. I think this makes it faster to get to the next action

* It looks like there's an issue where if you remove everything, go back to the welcome, then restore those items in files, then go back to the library, you'll get an empty app notification (just shows as a thin white bar)

676. By Artem Anufrij

removed 'rename' function; fixed 'set artwork'

677. By Artem Anufrij

moved notification box into main window

678. By Artem Anufrij

grab_focus for scrolled window

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

Hey right on. This seems to work fully as expected to me :)

review: Approve (ux)
679. By Artem Anufrij

fixed tiny line

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/Objects/Video.vala'
2--- src/Objects/Video.vala 2016-09-22 15:39:32 +0000
3+++ src/Objects/Video.vala 2016-09-25 00:39:06 +0000
4@@ -24,13 +24,14 @@
5
6 public signal void poster_changed ();
7 public signal void title_changed ();
8+ public signal void trashed (Video video);
9
10 public File video_file { get; private set; }
11 public string directory { get; construct set; }
12 public string file { get; construct set; }
13
14 public string title { get; private set; }
15- public int year { get; private set; }
16+ public int year { get; private set; default = -1;}
17
18 public Gdk.Pixbuf? poster { get; private set; }
19
20@@ -38,6 +39,7 @@
21 public string poster_cache_file { get; private set; }
22
23 public string hash { get; construct set; }
24+ public string thumbnail_large_path { get; construct set;}
25
26 public Video (string directory, string file, string mime_type) {
27 Object (directory: directory, file: file, mime_type: mime_type);
28@@ -52,8 +54,9 @@
29 this.extract_metadata ();
30 video_file = File.new_for_path (this.get_path ());
31
32- hash = GLib.Checksum.compute_for_string (ChecksumType.MD5, this.get_path (), this.get_path ().length);
33+ hash = GLib.Checksum.compute_for_string (ChecksumType.MD5, video_file.get_uri (), video_file.get_uri ().length);
34
35+ thumbnail_large_path = Path.build_filename (GLib.Environment.get_user_cache_dir (),"thumbnails", "large", hash + ".png");
36 poster_cache_file = Path.build_filename (App.get_instance ().get_cache_directory (), hash + ".jpg");
37
38 notify["poster"].connect (() => {
39@@ -69,7 +72,7 @@
40 MatchInfo info;
41 if (manager.regex_year.match (this.title, 0, out info)) {
42 this.year = int.parse (info.fetch (0).substring (1, 4));
43- this.title = this.title.replace (info.fetch (0) + ")", "");
44+ this.title = this.title.replace (info.fetch (0) + ")", "").strip ();
45 }
46 }
47
48@@ -85,7 +88,7 @@
49
50 ThreadFunc<void*> run = () => {
51
52- string poster_path = poster_cache_file;
53+ string? poster_path = poster_cache_file;
54 pixbuf = get_poster_from_file (poster_path);
55
56 // POSTER in Cache exists
57@@ -94,24 +97,9 @@
58 return null;
59 }
60
61- // Try to find a POSTER in same folder of video file
62- if (pixbuf == null) {
63- poster_path = this.get_path () + ".jpg";
64- pixbuf = get_poster_from_file (poster_path);
65- }
66-
67- if (pixbuf == null) {
68- poster_path = Path.build_filename (this.directory, Audience.get_title (file) + ".jpg");
69- pixbuf = get_poster_from_file (poster_path);
70- }
71-
72- foreach (string s in Audience.settings.poster_names) {
73- if (pixbuf == null) {
74- poster_path = Path.build_filename (this.directory, s);
75- pixbuf = get_poster_from_file (poster_path);
76- } else {
77- break;
78- }
79+ poster_path = get_native_poster_path ();
80+ if (poster_path != null) {
81+ pixbuf = get_poster_from_file (poster_path);
82 }
83
84 // POSTER found
85@@ -126,9 +114,8 @@
86 }
87
88 // Check if THUMBNAIL exists
89- string? thumbnail_path = manager.get_thumbnail_path (video_file);
90- if (thumbnail_path != null) {
91- pixbuf = get_poster_from_file (thumbnail_path);
92+ if (File.new_for_path (thumbnail_large_path).query_exists ()) {
93+ pixbuf = get_poster_from_file (thumbnail_large_path);
94 Idle.add ((owned) callback);
95 return null;
96 }
97@@ -158,11 +145,8 @@
98 }
99
100 private void dbus_finished (uint heandle) {
101- if (poster == null) {
102- string? thumbnail_path = manager.get_thumbnail_path (video_file);
103- if (thumbnail_path != null) {
104- poster = get_poster_from_file (thumbnail_path);
105- }
106+ if (poster == null && File.new_for_path (thumbnail_large_path).query_exists ()) {
107+ poster = get_poster_from_file (thumbnail_large_path);
108 }
109 }
110
111@@ -192,5 +176,42 @@
112
113 return pixbuf;
114 }
115+
116+ public string? get_native_poster_path () {
117+ string poster_path = this.get_path () + ".jpg";
118+ File file_poster = File.new_for_path (poster_path);
119+
120+ if (file_poster.query_exists ())
121+ return poster_path;
122+
123+ poster_path = Path.build_filename (this.directory, Audience.get_title (file) + ".jpg");
124+ file_poster = File.new_for_path (poster_path);
125+
126+ if (file_poster.query_exists ())
127+ return poster_path;
128+
129+ foreach (string s in Audience.settings.poster_names) {
130+ poster_path = Path.build_filename (this.directory, s);
131+ file_poster = File.new_for_path (poster_path);
132+ if (file_poster.query_exists ())
133+ return poster_path;
134+ }
135+
136+ return null;
137+ }
138+
139+ public void set_new_poster (Gdk.Pixbuf? new_poster) {
140+ manager.clear_cache (this);
141+ poster = new_poster;
142+ }
143+
144+ public void trash () {
145+ try {
146+ video_file.trash ();
147+ trashed (this);
148+ } catch (Error e) {
149+ warning (e.message);
150+ }
151+ }
152 }
153 }
154
155=== modified file 'src/Services/LibraryManager.vala'
156--- src/Services/LibraryManager.vala 2016-09-22 15:22:27 +0000
157+++ src/Services/LibraryManager.vala 2016-09-25 00:39:06 +0000
158@@ -27,6 +27,7 @@
159
160 public signal void video_file_detected (Audience.Objects.Video video);
161 public signal void video_file_deleted (string path);
162+ public signal void video_moved_to_trash (Audience.Objects.Video video);
163 public signal void finished ();
164
165 public Regex regex_year { get; construct set; }
166@@ -37,6 +38,8 @@
167 private Gee.ArrayList<string> poster_hash;
168 private Gee.ArrayList<FileMonitor> monitoring_directories;
169
170+ private Gee.ArrayList<Audience.Objects.Video> trashed_files;
171+
172 public static LibraryManager instance = null;
173 public static LibraryManager get_instance () {
174 if (instance == null) {
175@@ -50,6 +53,7 @@
176 }
177
178 construct {
179+ trashed_files = new Gee.ArrayList<Audience.Objects.Video> ();
180 poster_hash = new Gee.ArrayList<string> ();
181 monitoring_directories = new Gee.ArrayList<FileMonitor> ();
182 try {
183@@ -123,38 +127,11 @@
184 }
185 var video = new Audience.Objects.Video (source, name, file_info.get_content_type ());
186 video_file_detected (video);
187+ video.trashed.connect (deleted_items);
188 poster_hash.add (video.hash + ".jpg");
189 has_items = true;
190 }
191
192- public string? get_thumbnail_path (File file) {
193- if (!file.is_native ()) {
194- return null;
195- }
196- string? path = null;
197- try {
198- var info = file.query_info (FileAttribute.THUMBNAIL_PATH + "," + FileAttribute.THUMBNAILING_FAILED, FileQueryInfoFlags.NONE);
199- path = info.get_attribute_as_string (FileAttribute.THUMBNAIL_PATH);
200- var failed = info.get_attribute_boolean (FileAttribute.THUMBNAILING_FAILED);
201-
202- if (failed || path == null) {
203- return null;
204- }
205-
206- path = path.replace ("normal", "large");
207-
208- File large_thumbnail = File.new_for_path (path);
209- if (!large_thumbnail.query_exists ()) {
210- return null;
211- }
212- } catch (Error e) {
213- warning (e.message);
214- return null;
215- }
216-
217- return path;
218- }
219-
220 public void clear_cache (Audience.Objects.Video video) {
221 File file = File.new_for_path (video.poster_cache_file);
222 if (file.query_exists ()) {
223@@ -163,20 +140,61 @@
224 }
225
226 public async void clear_unused_cache_files () {
227- File directory = File.new_for_path (App.get_instance ().get_cache_directory ());
228+ string[] hash_items = poster_hash.to_array ();
229+ ThreadFunc<void*> run = () => {
230+
231+ File directory = File.new_for_path (App.get_instance ().get_cache_directory ());
232+ try {
233+ var children = directory.enumerate_children (FileAttribute.STANDARD_NAME, 0);
234+ if (children != null) {
235+ FileInfo file_info;
236+ while ((file_info = children.next_file ()) != null) {
237+ foreach (unowned string hash_item in hash_items) {
238+ if (hash_item == file_info.get_name ()) {
239+ continue;
240+ }
241+ children.get_child (file_info).delete_async.begin ();
242+ }
243+ }
244+ }
245+ } catch (Error e) {
246+ warning (e.message);
247+ }
248+
249+ return null;
250+ };
251+
252 try {
253- var children = directory.enumerate_children (FileAttribute.STANDARD_NAME, 0);
254-
255- if (children != null) {
256+ new Thread<void*>.try (null, run);
257+ } catch (Error e) {
258+ error (e.message);
259+ }
260+ }
261+
262+ private void deleted_items (Audience.Objects.Video video) {
263+ trashed_files.add (video);
264+ video_moved_to_trash (video);
265+ }
266+
267+ public void undo_delete_item () {
268+ if (trashed_files.size > 0) {
269+ Audience.Objects.Video restore = trashed_files.last ();
270+ File trash = File.new_for_uri ("trash:///");
271+ try {
272+ var children = trash.enumerate_children (FileAttribute.TRASH_ORIG_PATH, 0);
273 FileInfo file_info;
274 while ((file_info = children.next_file ()) != null) {
275- if (!poster_hash.contains (file_info.get_name ())) {
276- children.get_child (file_info).delete_async.begin ();
277+ string orinal_path = file_info.get_attribute_as_string (FileAttribute.TRASH_ORIG_PATH);
278+ if (orinal_path == restore.video_file.get_path ()) {
279+ File restore_file = File.new_for_uri ("trash:///" + restore.video_file.get_basename ());
280+ restore_file.move (restore.video_file, 0);
281+ trashed_files.remove (restore);
282+ return;
283 }
284 }
285+ } catch (Error e) {
286+ error (e.message);
287 }
288- } catch (Error e) {
289- warning (e.message);
290 }
291 }
292 }
293
294=== modified file 'src/Widgets/LibraryItem.vala'
295--- src/Widgets/LibraryItem.vala 2016-09-21 17:12:09 +0000
296+++ src/Widgets/LibraryItem.vala 2016-09-25 00:39:06 +0000
297@@ -22,14 +22,21 @@
298 namespace Audience {
299 public class LibraryItem : Gtk.FlowBoxChild {
300
301+ Gtk.EventBox event_box;
302 Gtk.Grid grid;
303 public Audience.Objects.Video video { get; construct set; }
304
305 Gtk.Image poster;
306- Gtk.Label title;
307+
308+ Gtk.Label title_label;
309+
310 Gtk.Spinner spinner;
311 Gtk.Grid spinner_container;
312
313+ Gtk.Menu context_menu;
314+ Gtk.MenuItem new_cover;
315+ Gtk.MenuItem move_to_trash;
316+
317 public LibraryItem (Audience.Objects.Video video) {
318 Object (video: video);
319 }
320@@ -60,8 +67,8 @@
321 });
322
323 video.title_changed.connect (() => {
324- title.label = video.title;
325- title.show ();
326+ title_label.label = video.title;
327+ title_label.show ();
328 });
329
330 spinner_container = new Gtk.Grid ();
331@@ -69,7 +76,7 @@
332 spinner_container.width_request = Audience.Services.POSTER_WIDTH;
333 spinner_container.margin_top = spinner_container.margin_left = spinner_container.margin_right = 12;
334 spinner_container.get_style_context ().add_class ("card");
335-
336+
337 spinner = new Gtk.Spinner ();
338 spinner.expand = true;
339 spinner.active = true;
340@@ -85,16 +92,69 @@
341 grid.valign = Gtk.Align.START;
342 grid.row_spacing = 12;
343
344- title = new Gtk.Label (video.title);
345- title.justify = Gtk.Justification.CENTER;
346- title.set_line_wrap (true);
347- title.max_width_chars = 0;
348-
349+ title_label = new Gtk.Label (video.title);
350+ title_label.justify = Gtk.Justification.CENTER;
351+ title_label.set_line_wrap (true);
352+ title_label.max_width_chars = 0;
353
354 grid.attach (spinner_container, 0, 0, 1, 1);
355- grid.attach (title, 0, 1, 1 ,1);
356-
357- this.add (grid);
358+ grid.attach (title_label, 0, 1, 1 ,1);
359+
360+ context_menu = new Gtk.Menu ();
361+ new_cover = new Gtk.MenuItem.with_label (_("Set Artwork"));
362+ new_cover.activate.connect ( set_new_cover );
363+ move_to_trash = new Gtk.MenuItem.with_label (_("Move to Trash"));
364+ move_to_trash.activate.connect ( move_video_to_trash );
365+
366+ context_menu.append (new_cover);
367+ context_menu.append (new Gtk.SeparatorMenuItem ());
368+ context_menu.append (move_to_trash);
369+ context_menu.show_all ();
370+
371+ event_box = new Gtk.EventBox ();
372+ event_box.button_press_event.connect (show_context_menu);
373+ event_box.add (grid);
374+
375+ this.add (event_box);
376+ }
377+
378+ private bool show_context_menu (Gtk.Widget sender, Gdk.EventButton evt) {
379+ if (evt.type == Gdk.EventType.BUTTON_PRESS && evt.button == 3) {
380+ context_menu.popup (null, null, null, evt.button, evt.time);
381+ return true;
382+ }
383+
384+ return false;
385+ }
386+
387+ private void set_new_cover () {
388+ var file = new Gtk.FileChooserDialog (_("Open"), Audience.App.get_instance ().mainwindow, Gtk.FileChooserAction.OPEN,
389+ _("_Cancel"), Gtk.ResponseType.CANCEL, _("_Open"), Gtk.ResponseType.ACCEPT);
390+
391+ var image_filter = new Gtk.FileFilter ();
392+ image_filter.set_filter_name (_("Image files"));
393+ image_filter.add_mime_type ("image/*");
394+
395+ file.add_filter (image_filter);
396+
397+ if (file.run () == Gtk.ResponseType.ACCEPT) {
398+ Gdk.Pixbuf? pixbuf = video.get_poster_from_file (file.get_file ().get_path ());
399+ if (pixbuf != null) {
400+ try {
401+ pixbuf.save (video.video_file.get_path() + ".jpg", "jpeg");
402+ video.set_new_poster (pixbuf);
403+ } catch (Error e) {
404+ warning (e.message);
405+ }
406+ video.initialize_poster.begin ();
407+ }
408+ }
409+
410+ file.destroy ();
411+ }
412+
413+ private void move_video_to_trash () {
414+ video.trash ();
415 }
416 }
417 }
418
419=== modified file 'src/Widgets/LibraryPage.vala'
420--- src/Widgets/LibraryPage.vala 2016-09-23 19:54:34 +0000
421+++ src/Widgets/LibraryPage.vala 2016-09-25 00:39:06 +0000
422@@ -20,13 +20,13 @@
423 */
424
425 namespace Audience {
426- public class LibraryPage : Gtk.ScrolledWindow {
427+ public class LibraryPage : Gtk.Grid {
428
429 public signal void filter_result_changed (bool has_results);
430
431 public Gtk.FlowBox view_movies;
432- Audience.Services.LibraryManager manager;
433-
434+ public Audience.Services.LibraryManager manager;
435+ public Gtk.ScrolledWindow scrolled_window;
436 bool poster_initialized = false;
437 int items_counter;
438 string query;
439@@ -45,6 +45,9 @@
440 query = "";
441 items_counter = 0;
442
443+ scrolled_window = new Gtk.ScrolledWindow (null, null);
444+ scrolled_window.expand = true;
445+
446 view_movies = new Gtk.FlowBox ();
447 view_movies.margin = 24;
448 view_movies.homogeneous = true;
449@@ -54,9 +57,15 @@
450 view_movies.selection_mode = Gtk.SelectionMode.NONE;
451 view_movies.child_activated.connect (play_video);
452
453+ scrolled_window.add (view_movies);
454+
455 manager = Audience.Services.LibraryManager.get_instance ();
456 manager.video_file_detected.connect (add_item);
457 manager.video_file_deleted.connect (remove_item_from_path);
458+ manager.video_moved_to_trash.connect ((video) => {
459+ Audience.App.get_instance ().mainwindow.set_app_notification (_("Video '%s' Removed.").printf (video.title));
460+ });
461+
462 manager.begin_scan ();
463
464 map.connect (() => {
465@@ -70,7 +79,7 @@
466 view_movies.set_sort_func (video_sort_func);
467 view_movies.set_filter_func (video_filter_func);
468
469- add (view_movies);
470+ add (scrolled_window);
471 }
472
473 private void add_item (Audience.Objects.Video video) {
474@@ -105,6 +114,10 @@
475 remove_item.begin (child as LibraryItem);
476 }
477 }
478+
479+ if (!has_child ()) {
480+ Audience.App.get_instance ().mainwindow.navigate_back ();
481+ }
482 }
483
484 private async void poster_initialisation () {
485@@ -143,9 +156,9 @@
486 view_movies.invalidate_filter ();
487 filter_result_changed (has_child ());
488 }
489-
490+
491 public bool has_child () {
492- if (view_movies.get_child_at_index (0) != null) {
493+ if (view_movies.get_children ().length () > 0) {
494 foreach (unowned Gtk.Widget child in view_movies.get_children ()) {
495 if (child.get_child_visible ()) {
496 return true;
497
498=== modified file 'src/Window.vala'
499--- src/Window.vala 2016-09-23 20:27:50 +0000
500+++ src/Window.vala 2016-09-25 00:39:06 +0000
501@@ -31,6 +31,8 @@
502 private NavigationButton navigation_button;
503 private ZeitgeistManager zeitgeist_manager;
504 private Gtk.SearchEntry search_entry;
505+ private Gtk.Revealer app_notification;
506+ private Gtk.Label notification_label;
507
508 // For better translation
509 const string navigation_button_welcomescreen = N_("Back");
510@@ -98,6 +100,14 @@
511 player_page.notify["playing"].connect (() => {
512 set_keep_above (player_page.playing && settings.stay_on_top);
513 });
514+
515+ player_page.map.connect (() => {
516+ app_notification.visible = false;
517+ });
518+
519+ player_page.unmap.connect (() => {
520+ app_notification.visible = true;
521+ });
522
523 alert_view = new Granite.Widgets.AlertView ("", "", "");
524 alert_view.get_style_context ().add_class (Gtk.STYLE_CLASS_DIM_LABEL);
525@@ -112,7 +122,41 @@
526 main_stack.add_named (alert_view, "alert");
527 main_stack.transition_type = Gtk.StackTransitionType.SLIDE_LEFT_RIGHT;
528
529- add (main_stack);
530+ notification_label = new Gtk.Label ("");
531+ var restore_button = new Gtk.Button.with_label (_("Restore"));
532+ restore_button.clicked.connect (() => {
533+ library_page.manager.undo_delete_item ();
534+ app_notification.reveal_child = false;
535+ main_stack.set_visible_child (library_page);
536+ });
537+
538+ var close_button = new Gtk.Button.from_icon_name ("close-symbolic", Gtk.IconSize.MENU);
539+ close_button.get_style_context ().add_class ("close-button");
540+ close_button.clicked.connect (() => {
541+ app_notification.reveal_child = false;
542+ });
543+
544+ var notification_box = new Gtk.Grid ();
545+ notification_box.column_spacing = 12;
546+ notification_box.add (close_button);
547+ notification_box.add (notification_label);
548+ notification_box.add (restore_button);
549+
550+ var notification_frame = new Gtk.Frame (null);
551+ notification_frame.get_style_context ().add_class ("app-notification");
552+ notification_frame.add (notification_box);
553+
554+ app_notification = new Gtk.Revealer ();
555+ app_notification.margin = 3;
556+ app_notification.halign = Gtk.Align.CENTER;
557+ app_notification.valign = Gtk.Align.START;
558+ app_notification.add (notification_frame);
559+
560+ var overlay = new Gtk.Overlay ();
561+ overlay.add_overlay (main_stack);
562+ overlay.add_overlay (app_notification);
563+
564+ add (overlay);
565 show_all ();
566
567 navigation_button.hide ();
568@@ -145,7 +189,8 @@
569 if (event.button == Gdk.BUTTON_SECONDARY) {
570 player_page.playing = !player_page.playing;
571 }
572- return false;
573+
574+ return base.button_press_event(event);
575 });
576
577 window_state_event.connect ((e) => {
578@@ -169,7 +214,7 @@
579
580 /** Returns true if the code parameter matches the keycode of the keyval parameter for
581 * any keyboard group or level (in order to allow for non-QWERTY keyboards) **/
582- protected bool match_keycode (int keyval, uint code) {
583+ public bool match_keycode (int keyval, uint code) {
584 Gdk.KeymapKey [] keys;
585 Gdk.Keymap keymap = Gdk.Keymap.get_default ();
586 if (keymap.get_entries_for_keyval (keyval, out keys)) {
587@@ -275,13 +320,15 @@
588 show_library ();
589 return true;
590 }
591- } else if (main_stack.get_visible_child () == library_page && !search_entry.is_focus && e.str.strip ().length > 0) {
592- search_entry.grab_focus ();
593 } else if (search_entry.visible) {
594 if (ctrl_pressed && match_keycode (Gdk.Key.f, keycode)) {
595 search_entry.grab_focus ();
596+ } else if (ctrl_pressed && match_keycode (Gdk.Key.z, keycode)) {
597+ library_page.manager.undo_delete_item ();
598 } else if (match_keycode (Gdk.Key.Escape, keycode)) {
599 search_entry.text = "";
600+ } else if (!search_entry.is_focus && e.str.strip ().length > 0) {
601+ search_entry.grab_focus ();
602 }
603 }
604
605@@ -331,7 +378,7 @@
606 navigation_button.label = navigation_button_welcomescreen;
607 navigation_button.show ();
608 main_stack.set_visible_child (library_page);
609- library_page.grab_focus ();
610+ library_page.scrolled_window.grab_focus ();
611 }
612
613 public void run_open_file (bool clear_playlist = false, bool force_play = true) {
614@@ -457,4 +504,9 @@
615 alert_view.icon_name = icon_name;
616 main_stack.set_visible_child_full ("alert", Gtk.StackTransitionType.NONE);
617 }
618+
619+ public void set_app_notification (string text) {
620+ notification_label.label = text;
621+ app_notification.reveal_child = true;
622+ }
623 }

Subscribers

People subscribed via source and target branches