Skip to content

Commit 1f0c6fc

Browse files
authored
Introduce a SearchBar (#801)
1 parent 95a7fbf commit 1f0c6fc

File tree

7 files changed

+87
-38
lines changed

7 files changed

+87
-38
lines changed

data/music.metainfo.xml.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
<issue url="https://github.com/elementary/music/issues/762">Search mechanism in the app</issue>
7272
<issue url="https://github.com/elementary/music/issues/777">Ctrl+Q to Quit app</issue>
7373
<issue url="https://github.com/elementary/music/issues/787">Ability to remove a track</issue>
74+
<issue url="https://github.com/elementary/music/issues/794">Search function only shows first occurrence of search term</issue>
7475
<issue url="https://github.com/elementary/music/issues/798">Gap below album cover when setting text size greater than 1</issue>
7576
<issue url="https://github.com/elementary/music/issues/803">NowPlaying area resizes when playing song with long artist name</issue>
7677
</issues>

meson.build

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ config_file = configure_file(
2020
)
2121

2222
adw_dep = dependency('libadwaita-1', version: '>=1.4.0')
23-
granite_dep = dependency('granite-7', version: '>=7.6.0')
23+
granite_dep = dependency('granite-7', version: '>=7.6.0') # Granite.Bin
2424
gstreamer_dep = dependency('gstreamer-1.0')
2525
gstreamer_pbutils_dep = dependency('gstreamer-pbutils-1.0')
2626
gstreamer_tag_dep = dependency('gstreamer-tag-1.0')

po/POTFILES

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ src/MainWindow.vala
33
src/PlaybackManager.vala
44
src/Views/NowPlayingView.vala
55
src/Widgets/AlbumImage.vala
6+
src/Widgets/SearchBar.vala
67
src/Widgets/SeekBar.vala

src/MainWindow.vala

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ public class Music.MainWindow : Gtk.ApplicationWindow {
88
private const string ACTION_OPEN = "action-open";
99

1010
private Granite.Placeholder queue_placeholder;
11+
private Granite.Placeholder search_placeholder;
1112
private Gtk.Button repeat_button;
1213
private Gtk.Button shuffle_button;
14+
private SearchBar search_bar;
1315
private Gtk.ListView queue_listview;
1416
private Gtk.Revealer search_revealer;
1517
private Gtk.ScrolledWindow scrolled;
16-
private Gtk.SearchEntry search_entry;
1718
private Gtk.SingleSelection selection_model;
1819
private Gtk.Stack queue_stack;
1920
private Settings settings;
@@ -30,12 +31,10 @@ public class Music.MainWindow : Gtk.ApplicationWindow {
3031

3132
repeat_button = new Gtk.Button ();
3233

33-
search_entry = new Gtk.SearchEntry () {
34-
placeholder_text = _("Search titles in playlist")
35-
};
34+
search_bar = new SearchBar (playback_manager.queue_liststore);
3635

3736
search_revealer = new Gtk.Revealer () {
38-
child = search_entry
37+
child = search_bar
3938
};
4039

4140
playback_manager.bind_property (
@@ -55,7 +54,12 @@ public class Music.MainWindow : Gtk.ApplicationWindow {
5554
icon = new ThemedIcon ("playlist-queue")
5655
};
5756

58-
selection_model = new Gtk.SingleSelection (playback_manager.queue_liststore);
57+
search_placeholder = new Granite.Placeholder ("") {
58+
description = _("Try changing search terms"),
59+
icon = new ThemedIcon ("edit-find-symbolic")
60+
};
61+
62+
selection_model = new Gtk.SingleSelection (search_bar.filter_model);
5963

6064
var factory = new Gtk.SignalListItemFactory ();
6165

@@ -71,6 +75,7 @@ public class Music.MainWindow : Gtk.ApplicationWindow {
7175

7276
queue_stack = new Gtk.Stack ();
7377
queue_stack.add_child (queue_placeholder);
78+
queue_stack.add_child (search_placeholder);
7479
queue_stack.add_child (scrolled);
7580

7681
var drop_target = new Gtk.DropTarget (typeof (Gdk.FileList), Gdk.DragAction.COPY);
@@ -201,6 +206,13 @@ public class Music.MainWindow : Gtk.ApplicationWindow {
201206
return false;
202207
});
203208

209+
playback_manager.queue_liststore.items_changed.connect (() => {
210+
if (playback_manager.n_items == 0) {
211+
queue_stack.visible_child = queue_placeholder;
212+
search_bar.search_entry.text = "";
213+
}
214+
});
215+
204216
playback_manager.invalids_found.connect ((count) => {
205217
error_toast.title = ngettext (
206218
"%d invalid file was not added to the queue",
@@ -234,25 +246,24 @@ public class Music.MainWindow : Gtk.ApplicationWindow {
234246

235247
selection_model.items_changed.connect (on_items_changed);
236248

237-
search_entry.search_changed.connect (() => {
238-
int pos = playback_manager.find_title (search_entry.text);
239-
if (pos >= 0) {
240-
queue_listview.scroll_to (pos, SELECT, null);
241-
}
242-
});
243-
244-
search_entry.activate.connect (() => {
249+
search_bar.activated.connect (() => {
245250
var selected = selection_model.get_selected ();
246251
if (selected != -1) {
247252
var selected_audio = (AudioObject) selection_model.get_item (selected);
248253
playback_manager.current_audio = selected_audio;
249254
}
250255
});
256+
257+
search_bar.search_entry.search_changed.connect (() => {
258+
if (selection_model.n_items <= 0 && search_bar.search_entry.text != "") {
259+
search_placeholder.title = _("No Results for “%s").printf (search_bar.search_entry.text);
260+
}
261+
});
251262
}
252263

253264
public void start_search () {
254265
if (search_revealer.child_revealed) {
255-
search_entry.grab_focus ();
266+
search_bar.start_search ();
256267
}
257268
}
258269

@@ -330,6 +341,11 @@ public class Music.MainWindow : Gtk.ApplicationWindow {
330341
private void on_items_changed () {
331342
if (selection_model.n_items > 0) {
332343
queue_stack.visible_child = scrolled;
344+
return;
345+
}
346+
347+
if (search_bar.search_entry.text != "") {
348+
queue_stack.visible_child = search_placeholder;
333349
} else {
334350
queue_stack.visible_child = queue_placeholder;
335351
}

src/PlaybackManager.vala

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -412,28 +412,6 @@ public class Music.PlaybackManager : Object {
412412
}
413413
}
414414

415-
public int find_title (string term) {
416-
var search_object = new AudioObject ("") {
417-
title = term
418-
};
419-
420-
int found_at = -1;
421-
uint position;
422-
if (queue_liststore.find_with_equal_func (
423-
search_object,
424-
(a, b) => {
425-
var term_a = ((AudioObject)a).title.down ();
426-
var term_b = ((AudioObject)b).title.down ();
427-
return term_a.contains (term_b);
428-
},
429-
out position
430-
)) {
431-
found_at = (int)position;
432-
}
433-
434-
return found_at;
435-
}
436-
437415
private void update_next_previous_sensitivity () {
438416
var next_sensitive = false;
439417
var previous_sensitive = false;

src/Widgets/SearchBar.vala

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-3.0-or-later
3+
* SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io)
4+
*/
5+
6+
public class Music.SearchBar : Granite.Bin {
7+
public signal void activated ();
8+
9+
public ListModel list_model { get; construct; }
10+
11+
public Gtk.SearchEntry search_entry { get; private set; }
12+
13+
/**
14+
* The new model with the search applied. Make sure to use this one in further UI
15+
* instead of the old given model.
16+
*/
17+
public Gtk.FilterListModel filter_model { get; private set; }
18+
19+
private Gtk.StringFilter filter;
20+
21+
public SearchBar (ListModel list_model) {
22+
Object (list_model: list_model);
23+
}
24+
25+
construct {
26+
var expression = new Gtk.PropertyExpression (typeof (AudioObject), null, "title");
27+
28+
filter = new Gtk.StringFilter (expression) {
29+
ignore_case = true,
30+
match_mode = SUBSTRING
31+
};
32+
33+
filter_model = new Gtk.FilterListModel (list_model, filter);
34+
35+
search_entry = new Gtk.SearchEntry () {
36+
placeholder_text = _("Search titles in playlist")
37+
};
38+
39+
child = search_entry;
40+
41+
search_entry.search_changed.connect (on_search_changed);
42+
search_entry.activate.connect (() => activated ());
43+
}
44+
45+
private void on_search_changed () {
46+
filter.search = search_entry.text;
47+
}
48+
49+
public void start_search () {
50+
search_entry.grab_focus ();
51+
}
52+
}

src/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ sources = [
77
'DBus/MprisRoot.vala',
88
'Views/NowPlayingView.vala',
99
'Widgets/AlbumImage.vala',
10+
'Widgets/SearchBar.vala',
1011
'Widgets/SeekBar.vala',
1112
'Widgets/TrackRow.vala',
1213
]

0 commit comments

Comments
 (0)