Use editor widget for performance editor

This commit is contained in:
Elias Projahn 2021-02-02 12:18:42 +01:00
parent d92fd419d3
commit 7eff62b5a4
7 changed files with 170 additions and 66 deletions

View file

@ -8,15 +8,28 @@
<object class="GtkBox" id="title_box"> <object class="GtkBox" id="title_box">
<property name="spacing">12</property> <property name="spacing">12</property>
<child> <child>
<object class="GtkLabel" id="title_label"> <object class="GtkBox">
<property name="ellipsize">end</property> <property name="orientation">vertical</property>
<property name="xalign">0.0</property> <property name="margin-top">18</property>
<property name="valign">end</property> <property name="valign">end</property>
<property name="hexpand">true</property> <property name="hexpand">true</property>
<property name="margin-top">18</property> <child>
<attributes> <object class="GtkLabel" id="title_label">
<attribute name="weight" value="bold"/> <property name="ellipsize">end</property>
</attributes> <property name="xalign">0.0</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
</child>
<child>
<object class="GtkLabel" id="subtitle_label">
<property name="wrap">true</property>
<property name="xalign">0.0</property>
<property name="visible">false</property>
<property name="margin-bottom">6</property>
</object>
</child>
</object> </object>
</child> </child>
</object> </object>

View file

