From 801a130ef89ca7cd9593e81c0a4e7b7e73a4f0bf Mon Sep 17 00:00:00 2001 From: Elias Projahn Date: Mon, 25 Jan 2021 14:00:57 +0100 Subject: [PATCH] Initial port to GTK4 --- Cargo.toml | 30 +- de.johrpan.musicus.json | 22 + res/musicus.gresource.xml | 8 - res/ui/ensemble_editor.ui | 257 +++++------- res/ui/ensemble_screen.ui | 162 ++----- res/ui/ensemble_selector.ui | 210 ---------- res/ui/instrument_editor.ui | 257 +++++------- res/ui/instrument_selector.ui | 210 ---------- res/ui/login_dialog.ui | 291 +++++-------- res/ui/medium_editor.ui | 271 ++++++------ res/ui/performance_editor.ui | 295 +++---------- res/ui/person_editor.ui | 293 +++++-------- res/ui/person_list.ui | 75 ---- res/ui/person_screen.ui | 199 ++------- res/ui/person_selector.ui | 210 ---------- res/ui/player_bar.ui | 168 ++------ res/ui/player_screen.ui | 325 ++++---------- res/ui/poe_list.ui | 70 ++-- res/ui/preferences.ui | 28 +- res/ui/recording_editor.ui | 473 +++++++-------------- res/ui/recording_screen.ui | 134 ++---- res/ui/recording_selector.ui | 258 ------------ res/ui/recording_selector_screen.ui | 153 ------- res/ui/selector.ui | 276 ++++-------- res/ui/server_dialog.ui | 86 ++-- res/ui/source_selector.ui | 192 ++++----- res/ui/track_editor.ui | 62 +-- res/ui/track_selector.ui | 62 +-- res/ui/track_set_editor.ui | 167 +++----- res/ui/window.ui | 424 +++++++------------ res/ui/work_editor.ui | 630 +++++++--------------------- res/ui/work_part_editor.ui | 194 +++------ res/ui/work_screen.ui | 175 +++----- res/ui/work_section_editor.ui | 121 +++--- res/ui/work_selector.ui | 275 ------------ res/ui/work_selector_screen.ui | 153 ------- src/dialogs/about.rs | 1 - src/dialogs/login_dialog.rs | 4 +- src/dialogs/preferences.rs | 33 +- src/dialogs/server_dialog.rs | 2 +- src/editors/ensemble.rs | 2 +- src/editors/instrument.rs | 2 +- src/editors/performance.rs | 37 +- src/editors/person.rs | 4 +- src/editors/recording.rs | 138 +++--- src/editors/work.rs | 304 +++++++------- src/editors/work_part.rs | 15 +- src/editors/work_section.rs | 2 +- src/import/disc_source.rs | 1 - src/import/medium_editor.rs | 24 +- src/import/source_selector.rs | 1 - src/import/track_editor.rs | 6 +- src/import/track_selector.rs | 6 +- src/import/track_set_editor.rs | 25 +- src/meson.build | 9 +- src/screens/ensemble_screen.rs | 98 ++--- src/screens/person_screen.rs | 166 ++++---- src/screens/player_screen.rs | 476 +++++++++++---------- src/screens/recording_screen.rs | 154 ++++--- src/screens/work_screen.rs | 99 +++-- src/selectors/ensemble.rs | 27 +- src/selectors/instrument.rs | 27 +- src/selectors/person.rs | 25 +- src/selectors/recording.rs | 25 +- src/selectors/selector.rs | 80 ++-- src/selectors/work.rs | 25 +- src/widgets/indexed_list_model.rs | 189 +++++++++ src/widgets/list.rs | 163 +++---- src/widgets/mod.rs | 5 +- src/widgets/navigator.rs | 6 +- src/widgets/navigator_window.rs | 2 +- src/widgets/new_list.rs | 50 --- src/widgets/player_bar.rs | 8 +- src/widgets/poe_list.rs | 80 ++-- src/widgets/selector_row.rs | 149 ------- src/window.rs | 37 +- 76 files changed, 3098 insertions(+), 6625 deletions(-) delete mode 100644 res/ui/ensemble_selector.ui delete mode 100644 res/ui/instrument_selector.ui delete mode 100644 res/ui/person_list.ui delete mode 100644 res/ui/person_selector.ui delete mode 100644 res/ui/recording_selector.ui delete mode 100644 res/ui/recording_selector_screen.ui delete mode 100644 res/ui/work_selector.ui delete mode 100644 res/ui/work_selector_screen.ui create mode 100644 src/widgets/indexed_list_model.rs delete mode 100644 src/widgets/new_list.rs delete mode 100644 src/widgets/selector_row.rs diff --git a/Cargo.toml b/Cargo.toml index 2467bf0..39ca08e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,19 +11,37 @@ discid = "0.4.4" fragile = "1.0.0" futures = "0.3.6" futures-channel = "0.3.5" -gdk = "0.13.2" gettext-rs = "0.5.0" -gio = "0.9.1" -glib = "0.10.2" -gtk = { version = "0.9.2", features = ["v3_24"] } gtk-macros = "0.2.0" gstreamer = "0.16.4" gstreamer-player = "0.16.3" isahc = "0.9.12" -libhandy = "0.7.0" -pango = "0.9.1" rand = "0.7.3" secret-service = "1.1.1" serde = { version = "1.0.117", features = ["derive"] } serde_json = "1.0.59" uuid = { version = "0.8", features = ["v4"] } + +[dependencies.gdk] +git = "https://github.com/gtk-rs/gtk4-rs/" +package = "gdk4" + +[dependencies.gio] +git = "https://github.com/gtk-rs/gtk-rs/" +features = ["v2_64"] + +[dependencies.glib] +git = "https://github.com/gtk-rs/gtk-rs/" +features = ["v2_64"] + +[dependencies.gtk] +git = "https://github.com/gtk-rs/gtk4-rs" +package = "gtk4" + +[dependencies.libhandy] +git = "https://gitlab.gnome.org/bilelmoussaoui/libhandy4-rs" +package = "libhandy4" + +[dependencies.pango] +git = "https://github.com/gtk-rs/gtk-rs/" +features = ["v1_44"] diff --git a/de.johrpan.musicus.json b/de.johrpan.musicus.json index ae44bd2..573f915 100644 --- a/de.johrpan.musicus.json +++ b/de.johrpan.musicus.json @@ -92,6 +92,28 @@ } ] }, + { + "name" : "libhandy", + "buildsystem" : "meson", + "config-opts" : [ + "-Dintrospection=enabled", + "-Dtests=false", + "-Dexamples=false", + "-Dvapi=false", + "-Dglade_catalog=disabled" + ], + "cleanup" : [ + "/include", + "/lib/pkgconfig" + ], + "sources" : [ + { + "type" : "git", + "url" : "https://gitlab.gnome.org/exalm/libhandy", + "branch" : "gtk4" + } + ] + }, { "name" : "musicus", "builddir" : true, diff --git a/res/musicus.gresource.xml b/res/musicus.gresource.xml index acc5530..bea74fa 100644 --- a/res/musicus.gresource.xml +++ b/res/musicus.gresource.xml @@ -3,24 +3,18 @@ ui/ensemble_editor.ui ui/ensemble_screen.ui - ui/ensemble_selector.ui ui/instrument_editor.ui - ui/instrument_selector.ui ui/login_dialog.ui ui/medium_editor.ui ui/performance_editor.ui ui/person_editor.ui - ui/person_list.ui ui/person_screen.ui - ui/person_selector.ui ui/player_bar.ui ui/player_screen.ui ui/poe_list.ui ui/preferences.ui ui/recording_editor.ui ui/recording_screen.ui - ui/recording_selector.ui - ui/recording_selector_screen.ui ui/selector.ui ui/server_dialog.ui ui/source_selector.ui @@ -32,7 +26,5 @@ ui/work_part_editor.ui ui/work_screen.ui ui/work_section_editor.ui - ui/work_selector.ui - ui/work_selector_screen.ui diff --git a/res/ui/ensemble_editor.ui b/res/ui/ensemble_editor.ui index cb2f81d..a20e540 100644 --- a/res/ui/ensemble_editor.ui +++ b/res/ui/ensemble_editor.ui @@ -1,189 +1,132 @@ - - + - True - False crossfade - - True - False - vertical - - - True - False - Ensemble + + content + + + vertical - - True - True - True + + false + + + Ensemble + + + - - True - False + go-previous-symbolic - - - - - True - True - True - - - True - False + + object-select-symbolic + - - - end - 1 - - - - False - True - 0 - - - - - True - False - False - + + False + - - - False - True - 1 - - - - - True - False - 500 - 300 - - - True - False - 18 - 12 - 6 + + true - - True - False - end - Name + + 12 + 12 + 18 + 12 + 500 + 300 + + + start + + + none + + + True + Name + name_entry + + + center + True + + + + + + + True + Publish to the server + upload_switch + + + center + True + + + + + + + + - - 0 - 0 - - - - - True - True - True - - - 1 - 0 - - - - - True - False - end - Publish - - - 0 - 1 - - - - - True - True - start - True - - - 1 - 1 - - - False - True - 2 - - + - - content - - - True - False - vertical - - - True - False - Ensemble - - - False - True - 0 - - - - - True - False - True - True - - - False - True - 1 - - - - + loading - 1 - + + + vertical + + + false + + + Ensemble + + + + + + + + true + true + true + center + center + + + + + diff --git a/res/ui/ensemble_screen.ui b/res/ui/ensemble_screen.ui index 140af01..a6aa910 100644 --- a/res/ui/ensemble_screen.ui +++ b/res/ui/ensemble_screen.ui @@ -1,132 +1,73 @@ - - + - True - False vertical - True - False - True + + + Ensemble + + + - True - True - True - - - True - False - go-previous-symbolic - - + go-previous-symbolic - + - True - True - False - True + view-more-symbolic menu - - - True - False - view-more-symbolic - - - - end - 1 - - + - True - True - True - - - True - False - edit-find-symbolic - - + edit-find-symbolic - - end - 1 - - - False - True - 0 - - - True - False + False - True - False 400 + true - True - True - edit-find-symbolic - False - False Search recordings … - - False - True - 1 - - True - False - - True - False - True - - + loading - + + + True + + + - - True - True - - - True - False - none + + content + + + true - True - False 12 12 18 @@ -134,71 +75,40 @@ 800 - True - False vertical 12 - True - False start Recordings - - False - True - 0 - - True - False - 0 - in - - - - - False - True - 1 - - + - - content - 1 - - - True - False - No recordings found. - - + nothing - 2 - + + + No recordings found. + + + - - True - True - 2 - diff --git a/res/ui/ensemble_selector.ui b/res/ui/ensemble_selector.ui deleted file mode 100644 index 1d60b15..0000000 --- a/res/ui/ensemble_selector.ui +++ /dev/null @@ -1,210 +0,0 @@ - - - - - - - False - True - True - dialog - - - True - False - vertical - - - True - False - Select ensemble - True - - - True - True - True - - - True - False - list-add-symbolic - - - - - - - False - True - 0 - - - - - True - False - True - - - True - False - vertical - 6 - - - True - True - True - edit-find-symbolic - False - False - Search ensembles … - - - False - True - 0 - - - - - Show ensembles from the Musicus server - True - True - False - True - True - - - False - True - 1 - - - - - - - False - True - 1 - - - - - True - False - crossfade - - - True - False - True - - - loading - - - - - True - True - - - - - - content - 1 - - - - - True - False - center - center - 18 - vertical - 18 - - - True - False - 0.5019607843137255 - 80 - network-error-symbolic - - - False - True - 0 - - - - - True - False - 0.5019607843137255 - An error occured! - - - - - - False - True - 1 - - - - - True - False - 0.5019607843137255 - The server was not reachable or responded with an error. Please check your internet connection. - center - True - 40 - - - False - True - 2 - - - - - Try again - True - True - True - center - - - - False - True - 3 - - - - - error - 2 - - - - - True - True - 3 - - - - - - diff --git a/res/ui/instrument_editor.ui b/res/ui/instrument_editor.ui index 11e80c2..b937ae7 100644 --- a/res/ui/instrument_editor.ui +++ b/res/ui/instrument_editor.ui @@ -1,189 +1,132 @@ - - + - True - False crossfade - - True - False - vertical - - - True - False - Instrument + + content + + + vertical - - True - True - True + + false + + + Instrument + + + - - True - False + go-previous-symbolic - - - - - True - True - True - - - True - False + + object-select-symbolic + - - - end - 1 - - - - False - True - 0 - - - - - True - False - False - + + False + - - - False - True - 1 - - - - - True - False - 500 - 300 - - - True - False - 18 - 12 - 6 + + true - - True - False - end - Name + + 12 + 12 + 18 + 12 + 500 + 300 + + + start + + + none + + + True + Name + name_entry + + + center + True + + + + + + + True + Publish to the server + upload_switch + + + center + True + + + + + + + + - - 0 - 0 - - - - - True - True - True - - - 1 - 0 - - - - - True - False - end - Publish - - - 0 - 1 - - - - - True - True - start - True - - - 1 - 1 - - - False - True - 2 - - + - - content - - - True - False - vertical - - - True - False - Instrument - - - False - True - 0 - - - - - True - False - True - True - - - False - True - 1 - - - - + loading - 1 - + + + vertical + + + false + + + Instrument + + + + + + + + true + true + true + center + center + + + + + diff --git a/res/ui/instrument_selector.ui b/res/ui/instrument_selector.ui deleted file mode 100644 index 4cff41d..0000000 --- a/res/ui/instrument_selector.ui +++ /dev/null @@ -1,210 +0,0 @@ - - - - - - - False - True - True - dialog - - - True - False - vertical - - - True - False - Select instrument - True - - - True - True - True - - - True - False - list-add-symbolic - - - - - - - False - True - 0 - - - - - True - False - True - - - True - False - vertical - 6 - - - True - True - True - edit-find-symbolic - False - False - Search instruments … - - - False - True - 0 - - - - - Show instruments from the Musicus server - True - True - False - True - True - - - False - True - 1 - - - - - - - False - True - 1 - - - - - True - False - crossfade - - - True - False - True - - - loading - - - - - True - True - - - - - - content - 1 - - - - - True - False - center - center - 18 - vertical - 18 - - - True - False - 0.5019607843137255 - 80 - network-error-symbolic - - - False - True - 0 - - - - - True - False - 0.5019607843137255 - An error occured! - - - - - - False - True - 1 - - - - - True - False - 0.5019607843137255 - The server was not reachable or responded with an error. Please check your internet connection. - center - True - 40 - - - False - True - 2 - - - - - Try again - True - True - True - center - - - - False - True - 3 - - - - - error - 2 - - - - - True - True - 3 - - - - - - diff --git a/res/ui/login_dialog.ui b/res/ui/login_dialog.ui index 25c83f2..ffd6a9e 100644 --- a/res/ui/login_dialog.ui +++ b/res/ui/login_dialog.ui @@ -1,219 +1,134 @@ - - - + + - False True 350 - dialog - True - False crossfade - - True - False - vertical - - - True - False - Login + + content + + + vertical - - Cancel - True - True - True - - - - - Login - True - True - True - True - False - - - - end - 1 - - - - - False - True - 0 - - - - - True - False - error - False - - - False - 6 - end + + false + + + Login + + + - + + Cancel + + + + + Login + True + + - - False - False - 0 - - - - False - 16 + + + error + False - True - False The login credentials were wrong! - - False - True - 0 - - - False - False - 0 - + + + + none + + + True + Username + username_entry + + + center + True + + + + + + + True + Password + password_entry + + + center + True + False + True + password + + + + + - - False - True - 1 - - - - - - True - False - 18 - 12 - 6 - - - True - False - end - Username - - - 0 - 0 - - - - - True - False - end - Password - - - 0 - 1 - - - - - True - True - True - True - - - 1 - 0 - - - - - True - True - False - True - password - - - 1 - 1 - - - - - False - True - 2 - - + - - content - - - True - False - vertical - - - True - False - Login - - - False - True - 0 - - - - - True - False - True - - - True - True - 1 - - - - + loading - 1 - - - - + + + vertical + + + false + + + Login + + + + + + + + True + true + true + center + center + + + + + + + + + + + diff --git a/res/ui/medium_editor.ui b/res/ui/medium_editor.ui index 0ad182e..4259d86 100644 --- a/res/ui/medium_editor.ui +++ b/res/ui/medium_editor.ui @@ -1,180 +1,145 @@ - + - True crossfade - - True - vertical - - - True - Import music + + content + + + vertical - - True - True + + false + + + Import music + + + - - True + go-previous-symbolic - - - - - True - True - False - - - True - crossfade + + + False - - True - True - - - - - True - object-select-symbolic - - - - - - - - end - - - - - - - True - False - False - - - - - True - True - True - - - True - - - True - 6 - 6 - 6 - vertical - - - True - start - 12 - 6 - Medium - - - - - - - - True - in + + crossfade - - True - none - - - True - True - True - Name of the medium - name_entry - - - True - True - center - True - - - - - - - True - True - True - Publish to the server - publish_switch - - - True - True - center - True - - - - + + True + + + + + object-select-symbolic + + + + + + + + False + + + + + True + + - True - horizontal - 12 + 6 + 6 6 + vertical - True start - end - True - Recordings + 12 + 6 + Medium - - True - True - none + - - True + + none + + + True + Name of the medium + name_entry + + + center + True + + + + + + + True + Publish to the server + publish_switch + + + center + True + + + + + + + + + + + horizontal + 12 + 6 + + + start + end + True + Recordings + + + + + + + + false list-add-symbolic - - - - - True - in + + + @@ -182,20 +147,22 @@ - + - - content - - - True - True - - + loading - + + + true + true + true + center + center + + + diff --git a/res/ui/performance_editor.ui b/res/ui/performance_editor.ui index 07e67b9..8031ce6 100644 --- a/res/ui/performance_editor.ui +++ b/res/ui/performance_editor.ui @@ -1,281 +1,106 @@ - - + - True - False vertical - True - False - Performance + false + + + Performance + + + - True - True - True - - - True - False - go-previous-symbolic - - + go-previous-symbolic - + - True False - True - True - - - True - False - object-select-symbolic - - + object-select-symbolic - - end - 1 - - - False - True - 0 - - - True - False - 500 - 300 + + true - - - True - False - 18 - 12 - 6 + + 12 + 12 + 18 + 12 + 500 + 300 - - True - False - Role - 1 - - - 0 - 2 - - - - - True - False - True + + start - - True - True - True + + none - - True - False - start - Select … - - - - - True - True - 0 - - - - - True - True - - - True - False - user-trash-symbolic - - - - - False - False - 1 - - - - - - 1 - 2 - - - - - True - False - Type - right - 1 - - - 0 - 0 - - - - - True - False - crossfade - - - True - False - 6 - - - True - False - Person - 1 - - - False - True - 0 - - - - - True - True - True + + True + Select a person + person_button - - True - False - start - Select … + + Select + center - - True - True - 1 - - - - - person - Person - - - - - True - False - 6 - - - True - False - end - Ensemble - - - False - True - 0 - - - True - True - True + + True + Select an ensemble + ensemble_button - - True - False - start - Select … + + Select + center + + + + + + + True + Select a role + role_button + + + false + user-trash-symbolic + center + + + + + Select + center - - True - True - 1 - - - ensemble - Ensemble - 1 - - - 0 - 1 - 2 - - - - - True - False - True - True - stack - - - 1 - 0 - - - False - True - 1 - - - - - - - - - diff --git a/res/ui/person_editor.ui b/res/ui/person_editor.ui index a75d52c..9b2e43a 100644 --- a/res/ui/person_editor.ui +++ b/res/ui/person_editor.ui @@ -1,212 +1,145 @@ - - + - True - False crossfade - - True - False - vertical - - - True - False - Person + + content + + + vertical - - True - True - True + + false + + + Person + + + - - True - False + go-previous-symbolic - - - - - True - True - True - - - True - False + + object-select-symbolic + - - - end - 1 - - - - False - True - 0 - - - - - True - False - False - + + False + - - - False - True - 1 - - - - - True - False - 500 - 300 - - - True - False - 18 - 12 - 6 + + true - - True - False - end - First name + + 12 + 12 + 18 + 12 + 500 + 300 + + + start + + + none + + + True + First name + first_name_entry + + + center + True + + + + + + + True + Last name + last_name_entry + + + center + True + + + + + + + True + Publish to the server + upload_switch + + + center + True + + + + + + + + - - 0 - 0 - - - - - True - True - True - - - 1 - 0 - - - - - True - False - end - Last name - - - 0 - 1 - - - - - True - True - True - - - 1 - 1 - - - - - True - False - end - Publish - - - 0 - 2 - - - - - True - True - start - True - - - 1 - 2 - - - False - True - 2 - - + - - content - - - True - False - vertical - - - True - False - Person - - - False - True - 0 - - - - - True - False - True - True - - - False - True - 1 - - - - + loading - 1 - + + + vertical + + + false + + + Person + + + + + + + + true + true + true + center + center + + + + + diff --git a/res/ui/person_list.ui b/res/ui/person_list.ui deleted file mode 100644 index 0346312..0000000 --- a/res/ui/person_list.ui +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - True - False - vertical - - - True - False - True - - - True - False - 400 - 300 - - - True - True - edit-find-symbolic - False - False - Search persons … - - - - - - - False - True - 0 - - - - - True - False - - - True - False - True - - - loading - - - - - True - True - - - - - - content - 1 - - - - - True - True - 1 - - - - diff --git a/res/ui/person_screen.ui b/res/ui/person_screen.ui index f80fece..5149dae 100644 --- a/res/ui/person_screen.ui +++ b/res/ui/person_screen.ui @@ -1,132 +1,77 @@ - - + - True - False vertical - True - False - True + + + Person + + + - True - True - True - - - True - False - go-previous-symbolic - - + go-previous-symbolic - + - True - True - False - True menu - - - True - False - view-more-symbolic - - + view-more-symbolic - - end - 1 - - + - True - True - True - - - True - False - edit-find-symbolic - - + edit-find-symbolic - - end - 1 - - - False - True - 0 - - - True - False + False - True - False 400 + true - True - True - edit-find-symbolic - False - False Search works and recordings … - - False - True - 1 - - True - False - - True - False - True - - + loading - + + + true + true + center + center + true + + + - - True - True - - - True - False - none + + content + + + true - True - False 12 12 18 @@ -134,129 +79,65 @@ 800 - True - False vertical 18 - True - False vertical 12 - True - False start Works - - False - True - 0 - - True - False - 0 - in - - - - - False - True - 1 - - - False - True - 0 - - True - False vertical 12 - True - False start Recordings - - False - True - 0 - - True - False - 0 - in - - - - - False - True - 1 - - - False - True - 1 - - + - - content - 1 - - - True - False - No works or recordings found. - - + nothing - 2 - + + + No works or recordings found. + + + - - True - True - 2 - diff --git a/res/ui/person_selector.ui b/res/ui/person_selector.ui deleted file mode 100644 index 72db289..0000000 --- a/res/ui/person_selector.ui +++ /dev/null @@ -1,210 +0,0 @@ - - - - - - - False - True - True - dialog - - - True - False - vertical - - - True - False - Select person - True - - - True - True - True - - - True - False - list-add-symbolic - - - - - - - False - True - 0 - - - - - True - False - True - - - True - False - vertical - 6 - - - True - True - True - edit-find-symbolic - False - False - Search persons … - - - False - True - 0 - - - - - Show persons from the Musicus server - True - True - False - True - True - - - False - True - 1 - - - - - - - False - True - 1 - - - - - True - False - crossfade - - - True - False - True - - - loading - - - - - True - True - - - - - - content - 1 - - - - - True - False - center - center - 18 - vertical - 18 - - - True - False - 0.5019607843137255 - 80 - network-error-symbolic - - - False - True - 0 - - - - - True - False - 0.5019607843137255 - An error occured! - - - - - - False - True - 1 - - - - - True - False - 0.5019607843137255 - The server was not reachable or responded with an error. Please check your internet connection. - center - True - 40 - - - False - True - 2 - - - - - Try again - True - True - True - center - - - - False - True - 3 - - - - - error - 2 - - - - - True - True - 3 - - - - - - diff --git a/res/ui/player_bar.ui b/res/ui/player_bar.ui index 1fe2112..cd9b1a8 100644 --- a/res/ui/player_bar.ui +++ b/res/ui/player_bar.ui @@ -1,119 +1,66 @@ - - + - True - False media-playback-start-symbolic - True - False slide-up - True - False vertical - - True - False - - - False - True - 0 - + - True - False - 6 + 6 + 6 + 6 + 6 12 - True - False center 6 - True False - True - True - True - False media-skip-backward-symbolic - - False - True - 0 - - True - True - True - True - False media-playback-pause-symbolic - - False - True - 1 - - True False - True True - True - False media-skip-forward-symbolic - - False - True - 2 - - - False - True - 0 - - True - False vertical + True - True - False start Title end @@ -121,108 +68,47 @@ - - False - True - 0 - - True - False start Subtitle end - - False - True - 1 - - - True - True - 1 - + + + + 2 + + + 0:00 + + + + + / + + + + + 0:00 + + + - True - True - True center - True - False view-list-bullet-symbolic - - False - True - end - 1 - - - - - True - False - 2 - - - True - False - 0:00 - - - False - True - 0 - - - - - True - False - / - - - False - True - 1 - - - - - True - False - 0:00 - - - False - True - 2 - - - - - False - True - 3 - - - False - True - 1 - diff --git a/res/ui/player_screen.ui b/res/ui/player_screen.ui index 561f2e2..9ec73e6 100644 --- a/res/ui/player_screen.ui +++ b/res/ui/player_screen.ui @@ -1,11 +1,8 @@ - - + - True - False media-playback-start-symbolic @@ -14,292 +11,150 @@ 0.05 - True - False vertical - True - False - Player - True + + + Player + + + + True - True - True - True - True - False go-previous-symbolic - - False - True - 0 - - True - True + true - - True - False - none + + 12 + 12 + 18 + 12 + 800 - - True - False - 12 - 12 - 12 - 12 - 18 - 12 - 800 + + vertical + 12 - True - False - vertical 12 - True - False - 12 + center + 6 - - True - False - center - 6 - - - True - False - True - True - - - True - False - media-skip-backward-symbolic - - - - - False - True - 0 - - - - - True - True - True - - - True - False - media-playback-pause-symbolic - - - - - False - True - 1 - - - - - True - False - True - True - - - True - False - media-skip-forward-symbolic - - - - - False - True - 2 - - - - - False - True - 0 - - - - - True - False - vertical - - - True - False - start - Title - end - - - - - - False - True - 0 - - - - - True - False - start - Subtitle - end - - - False - True - 1 - - - - - True - True - 1 - - - - - True + + False True - True - center - True - False - media-playback-stop-symbolic + media-skip-backward-symbolic + + + + + + + True + + + media-playback-pause-symbolic + + + + + + + False + True + + + media-skip-forward-symbolic - - False - True - 2 - - - False - True - 0 - - True - False - 6 + vertical + True - - True - False - 0:00 + + start + Title + end + + + - - False - True - 0 - - - True - True - GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - position - off - 1 - False + + start + Subtitle + end - - True - True - 1 - - - - - True - False - 0:00 - - - False - True - 2 - - - False - True - 1 - - - True - False - 0 - in + + True + center - - - - + + media-playback-stop-symbolic + - - False - True - 2 - + + + 6 + + + 0:00 + + + + + position + True + + + + + 0:00 + + + + + + + + - - True - True - 1 - diff --git a/res/ui/poe_list.ui b/res/ui/poe_list.ui index fce2b39..3200fa3 100644 --- a/res/ui/poe_list.ui +++ b/res/ui/poe_list.ui @@ -1,75 +1,59 @@ - - + - True - False vertical - - True - False + True - True - False 400 300 + true - True - True - edit-find-symbolic - False - False Search persons and ensembles … - - False - True - 0 - - True - False + True + crossfade - - True - False - True - - + loading - + + + True + True + True + center + center + + + - - True - True - - - - - + content - 1 - + + + True + True + + + + + + - - True - True - 1 - diff --git a/res/ui/preferences.ui b/res/ui/preferences.ui index 3ba91cd..1f1fce5 100644 --- a/res/ui/preferences.ui +++ b/res/ui/preferences.ui @@ -1,37 +1,25 @@ - - - + + - False True 400 400 - dialog - True - False General - True - False Music library - True - True - False Music library folder select_music_library_path_button None selected Select - True - True True center @@ -42,22 +30,15 @@ - True - False Server connection - True - True - False Server URL url_button Not set Change - True - True True center @@ -66,17 +47,12 @@ - True - True - False Login credentials login_button Not logged in Change - True - True True center diff --git a/res/ui/recording_editor.ui b/res/ui/recording_editor.ui index 100f6af..e9df94e 100644 --- a/res/ui/recording_editor.ui +++ b/res/ui/recording_editor.ui @@ -1,373 +1,188 @@ - - + - True - False - - True - False - vertical - - - True - False - Recording + + content + + + vertical - - True - True - True + + false + + + Recording + + + - - True - False - go-previous-symbolic + + go-previous-symbolic - - - - - True - False - True - True - - - True - False + + + False object-select-symbolic - - - - - - end - 1 - - - - - False - True - 0 - - - - - True - False - False - - - False - - - False - False - 0 - - - - - False - - - False - False - 0 - - - - - - - - False - True - 1 - - - - - True - True - False - - - True - False - 500 - 300 - - - - True - False - 18 - 12 - 6 - - - True - False - end - Comment - - - 0 - 1 - - - - - True - True - True - True - - - True - False - start - Select … - end - - - - - 1 - 0 - - - - - True - True - - - 1 - 1 - - - - - True - False - end - Work - - - 0 - 0 - - - - - True - False - end - Publish - - - 0 - 2 - - - - - True - True - start - True - - - 1 - 2 - - + - - - True - False - Overview + + + False - - False - - - True - False - 300 + + true - - True - False - 18 - 6 - - - True - True - in - - - - - - True - True - 0 - - + + 12 + 12 + 18 + 12 - True - False - 0 + 6 + 6 + 6 vertical - 6 - - True - True - True + + start + 12 + 6 + Overview + + + + + + + - - True - False + + none + + + True + Select a work + work_button + + + Select + center + + + + + + + True + Comment + comment_entry + + + center + True + + + + + + + True + Publish to the server + upload_switch + + + center + True + + + + + + + + + + + horizontal + 12 + 6 + + + start + end + True + Performers + + + + + + + + false list-add-symbolic - - False - True - 0 - - - True - True - True - - - True - False - edit-symbolic - - - - - False - True - 1 - - - - - True - True - True - - - True - False - list-remove-symbolic - - - - - False - True - 2 - + - - False - True - 1 - - - 1 - - - - - True - False - Performers - - - 1 - False - - - True - True - 2 - - + - - content - - - True - False - vertical - - - True - False - Recording - - - False - True - 0 - - - - - True - False - True - True - - - False - True - 1 - - - - + loading - 1 - + + + vertical + + + false + + + Recording + + + + + + + + true + true + center + center + true + + + + + diff --git a/res/ui/recording_screen.ui b/res/ui/recording_screen.ui index a33986f..59de962 100644 --- a/res/ui/recording_screen.ui +++ b/res/ui/recording_screen.ui @@ -1,85 +1,65 @@ - - + - True - False vertical - True - False - True + + + vertical + center + + + Recording + + + + + + + + + + - True - True - True - - - True - False - go-previous-symbolic - - + go-previous-symbolic - + - True - True - False - True + view-more-symbolic menu - - - True - False - view-more-symbolic - - - - end - 1 - - - False - True - 0 - - True - False - - True - False - True - - + loading - + + + True + + + - - True - True - - - True - False - none + + content + + + true - True - False 12 12 18 @@ -87,77 +67,39 @@ 800 - True - False vertical 12 - True - False start Tracks - - False - True - 0 - - True - False - 0 - in - - - - - False - True - 1 - Add to playlist - True - True - True end - - False - True - 2 - - + - - content - 1 - - - True - True - 1 - diff --git a/res/ui/recording_selector.ui b/res/ui/recording_selector.ui deleted file mode 100644 index 6c4279d..0000000 --- a/res/ui/recording_selector.ui +++ /dev/null @@ -1,258 +0,0 @@ - - - - - - - True - False - vertical - - - True - False - True - True - - - False - True - 0 - - - - - True - False - Select a composer on the left. - - - True - True - 1 - - - - - True - False - sidebar_box - - - 250 - True - False - False - vertical - - - True - False - Select a recording - False - - - True - True - True - - - True - False - list-add-symbolic - - - - - - - False - True - 0 - - - - - True - False - True - - - True - False - vertical - 6 - - - True - True - edit-find-symbolic - False - False - Search composers … - - - False - True - 0 - - - - - Show recordings from the server - True - True - False - True - True - - - False - True - 1 - - - - - - - False - True - 1 - - - - - True - False - False - crossfade - True - - - True - False - True - - - loading - - - - - True - True - - - - - - content - 1 - - - - - True - False - center - center - 18 - vertical - 18 - - - True - False - 0.5019607843137255 - 80 - network-error-symbolic - - - False - True - 0 - - - - - True - False - 0.5019607843137255 - An error occured! - - - - - - False - True - 1 - - - - - True - False - 0.5019607843137255 - The server was not reachable or responded with an error. Please check your internet connection. - center - True - 40 - - - False - True - 2 - - - - - Try again - True - True - True - center - - - - False - True - 3 - - - - - error - 2 - - - - - True - True - 2 - - - - - sidebar - - - - - True - False - vertical - - - - False - - - - diff --git a/res/ui/recording_selector_screen.ui b/res/ui/recording_selector_screen.ui deleted file mode 100644 index ee1e7ae..0000000 --- a/res/ui/recording_selector_screen.ui +++ /dev/null @@ -1,153 +0,0 @@ - - - - - - - True - False - vertical - - - True - False - True - True - - - True - True - True - - - True - False - go-previous-symbolic - - - - - - - False - True - 0 - - - - - True - False - False - crossfade - True - - - True - False - True - - - loading - - - - - True - True - - - - - - content - 1 - - - - - True - False - center - center - 18 - vertical - 18 - - - True - False - 0.5019607843137255 - 80 - network-error-symbolic - - - False - True - 0 - - - - - True - False - 0.5019607843137255 - An error occured! - - - - - - False - True - 1 - - - - - True - False - 0.5019607843137255 - The server was not reachable or responded with an error. Please check your internet connection. - center - True - 40 - - - False - True - 2 - - - - - Try again - True - True - True - center - - - - False - True - 3 - - - - - error - 2 - - - - - True - True - 1 - - - - diff --git a/res/ui/selector.ui b/res/ui/selector.ui index 0cba2a8..231d813 100644 --- a/res/ui/selector.ui +++ b/res/ui/selector.ui @@ -1,264 +1,174 @@ - - + 250 - True - False False vertical - True - False + false + + + vertical + center + + + + + + + + false + + + + + - True - True - True - - - True - False - go-previous-symbolic - - + go-previous-symbolic - + - True - True - True - - - True - False - list-add-symbolic - - + list-add-symbolic - - end - 1 - - - False - True - 0 - - - True - False + True - True - False 500 300 + true - True - False vertical 6 - True - True - edit-find-symbolic - False - False Search … - - False - True - 0 - Use the Musicus server - True - True - False start True - True - - False - True - 1 - - - False - True - 1 - - True - False False False crossfade True - - True - False - 12 - True - - + loading - + + + 12 + center + start + True + + + - - 200 - True - True - - - True - False - none + + content + + + 200 + true - True - False 500 300 - True - False start 6 6 12 6 - 0 - in - - - - - - - + - - content - 1 - - - True - False - center - center - 18 - vertical - 18 - - - True - False - 0.5 - 80 - network-error-symbolic - - - False - True - 0 - - - - - True - False - 0.5 - An error occured! - - - - - - False - True - 1 - - - - - True - False - 0.5 - The server was not reachable or responded with an error. Please check your internet connection. - center - True - 40 - - - False - True - 2 - - - - - Try again - True - True - True - center - - - - False - True - 3 - - - - + error - 2 - + + + center + center + 18 + 18 + 18 + 18 + vertical + 18 + + + 0.5 + 80 + network-error-symbolic + + + + + 0.5 + An error occured! + + + + + + + + 0.5 + The server was not reachable or responded with an error. Please check your internet connection. + center + True + 40 + + + + + Try again + center + + + + + + - - True - True - 2 - diff --git a/res/ui/server_dialog.ui b/res/ui/server_dialog.ui index c4401b5..98f9acd 100644 --- a/res/ui/server_dialog.ui +++ b/res/ui/server_dialog.ui @@ -1,94 +1,56 @@ - - - + + - False True - True - dialog - True - False vertical - True - False - Server + false + + + Server + + + Cancel - True - True - True - + Set - True - True - True True - True - - end - 1 - - - False - True - 0 - - - - True - False - 18 - 12 - 6 + + none - - True - False - end - URL + + True + URL + url_entry + + + center + True + + - - 0 - 0 - - - - - True - True - True - True - True - - - 1 - 0 - - - False - True - 1 - diff --git a/res/ui/source_selector.ui b/res/ui/source_selector.ui index 876f366..7de7e90 100644 --- a/res/ui/source_selector.ui +++ b/res/ui/source_selector.ui @@ -1,25 +1,24 @@ - + - True - False vertical - True - False - Import music + false + + + Import music + + + - True - True - True - True - False go-previous-symbolic @@ -29,131 +28,90 @@ - True - False crossfade - - True - False - vertical - - - True - False - error - False - - - False - 16 + + start + + + vertical + + + error + False - True - False Failed to load the CD. Make sure you have inserted it into your drive. True - - - - - True - False - True - center - center - 18 - vertical - 18 - - True - False - 0.5019607843137255 - 80 - media-optical-cd-audio-symbolic - - - False - True - 0 - - - - - True - False - 0.5019607843137255 - Import from audio CD - - - - - - False - True - 1 - - - - - True - False - 0.5019607843137255 - Insert an audio compact disc into your drive and click the button below. The disc will be copied in the background while you set up the metadata. - center - True - 40 - - - False - True - 2 - - - - - Import - True - True - True + + True center - + center + 18 + 18 + 18 + 18 + vertical + 18 + + + 0.5019607843137255 + 80 + media-optical-cd-audio-symbolic + + + + + 0.5019607843137255 + Import from audio CD + + + + + + + + 0.5019607843137255 + Insert an audio compact disc into your drive and click the button below. The disc will be copied in the background while you set up the metadata. + center + True + 40 + + + + + Import + True + center + + + - - False - True - 3 - - - True - True - 1 - - + - - start - - - True - False - True - - + loading - 1 - + + + True + center + center + true + true + + + diff --git a/res/ui/track_editor.ui b/res/ui/track_editor.ui index 9657d7d..63d35c1 100644 --- a/res/ui/track_editor.ui +++ b/res/ui/track_editor.ui @@ -1,69 +1,47 @@ - + - True vertical - True - Track + false + + + Track + + + - True - True - - - True - go-previous-symbolic - - + go-previous-symbolic - + - True - True + object-select-symbolic - - - True - object-select-symbolic - - - - end - - True - True True - - True - none + - - True - - - True - start - 12 - 6 - 6 - 6 - in - - + + start + 12 + 6 + 6 + 6 diff --git a/res/ui/track_selector.ui b/res/ui/track_selector.ui index 9654441..090966c 100644 --- a/res/ui/track_selector.ui +++ b/res/ui/track_selector.ui @@ -1,70 +1,48 @@ - + - True vertical - True - Select tracks + false + + + Select tracks + + + - True - True - - - True - go-previous-symbolic - - + go-previous-symbolic - + - True - True False + object-select-symbolic - - - True - object-select-symbolic - - - - end - - True - True True - - True - none + - - True - - - True - start - 12 - 6 - 6 - 6 - in - - + + start + 12 + 6 + 6 + 6 diff --git a/res/ui/track_set_editor.ui b/res/ui/track_set_editor.ui index 95f0c1a..4e822db 100644 --- a/res/ui/track_set_editor.ui +++ b/res/ui/track_set_editor.ui @@ -1,148 +1,107 @@ - + - True vertical - True - Tracks + false + + + Import music + + + - True - True - - - True - go-previous-symbolic - - + go-previous-symbolic - + - True - True + object-select-symbolic False - - - True - object-select-symbolic - - - - end - - True - True True - - True - none + - - True + + 6 + 6 + 6 + vertical + + + start + 12 + 6 + Recording + + + + + + + + + + none + + + True + Select a recording + select_recording_button + + + Select + center + + + + + + + + - True - 6 - 6 + horizontal + 12 6 - vertical - True start - 12 - 6 - Recording + end + True + Tracks - - True - in - - - True - none - - - True - True - True - Select a recording - select_recording_button - - - Select - True - True - center - - - - - - - - - - - True - horizontal - 12 - 6 - - - True - start - end - True - Tracks - - - - - - - - True - True - none - - - True - document-edit-symbolic - - - - - - - - - True - in + + false + document-edit-symbolic + + + diff --git a/res/ui/window.ui b/res/ui/window.ui index 633308c..8748236 100644 --- a/res/ui/window.ui +++ b/res/ui/window.ui @@ -1,326 +1,224 @@ - - + - True - False vertical - True - False True - True + True + + + - - False - True - 0 - - True - False + True center center - 18 + 18 + 18 + 18 + 18 vertical 18 - True - False 0.5 80 folder-music-symbolic - - False - True - 0 - - True - False 0.5 Welcome to Musicus! - - False - True - 1 - - True - False 0.5 Get startet by selecting something from the sidebar or adding new things to your library using the button in the top left corner. center True 40 - - False - True - 2 - - - True - True - 1 - - False 800 566 - True - False crossfade - - True - False - vertical - - - True - False - Musicus - True - - - False - True - 0 - - - - - True - False - center - center - 18 - vertical - 18 - - - True - False - 0.5019607843137255 - 80 - folder-music-symbolic - - - False - True - 0 - - - - - True - False - 0.5019607843137255 - Welcome to Musicus! - - - - - - False - True - 1 - - - - - True - False - 0.5019607843137255 - Get startet by selecting the folder containing your music files! Musicus will create a new database there or open one that already exists. - center - True - 40 - - - False - True - 2 - - - - - Select folder - True - True - True - center - - - - False - True - 3 - - - - - True - True - 1 - - - - + empty - - - - - True - False - vertical - - - True - False - True - - - False - True - 0 - - - - - True - False - True - - - True - True - 1 - - - - - loading - 1 - - - - - True - False - vertical - - - True - False + + + vertical - - 250 - True - False - False - vertical - - - True - False - Musicus - - - True - True - True - - - True - False - list-add-symbolic - - - - - - - True - True - False - True - menu - - - True - False - open-menu-symbolic - - - - - end - 1 - - + + + + Musicus + + + + + + + + True + center + center + 18 + 18 + 18 + 18 + vertical + 18 + + + 0.5019607843137255 + 80 + folder-music-symbolic + + + + + 0.5019607843137255 + Welcome to Musicus! + + + + + + + + 0.5019607843137255 + Get startet by selecting the folder containing your music files! Musicus will create a new database there or open one that already exists. + center + True + 40 + + + + + Select folder + True + center + - - False - True - 0 - - - sidebar - - - - - True - False - vertical - - - - False - - - True - True - 0 - - + - + + + + loading + + + vertical + + + + + + + True + True + True + center + center + + + + + + + + content - 2 - + + + vertical + + + true + + + + + 250 + False + vertical + + + False + + + Musicus + + + + + + True + + + list-add-symbolic + + + + + + + True + open-menu-symbolic + menu + + + + + + + + + + + False + + + vertical + + + + + + + + + + diff --git a/res/ui/work_editor.ui b/res/ui/work_editor.ui index 9c78695..b8a2769 100644 --- a/res/ui/work_editor.ui +++ b/res/ui/work_editor.ui @@ -1,529 +1,223 @@ - - + - True - False - - True - False - vertical - - + + content + + True False - Work + vertical - - True - True - True + + false + + + Work + + + - - True - False - go-previous-symbolic + + go-previous-symbolic - - - - - True - False - True - True - - - True - False + + + False object-select-symbolic + - - - end - 1 - - - - - False - True - 0 - - - - - True - False - False - - - False - - - False - False - 0 - - - - - False - - - False - False - 0 - - + + False + - - - False - True - 1 - - - - - True - True - False - - True - False - 500 - 300 + + true - - - True - False - 18 - 12 - 6 + + 12 + 12 + 18 + 12 - - True - True - True - True + + 6 + 6 + 6 + vertical - - True - False + start - Select … - end + 12 + 6 + Overview + + + - - - 1 - 1 - - - - - True - False - end - Composer - - - 0 - 1 - - - - - True - True - - - 1 - 0 - - - - - True - False - end - Title - - - 0 - 0 - - - - - True - False - end - Publish - - - 0 - 2 - - - - - True - True - start - True - - - 1 - 2 - - - - - - - - - True - False - Overview - - - False - - - - - True - False - 800 - 300 - - - True - False - 18 - 6 - - - True - True - in - - - - - True - True - 0 - - - - - True - False - 0 - vertical - 6 - - - True - True - True + - - True - False + + none + + + True + Select a composer + composer_button + + + Select + center + + + + + + + True + Title + title_entry + + + center + True + + + + + + + True + Publish to the server + upload_switch + + + center + True + + + + + + + + + + + horizontal + 12 + 6 + + + start + end + True + Instruments + + + + + + + + false list-add-symbolic - - False - True - 0 - - - True - True - True + + + + + horizontal + 12 + 6 - - True - False - list-remove-symbolic + + start + end + True + Structure + + + - - - False - True - 1 - - - - - False - True - 1 - - - - - - - 1 - - - - - True - False - Instruments - - - 1 - False - - - - - True - False - 800 - 300 - - - True - False - 18 - 6 - - - True - True - in - - - - - - True - True - 0 - - - - - True - False - vertical - 6 - - - True - True - True - - True - False - list-add-symbolic - - - - - False - True - 0 - - - - - True - True - True - - - True - False + + false folder-new-symbolic - - - False - True - 1 - - - - - True - True - True - - True - False - edit-symbolic + + false + list-add-symbolic - - False - True - 2 - - - True - True - True - - - True - False - list-remove-symbolic - - - - - False - True - 3 - - - - - True - True - True - - - True - False - go-down-symbolic - - - - - False - True - end - 4 - - - - - True - True - True - - - True - False - go-up-symbolic - - - - - False - True - end - 5 - + - - False - True - 1 - - - 2 - - - - - True - False - Structure - - - 2 - False - - - True - True - 2 - - + - - content - - - True - False - vertical - - - True - False - Work - - - False - True - 0 - - - - - True - False - True - True - - - False - True - 1 - - - - + loading - 1 - + + + vertical + + + false + + + Work + + + + + + + + true + true + center + center + True + + + + + diff --git a/res/ui/work_part_editor.ui b/res/ui/work_part_editor.ui index c64335b..c360b6e 100644 --- a/res/ui/work_part_editor.ui +++ b/res/ui/work_part_editor.ui @@ -1,169 +1,97 @@ - - + - True - False vertical - True - False - Work part + false + + + Work part + + + - True - True - True - - - True - False - go-previous-symbolic - - + go-previous-symbolic - + - True - True - True - - - True - False - object-select-symbolic - - + object-select-symbolic - - end - 1 - - - False - True - 0 - - - True - False - 500 - 300 + + False + + + + + true - - - True - False - 18 - 12 - 6 + + 12 + 12 + 18 + 12 + 500 + 300 - - True - False - end - Composer - - - 0 - 1 - - - - - True - True - True - - - 1 - 0 - - - - - True - False - end - Title - - - 0 - 0 - - - - - True - False - True + + start - - True - True - True - True + + none - - True - False - start - Select … + + True + Title + title_entry + + + center + True + + + + + + + True + Select a composer + composer_button + + + false + user-trash-symbolic + center + + + + + Select + center + + - - False - True - 0 - - - - True - True - - - True - False - user-trash-symbolic - - - - - False - True - 1 - - - - - 1 - 1 - - - False - True - 1 - diff --git a/res/ui/work_screen.ui b/res/ui/work_screen.ui index 2bc3ed6..2c5e5fe 100644 --- a/res/ui/work_screen.ui +++ b/res/ui/work_screen.ui @@ -1,132 +1,86 @@ - - + - True - False vertical - True - False - True + + + vertical + center + + + Work + + + + + + + + + + - True - True - True - - - True - False - go-previous-symbolic - - + go-previous-symbolic - + - True - True - False - True + view-more-symbolic menu - - - True - False - view-more-symbolic - - - - end - 1 - - + - True - True - True - - - True - False - edit-find-symbolic - - + edit-find-symbolic - - end - 1 - - - False - True - 0 - - - True - False + False - True - False 400 + true - True - True - edit-find-symbolic - False - False Search recordings … - - False - True - 1 - - True - False - - True - False - True - - + loading - + + + True + + + - - True - True - - - True - False - none + + content + + + true - True - False 12 12 18 @@ -134,71 +88,40 @@ 800 - True - False vertical 12 - True - False start Recordings - - False - True - 0 - - True - False - 0 - in - - - - - False - True - 1 - - + - - content - 1 - - - True - False - No recordings found. - - + nothing - 2 - + + + No recordings found. + + + - - True - True - 2 - diff --git a/res/ui/work_section_editor.ui b/res/ui/work_section_editor.ui index 450481f..db9480d 100644 --- a/res/ui/work_section_editor.ui +++ b/res/ui/work_section_editor.ui @@ -1,104 +1,77 @@ - - + - True - False vertical - True - False - Work section + false + + + Work section + + + - True - True - True - - - True - False - go-previous-symbolic - - + go-previous-symbolic - + - True - True - True - - - True - False - object-select-symbolic - - + object-select-symbolic - - end - 1 - - - False - True - 0 - - - True - False - 500 - 300 + + False + + + + + true - - - True - False - 18 - 12 - 6 + + 12 + 12 + 18 + 12 + 500 + 300 - - True - False - end - Title + + start + + + none + + + True + Title + title_entry + + + center + True + + + + + + - - 0 - 0 - - - - - True - True - True - - - 1 - 0 - - - False - True - 1 - diff --git a/res/ui/work_selector.ui b/res/ui/work_selector.ui deleted file mode 100644 index 7f079b9..0000000 --- a/res/ui/work_selector.ui +++ /dev/null @@ -1,275 +0,0 @@ - - - - - - - True - False - vertical - - - True - False - True - True - - - False - True - 0 - - - - - True - False - Select a composer on the left. - - - True - True - 1 - - - - - True - False - sidebar_box - - - 250 - True - False - False - vertical - - - True - False - Select a work - - - True - True - True - - - True - False - go-previous-symbolic - - - - - - - True - True - True - - - True - False - list-add-symbolic - - - - - end - 1 - - - - - False - True - 0 - - - - - True - False - True - - - True - False - vertical - 6 - - - True - True - edit-find-symbolic - False - False - Search composers … - - - False - True - 0 - - - - - Show works from the server - True - True - False - True - True - - - False - True - 1 - - - - - - - False - True - 1 - - - - - True - False - False - crossfade - True - - - True - False - True - - - loading - - - - - True - True - - - - - - content - 1 - - - - - True - False - center - center - 18 - vertical - 18 - - - True - False - 0.5019607843137255 - 80 - network-error-symbolic - - - False - True - 0 - - - - - True - False - 0.5019607843137255 - An error occured! - - - - - - False - True - 1 - - - - - True - False - 0.5019607843137255 - The server was not reachable or responded with an error. Please check your internet connection. - center - True - 40 - - - False - True - 2 - - - - - Try again - True - True - True - center - - - - False - True - 3 - - - - - error - 2 - - - - - True - True - 2 - - - - - sidebar - - - - - True - False - vertical - - - - False - - - - diff --git a/res/ui/work_selector_screen.ui b/res/ui/work_selector_screen.ui deleted file mode 100644 index ee1e7ae..0000000 --- a/res/ui/work_selector_screen.ui +++ /dev/null @@ -1,153 +0,0 @@ - - - - - - - True - False - vertical - - - True - False - True - True - - - True - True - True - - - True - False - go-previous-symbolic - - - - - - - False - True - 0 - - - - - True - False - False - crossfade - True - - - True - False - True - - - loading - - - - - True - True - - - - - - content - 1 - - - - - True - False - center - center - 18 - vertical - 18 - - - True - False - 0.5019607843137255 - 80 - network-error-symbolic - - - False - True - 0 - - - - - True - False - 0.5019607843137255 - An error occured! - - - - - - False - True - 1 - - - - - True - False - 0.5019607843137255 - The server was not reachable or responded with an error. Please check your internet connection. - center - True - 40 - - - False - True - 2 - - - - - Try again - True - True - True - center - - - - False - True - 3 - - - - - error - 2 - - - - - True - True - 1 - - - - diff --git a/src/dialogs/about.rs b/src/dialogs/about.rs index 147d179..abfa407 100644 --- a/src/dialogs/about.rs +++ b/src/dialogs/about.rs @@ -17,6 +17,5 @@ pub fn show_about_dialog>(parent: &W) { .authors(vec![String::from("Elias Projahn ")]) .build(); - dialog.connect_response(|dialog, _| dialog.close()); dialog.show(); } diff --git a/src/dialogs/login_dialog.rs b/src/dialogs/login_dialog.rs index e13c2cd..682c733 100644 --- a/src/dialogs/login_dialog.rs +++ b/src/dialogs/login_dialog.rs @@ -52,8 +52,8 @@ impl LoginDialog { this.stack.set_visible_child_name("loading"); let data = LoginData { - username: this.username_entry.get_text().to_string(), - password: this.password_entry.get_text().to_string(), + username: this.username_entry.get_text().unwrap().to_string(), + password: this.password_entry.get_text().unwrap().to_string(), }; let c = glib::MainContext::default(); diff --git a/src/dialogs/preferences.rs b/src/dialogs/preferences.rs index a12fe77..ccb94ed 100644 --- a/src/dialogs/preferences.rs +++ b/src/dialogs/preferences.rs @@ -43,21 +43,32 @@ impl Preferences { // Connect signals and callbacks select_music_library_path_button.connect_clicked(clone!(@strong this => move |_| { - let dialog = gtk::FileChooserNative::new( + let dialog = gtk::FileChooserDialog::new( Some(&gettext("Select music library folder")), - Some(&this.window), gtk::FileChooserAction::SelectFolder,None, None); + Some(&this.window), + gtk::FileChooserAction::SelectFolder, + &[ + (&gettext("Cancel"), gtk::ResponseType::Cancel), + (&gettext("Select"), gtk::ResponseType::Accept), + ]); - if let gtk::ResponseType::Accept = dialog.run() { - if let Some(path) = dialog.get_filename() { - this.music_library_path_row.set_subtitle(Some(path.to_str().unwrap())); + dialog.connect_response(clone!(@strong this => move |dialog, response| { + if let gtk::ResponseType::Accept = response { + if let Some(file) = dialog.get_file() { + if let Some(path) = file.get_path() { + this.music_library_path_row.set_subtitle(Some(path.to_str().unwrap())); - let context = glib::MainContext::default(); - let backend = this.backend.clone(); - context.spawn_local(async move { - backend.set_music_library_path(path).await.unwrap(); - }); + let context = glib::MainContext::default(); + let backend = this.backend.clone(); + context.spawn_local(async move { + backend.set_music_library_path(path).await.unwrap(); + }); + } + } } - } + })); + + dialog.show(); })); url_button.connect_clicked(clone!(@strong this => move |_| { diff --git a/src/dialogs/server_dialog.rs b/src/dialogs/server_dialog.rs index 4d23550..90d9d39 100644 --- a/src/dialogs/server_dialog.rs +++ b/src/dialogs/server_dialog.rs @@ -40,7 +40,7 @@ impl ServerDialog { })); set_button.connect_clicked(clone!(@strong this => move |_| { - let url = this.url_entry.get_text().to_string(); + let url = this.url_entry.get_text().unwrap().to_string(); this.backend.set_server_url(&url).unwrap(); if let Some(cb) = &*this.selected_cb.borrow() { diff --git a/src/editors/ensemble.rs b/src/editors/ensemble.rs index 8508f29..769b0cc 100644 --- a/src/editors/ensemble.rs +++ b/src/editors/ensemble.rs @@ -94,7 +94,7 @@ impl EnsembleEditor { /// Save the ensemble and possibly upload it to the server. async fn save(self: Rc) -> Result<()> { - let name = self.name_entry.get_text().to_string(); + let name = self.name_entry.get_text().unwrap().to_string(); let ensemble = Ensemble { id: self.id.clone(), diff --git a/src/editors/instrument.rs b/src/editors/instrument.rs index 34b0960..801a756 100644 --- a/src/editors/instrument.rs +++ b/src/editors/instrument.rs @@ -94,7 +94,7 @@ impl InstrumentEditor { /// Save the instrument and possibly upload it to the server. async fn save(self: Rc) -> Result<()> { - let name = self.name_entry.get_text().to_string(); + let name = self.name_entry.get_text().unwrap().to_string(); let instrument = Instrument { id: self.id.clone(), diff --git a/src/editors/performance.rs b/src/editors/performance.rs index a3f520f..e9cc0e0 100644 --- a/src/editors/performance.rs +++ b/src/editors/performance.rs @@ -6,6 +6,7 @@ use gettextrs::gettext; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; +use libhandy::prelude::*; use std::cell::RefCell; use std::rc::Rc; @@ -14,9 +15,9 @@ pub struct PerformanceEditor { backend: Rc, widget: gtk::Box, save_button: gtk::Button, - person_label: gtk::Label, - ensemble_label: gtk::Label, - role_label: gtk::Label, + person_row: libhandy::ActionRow, + ensemble_row: libhandy::ActionRow, + role_row: libhandy::ActionRow, reset_role_button: gtk::Button, person: RefCell>, ensemble: RefCell>, @@ -39,17 +40,17 @@ impl PerformanceEditor { get_widget!(builder, gtk::Button, ensemble_button); get_widget!(builder, gtk::Button, role_button); get_widget!(builder, gtk::Button, reset_role_button); - get_widget!(builder, gtk::Label, person_label); - get_widget!(builder, gtk::Label, ensemble_label); - get_widget!(builder, gtk::Label, role_label); + get_widget!(builder, libhandy::ActionRow, person_row); + get_widget!(builder, libhandy::ActionRow, ensemble_row); + get_widget!(builder, libhandy::ActionRow, role_row); let this = Rc::new(PerformanceEditor { backend, widget, save_button, - person_label, - ensemble_label, - role_label, + person_row, + ensemble_row, + role_row, reset_role_button, person: RefCell::new(None), ensemble: RefCell::new(None), @@ -166,30 +167,36 @@ impl PerformanceEditor { /// Update the UI according to person. fn show_person(&self, person: Option<&Person>) { if let Some(person) = person { - self.person_label.set_text(&person.name_fl()); + self.person_row.set_title(Some(&gettext("Person"))); + self.person_row.set_subtitle(Some(&person.name_fl())); self.save_button.set_sensitive(true); } else { - self.person_label.set_text(&gettext("Select …")); + self.person_row.set_title(Some(&gettext("Select a person"))); + self.person_row.set_subtitle(None); } } /// Update the UI according to ensemble. fn show_ensemble(&self, ensemble: Option<&Ensemble>) { if let Some(ensemble) = ensemble { - self.ensemble_label.set_text(&ensemble.name); + self.ensemble_row.set_title(Some(&gettext("Ensemble"))); + self.ensemble_row.set_subtitle(Some(&ensemble.name)); self.save_button.set_sensitive(true); } else { - self.ensemble_label.set_text(&gettext("Select …")); + self.ensemble_row.set_title(Some(&gettext("Select an ensemble"))); + self.ensemble_row.set_subtitle(None); } } /// Update the UI according to role. fn show_role(&self, role: Option<&Instrument>) { if let Some(role) = role { - self.role_label.set_text(&role.name); + self.role_row.set_title(Some(&gettext("Role"))); + self.role_row.set_subtitle(Some(&role.name)); self.reset_role_button.show(); } else { - self.role_label.set_text(&gettext("Select …")); + self.role_row.set_title(Some(&gettext("Select a role"))); + self.role_row.set_subtitle(None); self.reset_role_button.hide(); } } diff --git a/src/editors/person.rs b/src/editors/person.rs index fdf0e8e..9863397 100644 --- a/src/editors/person.rs +++ b/src/editors/person.rs @@ -98,8 +98,8 @@ impl PersonEditor { /// Save the person and possibly upload it to the server. async fn save(self: Rc) -> Result<()> { - let first_name = self.first_name_entry.get_text().to_string(); - let last_name = self.last_name_entry.get_text().to_string(); + let first_name = self.first_name_entry.get_text().unwrap().to_string(); + let last_name = self.last_name_entry.get_text().unwrap().to_string(); let person = Person { id: self.id.clone(), diff --git a/src/editors/recording.rs b/src/editors/recording.rs index a93bb93..706e780 100644 --- a/src/editors/recording.rs +++ b/src/editors/recording.rs @@ -8,20 +8,20 @@ use gettextrs::gettext; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; +use libhandy::prelude::*; use std::cell::RefCell; use std::rc::Rc; /// A widget for creating or editing a recording. -// TODO: Disable buttons if no performance is selected. pub struct RecordingEditor { pub widget: gtk::Stack, backend: Rc, save_button: gtk::Button, info_bar: gtk::InfoBar, - work_label: gtk::Label, + work_row: libhandy::ActionRow, comment_entry: gtk::Entry, upload_switch: gtk::Switch, - performance_list: Rc>, + performance_list: Rc, id: String, work: RefCell>, performances: RefCell>, @@ -40,17 +40,15 @@ impl RecordingEditor { get_widget!(builder, gtk::Button, back_button); get_widget!(builder, gtk::Button, save_button); get_widget!(builder, gtk::InfoBar, info_bar); + get_widget!(builder, libhandy::ActionRow, work_row); get_widget!(builder, gtk::Button, work_button); - get_widget!(builder, gtk::Label, work_label); get_widget!(builder, gtk::Entry, comment_entry); get_widget!(builder, gtk::Switch, upload_switch); - get_widget!(builder, gtk::ScrolledWindow, scroll); + get_widget!(builder, gtk::Frame, performance_frame); get_widget!(builder, gtk::Button, add_performer_button); - get_widget!(builder, gtk::Button, edit_performer_button); - get_widget!(builder, gtk::Button, remove_performer_button); - let performance_list = List::new(&gettext("No performers added.")); - scroll.add(&performance_list.widget); + let performance_list = List::new(); + performance_frame.set_child(Some(&performance_list.widget)); let (id, work, performances) = match recording { Some(recording) => { @@ -65,7 +63,7 @@ impl RecordingEditor { backend, save_button, info_bar, - work_label, + work_row, comment_entry, upload_switch, performance_list, @@ -130,16 +128,60 @@ impl RecordingEditor { } })); - this.performance_list.set_make_widget(|performance| { - let label = gtk::Label::new(Some(&performance.get_title())); - label.set_ellipsize(pango::EllipsizeMode::End); - label.set_halign(gtk::Align::Start); - label.set_margin_start(6); - label.set_margin_end(6); - label.set_margin_top(6); - label.set_margin_bottom(6); - label.upcast() - }); + this.performance_list.set_make_widget_cb(clone!(@strong this => move |index| { + let performance = &this.performances.borrow()[index]; + + let delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic")); + delete_button.set_valign(gtk::Align::Center); + + delete_button.connect_clicked(clone!(@strong this => move |_| { + let length = { + let mut performances = this.performances.borrow_mut(); + performances.remove(index); + performances.len() + }; + + this.performance_list.update(length); + })); + + let edit_button = gtk::Button::from_icon_name(Some("document-edit-symbolic")); + edit_button.set_valign(gtk::Align::Center); + + edit_button.connect_clicked(clone!(@strong this => move |_| { + let navigator = this.navigator.borrow().clone(); + if let Some(navigator) = navigator { + let performance = &this.performances.borrow()[index]; + + let editor = PerformanceEditor::new( + this.backend.clone(), + Some(performance.clone()), + ); + + editor.set_selected_cb(clone!(@strong this, @strong navigator => move |performance| { + let length = { + let mut performances = this.performances.borrow_mut(); + performances[index] = performance; + performances.len() + }; + + this.performance_list.update(length); + + navigator.clone().pop(); + })); + + navigator.push(editor); + } + })); + + let row = libhandy::ActionRow::new(); + row.set_activatable(true); + row.set_title(Some(&performance.get_title())); + row.add_suffix(&delete_button); + row.add_suffix(&edit_button); + row.set_activatable_widget(Some(&edit_button)); + + row.upcast() + })); add_performer_button.connect_clicked(clone!(@strong this => move |_| { let navigator = this.navigator.borrow().clone(); @@ -147,16 +189,13 @@ impl RecordingEditor { let editor = PerformanceEditor::new(this.backend.clone(), None); editor.set_selected_cb(clone!(@strong this, @strong navigator => move |performance| { - let mut performances = this.performances.borrow_mut(); - - let index = match this.performance_list.get_selected_index() { - Some(index) => index + 1, - None => performances.len(), + let length = { + let mut performances = this.performances.borrow_mut(); + performances.push(performance); + performances.len() }; - performances.insert(index, performance); - this.performance_list.show_items(performances.clone()); - this.performance_list.select_index(index); + this.performance_list.update(length); navigator.clone().pop(); })); @@ -165,47 +204,14 @@ impl RecordingEditor { } })); - edit_performer_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - if let Some(index) = this.performance_list.get_selected_index() { - let performance = &this.performances.borrow()[index]; - - let editor = PerformanceEditor::new( - this.backend.clone(), - Some(performance.clone()), - ); - - editor.set_selected_cb(clone!(@strong this, @strong navigator => move |performance| { - let mut performances = this.performances.borrow_mut(); - performances[index] = performance; - this.performance_list.show_items(performances.clone()); - this.performance_list.select_index(index); - navigator.clone().pop(); - })); - - navigator.push(editor); - } - } - })); - - remove_performer_button.connect_clicked(clone!(@strong this => move |_| { - if let Some(index) = this.performance_list.get_selected_index() { - let mut performances = this.performances.borrow_mut(); - performances.remove(index); - this.performance_list.show_items(performances.clone()); - this.performance_list.select_index(index); - } - })); - // Initialize if let Some(work) = &*this.work.borrow() { this.work_selected(work); } - this.performance_list - .show_items(this.performances.borrow().clone()); + let length = this.performances.borrow().len(); + this.performance_list.update(length); this } @@ -217,8 +223,8 @@ impl RecordingEditor { /// Update the UI according to work. fn work_selected(&self, work: &Work) { - self.work_label - .set_text(&format!("{}: {}", work.composer.name_fl(), work.title)); + self.work_row.set_title(Some(&gettext("Work"))); + self.work_row.set_subtitle(Some(&work.get_title())); self.save_button.set_sensitive(true); } @@ -231,7 +237,7 @@ impl RecordingEditor { .borrow() .clone() .expect("Tried to create recording without work!"), - comment: self.comment_entry.get_text().to_string(), + comment: self.comment_entry.get_text().unwrap().to_string(), performances: self.performances.borrow().clone(), }; diff --git a/src/editors/work.rs b/src/editors/work.rs index 4a1c3c4..814f73e 100644 --- a/src/editors/work.rs +++ b/src/editors/work.rs @@ -9,6 +9,7 @@ use gettextrs::gettext; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; +use libhandy::prelude::*; use std::cell::RefCell; use std::convert::TryInto; use std::rc::Rc; @@ -20,6 +21,15 @@ enum PartOrSection { Section(WorkSection), } +impl PartOrSection { + pub fn get_title(&self) -> String { + match self { + PartOrSection::Part(part) => part.title.clone(), + PartOrSection::Section(section) => section.title.clone(), + } + } +} + /// A widget for editing and creating works. pub struct WorkEditor { widget: gtk::Stack, @@ -27,10 +37,10 @@ pub struct WorkEditor { save_button: gtk::Button, title_entry: gtk::Entry, info_bar: gtk::InfoBar, - composer_label: gtk::Label, + composer_row: libhandy::ActionRow, upload_switch: gtk::Switch, - instrument_list: Rc>, - part_list: Rc>, + instrument_list: Rc, + part_list: Rc, id: String, composer: RefCell>, instruments: RefCell>, @@ -52,24 +62,19 @@ impl WorkEditor { get_widget!(builder, gtk::InfoBar, info_bar); get_widget!(builder, gtk::Entry, title_entry); get_widget!(builder, gtk::Button, composer_button); - get_widget!(builder, gtk::Label, composer_label); + get_widget!(builder, libhandy::ActionRow, composer_row); get_widget!(builder, gtk::Switch, upload_switch); - get_widget!(builder, gtk::ScrolledWindow, instruments_scroll); + get_widget!(builder, gtk::Frame, instrument_frame); get_widget!(builder, gtk::Button, add_instrument_button); - get_widget!(builder, gtk::Button, remove_instrument_button); - get_widget!(builder, gtk::ScrolledWindow, structure_scroll); + get_widget!(builder, gtk::Frame, structure_frame); get_widget!(builder, gtk::Button, add_part_button); - get_widget!(builder, gtk::Button, remove_part_button); get_widget!(builder, gtk::Button, add_section_button); - get_widget!(builder, gtk::Button, edit_part_button); - get_widget!(builder, gtk::Button, move_part_up_button); - get_widget!(builder, gtk::Button, move_part_down_button); - let instrument_list = List::new(&gettext("No instruments added.")); - instruments_scroll.add(&instrument_list.widget); + let instrument_list = List::new(); + instrument_frame.set_child(Some(&instrument_list.widget)); - let part_list = List::new(&gettext("No work parts added.")); - structure_scroll.add(&part_list.widget); + let part_list = List::new(); + structure_frame.set_child(Some(&part_list.widget)); let (id, composer, instruments, structure) = match work { Some(work) => { @@ -100,7 +105,7 @@ impl WorkEditor { id, info_bar, title_entry, - composer_label, + composer_row, upload_switch, instrument_list, part_list, @@ -157,16 +162,28 @@ impl WorkEditor { } })); - this.instrument_list.set_make_widget(|instrument| { - let label = gtk::Label::new(Some(&instrument.name)); - label.set_ellipsize(pango::EllipsizeMode::End); - label.set_halign(gtk::Align::Start); - label.set_margin_start(6); - label.set_margin_end(6); - label.set_margin_top(6); - label.set_margin_bottom(6); - label.upcast() - }); + this.instrument_list.set_make_widget_cb(clone!(@strong this => move |index| { + let instrument = &this.instruments.borrow()[index]; + + let delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic")); + delete_button.set_valign(gtk::Align::Center); + + delete_button.connect_clicked(clone!(@strong this => move |_| { + let length = { + let mut instruments = this.instruments.borrow_mut(); + instruments.remove(index); + instruments.len() + }; + + this.instrument_list.update(length); + })); + + let row = libhandy::ActionRow::new(); + row.set_title(Some(&instrument.name)); + row.add_suffix(&delete_button); + + row.upcast() + })); add_instrument_button.connect_clicked(clone!(@strong this => move |_| { let navigator = this.navigator.borrow().clone(); @@ -174,17 +191,13 @@ impl WorkEditor { let selector = InstrumentSelector::new(this.backend.clone()); selector.set_selected_cb(clone!(@strong this, @strong navigator => move |instrument| { - let mut instruments = this.instruments.borrow_mut(); - - let index = match this.instrument_list.get_selected_index() { - Some(index) => index + 1, - None => instruments.len(), + let length = { + let mut instruments = this.instruments.borrow_mut(); + instruments.push(instrument.clone()); + instruments.len() }; - instruments.insert(index, instrument.clone()); - this.instrument_list.show_items(instruments.clone()); - this.instrument_list.select_index(index); - + this.instrument_list.update(length); navigator.clone().pop(); })); @@ -192,57 +205,107 @@ impl WorkEditor { } })); - remove_instrument_button.connect_clicked(clone!(@strong this => move |_| { - if let Some(index) = this.instrument_list.get_selected_index() { - let mut instruments = this.instruments.borrow_mut(); - instruments.remove(index); - this.instrument_list.show_items(instruments.clone()); - this.instrument_list.select_index(index); + this.part_list.set_make_widget_cb(clone!(@strong this => move |index| { + let pos = &this.structure.borrow()[index]; + + let drag_source = gtk::DragSource::new(); + drag_source.set_content(Some(&gdk::ContentProvider::new_for_value(&(index as u32).to_value()))); + + let drop_target = gtk::DropTarget::new(glib::Type::U32, gdk::DragAction::MOVE); + + drop_target.connect_property_value_notify(clone!(@strong this => move |drop_target| { + println!("{:?} -> {:?}", drop_target.get_value(), index); + })); + + let handle = gtk::Image::from_icon_name(Some("open-menu-symbolic")); + handle.add_controller(&drag_source); + + let delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic")); + delete_button.set_valign(gtk::Align::Center); + + delete_button.connect_clicked(clone!(@strong this => move |_| { + let length = { + let mut structure = this.structure.borrow_mut(); + structure.remove(index); + structure.len() + }; + + this.part_list.update(length); + })); + + let edit_button = gtk::Button::from_icon_name(Some("document-edit-symbolic")); + edit_button.set_valign(gtk::Align::Center); + + edit_button.connect_clicked(clone!(@strong this => move |_| { + let navigator = this.navigator.borrow().clone(); + if let Some(navigator) = navigator { + match this.structure.borrow()[index].clone() { + PartOrSection::Part(part) => { + let editor = WorkPartEditor::new(this.backend.clone(), Some(part)); + + editor.set_ready_cb(clone!(@strong this, @strong navigator => move |part| { + let length = { + let mut structure = this.structure.borrow_mut(); + structure[index] = PartOrSection::Part(part); + structure.len() + }; + + this.part_list.update(length); + navigator.clone().pop(); + })); + + navigator.push(editor); + } + PartOrSection::Section(section) => { + let editor = WorkSectionEditor::new(Some(section)); + + editor.set_ready_cb(clone!(@strong this, @strong navigator => move |section| { + let length = { + let mut structure = this.structure.borrow_mut(); + structure[index] = PartOrSection::Section(section); + structure.len() + }; + + this.part_list.update(length); + navigator.clone().pop(); + })); + + navigator.push(editor); + } + } + } + })); + + let row = libhandy::ActionRow::new(); + row.set_activatable(true); + row.set_title(Some(&pos.get_title())); + row.add_prefix(&handle); + row.add_suffix(&delete_button); + row.add_suffix(&edit_button); + row.set_activatable_widget(Some(&edit_button)); + row.add_controller(&drop_target); + + if let PartOrSection::Part(_) = pos { + // TODO: Replace with better solution to differentiate parts and sections. + row.set_margin_start(12); } + + row.upcast() })); - this.part_list.set_make_widget(|pos| { - let label = gtk::Label::new(None); - label.set_ellipsize(pango::EllipsizeMode::End); - label.set_halign(gtk::Align::Start); - label.set_margin_end(6); - label.set_margin_top(6); - label.set_margin_bottom(6); - - match pos { - PartOrSection::Part(part) => { - label.set_text(&part.title); - label.set_margin_start(12); - } - PartOrSection::Section(section) => { - let attrs = pango::AttrList::new(); - attrs.insert(pango::Attribute::new_weight(pango::Weight::Bold).unwrap()); - label.set_attributes(Some(&attrs)); - label.set_text(§ion.title); - label.set_margin_start(6); - } - } - - label.upcast() - }); - add_part_button.connect_clicked(clone!(@strong this => move |_| { let navigator = this.navigator.borrow().clone(); if let Some(navigator) = navigator { let editor = WorkPartEditor::new(this.backend.clone(), None); editor.set_ready_cb(clone!(@strong this, @strong navigator => move |part| { - let mut structure = this.structure.borrow_mut(); - - let index = match this.part_list.get_selected_index() { - Some(index) => index + 1, - None => structure.len(), + let length = { + let mut structure = this.structure.borrow_mut(); + structure.push(PartOrSection::Part(part)); + structure.len() }; - structure.insert(index, PartOrSection::Part(part)); - this.part_list.show_items(structure.clone()); - this.part_list.select_index(index); - + this.part_list.update(length); navigator.clone().pop(); })); @@ -256,17 +319,13 @@ impl WorkEditor { let editor = WorkSectionEditor::new(None); editor.set_ready_cb(clone!(@strong this, @strong navigator => move |section| { - let mut structure = this.structure.borrow_mut(); - - let index = match this.part_list.get_selected_index() { - Some(index) => index + 1, - None => structure.len(), + let length = { + let mut structure = this.structure.borrow_mut(); + structure.push(PartOrSection::Section(section)); + structure.len() }; - structure.insert(index, PartOrSection::Section(section)); - this.part_list.show_items(structure.clone()); - this.part_list.select_index(index); - + this.part_list.update(length); navigator.clone().pop(); })); @@ -274,82 +333,14 @@ impl WorkEditor { } })); - edit_part_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - if let Some(index) = this.part_list.get_selected_index() { - match this.structure.borrow()[index].clone() { - PartOrSection::Part(part) => { - let editor = WorkPartEditor::new(this.backend.clone(), Some(part)); - - editor.set_ready_cb(clone!(@strong this, @strong navigator => move |part| { - let mut structure = this.structure.borrow_mut(); - structure[index] = PartOrSection::Part(part); - this.part_list.show_items(structure.clone()); - this.part_list.select_index(index); - navigator.clone().pop(); - })); - - navigator.push(editor); - } - PartOrSection::Section(section) => { - let editor = WorkSectionEditor::new(Some(section)); - - editor.set_ready_cb(clone!(@strong this, @strong navigator => move |section| { - let mut structure = this.structure.borrow_mut(); - structure[index] = PartOrSection::Section(section); - this.part_list.show_items(structure.clone()); - this.part_list.select_index(index); - navigator.clone().pop(); - })); - - navigator.push(editor); - } - } - } - } - })); - - remove_part_button.connect_clicked(clone!(@strong this => move |_| { - if let Some(index) = this.part_list.get_selected_index() { - let mut structure = this.structure.borrow_mut(); - structure.remove(index); - this.part_list.show_items(structure.clone()); - this.part_list.select_index(index); - } - })); - - move_part_up_button.connect_clicked(clone!(@strong this => move |_| { - if let Some(index) = this.part_list.get_selected_index() { - if index > 0 { - let mut structure = this.structure.borrow_mut(); - structure.swap(index - 1, index); - this.part_list.show_items(structure.clone()); - this.part_list.select_index(index - 1); - } - } - })); - - move_part_down_button.connect_clicked(clone!(@strong this => move |_| { - if let Some(index) = this.part_list.get_selected_index() { - let mut structure = this.structure.borrow_mut(); - if index < structure.len() - 1 { - structure.swap(index, index + 1); - this.part_list.show_items(structure.clone()); - this.part_list.select_index(index + 1); - } - } - })); - // Initialization if let Some(composer) = &*this.composer.borrow() { this.show_composer(composer); } - this.instrument_list - .show_items(this.instruments.borrow().clone()); - this.part_list.show_items(this.structure.borrow().clone()); + this.instrument_list.update(this.instruments.borrow().len()); + this.part_list.update(this.structure.borrow().len()); this } @@ -361,7 +352,8 @@ impl WorkEditor { /// Update the UI according to person. fn show_composer(&self, person: &Person) { - self.composer_label.set_text(&person.name_fl()); + self.composer_row.set_title(Some(&gettext("Composer"))); + self.composer_row.set_subtitle(Some(&person.name_fl())); self.save_button.set_sensitive(true); } @@ -385,7 +377,7 @@ impl WorkEditor { let work = Work { id: self.id.clone(), - title: self.title_entry.get_text().to_string(), + title: self.title_entry.get_text().unwrap().to_string(), composer: self .composer .borrow() diff --git a/src/editors/work_part.rs b/src/editors/work_part.rs index 3ef3a3a..9f48e34 100644 --- a/src/editors/work_part.rs +++ b/src/editors/work_part.rs @@ -6,6 +6,7 @@ use gettextrs::gettext; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; +use libhandy::prelude::*; use std::cell::RefCell; use std::rc::Rc; @@ -14,7 +15,7 @@ pub struct WorkPartEditor { backend: Rc, widget: gtk::Box, title_entry: gtk::Entry, - composer_label: gtk::Label, + composer_row: libhandy::ActionRow, reset_composer_button: gtk::Button, composer: RefCell>, ready_cb: RefCell ()>>>, @@ -33,7 +34,7 @@ impl WorkPartEditor { get_widget!(builder, gtk::Button, save_button); get_widget!(builder, gtk::Entry, title_entry); get_widget!(builder, gtk::Button, composer_button); - get_widget!(builder, gtk::Label, composer_label); + get_widget!(builder, libhandy::ActionRow, composer_row); get_widget!(builder, gtk::Button, reset_composer_button); let composer = match part { @@ -48,7 +49,7 @@ impl WorkPartEditor { backend, widget, title_entry, - composer_label, + composer_row, reset_composer_button, composer: RefCell::new(composer), ready_cb: RefCell::new(None), @@ -67,7 +68,7 @@ impl WorkPartEditor { save_button.connect_clicked(clone!(@strong this => move |_| { if let Some(cb) = &*this.ready_cb.borrow() { cb(WorkPart { - title: this.title_entry.get_text().to_string(), + title: this.title_entry.get_text().unwrap().to_string(), composer: this.composer.borrow().clone(), }); } @@ -117,10 +118,12 @@ impl WorkPartEditor { /// Update the UI according to person. fn show_composer(&self, person: Option<&Person>) { if let Some(person) = person { - self.composer_label.set_text(&person.name_fl()); + self.composer_row.set_title(Some(&gettext("Composer"))); + self.composer_row.set_subtitle(Some(&person.name_fl())); self.reset_composer_button.show(); } else { - self.composer_label.set_text(&gettext("Select …")); + self.composer_row.set_title(Some(&gettext("Select a composer"))); + self.composer_row.set_subtitle(None); self.reset_composer_button.hide(); } } diff --git a/src/editors/work_section.rs b/src/editors/work_section.rs index d162b79..7b09b26 100644 --- a/src/editors/work_section.rs +++ b/src/editors/work_section.rs @@ -50,7 +50,7 @@ impl WorkSectionEditor { if let Some(cb) = &*this.ready_cb.borrow() { cb(WorkSection { before_index: 0, - title: this.title_entry.get_text().to_string(), + title: this.title_entry.get_text().unwrap().to_string(), }); } diff --git a/src/import/disc_source.rs b/src/import/disc_source.rs index c496eb4..f1a33c3 100644 --- a/src/import/disc_source.rs +++ b/src/import/disc_source.rs @@ -3,7 +3,6 @@ use discid::DiscId; use futures_channel::oneshot; use gstreamer::prelude::*; use gstreamer::{Element, ElementFactory, Pipeline}; -use std::cell::RefCell; use std::path::{Path, PathBuf}; use std::thread; diff --git a/src/import/medium_editor.rs b/src/import/medium_editor.rs index 52ebb13..361ec04 100644 --- a/src/import/medium_editor.rs +++ b/src/import/medium_editor.rs @@ -2,8 +2,7 @@ use super::disc_source::DiscSource; use super::track_set_editor::{TrackSetData, TrackSetEditor}; use crate::database::{generate_id, Medium, Track, TrackSet}; use crate::backend::Backend; -use crate::widgets::{Navigator, NavigatorScreen}; -use crate::widgets::new_list::List; +use crate::widgets::{List, Navigator, NavigatorScreen}; use anyhow::Result; use glib::clone; use glib::prelude::*; @@ -23,7 +22,7 @@ pub struct MediumEditor { done: gtk::Image, name_entry: gtk::Entry, publish_switch: gtk::Switch, - track_set_list: List, + track_set_list: Rc, track_sets: RefCell>, navigator: RefCell>>, } @@ -45,8 +44,8 @@ impl MediumEditor { get_widget!(builder, gtk::Button, add_button); get_widget!(builder, gtk::Frame, frame); - let list = List::new("No recordings added."); - frame.add(&list.widget); + let list = List::new(); + frame.set_child(Some(&list.widget)); let this = Rc::new(Self { backend, @@ -106,25 +105,24 @@ impl MediumEditor { } })); - this.track_set_list.set_make_widget(clone!(@strong this => move |index| { + this.track_set_list.set_make_widget_cb(clone!(@strong this => move |index| { let track_set = &this.track_sets.borrow()[index]; let title = track_set.recording.work.get_title(); let subtitle = track_set.recording.get_performers(); - let edit_image = gtk::Image::from_icon_name(Some("document-edit-symbolic"), gtk::IconSize::Button); + let edit_image = gtk::Image::from_icon_name(Some("document-edit-symbolic")); let edit_button = gtk::Button::new(); - edit_button.set_relief(gtk::ReliefStyle::None); + edit_button.set_has_frame(false); edit_button.set_valign(gtk::Align::Center); - edit_button.add(&edit_image); + edit_button.set_child(Some(&edit_image)); let row = libhandy::ActionRow::new(); row.set_activatable(true); row.set_title(Some(&title)); row.set_subtitle(Some(&subtitle)); - row.add(&edit_button); + row.add_suffix(&edit_button); row.set_activatable_widget(Some(&edit_button)); - row.show_all(); edit_button.connect_clicked(clone!(@strong this => move |_| { @@ -154,7 +152,7 @@ impl MediumEditor { /// Save the medium and possibly upload it to the server. async fn save(self: Rc) -> Result<()> { - let name = self.name_entry.get_text().to_string(); + let name = self.name_entry.get_text().unwrap().to_string(); // Create a new directory in the music library path for the imported medium. @@ -200,7 +198,7 @@ impl MediumEditor { let medium = Medium { id: generate_id(), - name: self.name_entry.get_text().to_string(), + name: self.name_entry.get_text().unwrap().to_string(), discid: Some(self.source.discid.clone()), tracks: track_sets, }; diff --git a/src/import/source_selector.rs b/src/import/source_selector.rs index 121bc34..0dbde5e 100644 --- a/src/import/source_selector.rs +++ b/src/import/source_selector.rs @@ -2,7 +2,6 @@ use super::medium_editor::MediumEditor; use super::disc_source::DiscSource; use crate::backend::Backend; use crate::widgets::{Navigator, NavigatorScreen}; -use anyhow::Result; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; diff --git a/src/import/track_editor.rs b/src/import/track_editor.rs index ff02c04..be967ea 100644 --- a/src/import/track_editor.rs +++ b/src/import/track_editor.rs @@ -1,6 +1,5 @@ use crate::database::Recording; use crate::widgets::{Navigator, NavigatorScreen}; -use crate::widgets::new_list::List; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; @@ -32,7 +31,7 @@ impl TrackEditor { parts_list.set_selection_mode(gtk::SelectionMode::None); parts_list.set_vexpand(false); parts_list.show(); - parts_frame.add(&parts_list); + parts_frame.set_child(Some(&parts_list)); let this = Rc::new(Self { widget, @@ -81,9 +80,8 @@ impl TrackEditor { row.add_prefix(&check); row.set_activatable_widget(Some(&check)); row.set_title(Some(&part.title)); - row.show_all(); - parts_list.add(&row); + parts_list.append(&row); } this diff --git a/src/import/track_selector.rs b/src/import/track_selector.rs index 3404593..ae7621a 100644 --- a/src/import/track_selector.rs +++ b/src/import/track_selector.rs @@ -33,7 +33,7 @@ impl TrackSelector { track_list.set_selection_mode(gtk::SelectionMode::None); track_list.set_vexpand(false); track_list.show(); - tracks_frame.add(&track_list); + tracks_frame.set_child(Some(&track_list)); let this = Rc::new(Self { source, @@ -90,10 +90,10 @@ impl TrackSelector { let row = libhandy::ActionRow::new(); row.add_prefix(&check); row.set_activatable_widget(Some(&check)); + row.set_activatable(true); row.set_title(Some(&title)); - row.show_all(); - track_list.add(&row); + track_list.append(&row); } this diff --git a/src/import/track_set_editor.rs b/src/import/track_set_editor.rs index 8b18856..852744e 100644 --- a/src/import/track_set_editor.rs +++ b/src/import/track_set_editor.rs @@ -2,17 +2,15 @@ use super::disc_source::DiscSource; use super::track_editor::TrackEditor; use super::track_selector::TrackSelector; use crate::backend::Backend; -use crate::database::{Recording, Track, TrackSet}; +use crate::database::Recording; use crate::selectors::{PersonSelector, RecordingSelector, WorkSelector}; -use crate::widgets::{Navigator, NavigatorScreen}; -use crate::widgets::new_list::List; +use crate::widgets::{List, Navigator, NavigatorScreen}; use gettextrs::gettext; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; use libhandy::prelude::*; -use std::cell::{Cell, RefCell}; -use std::collections::HashSet; +use std::cell::RefCell; use std::rc::Rc; /// A track set before being imported. @@ -39,7 +37,7 @@ pub struct TrackSetEditor { widget: gtk::Box, save_button: gtk::Button, recording_row: libhandy::ActionRow, - track_list: List, + track_list: Rc, recording: RefCell>, tracks: RefCell>, done_cb: RefCell>>, @@ -61,8 +59,8 @@ impl TrackSetEditor { get_widget!(builder, gtk::Button, edit_tracks_button); get_widget!(builder, gtk::Frame, tracks_frame); - let track_list = List::new(&gettext!("No tracks added")); - tracks_frame.add(&track_list.widget); + let track_list = List::new(); + tracks_frame.set_child(Some(&track_list.widget)); let this = Rc::new(Self { backend, @@ -159,7 +157,7 @@ impl TrackSetEditor { } })); - this.track_list.set_make_widget(clone!(@strong this => move |index| { + this.track_list.set_make_widget_cb(clone!(@strong this => move |index| { let track = &this.tracks.borrow()[index]; let mut title_parts = Vec::::new(); @@ -179,19 +177,18 @@ impl TrackSetEditor { let number = this.source.tracks[track.track_source].number; let subtitle = format!("Track {}", number); - let edit_image = gtk::Image::from_icon_name(Some("document-edit-symbolic"), gtk::IconSize::Button); + let edit_image = gtk::Image::from_icon_name(Some("document-edit-symbolic")); let edit_button = gtk::Button::new(); - edit_button.set_relief(gtk::ReliefStyle::None); + edit_button.set_has_frame(false); edit_button.set_valign(gtk::Align::Center); - edit_button.add(&edit_image); + edit_button.set_child(Some(&edit_image)); let row = libhandy::ActionRow::new(); row.set_activatable(true); row.set_title(Some(&title)); row.set_subtitle(Some(&subtitle)); - row.add(&edit_button); + row.add_suffix(&edit_button); row.set_activatable_widget(Some(&edit_button)); - row.show_all(); edit_button.connect_clicked(clone!(@strong this => move |_| { let recording = this.recording.borrow().clone(); diff --git a/src/meson.build b/src/meson.build index 60d0bcf..46c8738 100644 --- a/src/meson.build +++ b/src/meson.build @@ -65,6 +65,13 @@ sources = files( 'editors/work.rs', 'editors/work_part.rs', 'editors/work_section.rs', + 'import/disc_source.rs', + 'import/medium_editor.rs', + 'import/mod.rs', + 'import/source_selector.rs', + 'import/track_editor.rs', + 'import/track_selector.rs', + 'import/track_set_editor.rs', 'screens/ensemble_screen.rs', 'screens/mod.rs', 'screens/person_screen.rs', @@ -78,13 +85,13 @@ sources = files( 'selectors/recording.rs', 'selectors/selector.rs', 'selectors/work.rs', + 'widgets/indexed_list_model.rs', 'widgets/list.rs', 'widgets/mod.rs', 'widgets/navigator.rs', 'widgets/navigator_window.rs', 'widgets/player_bar.rs', 'widgets/poe_list.rs', - 'widgets/selector_row.rs', 'config.rs', 'config.rs.in', 'main.rs', diff --git a/src/screens/ensemble_screen.rs b/src/screens/ensemble_screen.rs index 6a4a738..16101c7 100644 --- a/src/screens/ensemble_screen.rs +++ b/src/screens/ensemble_screen.rs @@ -3,12 +3,11 @@ use crate::backend::*; use crate::database::*; use crate::editors::EnsembleEditor; use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow}; -use gettextrs::gettext; use gio::prelude::*; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; -use libhandy::HeaderBarExt; +use libhandy::prelude::*; use std::cell::RefCell; use std::rc::Rc; @@ -16,8 +15,10 @@ pub struct EnsembleScreen { backend: Rc, ensemble: Ensemble, widget: gtk::Box, + search_entry: gtk::SearchEntry, stack: gtk::Stack, - recording_list: Rc>, + recording_list: Rc, + recordings: RefCell>, navigator: RefCell>>, } @@ -26,13 +27,13 @@ impl EnsembleScreen { let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/ensemble_screen.ui"); get_widget!(builder, gtk::Box, widget); - get_widget!(builder, libhandy::HeaderBar, header); + get_widget!(builder, gtk::Label, title_label); get_widget!(builder, gtk::Button, back_button); get_widget!(builder, gtk::SearchEntry, search_entry); get_widget!(builder, gtk::Stack, stack); get_widget!(builder, gtk::Frame, recording_frame); - header.set_title(Some(&ensemble.name)); + title_label.set_label(&ensemble.name); let edit_action = gio::SimpleAction::new("edit", None); let delete_action = gio::SimpleAction::new("delete", None); @@ -43,75 +44,66 @@ impl EnsembleScreen { widget.insert_action_group("widget", Some(&actions)); - let recording_list = List::new(&gettext("No recordings found.")); + let recording_list = List::new(); + recording_frame.set_child(Some(&recording_list.widget)); - recording_list.set_make_widget(|recording: &Recording| { - let work_label = gtk::Label::new(Some(&recording.work.get_title())); - - work_label.set_ellipsize(pango::EllipsizeMode::End); - work_label.set_halign(gtk::Align::Start); - - let performers_label = gtk::Label::new(Some(&recording.get_performers())); - performers_label.set_ellipsize(pango::EllipsizeMode::End); - performers_label.set_opacity(0.5); - performers_label.set_halign(gtk::Align::Start); - - let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); - vbox.set_border_width(6); - vbox.add(&work_label); - vbox.add(&performers_label); - - vbox.upcast() - }); - - recording_list.set_filter( - clone!(@strong search_entry => move |recording: &Recording| { - let search = search_entry.get_text().to_string().to_lowercase(); - let text = recording.work.get_title() + &recording.get_performers(); - search.is_empty() || text.contains(&search) - }), - ); - - recording_frame.add(&recording_list.widget.clone()); - - let result = Rc::new(Self { + let this = Rc::new(Self { backend, ensemble, widget, + search_entry, stack, recording_list, + recordings: RefCell::new(Vec::new()), navigator: RefCell::new(None), }); - search_entry.connect_search_changed(clone!(@strong result => move |_| { - result.recording_list.invalidate_filter(); + this.search_entry.connect_search_changed(clone!(@strong this => move |_| { + this.recording_list.invalidate_filter(); })); - back_button.connect_clicked(clone!(@strong result => move |_| { - let navigator = result.navigator.borrow().clone(); + back_button.connect_clicked(clone!(@strong this => move |_| { + let navigator = this.navigator.borrow().clone(); if let Some(navigator) = navigator { navigator.pop(); } })); - result - .recording_list - .set_selected(clone!(@strong result => move |recording| { - let navigator = result.navigator.borrow().clone(); + this.recording_list.set_make_widget_cb(clone!(@strong this => move |index| { + let recording = &this.recordings.borrow()[index]; + + let row = libhandy::ActionRow::new(); + row.set_activatable(true); + row.set_title(Some(&recording.work.get_title())); + row.set_subtitle(Some(&recording.get_performers())); + + let recording = recording.to_owned(); + row.connect_activated(clone!(@strong this => move |_| { + let navigator = this.navigator.borrow().clone(); if let Some(navigator) = navigator { - navigator.push(RecordingScreen::new(result.backend.clone(), recording.clone())); + navigator.push(RecordingScreen::new(this.backend.clone(), recording.clone())); } })); - edit_action.connect_activate(clone!(@strong result => move |_, _| { - let editor = EnsembleEditor::new(result.backend.clone(), Some(result.ensemble.clone())); + row.upcast() + })); + + this.recording_list.set_filter_cb(clone!(@strong this => move |index| { + let recording = &this.recordings.borrow()[index]; + let search = this.search_entry.get_text().unwrap().to_string().to_lowercase(); + let text = recording.work.get_title() + &recording.get_performers(); + search.is_empty() || text.to_lowercase().contains(&search) + })); + + edit_action.connect_activate(clone!(@strong this => move |_, _| { + let editor = EnsembleEditor::new(this.backend.clone(), Some(this.ensemble.clone())); let window = NavigatorWindow::new(editor); window.show(); })); - delete_action.connect_activate(clone!(@strong result => move |_, _| { + delete_action.connect_activate(clone!(@strong this => move |_, _| { let context = glib::MainContext::default(); - let clone = result.clone(); + let clone = this.clone(); context.spawn_local(async move { clone.backend.db().delete_ensemble(&clone.ensemble.id).await.unwrap(); clone.backend.library_changed(); @@ -119,7 +111,7 @@ impl EnsembleScreen { })); let context = glib::MainContext::default(); - let clone = result.clone(); + let clone = this.clone(); context.spawn_local(async move { let recordings = clone .backend @@ -131,12 +123,14 @@ impl EnsembleScreen { if recordings.is_empty() { clone.stack.set_visible_child_name("nothing"); } else { - clone.recording_list.show_items(recordings); + let length = recordings.len(); + clone.recordings.replace(recordings); + clone.recording_list.update(length); clone.stack.set_visible_child_name("content"); } }); - result + this } } diff --git a/src/screens/person_screen.rs b/src/screens/person_screen.rs index 475e36f..3955461 100644 --- a/src/screens/person_screen.rs +++ b/src/screens/person_screen.rs @@ -3,12 +3,11 @@ use crate::backend::*; use crate::database::*; use crate::editors::PersonEditor; use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow}; -use gettextrs::gettext; use gio::prelude::*; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; -use libhandy::HeaderBarExt; +use libhandy::prelude::*; use std::cell::RefCell; use std::rc::Rc; @@ -17,8 +16,13 @@ pub struct PersonScreen { person: Person, widget: gtk::Box, stack: gtk::Stack, - work_list: Rc>, - recording_list: Rc>, + search_entry: gtk::SearchEntry, + work_box: gtk::Box, + work_list: Rc, + recording_box: gtk::Box, + recording_list: Rc, + works: RefCell>, + recordings: RefCell>, navigator: RefCell>>, } @@ -27,7 +31,7 @@ impl PersonScreen { let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/person_screen.ui"); get_widget!(builder, gtk::Box, widget); - get_widget!(builder, libhandy::HeaderBar, header); + get_widget!(builder, gtk::Label, title_label); get_widget!(builder, gtk::Button, back_button); get_widget!(builder, gtk::SearchEntry, search_entry); get_widget!(builder, gtk::Stack, stack); @@ -36,7 +40,7 @@ impl PersonScreen { get_widget!(builder, gtk::Box, recording_box); get_widget!(builder, gtk::Frame, recording_frame); - header.set_title(Some(&person.name_fl())); + title_label.set_label(&person.name_fl()); let edit_action = gio::SimpleAction::new("edit", None); let delete_action = gio::SimpleAction::new("delete", None); @@ -47,107 +51,98 @@ impl PersonScreen { widget.insert_action_group("widget", Some(&actions)); - let work_list = List::new(&gettext("No works found.")); + let work_list = List::new(); + let recording_list = List::new(); + work_frame.set_child(Some(&work_list.widget)); + recording_frame.set_child(Some(&recording_list.widget)); - work_list.set_make_widget(|work: &Work| { - let label = gtk::Label::new(Some(&work.title)); - label.set_halign(gtk::Align::Start); - label.set_margin_start(6); - label.set_margin_end(6); - label.set_margin_top(6); - label.set_margin_bottom(6); - label.upcast() - }); - - work_list.set_filter(clone!(@strong search_entry => move |work: &Work| { - let search = search_entry.get_text().to_string().to_lowercase(); - let title = work.title.to_lowercase(); - search.is_empty() || title.contains(&search) - })); - - let recording_list = List::new(&gettext("No recordings found.")); - - recording_list.set_make_widget(|recording: &Recording| { - let work_label = gtk::Label::new(Some(&recording.work.get_title())); - - work_label.set_ellipsize(pango::EllipsizeMode::End); - work_label.set_halign(gtk::Align::Start); - - let performers_label = gtk::Label::new(Some(&recording.get_performers())); - performers_label.set_ellipsize(pango::EllipsizeMode::End); - performers_label.set_opacity(0.5); - performers_label.set_halign(gtk::Align::Start); - - let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); - vbox.set_border_width(6); - vbox.add(&work_label); - vbox.add(&performers_label); - - vbox.upcast() - }); - - recording_list.set_filter( - clone!(@strong search_entry => move |recording: &Recording| { - let search = search_entry.get_text().to_string().to_lowercase(); - let text = recording.work.get_title() + &recording.get_performers(); - search.is_empty() || text.contains(&search) - }), - ); - - work_frame.add(&work_list.widget); - recording_frame.add(&recording_list.widget); - - let result = Rc::new(Self { + let this = Rc::new(Self { backend, person, widget, stack, + search_entry, + work_box, work_list, + recording_box, recording_list, + works: RefCell::new(Vec::new()), + recordings: RefCell::new(Vec::new()), navigator: RefCell::new(None), }); - search_entry.connect_search_changed(clone!(@strong result => move |_| { - result.work_list.invalidate_filter(); - result.recording_list.invalidate_filter(); + this.search_entry.connect_search_changed(clone!(@strong this => move |_| { + this.work_list.invalidate_filter(); + this.recording_list.invalidate_filter(); })); - back_button.connect_clicked(clone!(@strong result => move |_| { - let navigator = result.navigator.borrow().clone(); + back_button.connect_clicked(clone!(@strong this => move |_| { + let navigator = this.navigator.borrow().clone(); if let Some(navigator) = navigator { navigator.clone().pop(); } })); - result - .work_list - .set_selected(clone!(@strong result => move |work| { - result.recording_list.clear_selection(); - let navigator = result.navigator.borrow().clone(); + this.work_list.set_make_widget_cb(clone!(@strong this => move |index| { + let work = &this.works.borrow()[index]; + + let row = libhandy::ActionRow::new(); + row.set_activatable(true); + row.set_title(Some(&work.title)); + + let work = work.to_owned(); + row.connect_activated(clone!(@strong this => move |_| { + let navigator = this.navigator.borrow().clone(); if let Some(navigator) = navigator { - navigator.push(WorkScreen::new(result.backend.clone(), work.clone())); + navigator.push(WorkScreen::new(this.backend.clone(), work.clone())); } })); - result - .recording_list - .set_selected(clone!(@strong result => move |recording| { - result.work_list.clear_selection(); - let navigator = result.navigator.borrow().clone(); + row.upcast() + })); + + this.work_list.set_filter_cb(clone!(@strong this => move |index| { + let work = &this.works.borrow()[index]; + let search = this.search_entry.get_text().unwrap().to_string().to_lowercase(); + let title = work.title.to_lowercase(); + search.is_empty() || title.contains(&search) + })); + + this.recording_list.set_make_widget_cb(clone!(@strong this => move |index| { + let recording = &this.recordings.borrow()[index]; + + let row = libhandy::ActionRow::new(); + row.set_activatable(true); + row.set_title(Some(&recording.work.get_title())); + row.set_subtitle(Some(&recording.get_performers())); + + let recording = recording.to_owned(); + row.connect_activated(clone!(@strong this => move |_| { + let navigator = this.navigator.borrow().clone(); if let Some(navigator) = navigator { - navigator.push(RecordingScreen::new(result.backend.clone(), recording.clone())); + navigator.push(RecordingScreen::new(this.backend.clone(), recording.clone())); } })); - edit_action.connect_activate(clone!(@strong result => move |_, _| { - let editor = PersonEditor::new(result.backend.clone(), Some(result.person.clone())); + row.upcast() + })); + + this.recording_list.set_filter_cb(clone!(@strong this => move |index| { + let recording = &this.recordings.borrow()[index]; + let search = this.search_entry.get_text().unwrap().to_string().to_lowercase(); + let text = recording.work.get_title() + &recording.get_performers(); + search.is_empty() || text.contains(&search) + })); + + edit_action.connect_activate(clone!(@strong this => move |_, _| { + let editor = PersonEditor::new(this.backend.clone(), Some(this.person.clone())); let window = NavigatorWindow::new(editor); window.show(); })); - delete_action.connect_activate(clone!(@strong result => move |_, _| { + delete_action.connect_activate(clone!(@strong this => move |_, _| { let context = glib::MainContext::default(); - let clone = result.clone(); + let clone = this.clone(); context.spawn_local(async move { clone.backend.db().delete_person(&clone.person.id).await.unwrap(); clone.backend.library_changed(); @@ -155,7 +150,7 @@ impl PersonScreen { })); let context = glib::MainContext::default(); - let clone = result.clone(); + let clone = this.clone(); context.spawn_local(async move { let works = clone .backend @@ -163,6 +158,7 @@ impl PersonScreen { .get_works(&clone.person.id) .await .unwrap(); + let recordings = clone .backend .db() @@ -174,22 +170,26 @@ impl PersonScreen { clone.stack.set_visible_child_name("nothing"); } else { if works.is_empty() { - work_box.hide(); + clone.work_box.hide(); } else { - clone.work_list.show_items(works); + let length = works.len(); + clone.works.replace(works); + clone.work_list.update(length); } if recordings.is_empty() { - recording_box.hide(); + clone.recording_box.hide(); } else { - clone.recording_list.show_items(recordings); + let length = recordings.len(); + clone.recordings.replace(recordings); + clone.recording_list.update(length); } clone.stack.set_visible_child_name("content"); } }); - result + this } } diff --git a/src/screens/player_screen.rs b/src/screens/player_screen.rs index fe3ae81..c015d4e 100644 --- a/src/screens/player_screen.rs +++ b/src/screens/player_screen.rs @@ -4,15 +4,22 @@ use gettextrs::gettext; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; +use libhandy::prelude::*; use std::cell::{Cell, RefCell}; use std::rc::Rc; -struct PlaylistElement { - pub item: usize, - pub track: usize, - pub title: String, - pub subtitle: Option, - pub playable: bool, +/// Elements for visually representing the playlist. +enum ListItem { + /// A header shown on top of a track set. This contains an index + /// referencing the playlist item containing this track set. + Header(usize), + + /// A playable track. This contains an index to the playlist item, an + /// index to the track and whether it is the currently played one. + Track(usize, usize, bool), + + /// A separator shown between track sets. + Separator, } pub struct PlayerScreen { @@ -27,16 +34,18 @@ pub struct PlayerScreen { duration_label: gtk::Label, play_image: gtk::Image, pause_image: gtk::Image, - list: Rc>, - player: Rc>>>, - seeking: Rc>, - current_item: Rc>, - current_track: Rc>, - back_cb: Rc ()>>>>, + list: Rc, + playlist: RefCell>, + items: RefCell>, + player: RefCell>>, + seeking: Cell, + current_item: Cell, + current_track: Cell, + back_cb: RefCell>>, } impl PlayerScreen { - pub fn new() -> Self { + pub fn new() -> Rc { let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/player_screen.ui"); get_widget!(builder, gtk::Box, widget); @@ -55,124 +64,10 @@ impl PlayerScreen { get_widget!(builder, gtk::Image, pause_image); get_widget!(builder, gtk::Frame, frame); - let back_cb = Rc::new(RefCell::new(None:: ()>>)); + let list = List::new(); + frame.set_child(Some(&list.widget)); - back_button.connect_clicked(clone!(@strong back_cb => move |_| { - if let Some(cb) = &*back_cb.borrow() { - cb(); - } - })); - - let player = Rc::new(RefCell::new(None::>)); - let seeking = Rc::new(Cell::new(false)); - - previous_button.connect_clicked(clone!(@strong player => move |_| { - if let Some(player) = &*player.borrow() { - player.previous().unwrap(); - } - })); - - play_button.connect_clicked(clone!(@strong player => move |_| { - if let Some(player) = &*player.borrow() { - player.play_pause(); - } - })); - - next_button.connect_clicked(clone!(@strong player => move |_| { - if let Some(player) = &*player.borrow() { - player.next().unwrap(); - } - })); - - stop_button.connect_clicked(clone!(@strong player, @strong back_cb => move |_| { - if let Some(player) = &*player.borrow() { - if let Some(cb) = &*back_cb.borrow() { - cb(); - } - - player.clear(); - } - })); - - position_scale.connect_button_press_event(clone!(@strong seeking => move |_, _| { - seeking.replace(true); - Inhibit(false) - })); - - position_scale.connect_button_release_event( - clone!(@strong seeking, @strong position, @strong player => move |_, _| { - if let Some(player) = &*player.borrow() { - player.seek(position.get_value() as u64); - } - - seeking.replace(false); - Inhibit(false) - }), - ); - - position_scale.connect_value_changed( - clone!(@strong seeking, @strong position, @strong position_label => move |_| { - if seeking.get() { - let ms = position.get_value() as u64; - let min = ms / 60000; - let sec = (ms % 60000) / 1000; - position_label.set_text(&format!("{}:{:02}", min, sec)); - } - }), - ); - - let current_item = Rc::new(Cell::::new(0)); - let current_track = Rc::new(Cell::::new(0)); - let list = List::new(""); - - list.set_make_widget(clone!( - @strong current_item, - @strong current_track - => move |element: &PlaylistElement| { - let title_label = gtk::Label::new(Some(&element.title)); - title_label.set_ellipsize(pango::EllipsizeMode::End); - title_label.set_halign(gtk::Align::Start); - let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); - vbox.add(&title_label); - if let Some(subtitle) = &element.subtitle { - let subtitle_label = gtk::Label::new(Some(&subtitle)); - subtitle_label.set_ellipsize(pango::EllipsizeMode::End); - subtitle_label.set_halign(gtk::Align::Start); - subtitle_label.set_opacity(0.5); - vbox.add(&subtitle_label); - } - - let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 6); - hbox.set_border_width(6); - - if element.playable { - let image = gtk::Image::new(); - - if element.item == current_item.get() && element.track == current_track.get() { - image.set_from_icon_name( - Some("media-playback-start-symbolic"), - gtk::IconSize::Button, - ); - } - - hbox.add(&image); - } else if element.item > 0 { - hbox.set_margin_top(18); - } - hbox.add(&vbox); - hbox.upcast() - } - )); - - list.set_selected(clone!(@strong player => move |element| { - if let Some(player) = &*player.borrow() { - player.set_track(element.item, element.track).unwrap(); - } - })); - - frame.add(&list.widget); - - Self { + let this = Rc::new(Self { widget, title_label, subtitle_label, @@ -185,106 +80,222 @@ impl PlayerScreen { play_image, pause_image, list, - player, - seeking, - current_item, - current_track, - back_cb, - } - } + items: RefCell::new(Vec::new()), + playlist: RefCell::new(Vec::new()), + player: RefCell::new(None), + seeking: Cell::new(false), + current_item: Cell::new(0), + current_track: Cell::new(0), + back_cb: RefCell::new(None), + }); - pub fn set_player(&self, player: Option>) { - self.player.replace(player.clone()); + back_button.connect_clicked(clone!(@strong this => move |_| { + if let Some(cb) = &*this.back_cb.borrow() { + cb(); + } + })); - if let Some(player) = player { - let playlist = Rc::new(RefCell::new(Vec::::new())); + this.previous_button.connect_clicked(clone!(@strong this => move |_| { + if let Some(player) = &*this.player.borrow() { + player.previous().unwrap(); + } + })); - player.add_playlist_cb(clone!( - @strong player, - @strong self.previous_button as previous_button, - @strong self.next_button as next_button, - @strong self.list as list, - @strong playlist - => move |new_playlist| { - playlist.replace(new_playlist); - previous_button.set_sensitive(player.has_previous()); - next_button.set_sensitive(player.has_next()); + this.play_button.connect_clicked(clone!(@strong this => move |_| { + if let Some(player) = &*this.player.borrow() { + player.play_pause(); + } + })); - let mut elements = Vec::new(); - for (item_index, item) in playlist.borrow().iter().enumerate() { - elements.push(PlaylistElement { - item: item_index, - track: 0, - title: item.track_set.recording.work.get_title(), - subtitle: Some(item.track_set.recording.get_performers()), - playable: false, - }); + this.next_button.connect_clicked(clone!(@strong this => move |_| { + if let Some(player) = &*this.player.borrow() { + player.next().unwrap(); + } + })); - for track_index in &item.indices { - let track = &item.track_set.tracks[*track_index]; - - let mut parts = Vec::::new(); - for part in &track.work_parts { - parts.push(item.track_set.recording.work.parts[*part].title.clone()); - } - - let title = if parts.is_empty() { - gettext("Unknown") - } else { - parts.join(", ") - }; - - elements.push(PlaylistElement { - item: item_index, - track: *track_index, - title: title, - subtitle: None, - playable: true, - }); - } - } - - list.show_items(elements); + stop_button.connect_clicked(clone!(@strong this => move |_| { + if let Some(player) = &*this.player.borrow() { + if let Some(cb) = &*this.back_cb.borrow() { + cb(); } - )); - player.add_track_cb(clone!( - @strong player, - @strong playlist, - @strong self.previous_button as previous_button, - @strong self.next_button as next_button, - @strong self.title_label as title_label, - @strong self.subtitle_label as subtitle_label, - @strong self.position_label as position_label, - @strong self.current_item as self_item, - @strong self.current_track as self_track, - @strong self.list as list - => move |current_item, current_track| { - previous_button.set_sensitive(player.has_previous()); - next_button.set_sensitive(player.has_next()); + player.clear(); + } + })); - let item = &playlist.borrow()[current_item]; - let track = &item.track_set.tracks[current_track]; + // position_scale.connect_button_press_event(clone!(@strong seeking => move |_, _| { + // seeking.replace(true); + // Inhibit(false) + // })); + + // position_scale.connect_button_release_event( + // clone!(@strong seeking, @strong position, @strong player => move |_, _| { + // if let Some(player) = &*player.borrow() { + // player.seek(position.get_value() as u64); + // } + + // seeking.replace(false); + // Inhibit(false) + // }), + // ); + + position_scale.connect_value_changed(clone!(@strong this => move |_| { + if this.seeking.get() { + let ms = this.position.get_value() as u64; + let min = ms / 60000; + let sec = (ms % 60000) / 1000; + + this.position_label.set_text(&format!("{}:{:02}", min, sec)); + } + })); + + this.list.set_make_widget_cb(clone!(@strong this => move |index| { + match this.items.borrow()[index] { + ListItem::Header(item_index) => { + let playlist_item = &this.playlist.borrow()[item_index]; + let recording = &playlist_item.track_set.recording; + + let row = libhandy::ActionRow::new(); + row.set_activatable(false); + row.set_selectable(false); + row.set_title(Some(&recording.work.get_title())); + row.set_subtitle(Some(&recording.get_performers())); + + row.upcast() + } + ListItem::Track(item_index, track_index, playing) => { + let playlist_item = &this.playlist.borrow()[item_index]; + let index = playlist_item.indices[track_index]; + let track = &playlist_item.track_set.tracks[index]; let mut parts = Vec::::new(); for part in &track.work_parts { - parts.push(item.track_set.recording.work.parts[*part].title.clone()); + parts.push(playlist_item.track_set.recording.work.parts[*part].title.clone()); } - let mut title = item.track_set.recording.work.get_title(); - if !parts.is_empty() { - title = format!("{}: {}", title, parts.join(", ")); - } + let title = if parts.is_empty() { + gettext("Unknown") + } else { + parts.join(", ") + }; - title_label.set_text(&title); - subtitle_label.set_text(&item.track_set.recording.get_performers()); - position_label.set_text("0:00"); + let row = libhandy::ActionRow::new(); + row.set_selectable(false); + row.set_activatable(true); + row.set_title(Some(&title)); - self_item.replace(current_item); - self_track.replace(current_track); - list.update(); + row.connect_activated(clone!(@strong this => move |_| { + if let Some(player) = &*this.player.borrow() { + player.set_track(item_index, track_index).unwrap(); + } + })); + + let icon = if playing { + Some("media-playback-start-symbolic") + } else { + None + }; + + let image = gtk::Image::from_icon_name(icon); + row.add_prefix(&image); + + row.upcast() } - )); + ListItem::Separator => { + let separator = gtk::Separator::new(gtk::Orientation::Horizontal); + separator.upcast() + } + } + })); + + // list.set_make_widget(clone!( + // @strong current_item, + // @strong current_track + // => move |element: &PlaylistElement| { + // let title_label = gtk::Label::new(Some(&element.title)); + // title_label.set_ellipsize(pango::EllipsizeMode::End); + // title_label.set_halign(gtk::Align::Start); + + // let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); + // vbox.append(&title_label); + + // if let Some(subtitle) = &element.subtitle { + // let subtitle_label = gtk::Label::new(Some(&subtitle)); + // subtitle_label.set_ellipsize(pango::EllipsizeMode::End); + // subtitle_label.set_halign(gtk::Align::Start); + // subtitle_label.set_opacity(0.5); + // vbox.append(&subtitle_label); + // } + + // let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 6); + // hbox.set_margin_top(6); + // hbox.set_margin_bottom(6); + // hbox.set_margin_start(6); + // hbox.set_margin_end(6); + + // if element.playable { + // let image = gtk::Image::new(); + + // if element.item == current_item.get() && element.track == current_track.get() { + // image.set_from_icon_name( + // Some("media-playback-start-symbolic"), + // gtk::IconSize::Button, + // ); + // } + + // hbox.append(&image); + // } else if element.item > 0 { + // hbox.set_margin_top(18); + // } + // hbox.append(&vbox); + // hbox.upcast() + // } + // )); + + // list.set_selected(clone!(@strong player => move |element| { + // if let Some(player) = &*player.borrow() { + // player.set_track(element.item, element.track).unwrap(); + // } + // })); + + this + } + + pub fn set_player(self: Rc, player: Option>) { + self.player.replace(player.clone()); + + if let Some(player) = player { + player.add_playlist_cb(clone!(@strong self as this => move |playlist| { + this.playlist.replace(playlist); + this.show_playlist(); + })); + + player.add_track_cb(clone!(@strong self as this, @strong player => move |current_item, current_track| { + this.previous_button.set_sensitive(player.has_previous()); + this.next_button.set_sensitive(player.has_next()); + + let item = &this.playlist.borrow()[current_item]; + let track = &item.track_set.tracks[current_track]; + + let mut parts = Vec::::new(); + for part in &track.work_parts { + parts.push(item.track_set.recording.work.parts[*part].title.clone()); + } + + let mut title = item.track_set.recording.work.get_title(); + if !parts.is_empty() { + title = format!("{}: {}", title, parts.join(", ")); + } + + this.title_label.set_text(&title); + this.subtitle_label.set_text(&item.track_set.recording.get_performers()); + this.position_label.set_text("0:00"); + + this.current_item.set(current_item); + this.current_track.set(current_track); + + this.show_playlist(); + })); player.add_duration_cb(clone!( @strong self.duration_label as duration_label, @@ -302,15 +313,11 @@ impl PlayerScreen { @strong self.play_image as play_image, @strong self.pause_image as pause_image => move |playing| { - if let Some(child) = play_button.get_child() { - play_button.remove( &child); - } - - play_button.add(if playing { + play_button.set_child(Some(if playing { &pause_image } else { &play_image - }); + })); } )); @@ -333,4 +340,33 @@ impl PlayerScreen { pub fn set_back_cb () + 'static>(&self, cb: F) { self.back_cb.replace(Some(Box::new(cb))); } + + /// Update the user interface according to the playlist. + fn show_playlist(&self) { + let playlist = self.playlist.borrow(); + let current_item = self.current_item.get(); + let current_track = self.current_track.get(); + + let mut first = true; + let mut items = Vec::new(); + + for (item_index, playlist_item) in playlist.iter().enumerate() { + if !first { + items.push(ListItem::Separator); + } else { + first = false; + } + + items.push(ListItem::Header(item_index)); + + for (index, _) in playlist_item.indices.iter().enumerate() { + let playing = current_item == item_index && current_track == index; + items.push(ListItem::Track(item_index, index, playing)); + } + } + + let length = items.len(); + self.items.replace(items); + self.list.update(length); + } } diff --git a/src/screens/recording_screen.rs b/src/screens/recording_screen.rs index 4d33aa9..d97aa69 100644 --- a/src/screens/recording_screen.rs +++ b/src/screens/recording_screen.rs @@ -8,16 +8,28 @@ use gio::prelude::*; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; -use libhandy::HeaderBarExt; +use libhandy::prelude::*; use std::cell::RefCell; use std::rc::Rc; +/// Representation of one entry within the track list. +enum ListItem { + /// A track row. This hold an index to the track set and an index to the + /// track within the track set. + Track(usize, usize), + + /// A separator intended for use between track sets. + Separator, +} + pub struct RecordingScreen { backend: Rc, recording: Recording, widget: gtk::Box, stack: gtk::Stack, + list: Rc, track_sets: RefCell>, + items: RefCell>, navigator: RefCell>>, } @@ -26,14 +38,15 @@ impl RecordingScreen { let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_screen.ui"); get_widget!(builder, gtk::Box, widget); - get_widget!(builder, libhandy::HeaderBar, header); + get_widget!(builder, gtk::Label, title_label); + get_widget!(builder, gtk::Label, subtitle_label); get_widget!(builder, gtk::Button, back_button); get_widget!(builder, gtk::Stack, stack); get_widget!(builder, gtk::Frame, frame); get_widget!(builder, gtk::Button, add_to_playlist_button); - header.set_title(Some(&recording.work.get_title())); - header.set_subtitle(Some(&recording.get_performers())); + title_label.set_label(&recording.work.get_title()); + subtitle_label.set_label(&recording.get_performers()); let edit_action = gio::SimpleAction::new("edit", None); let delete_action = gio::SimpleAction::new("delete", None); @@ -48,95 +61,96 @@ impl RecordingScreen { widget.insert_action_group("widget", Some(&actions)); - let list = List::new(&gettext("No tracks found.")); - frame.add(&list.widget); + let list = List::new(); + frame.set_child(Some(&list.widget)); - let result = Rc::new(Self { + let this = Rc::new(Self { backend, recording, widget, stack, + list, track_sets: RefCell::new(Vec::new()), + items: RefCell::new(Vec::new()), navigator: RefCell::new(None), }); - list.set_make_widget(clone!(@strong result => move |track_set: &TrackSet| { - let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); - vbox.set_border_width(6); - vbox.set_spacing(6); + this.list.set_make_widget_cb(clone!(@strong this => move |index| { + match this.items.borrow()[index] { + ListItem::Track(track_set_index, track_index) => { + let track_set = &this.track_sets.borrow()[track_set_index]; + let track = &track_set.tracks[track_index]; - for track in &track_set.tracks { - let track_box = gtk::Box::new(gtk::Orientation::Vertical, 0); + let mut title_parts = Vec::::new(); + for part in &track.work_parts { + title_parts.push(this.recording.work.parts[*part].title.clone()); + } - let mut title_parts = Vec::::new(); - for part in &track.work_parts { - title_parts.push(result.recording.work.parts[*part].title.clone()); + let title = if title_parts.is_empty() { + gettext("Unknown") + } else { + title_parts.join(", ") + }; + + let row = libhandy::ActionRow::new(); + row.set_title(Some(&title)); + + row.upcast() + } + ListItem::Separator => { + let separator = gtk::Separator::new(gtk::Orientation::Horizontal); + separator.upcast() } - - let title = if title_parts.is_empty() { - gettext("Unknown") - } else { - title_parts.join(", ") - }; - - let title_label = gtk::Label::new(Some(&title)); - title_label.set_ellipsize(pango::EllipsizeMode::End); - title_label.set_halign(gtk::Align::Start); - - let file_name_label = gtk::Label::new(Some(&track.path)); - file_name_label.set_ellipsize(pango::EllipsizeMode::End); - file_name_label.set_opacity(0.5); - file_name_label.set_halign(gtk::Align::Start); - - track_box.add(&title_label); - track_box.add(&file_name_label); - - vbox.add(&track_box); } - - vbox.upcast() })); - back_button.connect_clicked(clone!(@strong result => move |_| { - let navigator = result.navigator.borrow().clone(); + back_button.connect_clicked(clone!(@strong this => move |_| { + let navigator = this.navigator.borrow().clone(); if let Some(navigator) = navigator { navigator.clone().pop(); } })); - add_to_playlist_button.connect_clicked(clone!(@strong result => move |_| { - // if let Some(player) = result.backend.get_player() { - // player.add_item(PlaylistItem { - // track_set: result.track_sets.get(0).unwrap().clone(), - // indices: result.tracks.borrow().clone(), - // }).unwrap(); - // } + // TODO: Decide whether to handle multiple track sets. + add_to_playlist_button.connect_clicked(clone!(@strong this => move |_| { + if let Some(player) = this.backend.get_player() { + if let Some(track_set) = this.track_sets.borrow().get(0).cloned() { + let indices = (0..track_set.tracks.len()).collect(); + + let playlist_item = PlaylistItem { + track_set, + indices, + }; + + player.add_item(playlist_item).unwrap(); + } + } })); - edit_action.connect_activate(clone!(@strong result => move |_, _| { - let editor = RecordingEditor::new(result.backend.clone(), Some(result.recording.clone())); + edit_action.connect_activate(clone!(@strong this => move |_, _| { + let editor = RecordingEditor::new(this.backend.clone(), Some(this.recording.clone())); let window = NavigatorWindow::new(editor); window.show(); })); - delete_action.connect_activate(clone!(@strong result => move |_, _| { + delete_action.connect_activate(clone!(@strong this => move |_, _| { let context = glib::MainContext::default(); - let clone = result.clone(); + let clone = this.clone(); context.spawn_local(async move { clone.backend.db().delete_recording(&clone.recording.id).await.unwrap(); clone.backend.library_changed(); }); })); - edit_tracks_action.connect_activate(clone!(@strong result => move |_, _| { - // let editor = TracksEditor::new(result.backend.clone(), Some(result.recording.clone()), result.tracks.borrow().clone()); + edit_tracks_action.connect_activate(clone!(@strong this => move |_, _| { + // let editor = TracksEditor::new(this.backend.clone(), Some(this.recording.clone()), this.tracks.borrow().clone()); // let window = NavigatorWindow::new(editor); // window.show(); })); - delete_tracks_action.connect_activate(clone!(@strong result => move |_, _| { + delete_tracks_action.connect_activate(clone!(@strong this => move |_, _| { let context = glib::MainContext::default(); - let clone = result.clone(); + let clone = this.clone(); context.spawn_local(async move { // clone.backend.db().delete_tracks(&clone.recording.id).await.unwrap(); // clone.backend.library_changed(); @@ -144,7 +158,7 @@ impl RecordingScreen { })); let context = glib::MainContext::default(); - let clone = result.clone(); + let clone = this.clone(); context.spawn_local(async move { let track_sets = clone .backend @@ -153,12 +167,34 @@ impl RecordingScreen { .await .unwrap(); - list.show_items(track_sets.clone()); + clone.show_track_sets(track_sets); clone.stack.set_visible_child_name("content"); - clone.track_sets.replace(track_sets); }); - result + this + } + + /// Update the track sets variable as well as the user interface. + fn show_track_sets(&self, track_sets: Vec) { + let mut first = true; + let mut items = Vec::new(); + + for (track_set_index, track_set) in track_sets.iter().enumerate() { + if !first { + items.push(ListItem::Separator); + } else { + first = false; + } + + for (track_index, _) in track_set.tracks.iter().enumerate() { + items.push(ListItem::Track(track_set_index, track_index)); + } + } + + let length = items.len(); + self.items.replace(items); + self.track_sets.replace(track_sets); + self.list.update(length); } } diff --git a/src/screens/work_screen.rs b/src/screens/work_screen.rs index a84cef2..9979e5a 100644 --- a/src/screens/work_screen.rs +++ b/src/screens/work_screen.rs @@ -3,12 +3,11 @@ use crate::backend::*; use crate::database::*; use crate::editors::WorkEditor; use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow}; -use gettextrs::gettext; use gio::prelude::*; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; -use libhandy::HeaderBarExt; +use libhandy::prelude::*; use std::cell::RefCell; use std::rc::Rc; @@ -17,7 +16,9 @@ pub struct WorkScreen { work: Work, widget: gtk::Box, stack: gtk::Stack, - recording_list: Rc>, + search_entry: gtk::SearchEntry, + recording_list: Rc, + recordings: RefCell>, navigator: RefCell>>, } @@ -26,14 +27,15 @@ impl WorkScreen { let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_screen.ui"); get_widget!(builder, gtk::Box, widget); - get_widget!(builder, libhandy::HeaderBar, header); + get_widget!(builder, gtk::Label, title_label); + get_widget!(builder, gtk::Label, subtitle_label); get_widget!(builder, gtk::Button, back_button); get_widget!(builder, gtk::SearchEntry, search_entry); get_widget!(builder, gtk::Stack, stack); get_widget!(builder, gtk::Frame, recording_frame); - header.set_title(Some(&work.title)); - header.set_subtitle(Some(&work.composer.name_fl())); + title_label.set_label(&work.composer.name_fl()); + subtitle_label.set_label(&work.title); let edit_action = gio::SimpleAction::new("edit", None); let delete_action = gio::SimpleAction::new("delete", None); @@ -44,73 +46,66 @@ impl WorkScreen { widget.insert_action_group("widget", Some(&actions)); - let recording_list = List::new(&gettext("No recordings found.")); + let recording_list = List::new(); + recording_frame.set_child(Some(&recording_list.widget)); - recording_list.set_make_widget(|recording: &Recording| { - let work_label = gtk::Label::new(Some(&recording.work.get_title())); - - work_label.set_ellipsize(pango::EllipsizeMode::End); - work_label.set_halign(gtk::Align::Start); - - let performers_label = gtk::Label::new(Some(&recording.get_performers())); - performers_label.set_ellipsize(pango::EllipsizeMode::End); - performers_label.set_opacity(0.5); - performers_label.set_halign(gtk::Align::Start); - - let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); - vbox.set_border_width(6); - vbox.add(&work_label); - vbox.add(&performers_label); - - vbox.upcast() - }); - - recording_list.set_filter(clone!(@strong search_entry => move |recording: &Recording| { - let search = search_entry.get_text().to_string().to_lowercase(); - let text = recording.work.get_title().to_lowercase() + &recording.get_performers().to_lowercase(); - search.is_empty() || text.contains(&search) - }),); - - recording_frame.add(&recording_list.widget); - - let result = Rc::new(Self { + let this = Rc::new(Self { backend, work, widget, stack, + search_entry, recording_list, + recordings: RefCell::new(Vec::new()), navigator: RefCell::new(None), }); - search_entry.connect_search_changed(clone!(@strong result => move |_| { - result.recording_list.invalidate_filter(); + this.search_entry.connect_search_changed(clone!(@strong this => move |_| { + this.recording_list.invalidate_filter(); })); - back_button.connect_clicked(clone!(@strong result => move |_| { - let navigator = result.navigator.borrow().clone(); + back_button.connect_clicked(clone!(@strong this => move |_| { + let navigator = this.navigator.borrow().clone(); if let Some(navigator) = navigator { navigator.clone().pop(); } })); - result - .recording_list - .set_selected(clone!(@strong result => move |recording| { - let navigator = result.navigator.borrow().clone(); + this.recording_list.set_make_widget_cb(clone!(@strong this => move |index| { + let recording = &this.recordings.borrow()[index]; + + let row = libhandy::ActionRow::new(); + row.set_activatable(true); + row.set_title(Some(&recording.work.get_title())); + row.set_subtitle(Some(&recording.get_performers())); + + let recording = recording.to_owned(); + row.connect_activated(clone!(@strong this => move |_| { + let navigator = this.navigator.borrow().clone(); if let Some(navigator) = navigator { - navigator.push(RecordingScreen::new(result.backend.clone(), recording.clone())); + navigator.push(RecordingScreen::new(this.backend.clone(), recording.clone())); } })); - edit_action.connect_activate(clone!(@strong result => move |_, _| { - let editor = WorkEditor::new(result.backend.clone(), Some(result.work.clone())); + row.upcast() + })); + + this.recording_list.set_filter_cb(clone!(@strong this => move |index| { + let recording = &this.recordings.borrow()[index]; + let search = this.search_entry.get_text().unwrap().to_string().to_lowercase(); + let text = recording.work.get_title() + &recording.get_performers(); + search.is_empty() || text.to_lowercase().contains(&search) + })); + + edit_action.connect_activate(clone!(@strong this => move |_, _| { + let editor = WorkEditor::new(this.backend.clone(), Some(this.work.clone())); let window = NavigatorWindow::new(editor); window.show(); })); - delete_action.connect_activate(clone!(@strong result => move |_, _| { + delete_action.connect_activate(clone!(@strong this => move |_, _| { let context = glib::MainContext::default(); - let clone = result.clone(); + let clone = this.clone(); context.spawn_local(async move { clone.backend.db().delete_work(&clone.work.id).await.unwrap(); clone.backend.library_changed(); @@ -118,7 +113,7 @@ impl WorkScreen { })); let context = glib::MainContext::default(); - let clone = result.clone(); + let clone = this.clone(); context.spawn_local(async move { let recordings = clone .backend @@ -130,12 +125,14 @@ impl WorkScreen { if recordings.is_empty() { clone.stack.set_visible_child_name("nothing"); } else { - clone.recording_list.show_items(recordings); + let length = recordings.len(); + clone.recordings.replace(recordings); + clone.recording_list.update(length); clone.stack.set_visible_child_name("content"); } }); - result + this } } diff --git a/src/selectors/ensemble.rs b/src/selectors/ensemble.rs index 1e61650..76ecf6e 100644 --- a/src/selectors/ensemble.rs +++ b/src/selectors/ensemble.rs @@ -6,6 +6,7 @@ use crate::widgets::{Navigator, NavigatorScreen}; use gettextrs::gettext; use glib::clone; use gtk::prelude::*; +use libhandy::prelude::*; use std::cell::RefCell; use std::rc::Rc; @@ -63,22 +64,22 @@ impl EnsembleSelector { async move { clone.backend.db().get_ensembles().await.unwrap() } })); - this.selector.set_make_widget(|ensemble| { - let label = gtk::Label::new(Some(&ensemble.name)); - label.set_halign(gtk::Align::Start); - label.set_margin_start(6); - label.set_margin_end(6); - label.set_margin_top(6); - label.set_margin_bottom(6); - label.upcast() - }); + this.selector.set_make_widget(clone!(@strong this => move |ensemble| { + let row = libhandy::ActionRow::new(); + row.set_activatable(true); + row.set_title(Some(&ensemble.name)); + + let ensemble = ensemble.to_owned(); + row.connect_activated(clone!(@strong this => move |_| { + this.select(&ensemble); + })); + + row.upcast() + })); this.selector .set_filter(|search, ensemble| ensemble.name.to_lowercase().contains(search)); - this.selector - .set_selected_cb(clone!(@strong this => move |ensemble| this.select(ensemble))); - this } @@ -87,7 +88,7 @@ impl EnsembleSelector { self.selected_cb.replace(Some(Box::new(cb))); } - /// Select a ensemble. + /// Select an ensemble. fn select(&self, ensemble: &Ensemble) { if let Some(cb) = &*self.selected_cb.borrow() { cb(&ensemble); diff --git a/src/selectors/instrument.rs b/src/selectors/instrument.rs index a5745f3..2bf835e 100644 --- a/src/selectors/instrument.rs +++ b/src/selectors/instrument.rs @@ -6,6 +6,7 @@ use crate::widgets::{Navigator, NavigatorScreen}; use gettextrs::gettext; use glib::clone; use gtk::prelude::*; +use libhandy::prelude::*; use std::cell::RefCell; use std::rc::Rc; @@ -63,22 +64,22 @@ impl InstrumentSelector { async move { clone.backend.db().get_instruments().await.unwrap() } })); - this.selector.set_make_widget(|instrument| { - let label = gtk::Label::new(Some(&instrument.name)); - label.set_halign(gtk::Align::Start); - label.set_margin_start(6); - label.set_margin_end(6); - label.set_margin_top(6); - label.set_margin_bottom(6); - label.upcast() - }); + this.selector.set_make_widget(clone!(@strong this => move |instrument| { + let row = libhandy::ActionRow::new(); + row.set_activatable(true); + row.set_title(Some(&instrument.name)); + + let instrument = instrument.to_owned(); + row.connect_activated(clone!(@strong this => move |_| { + this.select(&instrument); + })); + + row.upcast() + })); this.selector .set_filter(|search, instrument| instrument.name.to_lowercase().contains(search)); - this.selector - .set_selected_cb(clone!(@strong this => move |instrument| this.select(instrument))); - this } @@ -87,7 +88,7 @@ impl InstrumentSelector { self.selected_cb.replace(Some(Box::new(cb))); } - /// Select a instrument. + /// Select an instrument. fn select(&self, instrument: &Instrument) { if let Some(cb) = &*self.selected_cb.borrow() { cb(&instrument); diff --git a/src/selectors/person.rs b/src/selectors/person.rs index 4d4298c..f991758 100644 --- a/src/selectors/person.rs +++ b/src/selectors/person.rs @@ -6,6 +6,7 @@ use crate::widgets::{Navigator, NavigatorScreen}; use gettextrs::gettext; use glib::clone; use gtk::prelude::*; +use libhandy::prelude::*; use std::cell::RefCell; use std::rc::Rc; @@ -63,22 +64,22 @@ impl PersonSelector { async move { clone.backend.db().get_persons().await.unwrap() } })); - this.selector.set_make_widget(|person| { - let label = gtk::Label::new(Some(&person.name_lf())); - label.set_halign(gtk::Align::Start); - label.set_margin_start(6); - label.set_margin_end(6); - label.set_margin_top(6); - label.set_margin_bottom(6); - label.upcast() - }); + this.selector.set_make_widget(clone!(@strong this => move |person| { + let row = libhandy::ActionRow::new(); + row.set_activatable(true); + row.set_title(Some(&person.name_lf())); + + let person = person.to_owned(); + row.connect_activated(clone!(@strong this => move |_| { + this.select(&person); + })); + + row.upcast() + })); this.selector .set_filter(|search, person| person.name_fl().to_lowercase().contains(search)); - this.selector - .set_selected_cb(clone!(@strong this => move |person| this.select(person))); - this } diff --git a/src/selectors/recording.rs b/src/selectors/recording.rs index 690f8bb..7d66f20 100644 --- a/src/selectors/recording.rs +++ b/src/selectors/recording.rs @@ -6,6 +6,7 @@ use crate::widgets::{Navigator, NavigatorScreen}; use gettextrs::gettext; use glib::clone; use gtk::prelude::*; +use libhandy::prelude::*; use std::cell::RefCell; use std::rc::Rc; @@ -73,23 +74,23 @@ impl RecordingSelector { async move { clone.backend.db().get_recordings_for_work(&clone.work.id).await.unwrap() } })); - this.selector.set_make_widget(|recording| { - let label = gtk::Label::new(Some(&recording.get_performers())); - label.set_halign(gtk::Align::Start); - label.set_margin_start(6); - label.set_margin_end(6); - label.set_margin_top(6); - label.set_margin_bottom(6); - label.upcast() - }); + this.selector.set_make_widget(clone!(@strong this => move |recording| { + let row = libhandy::ActionRow::new(); + row.set_activatable(true); + row.set_title(Some(&recording.get_performers())); + + let recording = recording.to_owned(); + row.connect_activated(clone!(@strong this => move |_| { + this.select(&recording); + })); + + row.upcast() + })); this.selector.set_filter(|search, recording| { recording.get_performers().to_lowercase().contains(search) }); - this.selector - .set_selected_cb(clone!(@strong this => move |recording| this.select(recording))); - this } diff --git a/src/selectors/selector.rs b/src/selectors/selector.rs index f7b0de4..b6d27b5 100644 --- a/src/selectors/selector.rs +++ b/src/selectors/selector.rs @@ -1,10 +1,8 @@ use crate::widgets::List; use anyhow::Result; -use gettextrs::gettext; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; -use libhandy::HeaderBarExt; use std::cell::RefCell; use std::future::Future; use std::pin::Pin; @@ -14,12 +12,16 @@ use std::rc::Rc; /// database and to search within the list. pub struct Selector { pub widget: gtk::Box, - header: libhandy::HeaderBar, + title_label: gtk::Label, + subtitle_label: gtk::Label, + search_entry: gtk::SearchEntry, server_check_button: gtk::CheckButton, stack: gtk::Stack, - list: Rc>, + list: Rc, + items: RefCell>, back_cb: RefCell ()>>>, add_cb: RefCell ()>>>, + make_widget: RefCell gtk::Widget>>>, load_online: RefCell Box>>>>>>, load_local: RefCell Box>>>>>, filter: RefCell bool>>>, @@ -33,7 +35,8 @@ impl Selector { let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/selector.ui"); get_widget!(builder, gtk::Box, widget); - get_widget!(builder, libhandy::HeaderBar, header); + get_widget!(builder, gtk::Label, title_label); + get_widget!(builder, gtk::Label, subtitle_label); get_widget!(builder, gtk::Button, back_button); get_widget!(builder, gtk::Button, add_button); get_widget!(builder, gtk::SearchEntry, search_entry); @@ -42,17 +45,21 @@ impl Selector { get_widget!(builder, gtk::Frame, frame); get_widget!(builder, gtk::Button, try_again_button); - let list = List::::new(&gettext("Nothing found.")); - frame.add(&list.widget); + let list = List::new(); + frame.set_child(Some(&list.widget)); let this = Rc::new(Self { widget, - header, + title_label, + subtitle_label, + search_entry, server_check_button, stack, list, + items: RefCell::new(Vec::new()), back_cb: RefCell::new(None), add_cb: RefCell::new(None), + make_widget: RefCell::new(None), load_online: RefCell::new(None), load_local: RefCell::new(None), filter: RefCell::new(None), @@ -72,7 +79,7 @@ impl Selector { } })); - search_entry.connect_search_changed(clone!(@strong this => move |_| { + this.search_entry.connect_search_changed(clone!(@strong this => move |_| { this.list.invalidate_filter(); })); @@ -85,17 +92,25 @@ impl Selector { } })); - this.list.set_filter( - clone!(@strong this, @strong search_entry => move |item: &T| { - match &*this.filter.borrow() { - Some(filter) => { - let search = search_entry.get_text().to_string().to_lowercase(); - search.is_empty() || filter(&search, item) - } - None => true, + this.list.set_make_widget_cb(clone!(@strong this => move |index| { + if let Some(cb) = &*this.make_widget.borrow() { + let item = &this.items.borrow()[index]; + cb(item) + } else { + gtk::Label::new(None).upcast() + } + })); + + this.list.set_filter_cb(clone!(@strong this => move |index| { + match &*this.filter.borrow() { + Some(filter) => { + let item = &this.items.borrow()[index]; + let search = this.search_entry.get_text().unwrap().to_string().to_lowercase(); + search.is_empty() || filter(&search, item) } - }), - ); + None => true, + } + })); try_again_button.connect_clicked(clone!(@strong this => move |_| { this.clone().load_online(); @@ -109,12 +124,13 @@ impl Selector { /// Set the title to be shown in the header. pub fn set_title(&self, title: &str) { - self.header.set_title(Some(title)); + self.title_label.set_label(&title); } /// Set the subtitle to be shown in the header. pub fn set_subtitle(&self, subtitle: &str) { - self.header.set_subtitle(Some(subtitle)); + self.subtitle_label.set_label(&subtitle); + self.subtitle_label.show(); } /// Set the closure to be called when the user wants to go back. @@ -150,7 +166,7 @@ impl Selector { /// Set the closure to be called for creating a new list row. pub fn set_make_widget gtk::Widget + 'static>(&self, make_widget: F) { - self.list.set_make_widget(make_widget); + self.make_widget.replace(Some(Box::new(make_widget))); } /// Set a closure to call when deciding whether to show an item based on a search string. The @@ -159,11 +175,6 @@ impl Selector { self.filter.replace(Some(Box::new(filter))); } - /// Set the closure to be called when an item is selected. - pub fn set_selected_cb () + 'static>(&self, cb: F) { - self.list.set_selected(cb); - } - fn load_online(self: Rc) { let context = glib::MainContext::default(); let clone = self.clone(); @@ -173,11 +184,10 @@ impl Selector { match Pin::from(cb()).await { Ok(items) => { - clone.list.show_items(items); - clone.stack.set_visible_child_name("content"); + clone.show_items(items); } Err(_) => { - clone.list.show_items(Vec::new()); + clone.show_items(Vec::new()); clone.stack.set_visible_child_name("error"); } } @@ -193,9 +203,15 @@ impl Selector { self.stack.set_visible_child_name("loading"); let items = Pin::from(cb()).await; - clone.list.show_items(items); - clone.stack.set_visible_child_name("content"); + clone.show_items(items); } }); } + + fn show_items(&self, items: Vec) { + let length = items.len(); + self.items.replace(items); + self.list.update(length); + self.stack.set_visible_child_name("content"); + } } diff --git a/src/selectors/work.rs b/src/selectors/work.rs index 83e6a5a..d937770 100644 --- a/src/selectors/work.rs +++ b/src/selectors/work.rs @@ -6,6 +6,7 @@ use crate::widgets::{Navigator, NavigatorScreen}; use gettextrs::gettext; use glib::clone; use gtk::prelude::*; +use libhandy::prelude::*; use std::cell::RefCell; use std::rc::Rc; @@ -73,22 +74,22 @@ impl WorkSelector { async move { clone.backend.db().get_works(&clone.person.id).await.unwrap() } })); - this.selector.set_make_widget(|work| { - let label = gtk::Label::new(Some(&work.title)); - label.set_halign(gtk::Align::Start); - label.set_margin_start(6); - label.set_margin_end(6); - label.set_margin_top(6); - label.set_margin_bottom(6); - label.upcast() - }); + this.selector.set_make_widget(clone!(@strong this => move |work| { + let row = libhandy::ActionRow::new(); + row.set_activatable(true); + row.set_title(Some(&work.title)); + + let work = work.to_owned(); + row.connect_activated(clone!(@strong this => move |_| { + this.select(&work); + })); + + row.upcast() + })); this.selector .set_filter(|search, work| work.title.to_lowercase().contains(search)); - this.selector - .set_selected_cb(clone!(@strong this => move |work| this.select(work))); - this } diff --git a/src/widgets/indexed_list_model.rs b/src/widgets/indexed_list_model.rs new file mode 100644 index 0000000..b7a2f90 --- /dev/null +++ b/src/widgets/indexed_list_model.rs @@ -0,0 +1,189 @@ +use glib::prelude::*; +use glib::subclass; +use glib::subclass::prelude::*; +use gio::prelude::*; +use gio::subclass::prelude::*; +use std::cell::Cell; + +glib::wrapper! { + pub struct IndexedListModel(ObjectSubclass) + @implements gio::ListModel; +} + +impl IndexedListModel { + /// Create a new indexed list model, which will be empty initially. + pub fn new() -> Self { + glib::Object::new(&[]).unwrap() + } + + /// Set the length of the list model. + pub fn set_length(&self, length: u32) { + let old_length = self.get_property("length").unwrap().get_some::().unwrap(); + self.set_property("length", &length).unwrap(); + self.items_changed(0, old_length, length); + } +} + +mod indexed_list_model { + use super::*; + + #[derive(Debug)] + pub struct IndexedListModel { + length: Cell, + } + + static PROPERTIES: [subclass::Property; 1] = [ + subclass::Property("length", |length| { + glib::ParamSpec::uint( + length, + "Length", + "Length", + 0, + std::u32::MAX, + 0, + glib::ParamFlags::READWRITE, + ) + }), + ]; + + impl ObjectSubclass for IndexedListModel { + const NAME: &'static str = "IndexedListModel"; + + type Type = super::IndexedListModel; + type ParentType = glib::Object; + type Instance = subclass::simple::InstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib::object_subclass!(); + + fn type_init(type_: &mut subclass::InitializingType) { + type_.add_interface::(); + } + + fn class_init(klass: &mut Self::Class) { + klass.install_properties(&PROPERTIES); + } + + fn new() -> Self { + Self { length: Cell::new(0) } + } + } + + impl ObjectImpl for IndexedListModel { + fn set_property(&self, _obj: &Self::Type, id: usize, value: &glib::Value) { + let prop = &PROPERTIES[id]; + + match *prop { + subclass::Property("length", ..) => { + let length = value.get().unwrap().unwrap(); + self.length.set(length); + } + _ => unimplemented!(), + } + } + + fn get_property(&self, _obj: &Self::Type, id: usize) -> glib::Value { + let prop = &PROPERTIES[id]; + + match *prop { + subclass::Property("length", ..) => self.length.get().to_value(), + _ => unimplemented!(), + } + } + } + + impl ListModelImpl for IndexedListModel { + fn get_item_type(&self, _: &Self::Type) -> glib::Type { + ItemIndex::static_type() + } + + fn get_n_items(&self, _: &Self::Type) -> u32 { + self.length.get() + } + + fn get_item(&self, _: &Self::Type, position: u32) -> Option { + Some(ItemIndex::new(position).upcast()) + } + } +} + +glib::wrapper! { + pub struct ItemIndex(ObjectSubclass); +} + +impl ItemIndex { + /// Create a new item index. + pub fn new(value: u32) -> Self { + glib::Object::new(&[("value", &value)]).unwrap() + } + + /// Get the value of the item index.. + pub fn get(&self) -> u32 { + self.get_property("value").unwrap().get_some::().unwrap() + } +} + +mod item_index { + use super::*; + + #[derive(Debug)] + pub struct ItemIndex { + value: Cell, + } + + static PROPERTIES: [subclass::Property; 1] = [ + subclass::Property("value", |value| { + glib::ParamSpec::uint( + value, + "Value", + "Value", + 0, + std::u32::MAX, + 0, + glib::ParamFlags::READWRITE, + ) + }), + ]; + + impl ObjectSubclass for ItemIndex { + const NAME: &'static str = "ItemIndex"; + + type Type = super::ItemIndex; + type ParentType = glib::Object; + type Instance = subclass::simple::InstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib::object_subclass!(); + + fn class_init(klass: &mut Self::Class) { + klass.install_properties(&PROPERTIES); + } + + fn new() -> Self { + Self { value: Cell::new(0) } + } + } + + impl ObjectImpl for ItemIndex { + fn set_property(&self, _obj: &Self::Type, id: usize, value: &glib::Value) { + let prop = &PROPERTIES[id]; + + match *prop { + subclass::Property("value", ..) => { + let value = value.get().unwrap().unwrap(); + self.value.set(value); + } + _ => unimplemented!(), + } + } + + fn get_property(&self, _obj: &Self::Type, id: usize) -> glib::Value { + let prop = &PROPERTIES[id]; + + match *prop { + subclass::Property("value", ..) => self.value.get().to_value(), + _ => unimplemented!(), + } + } + } +} diff --git a/src/widgets/list.rs b/src/widgets/list.rs index 890f866..4f4d90d 100644 --- a/src/widgets/list.rs +++ b/src/widgets/list.rs @@ -1,139 +1,88 @@ -use super::*; +use super::indexed_list_model::{IndexedListModel, ItemIndex}; use glib::clone; use gtk::prelude::*; use std::cell::RefCell; -use std::convert::TryInto; use std::rc::Rc; -pub struct List -where - T: 'static, -{ +/// A simple list of widgets. +pub struct List { pub widget: gtk::ListBox, - items: RefCell>, - make_widget: RefCell gtk::Widget>>>, - filter: RefCell bool>>>, - selected: RefCell ()>>>, + model: IndexedListModel, + filter: gtk::CustomFilter, + make_widget_cb: RefCell gtk::Widget>>>, + filter_cb: RefCell bool>>>, } -impl List -where - T: 'static, -{ - pub fn new(placeholder_text: &str) -> Rc { - let placeholder_label = gtk::Label::new(Some(placeholder_text)); - placeholder_label.set_margin_top(6); - placeholder_label.set_margin_bottom(6); - placeholder_label.set_margin_start(6); - placeholder_label.set_margin_end(6); - placeholder_label.show(); +impl List { + /// Create a new list. The list will be empty initially. + pub fn new() -> Rc { + let model = IndexedListModel::new(); + let filter = gtk::CustomFilter::new(|_| true); + let filter_model = gtk::FilterListModel::new(Some(&model), Some(&filter)); + + // TODO: Switch to gtk::ListView. + // let selection = gtk::NoSelection::new(Some(&model)); + // let factory = gtk::SignalListItemFactory::new(); + // let widget = gtk::ListView::new(Some(&selection), Some(&factory)); let widget = gtk::ListBox::new(); - widget.set_placeholder(Some(&placeholder_label)); - widget.show(); let this = Rc::new(Self { widget, - items: RefCell::new(Vec::new()), - make_widget: RefCell::new(None), - filter: RefCell::new(None), - selected: RefCell::new(None), + model, + filter, + make_widget_cb: RefCell::new(None), + filter_cb: RefCell::new(None), }); - this.widget - .connect_row_activated(clone!(@strong this => move |_, row| { - if let Some(selected) = &*this.selected.borrow() { - let row = row.get_child().unwrap().downcast::().unwrap(); - let index: usize = row.get_index().try_into().unwrap(); - selected(&this.items.borrow()[index]); - } - })); + this.filter.set_filter_func(clone!(@strong this => move |index| { + if let Some(cb) = &*this.filter_cb.borrow() { + let index = index.downcast_ref::().unwrap().get() as usize; + cb(index) + } else { + true + } + })); - this.widget - .set_filter_func(Some(Box::new(clone!(@strong this => move |row| { - if let Some(filter) = &*this.filter.borrow() { - let row = row.get_child().unwrap().downcast::().unwrap(); - let index: usize = row.get_index().try_into().unwrap(); - filter(&this.items.borrow()[index]) - } else { - true - } - })))); + this.widget.bind_model(Some(&filter_model), clone!(@strong this => move |index| { + let index = index.downcast_ref::().unwrap().get() as usize; + if let Some(cb) = &*this.make_widget_cb.borrow() { + cb(index) + } else { + gtk::Label::new(None).upcast() + } + })); this } - pub fn set_selectable(&self, selectable: bool) { - let mode = if selectable { - gtk::SelectionMode::Single - } else { - gtk::SelectionMode::None - }; - - self.widget.set_selection_mode(mode); + /// Set the closure to be called to construct widgets for the items. + pub fn set_make_widget_cb gtk::Widget + 'static>(&self, cb: F) { + self.make_widget_cb.replace(Some(Box::new(cb))); } - pub fn set_make_widget gtk::Widget + 'static>(&self, make_widget: F) { - self.make_widget.replace(Some(Box::new(make_widget))); + /// Set the closure to be called to filter the items. If this returns + /// false, the item will not be shown. + pub fn set_filter_cb bool + 'static>(&self, cb: F) { + self.filter_cb.replace(Some(Box::new(cb))); } - pub fn set_filter bool + 'static>(&self, filter: F) { - self.filter.replace(Some(Box::new(filter))); - } - - pub fn set_selected () + 'static>(&self, selected: S) { - self.selected.replace(Some(Box::new(selected))); - } - - pub fn get_selected_index(&self) -> Option { - match self.widget.get_selected_rows().first() { - Some(row) => match row.get_child() { - Some(child) => Some( - child - .downcast::() - .unwrap() - .get_index() - .try_into() - .unwrap(), - ), - None => None, - }, - None => None, + /// Select an item by its index. If the index is out of range, nothing will happen. + pub fn select(&self, index: usize) { + let row = self.widget.get_row_at_index(index as i32); + if let Some(row) = row { + self.widget.select_row(Some(&row)); } } - pub fn select_index(&self, index: usize) { - self.widget.select_row( - self.widget - .get_row_at_index(index.try_into().unwrap()) - .as_ref(), - ); - } - - pub fn show_items(&self, items: Vec) { - self.items.replace(items); - self.update(); - } - + /// Refilter the list based on the filter callback. pub fn invalidate_filter(&self) { - self.widget.invalidate_filter(); + self.filter.changed(gtk::FilterChange::Different); } - pub fn update(&self) { - for child in self.widget.get_children() { - self.widget.remove(&child); - } - - if let Some(make_widget) = &*self.make_widget.borrow() { - for (index, item) in self.items.borrow().iter().enumerate() { - let row = SelectorRow::new(index.try_into().unwrap(), &make_widget(item)); - row.show_all(); - self.widget.insert(&row, -1); - } - } - } - - pub fn clear_selection(&self) { - self.widget.unselect_all(); + /// Call the make_widget function for each item. This will automatically + /// show all children by indices 0..length. + pub fn update(&self, length: usize) { + self.model.set_length(length as u32); } } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 089a3ec..923e3a2 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -7,13 +7,10 @@ pub use navigator::*; pub mod navigator_window; pub use navigator_window::*; -pub mod new_list; - pub mod player_bar; pub use player_bar::*; pub mod poe_list; pub use poe_list::*; -pub mod selector_row; -pub use selector_row::*; +mod indexed_list_model; diff --git a/src/widgets/navigator.rs b/src/widgets/navigator.rs index 2ae343b..c2c6a87 100644 --- a/src/widgets/navigator.rs +++ b/src/widgets/navigator.rs @@ -29,7 +29,7 @@ impl Navigator { widget.set_interpolate_size(true); widget.set_transition_type(gtk::StackTransitionType::Crossfade); widget.set_hexpand(true); - widget.add_named(empty_screen, "empty_screen"); + widget.add_named(empty_screen, Some("empty_screen")); widget.show(); let result = Rc::new(Self { @@ -70,7 +70,7 @@ impl Navigator { } let widget = screen.get_widget(); - self.widget.add(&widget); + self.widget.add_child(&widget); self.widget.set_visible_child(&widget); screen.attach_navigator(self.clone()); @@ -116,7 +116,7 @@ impl Navigator { } let widget = screen.get_widget(); - self.widget.add(&widget); + self.widget.add_child(&widget); self.widget.set_visible_child(&widget); screen.attach_navigator(self.clone()); diff --git a/src/widgets/navigator_window.rs b/src/widgets/navigator_window.rs index 66fccb7..297383b 100644 --- a/src/widgets/navigator_window.rs +++ b/src/widgets/navigator_window.rs @@ -18,7 +18,7 @@ impl NavigatorWindow { window.set_default_size(600, 424); let placeholder = gtk::Label::new(None); let navigator = Navigator::new(&window, &placeholder); - window.add(&navigator.widget); + libhandy::WindowExt::set_child(&window, Some(&navigator.widget)); let this = Rc::new(Self { window, navigator }); diff --git a/src/widgets/new_list.rs b/src/widgets/new_list.rs deleted file mode 100644 index 98261e6..0000000 --- a/src/widgets/new_list.rs +++ /dev/null @@ -1,50 +0,0 @@ -use gtk::prelude::*; -use std::cell::RefCell; - -/// A simple list of widgets. -pub struct List { - pub widget: gtk::ListBox, - make_widget: RefCell gtk::Widget>>>, -} - -impl List { - /// Create a new list. The list will be empty. - pub fn new(placeholder_text: &str) -> Self { - let placeholder_label = gtk::Label::new(Some(placeholder_text)); - placeholder_label.set_margin_top(6); - placeholder_label.set_margin_bottom(6); - placeholder_label.set_margin_start(6); - placeholder_label.set_margin_end(6); - placeholder_label.show(); - - let widget = gtk::ListBox::new(); - widget.set_selection_mode(gtk::SelectionMode::None); - widget.set_placeholder(Some(&placeholder_label)); - widget.show(); - - Self { - widget, - make_widget: RefCell::new(None), - } - } - - /// Set the closure to be called to construct widgets for the items. - pub fn set_make_widget gtk::Widget + 'static>(&self, make_widget: F) { - self.make_widget.replace(Some(Box::new(make_widget))); - } - - /// Call the make_widget function for each item. This will automatically - /// show all children by indices 0..length. - pub fn update(&self, length: usize) { - for child in self.widget.get_children() { - self.widget.remove(&child); - } - - if let Some(make_widget) = &*self.make_widget.borrow() { - for index in 0..length { - let row = make_widget(index); - self.widget.insert(&row, -1); - } - } - } -} diff --git a/src/widgets/player_bar.rs b/src/widgets/player_bar.rs index 1e61df6..50a084a 100644 --- a/src/widgets/player_bar.rs +++ b/src/widgets/player_bar.rs @@ -144,15 +144,11 @@ impl PlayerBar { @strong self.play_image as play_image, @strong self.pause_image as pause_image => move |playing| { - if let Some(child) = play_button.get_child() { - play_button.remove( &child); - } - - play_button.add(if playing { + play_button.set_child(Some(if playing { &pause_image } else { &play_image - }); + })); } )); diff --git a/src/widgets/poe_list.rs b/src/widgets/poe_list.rs index 7cf3982..3077726 100644 --- a/src/widgets/poe_list.rs +++ b/src/widgets/poe_list.rs @@ -1,10 +1,11 @@ use super::*; use crate::backend::Backend; use crate::database::*; -use gettextrs::gettext; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; +use libhandy::prelude::*; +use std::cell::RefCell; use std::rc::Rc; #[derive(Clone)] @@ -24,9 +25,12 @@ impl PersonOrEnsemble { pub struct PoeList { pub widget: gtk::Box, - list: Rc>, backend: Rc, stack: gtk::Stack, + search_entry: gtk::SearchEntry, + list: Rc, + data: RefCell>, + selected_cb: RefCell>>, } impl PoeList { @@ -38,47 +42,54 @@ impl PoeList { get_widget!(builder, gtk::Stack, stack); get_widget!(builder, gtk::ScrolledWindow, scrolled_window); - let list = List::new(&gettext("No persons or ensembles found.")); + let list = List::new(); + list.widget.add_css_class("navigation-sidebar"); - list.set_make_widget(|poe: &PersonOrEnsemble| { - let label = gtk::Label::new(Some(&poe.get_title())); - label.set_halign(gtk::Align::Start); - label.set_margin_start(6); - label.set_margin_end(6); - label.set_margin_top(6); - label.set_margin_bottom(6); - label.upcast() - }); + scrolled_window.set_child(Some(&list.widget)); - list.set_filter( - clone!(@strong search_entry => move |poe: &PersonOrEnsemble| { - let search = search_entry.get_text().to_string().to_lowercase(); - let title = poe.get_title().to_lowercase(); - search.is_empty() || title.contains(&search) - }), - ); - - scrolled_window.add(&list.widget); - - let result = Rc::new(Self { + let this = Rc::new(Self { widget, - list, backend, stack, + search_entry, + list, + data: RefCell::new(Vec::new()), + selected_cb: RefCell::new(None), }); - search_entry.connect_search_changed(clone!(@strong result => move |_| { - result.list.invalidate_filter(); + this.search_entry.connect_search_changed(clone!(@strong this => move |_| { + this.list.invalidate_filter(); })); - result + this.list.set_make_widget_cb(clone!(@strong this => move |index| { + let poe = &this.data.borrow()[index]; + + let row = libhandy::ActionRow::new(); + row.set_activatable(true); + row.set_title(Some(&poe.get_title())); + + let poe = poe.to_owned(); + row.connect_activated(clone!(@strong this => move |_| { + if let Some(cb) = &*this.selected_cb.borrow() { + cb(&poe); + } + })); + + row.upcast() + })); + + this.list.set_filter_cb(clone!(@strong this => move |index| { + let poe = &this.data.borrow()[index]; + let search = this.search_entry.get_text().unwrap().to_string().to_lowercase(); + let title = poe.get_title().to_lowercase(); + search.is_empty() || title.contains(&search) + })); + + this } - pub fn set_selected(&self, selected: S) - where - S: Fn(&PersonOrEnsemble) -> () + 'static, - { - self.list.set_selected(selected); + pub fn set_selected_cb(&self, cb: F) { + self.selected_cb.replace(Some(Box::new(cb))); } pub fn reload(self: Rc) { @@ -86,7 +97,6 @@ impl PoeList { let context = glib::MainContext::default(); let backend = self.backend.clone(); - let list = self.list.clone(); context.spawn_local(async move { let persons = backend.db().get_persons().await.unwrap(); @@ -101,7 +111,9 @@ impl PoeList { poes.push(PersonOrEnsemble::Ensemble(ensemble)); } - list.show_items(poes); + let length = poes.len(); + self.data.replace(poes); + self.list.update(length); self.stack.set_visible_child_name("content"); }); diff --git a/src/widgets/selector_row.rs b/src/widgets/selector_row.rs deleted file mode 100644 index a00f5c7..0000000 --- a/src/widgets/selector_row.rs +++ /dev/null @@ -1,149 +0,0 @@ -use glib::prelude::*; -use glib::subclass; -use glib::subclass::prelude::*; -use glib::translate::*; -use glib::{glib_object_impl, glib_object_subclass, glib_wrapper}; -use gtk::prelude::*; -use gtk::subclass::prelude::*; -use std::cell::{Cell, RefCell}; - -glib_wrapper! { - pub struct SelectorRow( - Object, - subclass::simple::ClassStruct, - SelectorRowClass> - ) @extends gtk::Bin, gtk::Container, gtk::Widget; - - match fn { - get_type => || SelectorRowPriv::get_type().to_glib(), - } -} - -impl SelectorRow { - pub fn new>(index: u64, child: &T) -> Self { - glib::Object::new( - Self::static_type(), - &[("index", &index), ("child", child.upcast_ref())], - ) - .expect("Failed to create SelectorRow GObject!") - .downcast() - .expect("SelectorRow GObject is of the wrong type!") - } - - pub fn get_index(&self) -> u64 { - self.get_property("index").unwrap().get().unwrap().unwrap() - } -} - -pub struct SelectorRowPriv { - index: Cell, - child: RefCell>, -} - -static PROPERTIES: [subclass::Property; 2] = [ - subclass::Property("index", |name| { - glib::ParamSpec::uint64( - name, - "Index", - "Index", - 0, - u64::MAX, - 0, - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("child", |name| { - glib::ParamSpec::object( - name, - "Child", - "Child", - gtk::Widget::static_type(), - glib::ParamFlags::READWRITE, - ) - }), -]; - -impl ObjectSubclass for SelectorRowPriv { - const NAME: &'static str = "SelectorRow"; - type ParentType = gtk::Bin; - type Instance = subclass::simple::InstanceStruct; - type Class = subclass::simple::ClassStruct; - - glib_object_subclass!(); - - fn class_init(klass: &mut Self::Class) { - klass.install_properties(&PROPERTIES); - } - - fn new() -> Self { - Self { - index: Cell::new(0), - child: RefCell::new(None), - } - } -} - -impl ObjectImpl for SelectorRowPriv { - glib_object_impl!(); - - fn constructed(&self, object: &glib::Object) { - self.parent_constructed(object); - - let row = object.downcast_ref::().unwrap(); - - let child = self.child.borrow(); - match child.as_ref() { - Some(child) => row.add(child), - None => (), - } - } - - fn set_property(&self, object: &glib::Object, id: usize, value: &glib::Value) { - let prop = &PROPERTIES[id]; - - match *prop { - subclass::Property("index", ..) => { - let index = value - .get_some() - .expect("Wrong type for SelectorRow GObject index property!"); - self.index.set(index); - } - subclass::Property("child", ..) => { - let child = value - .get() - .expect("Wrong type for SelectorRow GObject child property!"); - - let row = object.downcast_ref::().unwrap(); - - { - let old = self.child.borrow(); - match old.as_ref() { - Some(old) => row.remove(old), - None => (), - } - } - - self.child.replace(child.clone()); - match child { - Some(child) => row.add(&child), - None => (), - } - } - _ => unimplemented!(), - } - } - - fn get_property(&self, _obj: &glib::Object, id: usize) -> Result { - let prop = &PROPERTIES[id]; - - match *prop { - subclass::Property("index", ..) => Ok(self.index.get().to_value()), - subclass::Property("child", ..) => Ok(self.child.borrow().to_value()), - _ => unimplemented!(), - } - } -} - -impl WidgetImpl for SelectorRowPriv {} -impl ContainerImpl for SelectorRowPriv {} -impl BinImpl for SelectorRowPriv {} diff --git a/src/window.rs b/src/window.rs index 429a870..b71bcd7 100644 --- a/src/window.rs +++ b/src/window.rs @@ -9,7 +9,6 @@ use gio::prelude::*; use glib::clone; use gtk::prelude::*; use gtk_macros::{action, get_widget}; -use libhandy::prelude::*; use std::rc::Rc; pub struct Window { @@ -21,7 +20,7 @@ pub struct Window { poe_list: Rc, navigator: Rc, player_bar: PlayerBar, - player_screen: PlayerScreen, + player_screen: Rc, } impl Window { @@ -40,7 +39,7 @@ impl Window { let backend = Rc::new(Backend::new()); let player_screen = PlayerScreen::new(); - stack.add_named(&player_screen.widget, "player_screen"); + stack.add_named(&player_screen.widget, Some("player_screen")); let poe_list = PoeList::new(backend.clone()); let navigator = Navigator::new(&window, &empty_screen); @@ -49,7 +48,7 @@ impl Window { })); let player_bar = PlayerBar::new(); - content_box.add(&player_bar.widget); + content_box.append(&player_bar.widget); let result = Rc::new(Self { backend, @@ -73,15 +72,21 @@ impl Window { None, None); - if let gtk::ResponseType::Accept = dialog.run() { - if let Some(path) = dialog.get_filename() { - let context = glib::MainContext::default(); - let backend = result.backend.clone(); - context.spawn_local(async move { - backend.set_music_library_path(path).await.unwrap(); - }); + dialog.connect_response(clone!(@strong result => move |dialog, response| { + if response == gtk::ResponseType::Accept { + if let Some(file) = dialog.get_file() { + if let Some(path) = file.get_path() { + let context = glib::MainContext::default(); + let backend = result.backend.clone(); + context.spawn_local(async move { + backend.set_music_library_path(path).await.unwrap(); + }); + } + } } - } + })); + + dialog.show(); })); add_button.connect_clicked(clone!(@strong result => move |_| { @@ -156,7 +161,7 @@ impl Window { let player = clone.backend.get_player().unwrap(); clone.player_bar.set_player(Some(player.clone())); - clone.player_screen.set_player(Some(player)); + clone.player_screen.clone().set_player(Some(player)); } } } @@ -169,11 +174,11 @@ impl Window { clone.backend.clone().init().await.unwrap(); }); - result.leaflet.add(&result.navigator.widget); + result.leaflet.append(&result.navigator.widget); result .poe_list - .set_selected(clone!(@strong result => move |poe| { + .set_selected_cb(clone!(@strong result => move |poe| { result.leaflet.set_visible_child(&result.navigator.widget); match poe { PersonOrEnsemble::Person(person) => { @@ -187,7 +192,7 @@ impl Window { result .sidebar_box - .pack_start(&result.poe_list.widget, true, true, 0); + .append(&result.poe_list.widget); result }