@ -1,11 +1,10 @@
use crate::backend::Backend; use crate::backend::Backend;
use crate::database::*; use crate::database::*;
use crate::selectors::{EnsembleSelector, InstrumentSelector, PersonSelector}; use crate::selectors::{EnsembleSelector, InstrumentSelector, PersonSelector};
use crate::widgets::{Navigator, NavigatorScreen}; use crate::widgets::{Editor, Navigator, NavigatorScreen, Section, ButtonRow, Widget};
use gettextrs::gettext; use gettextrs::gettext;
use glib::clone; use glib::clone;
use gtk::prelude::*; use gtk::prelude::*;
use gtk_macros::get_widget;
use libadwaita::prelude::*; use libadwaita::prelude::*;
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
@ -13,11 +12,10 @@ use std::rc::Rc;
/// A dialog for editing a performance within a recording. /// A dialog for editing a performance within a recording.
pub struct PerformanceEditor { pub struct PerformanceEditor {
backend: Rc<Backend>, backend: Rc<Backend>,
widget: gtk::Box, editor: Editor,
save_button: gtk::Button, person_row: ButtonRow,
person_row: libadwaita::ActionRow, ensemble_row: ButtonRow,
ensemble_row: libadwaita::ActionRow, role_row: ButtonRow,
role_row: libadwaita::ActionRow,
reset_role_button: gtk::Button, reset_role_button: gtk::Button,
person: RefCell<Option<Person>>, person: RefCell<Option<Person>>,
ensemble: RefCell<Option<Ensemble>>, ensemble: RefCell<Option<Ensemble>>,
@ -29,25 +27,49 @@ pub struct PerformanceEditor {
impl PerformanceEditor { impl PerformanceEditor {
/// Create a new performance editor. /// Create a new performance editor.
pub fn new(backend: Rc<Backend>, performance: Option<Performance>) -> Rc<Self> { pub fn new(backend: Rc<Backend>, performance: Option<Performance>) -> Rc<Self> {
// Create UI let editor = Editor::new();
editor.set_title("Performance");
editor.set_may_save(false);
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/performance_editor.ui"); let performer_list = gtk::ListBoxBuilder::new()
.selection_mode(gtk::SelectionMode::None)
.build();
get_widget!(builder, gtk::Box, widget); let person_row = ButtonRow::new("Person", "Select");
get_widget!(builder, gtk::Button, back_button); let ensemble_row = ButtonRow::new("Ensemble", "Select");
get_widget!(builder, gtk::Button, save_button);
get_widget!(builder, gtk::Button, person_button); performer_list.append(&person_row.get_widget());
get_widget!(builder, gtk::Button, ensemble_button); performer_list.append(&ensemble_row.get_widget());
get_widget!(builder, gtk::Button, role_button);
get_widget!(builder, gtk::Button, reset_role_button); let performer_section = Section::new(&gettext("Performer"), &performer_list);
get_widget!(builder, libadwaita::ActionRow, person_row); performer_section.set_subtitle(
get_widget!(builder, libadwaita::ActionRow, ensemble_row); &gettext("Select either a person or an ensemble as a performer."));
get_widget!(builder, libadwaita::ActionRow, role_row);
let role_list = gtk::ListBoxBuilder::new()
.selection_mode(gtk::SelectionMode::None)
.build();
let reset_role_button = gtk::ButtonBuilder::new()
.icon_name("user-trash-symbolic")
.valign(gtk::Align::Center)
.visible(false)
.build();
let role_row = ButtonRow::new("Role", "Select");
role_row.widget.add_suffix(&reset_role_button);
role_list.append(&role_row.get_widget());
let role_section = Section::new(&gettext("Role"), &role_list);
role_section.set_subtitle(
&gettext("Optionally, choose a role to specify what the performer does."));
editor.add_content(&performer_section);
editor.add_content(&role_section);
let this = Rc::new(PerformanceEditor { let this = Rc::new(PerformanceEditor {
backend, backend,
widget, editor,
save_button,
person_row, person_row,
ensemble_row, ensemble_row,
role_row, role_row,
@ -59,32 +81,29 @@ impl PerformanceEditor {
navigator: RefCell::new(None), navigator: RefCell::new(None),
}); });
// Connect signals and callbacks this.editor.set_back_cb(clone!(@strong this => move || {
back_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone(); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator { if let Some(navigator) = navigator {
navigator.pop(); navigator.pop();
} }
})); }));
this.save_button this.editor.set_save_cb(clone!(@weak this => move || {
.connect_clicked(clone!(@strong this => move |_| { if let Some(cb) = &*this.selected_cb.borrow() {
if let Some(cb) = &*this.selected_cb.borrow() { cb(Performance {
cb(Performance { person: this.person.borrow().clone(),
person: this.person.borrow().clone(), ensemble: this.ensemble.borrow().clone(),
ensemble: this.ensemble.borrow().clone(), role: this.role.borrow().clone(),
role: this.role.borrow().clone(), });
}); }
}
let navigator = this.navigator.borrow().clone(); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator { if let Some(navigator) = navigator {
navigator.pop(); navigator.pop();
} }
})); }));
person_button.connect_clicked(clone!(@strong this => move |_| { this.person_row.set_cb(clone!(@weak this => move || {
let navigator = this.navigator.borrow().clone(); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator { if let Some(navigator) = navigator {
let selector = PersonSelector::new(this.backend.clone()); let selector = PersonSelector::new(this.backend.clone());
@ -101,7 +120,7 @@ impl PerformanceEditor {
} }
})); }));
ensemble_button.connect_clicked(clone!(@strong this => move |_| { this.ensemble_row.set_cb(clone!(@weak this => move || {
let navigator = this.navigator.borrow().clone(); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator { if let Some(navigator) = navigator {
let selector = EnsembleSelector::new(this.backend.clone()); let selector = EnsembleSelector::new(this.backend.clone());
@ -118,7 +137,7 @@ impl PerformanceEditor {
} }
})); }));
role_button.connect_clicked(clone!(@strong this => move |_| { this.role_row.set_cb(clone!(@weak this => move || {
let navigator = this.navigator.borrow().clone(); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator { if let Some(navigator) = navigator {
let selector = InstrumentSelector::new(this.backend.clone()); let selector = InstrumentSelector::new(this.backend.clone());
@ -133,11 +152,10 @@ impl PerformanceEditor {
} }
})); }));
this.reset_role_button this.reset_role_button.connect_clicked(clone!(@weak this => move |_| {
.connect_clicked(clone!(@strong this => move |_| { this.show_role(None);
this.show_role(None); this.role.replace(None);
this.role.replace(None); }));
}));
// Initialize // Initialize
@ -167,11 +185,9 @@ impl PerformanceEditor {
/// Update the UI according to person. /// Update the UI according to person.
fn show_person(&self, person: Option<&Person>) { fn show_person(&self, person: Option<&Person>) {
if let Some(person) = person { if let Some(person) = person {
self.person_row.set_title(Some(&gettext("Person")));
self.person_row.set_subtitle(Some(&person.name_fl())); self.person_row.set_subtitle(Some(&person.name_fl()));
self.save_button.set_sensitive(true); self.editor.set_may_save(true);
} else { } else {
self.person_row.set_title(Some(&gettext("Select a person")));
self.person_row.set_subtitle(None); self.person_row.set_subtitle(None);
} }
} }
@ -179,11 +195,9 @@ impl PerformanceEditor {
/// Update the UI according to ensemble. /// Update the UI according to ensemble.
fn show_ensemble(&self, ensemble: Option<&Ensemble>) { fn show_ensemble(&self, ensemble: Option<&Ensemble>) {
if let Some(ensemble) = ensemble { if let Some(ensemble) = ensemble {
self.ensemble_row.set_title(Some(&gettext("Ensemble")));
self.ensemble_row.set_subtitle(Some(&ensemble.name)); self.ensemble_row.set_subtitle(Some(&ensemble.name));
self.save_button.set_sensitive(true); self.editor.set_may_save(true);
} else { } else {
self.ensemble_row.set_title(Some(&gettext("Select an ensemble")));
self.ensemble_row.set_subtitle(None); self.ensemble_row.set_subtitle(None);
} }
} }
@ -191,11 +205,9 @@ impl PerformanceEditor {
/// Update the UI according to role. /// Update the UI according to role.
fn show_role(&self, role: Option<&Instrument>) { fn show_role(&self, role: Option<&Instrument>) {
if let Some(role) = role { if let Some(role) = role {
self.role_row.set_title(Some(&gettext("Role")));
self.role_row.set_subtitle(Some(&role.name)); self.role_row.set_subtitle(Some(&role.name));
self.reset_role_button.show(); self.reset_role_button.show();
} else { } else {
self.role_row.set_title(Some(&gettext("Select a role")));
self.role_row.set_subtitle(None); self.role_row.set_subtitle(None);
self.reset_role_button.hide(); self.reset_role_button.hide();
} }
@ -208,7 +220,7 @@ impl NavigatorScreen for PerformanceEditor {
} }
fn get_widget(&self) -> gtk::Widget { fn get_widget(&self) -> gtk::Widget {
self.widget.clone().upcast() self.editor.widget.clone().upcast()
} }
fn detach_navigator(&self) { fn detach_navigator(&self) {

View file

@ -90,6 +90,7 @@ sources = files(
'selectors/recording.rs', 'selectors/recording.rs',
'selectors/selector.rs', 'selectors/selector.rs',
'selectors/work.rs', 'selectors/work.rs',
'widgets/button_row.rs',
'widgets/editor.rs', 'widgets/editor.rs',
'widgets/entry_row.rs', 'widgets/entry_row.rs',
'widgets/upload_section.rs', 'widgets/upload_section.rs',

51
src/widgets/button_row.rs Normal file
View file

@ -0,0 +1,51 @@
use super::Widget;
use gtk::prelude::*;
use libadwaita::prelude::*;
/// A list box row with a single button.
pub struct ButtonRow {
/// The actual GTK widget.
pub widget: libadwaita::ActionRow,
/// The managed button.
button: gtk::Button,
}
impl ButtonRow {
/// Create a new button row.
pub fn new(title: &str, label: &str) -> Self {
let button = gtk::ButtonBuilder::new()
.valign(gtk::Align::Center)
.label(label)
.build();
let widget = libadwaita::ActionRowBuilder::new()
.activatable(true)
.activatable_widget(&button)
.title(title)
.build();
widget.add_suffix(&button);
Self {
widget,
button,
}
}
/// Set the subtitle of the row.
pub fn set_subtitle(&self, subtitle: Option<&str>) {
self.widget.set_subtitle(subtitle);
}
/// Set the closure to be called on activation
pub fn set_cb<F: Fn() + 'static>(&self, cb: F) {
self.button.connect_clicked(move |_| cb());
}
}
impl Widget for ButtonRow {
fn get_widget(&self) -> gtk::Widget {
self.widget.clone().upcast()
}
}

View file

@ -1,3 +1,4 @@
use super::Widget;
use gio::prelude::*; use gio::prelude::*;
use glib::clone; use glib::clone;
use gtk::prelude::*; use gtk::prelude::*;
@ -61,6 +62,11 @@ impl Editor {
self.window_title.set_title(Some(title)); self.window_title.set_title(Some(title));
} }
/// Set whether the user should be able to click the save button.
pub fn set_may_save(&self, save: bool) {
self.save_button.set_sensitive(save);
}
pub fn set_save_cb<F: Fn() + 'static>(&self, cb: F) { pub fn set_save_cb<F: Fn() + 'static>(&self, cb: F) {
self.save_button.connect_clicked(move |_| cb()); self.save_button.connect_clicked(move |_| cb());
} }
@ -79,7 +85,7 @@ impl Editor {
} }
/// Add content to the bottom of the content area. /// Add content to the bottom of the content area.
pub fn add_content<W: IsA<gtk::Widget>>(&self, content: &W) { pub fn add_content<W: Widget>(&self, content: &W) {
self.content_box.append(content); self.content_box.append(&content.get_widget());
} }
} }

View file

@ -1,5 +1,8 @@
use gtk::prelude::*; use gtk::prelude::*;
pub mod button_row;
pub use button_row::*;
pub mod editor; pub mod editor;
pub use editor::*; pub use editor::*;

View file

@ -1,3 +1,4 @@
use super::Widget;
use gtk::prelude::*; use gtk::prelude::*;
use gtk_macros::get_widget; use gtk_macros::get_widget;
@ -9,27 +10,38 @@ pub struct Section {
/// The box containing the title and action buttons. /// The box containing the title and action buttons.
title_box: gtk::Box, title_box: gtk::Box,
/// An optional subtitle below the title.
subtitle_label: gtk::Label,
} }
impl Section { impl Section {
/// Create a new section. /// Create a new section.
pub fn new<W: IsA<gtk::Widget>>(title: &str, content: &W) -> Self { pub fn new<W: Widget>(title: &str, content: &W) -> Self {
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/section.ui"); let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/section.ui");
get_widget!(builder, gtk::Box, widget); get_widget!(builder, gtk::Box, widget);
get_widget!(builder, gtk::Box, title_box); get_widget!(builder, gtk::Box, title_box);
get_widget!(builder, gtk::Label, title_label); get_widget!(builder, gtk::Label, title_label);
get_widget!(builder, gtk::Label, subtitle_label);
get_widget!(builder, gtk::Frame, frame); get_widget!(builder, gtk::Frame, frame);
title_label.set_label(title); title_label.set_label(title);
frame.set_child(Some(content)); frame.set_child(Some(&content.get_widget()));
Self { Self {
widget, widget,
title_box, title_box,
subtitle_label,
} }
} }
/// Add a subtitle below the title.
pub fn set_subtitle(&self, subtitle: &str) {
self.subtitle_label.set_label(subtitle);
self.subtitle_label.show();
}
/// Add an action button. This should by definition be something that is /// Add an action button. This should by definition be something that is
/// doing something with the child widget that is applicable in all /// doing something with the child widget that is applicable in all
/// situations where the widget is visible. The new button will be packed /// situations where the widget is visible. The new button will be packed
@ -46,3 +58,9 @@ impl Section {
self.title_box.append(&button); self.title_box.append(&button);
} }
} }
impl Widget for Section {
fn get_widget(&self) -> gtk::Widget {
self.widget.clone().upcast()
}
}