mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 19:57:25 +01:00
Share UI between screens
The recording screen was reverted to a dummy in the process.
This commit is contained in:
parent
2d846a7b1a
commit
6abd450452
17 changed files with 555 additions and 977 deletions
|
|
@ -2,20 +2,19 @@
|
||||||
<gresources>
|
<gresources>
|
||||||
<gresource prefix="/de/johrpan/musicus">
|
<gresource prefix="/de/johrpan/musicus">
|
||||||
<file preprocess="xml-stripblanks">ui/ensemble_editor.ui</file>
|
<file preprocess="xml-stripblanks">ui/ensemble_editor.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/ensemble_screen.ui</file>
|
|
||||||
<file preprocess="xml-stripblanks">ui/instrument_editor.ui</file>
|
<file preprocess="xml-stripblanks">ui/instrument_editor.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/login_dialog.ui</file>
|
<file preprocess="xml-stripblanks">ui/login_dialog.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/medium_editor.ui</file>
|
<file preprocess="xml-stripblanks">ui/medium_editor.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/performance_editor.ui</file>
|
<file preprocess="xml-stripblanks">ui/performance_editor.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/person_editor.ui</file>
|
<file preprocess="xml-stripblanks">ui/person_editor.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/person_screen.ui</file>
|
|
||||||
<file preprocess="xml-stripblanks">ui/player_bar.ui</file>
|
<file preprocess="xml-stripblanks">ui/player_bar.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/player_screen.ui</file>
|
<file preprocess="xml-stripblanks">ui/player_screen.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/poe_list.ui</file>
|
<file preprocess="xml-stripblanks">ui/poe_list.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/preferences.ui</file>
|
<file preprocess="xml-stripblanks">ui/preferences.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/recording_editor.ui</file>
|
<file preprocess="xml-stripblanks">ui/recording_editor.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/recording_screen.ui</file>
|
|
||||||
<file preprocess="xml-stripblanks">ui/register_dialog.ui</file>
|
<file preprocess="xml-stripblanks">ui/register_dialog.ui</file>
|
||||||
|
<file preprocess="xml-stripblanks">ui/screen.ui</file>
|
||||||
|
<file preprocess="xml-stripblanks">ui/section.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/selector.ui</file>
|
<file preprocess="xml-stripblanks">ui/selector.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/server_dialog.ui</file>
|
<file preprocess="xml-stripblanks">ui/server_dialog.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/source_selector.ui</file>
|
<file preprocess="xml-stripblanks">ui/source_selector.ui</file>
|
||||||
|
|
@ -25,7 +24,6 @@
|
||||||
<file preprocess="xml-stripblanks">ui/window.ui</file>
|
<file preprocess="xml-stripblanks">ui/window.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/work_editor.ui</file>
|
<file preprocess="xml-stripblanks">ui/work_editor.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/work_part_editor.ui</file>
|
<file preprocess="xml-stripblanks">ui/work_part_editor.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/work_screen.ui</file>
|
|
||||||
<file preprocess="xml-stripblanks">ui/work_section_editor.ui</file>
|
<file preprocess="xml-stripblanks">ui/work_section_editor.ui</file>
|
||||||
</gresource>
|
</gresource>
|
||||||
</gresources>
|
</gresources>
|
||||||
|
|
|
||||||
|
|
@ -1,126 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<interface>
|
|
||||||
<requires lib="gtk" version="4.0"/>
|
|
||||||
<requires lib="libadwaita" version="1.0"/>
|
|
||||||
<object class="GtkBox" id="widget">
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<child>
|
|
||||||
<object class="AdwHeaderBar" id="header">
|
|
||||||
<property name="title-widget">
|
|
||||||
<object class="GtkLabel" id="title_label">
|
|
||||||
<property name="label" translatable="yes">Ensemble</property>
|
|
||||||
<style>
|
|
||||||
<class name="title"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkButton" id="back_button">
|
|
||||||
<property name="icon-name">go-previous-symbolic</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child type="end">
|
|
||||||
<object class="GtkMenuButton">
|
|
||||||
<property name="icon-name">view-more-symbolic</property>
|
|
||||||
<property name="menu-model">menu</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child type="end">
|
|
||||||
<object class="GtkToggleButton" id="search_button">
|
|
||||||
<property name="icon-name">edit-find-symbolic</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkSearchBar">
|
|
||||||
<property name="search-mode-enabled" bind-source="search_button" bind-property="active" bind-flags="bidirectional|sync-create">False</property>
|
|
||||||
<child>
|
|
||||||
<object class="AdwClamp">
|
|
||||||
<property name="maximum-size">400</property>
|
|
||||||
<property name="hexpand">true</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkSearchEntry" id="search_entry">
|
|
||||||
<property name="placeholder-text" translatable="yes">Search recordings …</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkStack" id="stack">
|
|
||||||
<child>
|
|
||||||
<object class="GtkStackPage">
|
|
||||||
<property name="name">loading</property>
|
|
||||||
<property name="child">
|
|
||||||
<object class="GtkSpinner">
|
|
||||||
<property name="spinning">True</property>
|
|
||||||
</object>
|
|
||||||
</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkStackPage">
|
|
||||||
<property name="name">content</property>
|
|
||||||
<property name="child">
|
|
||||||
<object class="GtkScrolledWindow">
|
|
||||||
<property name="vexpand">true</property>
|
|
||||||
<child>
|
|
||||||
<object class="AdwClamp">
|
|
||||||
<property name="margin-start">12</property>
|
|
||||||
<property name="margin-end">12</property>
|
|
||||||
<property name="margin-top">18</property>
|
|
||||||
<property name="margin-bottom">12</property>
|
|
||||||
<property name="maximum-size">800</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox" id="recording_box">
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<property name="spacing">12</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel">
|
|
||||||
<property name="halign">start</property>
|
|
||||||
<property name="label" translatable="yes">Recordings</property>
|
|
||||||
<attributes>
|
|
||||||
<attribute name="weight" value="bold"/>
|
|
||||||
</attributes>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkFrame" id="recording_frame">
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkStackPage">
|
|
||||||
<property name="name">nothing</property>
|
|
||||||
<property name="child">
|
|
||||||
<object class="GtkLabel">
|
|
||||||
<property name="label" translatable="yes">No recordings found.</property>
|
|
||||||
</object>
|
|
||||||
</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
<menu id="menu">
|
|
||||||
<section>
|
|
||||||
<item>
|
|
||||||
<attribute name="label" translatable="yes">Edit ensemble</attribute>
|
|
||||||
<attribute name="action">widget.edit</attribute>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<attribute name="label" translatable="yes">Delete ensemble</attribute>
|
|
||||||
<attribute name="action">widget.delete</attribute>
|
|
||||||
</item>
|
|
||||||
</section>
|
|
||||||
</menu>
|
|
||||||
</interface>
|
|
||||||
|
|
@ -1,155 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<interface>
|
|
||||||
<requires lib="gtk" version="4.0"/>
|
|
||||||
<requires lib="libadwaita" version="1.0"/>
|
|
||||||
<object class="GtkBox" id="widget">
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<child>
|
|
||||||
<object class="AdwHeaderBar" id="header">
|
|
||||||
<property name="title-widget">
|
|
||||||
<object class="GtkLabel" id="title_label">
|
|
||||||
<property name="label" translatable="yes">Person</property>
|
|
||||||
<style>
|
|
||||||
<class name="title"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkButton" id="back_button">
|
|
||||||
<property name="icon-name">go-previous-symbolic</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child type="end">
|
|
||||||
<object class="GtkMenuButton">
|
|
||||||
<property name="menu-model">menu</property>
|
|
||||||
<property name="icon-name">view-more-symbolic</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child type="end">
|
|
||||||
<object class="GtkToggleButton" id="search_button">
|
|
||||||
<property name="icon-name">edit-find-symbolic</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkSearchBar">
|
|
||||||
<property name="search-mode-enabled" bind-source="search_button" bind-property="active" bind-flags="bidirectional|sync-create">False</property>
|
|
||||||
<child>
|
|
||||||
<object class="AdwClamp">
|
|
||||||
<property name="maximum-size">400</property>
|
|
||||||
<property name="hexpand">true</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkSearchEntry" id="search_entry">
|
|
||||||
<property name="placeholder-text" translatable="yes">Search works and recordings …</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkStack" id="stack">
|
|
||||||
<child>
|
|
||||||
<object class="GtkStackPage">
|
|
||||||
<property name="name">loading</property>
|
|
||||||
<property name="child">
|
|
||||||
<object class="GtkSpinner">
|
|
||||||
<property name="hexpand">true</property>
|
|
||||||
<property name="vexpand">true</property>
|
|
||||||
<property name="halign">center</property>
|
|
||||||
<property name="valign">center</property>
|
|
||||||
<property name="spinning">true</property>
|
|
||||||
</object>
|
|
||||||
</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkStackPage">
|
|
||||||
<property name="name">content</property>
|
|
||||||
<property name="child">
|
|
||||||
<object class="GtkScrolledWindow">
|
|
||||||
<property name="vexpand">true</property>
|
|
||||||
<child>
|
|
||||||
<object class="AdwClamp">
|
|
||||||
<property name="margin-start">12</property>
|
|
||||||
<property name="margin-end">12</property>
|
|
||||||
<property name="margin-top">18</property>
|
|
||||||
<property name="margin-bottom">12</property>
|
|
||||||
<property name="maximum-size">800</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox">
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<property name="spacing">18</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox" id="work_box">
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<property name="spacing">12</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel">
|
|
||||||
<property name="halign">start</property>
|
|
||||||
<property name="label" translatable="yes">Works</property>
|
|
||||||
<attributes>
|
|
||||||
<attribute name="weight" value="bold"/>
|
|
||||||
</attributes>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkFrame" id="work_frame">
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox" id="recording_box">
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<property name="spacing">12</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel">
|
|
||||||
<property name="halign">start</property>
|
|
||||||
<property name="label" translatable="yes">Recordings</property>
|
|
||||||
<attributes>
|
|
||||||
<attribute name="weight" value="bold"/>
|
|
||||||
</attributes>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkFrame" id="recording_frame">
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkStackPage">
|
|
||||||
<property name="name">nothing</property>
|
|
||||||
<property name="child">
|
|
||||||
<object class="GtkLabel">
|
|
||||||
<property name="label" translatable="yes">No works or recordings found.</property>
|
|
||||||
</object>
|
|
||||||
</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
<menu id="menu">
|
|
||||||
<section>
|
|
||||||
<item>
|
|
||||||
<attribute name="label" translatable="yes">Edit person</attribute>
|
|
||||||
<attribute name="action">widget.edit</attribute>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<attribute name="label" translatable="yes">Delete person</attribute>
|
|
||||||
<attribute name="action">widget.delete</attribute>
|
|
||||||
</item>
|
|
||||||
</section>
|
|
||||||
</menu>
|
|
||||||
</interface>
|
|
||||||
|
|
@ -1,127 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<interface>
|
|
||||||
<requires lib="gtk" version="4.0"/>
|
|
||||||
<requires lib="libadwaita" version="1.0"/>
|
|
||||||
<object class="GtkBox" id="widget">
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<child>
|
|
||||||
<object class="AdwHeaderBar" id="header">
|
|
||||||
<property name="title-widget">
|
|
||||||
<object class="GtkBox">
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<property name="valign">center</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel" id="title_label">
|
|
||||||
<property name="label" translatable="yes">Recording</property>
|
|
||||||
<style>
|
|
||||||
<class name="title"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel" id="subtitle_label">
|
|
||||||
<style>
|
|
||||||
<class name="subtitle"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkButton" id="back_button">
|
|
||||||
<property name="icon-name">go-previous-symbolic</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child type="end">
|
|
||||||
<object class="GtkMenuButton">
|
|
||||||
<property name="icon-name">view-more-symbolic</property>
|
|
||||||
<property name="menu-model">menu</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkStack" id="stack">
|
|
||||||
<child>
|
|
||||||
<object class="GtkStackPage">
|
|
||||||
<property name="name">loading</property>
|
|
||||||
<property name="child">
|
|
||||||
<object class="GtkSpinner">
|
|
||||||
<property name="spinning">True</property>
|
|
||||||
</object>
|
|
||||||
</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkStackPage">
|
|
||||||
<property name="name">content</property>
|
|
||||||
<property name="child">
|
|
||||||
<object class="GtkScrolledWindow">
|
|
||||||
<property name="vexpand">true</property>
|
|
||||||
<child>
|
|
||||||
<object class="AdwClamp">
|
|
||||||
<property name="margin-start">12</property>
|
|
||||||
<property name="margin-end">12</property>
|
|
||||||
<property name="margin-top">18</property>
|
|
||||||
<property name="margin-bottom">12</property>
|
|
||||||
<property name="maximum-size">800</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox">
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<property name="spacing">12</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel">
|
|
||||||
<property name="halign">start</property>
|
|
||||||
<property name="label" translatable="yes">Tracks</property>
|
|
||||||
<attributes>
|
|
||||||
<attribute name="weight" value="bold"/>
|
|
||||||
</attributes>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkFrame" id="frame">
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkButton" id="add_to_playlist_button">
|
|
||||||
<property name="label" translatable="yes">Add to playlist</property>
|
|
||||||
<property name="halign">end</property>
|
|
||||||
<style>
|
|
||||||
<class name="suggested-action"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
<menu id="menu">
|
|
||||||
<section>
|
|
||||||
<item>
|
|
||||||
<attribute name="label" translatable="yes">Edit recording</attribute>
|
|
||||||
<attribute name="action">widget.edit</attribute>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<attribute name="label" translatable="yes">Delete recording</attribute>
|
|
||||||
<attribute name="action">widget.delete</attribute>
|
|
||||||
</item>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<item>
|
|
||||||
<attribute name="label" translatable="yes">Edit tracks</attribute>
|
|
||||||
<attribute name="action">widget.edit-tracks</attribute>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<attribute name="label" translatable="yes">Delete tracks</attribute>
|
|
||||||
<attribute name="action">widget.delete-tracks</attribute>
|
|
||||||
</item>
|
|
||||||
</section>
|
|
||||||
</menu>
|
|
||||||
</interface>
|
|
||||||
86
res/ui/screen.ui
Normal file
86
res/ui/screen.ui
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk" version="4.0"/>
|
||||||
|
<requires lib="libadwaita" version="1.0"/>
|
||||||
|
<object class="GtkBox" id="widget">
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="AdwHeaderBar">
|
||||||
|
<property name="title-widget">
|
||||||
|
<object class="AdwWindowTitle" id="window_title"/>
|
||||||
|
</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="back_button">
|
||||||
|
<property name="icon-name">go-previous-symbolic</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child type="end">
|
||||||
|
<object class="GtkMenuButton">
|
||||||
|
<property name="menu-model">menu</property>
|
||||||
|
<property name="icon-name">view-more-symbolic</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child type="end">
|
||||||
|
<object class="GtkToggleButton" id="search_button">
|
||||||
|
<property name="icon-name">edit-find-symbolic</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSearchBar">
|
||||||
|
<property name="search-mode-enabled" bind-source="search_button" bind-property="active" bind-flags="bidirectional|sync-create">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="AdwClamp">
|
||||||
|
<property name="hexpand">true</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSearchEntry" id="search_entry"/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkStack" id="stack">
|
||||||
|
<child>
|
||||||
|
<object class="GtkStackPage">
|
||||||
|
<property name="name">loading</property>
|
||||||
|
<property name="child">
|
||||||
|
<object class="GtkSpinner">
|
||||||
|
<property name="hexpand">true</property>
|
||||||
|
<property name="vexpand">true</property>
|
||||||
|
<property name="halign">center</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="width-request">32</property>
|
||||||
|
<property name="height-request">32</property>
|
||||||
|
<property name="spinning">true</property>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkStackPage">
|
||||||
|
<property name="name">content</property>
|
||||||
|
<property name="child">
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
|
<child>
|
||||||
|
<object class="AdwClamp">
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="content_box">
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="margin-start">12</property>
|
||||||
|
<property name="margin-end">12</property>
|
||||||
|
<property name="margin-bottom">36</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<menu id="menu"/>
|
||||||
|
</interface>
|
||||||
28
res/ui/section.ui
Normal file
28
res/ui/section.ui
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk" version="4.0"/>
|
||||||
|
<object class="GtkBox" id="widget">
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="title_box">
|
||||||
|
<property name="spacing">12</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="title_label">
|
||||||
|
<property name="ellipsize">end</property>
|
||||||
|
<property name="xalign">0.0</property>
|
||||||
|
<property name="valign">end</property>
|
||||||
|
<property name="hexpand">true</property>
|
||||||
|
<property name="margin-top">18</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="weight" value="bold"/>
|
||||||
|
</attributes>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkFrame" id="frame"/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</interface>
|
||||||
|
|
@ -1,139 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<interface>
|
|
||||||
<requires lib="gtk" version="4.0"/>
|
|
||||||
<requires lib="libadwaita" version="1.0"/>
|
|
||||||
<object class="GtkBox" id="widget">
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<child>
|
|
||||||
<object class="AdwHeaderBar" id="header">
|
|
||||||
<property name="title-widget">
|
|
||||||
<object class="GtkBox">
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<property name="valign">center</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel" id="title_label">
|
|
||||||
<property name="label" translatable="yes">Work</property>
|
|
||||||
<style>
|
|
||||||
<class name="title"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel" id="subtitle_label">
|
|
||||||
<style>
|
|
||||||
<class name="subtitle"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkButton" id="back_button">
|
|
||||||
<property name="icon-name">go-previous-symbolic</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child type="end">
|
|
||||||
<object class="GtkMenuButton">
|
|
||||||
<property name="icon-name">view-more-symbolic</property>
|
|
||||||
<property name="menu-model">menu</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child type="end">
|
|
||||||
<object class="GtkToggleButton" id="search_button">
|
|
||||||
<property name="icon-name">edit-find-symbolic</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkSearchBar">
|
|
||||||
<property name="search-mode-enabled" bind-source="search_button" bind-property="active" bind-flags="bidirectional|sync-create">False</property>
|
|
||||||
<child>
|
|
||||||
<object class="AdwClamp">
|
|
||||||
<property name="maximum-size">400</property>
|
|
||||||
<property name="hexpand">true</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkSearchEntry" id="search_entry">
|
|
||||||
<property name="placeholder-text" translatable="yes">Search recordings …</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkStack" id="stack">
|
|
||||||
<child>
|
|
||||||
<object class="GtkStackPage">
|
|
||||||
<property name="name">loading</property>
|
|
||||||
<property name="child">
|
|
||||||
<object class="GtkSpinner">
|
|
||||||
<property name="spinning">True</property>
|
|
||||||
</object>
|
|
||||||
</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkStackPage">
|
|
||||||
<property name="name">content</property>
|
|
||||||
<property name="child">
|
|
||||||
<object class="GtkScrolledWindow">
|
|
||||||
<property name="vexpand">true</property>
|
|
||||||
<child>
|
|
||||||
<object class="AdwClamp">
|
|
||||||
<property name="margin-start">12</property>
|
|
||||||
<property name="margin-end">12</property>
|
|
||||||
<property name="margin-top">18</property>
|
|
||||||
<property name="margin-bottom">12</property>
|
|
||||||
<property name="maximum-size">800</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox" id="recording_box">
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<property name="spacing">12</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel">
|
|
||||||
<property name="halign">start</property>
|
|
||||||
<property name="label" translatable="yes">Recordings</property>
|
|
||||||
<attributes>
|
|
||||||
<attribute name="weight" value="bold"/>
|
|
||||||
</attributes>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkFrame" id="recording_frame">
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkStackPage">
|
|
||||||
<property name="name">nothing</property>
|
|
||||||
<property name="child">
|
|
||||||
<object class="GtkLabel">
|
|
||||||
<property name="label" translatable="yes">No recordings found.</property>
|
|
||||||
</object>
|
|
||||||
</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
<menu id="menu">
|
|
||||||
<section>
|
|
||||||
<item>
|
|
||||||
<attribute name="label" translatable="yes">Edit work</attribute>
|
|
||||||
<attribute name="action">widget.edit</attribute>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<attribute name="label" translatable="yes">Delete work</attribute>
|
|
||||||
<attribute name="action">widget.delete</attribute>
|
|
||||||
</item>
|
|
||||||
</section>
|
|
||||||
</menu>
|
|
||||||
</interface>
|
|
||||||
|
|
@ -77,12 +77,12 @@ sources = files(
|
||||||
'import/track_editor.rs',
|
'import/track_editor.rs',
|
||||||
'import/track_selector.rs',
|
'import/track_selector.rs',
|
||||||
'import/track_set_editor.rs',
|
'import/track_set_editor.rs',
|
||||||
'screens/ensemble_screen.rs',
|
'screens/ensemble.rs',
|
||||||
'screens/mod.rs',
|
'screens/mod.rs',
|
||||||
'screens/person_screen.rs',
|
'screens/person.rs',
|
||||||
'screens/player_screen.rs',
|
'screens/player_screen.rs',
|
||||||
'screens/recording_screen.rs',
|
'screens/recording.rs',
|
||||||
'screens/work_screen.rs',
|
'screens/work.rs',
|
||||||
'selectors/ensemble.rs',
|
'selectors/ensemble.rs',
|
||||||
'selectors/instrument.rs',
|
'selectors/instrument.rs',
|
||||||
'selectors/mod.rs',
|
'selectors/mod.rs',
|
||||||
|
|
@ -97,6 +97,8 @@ sources = files(
|
||||||
'widgets/navigator_window.rs',
|
'widgets/navigator_window.rs',
|
||||||
'widgets/player_bar.rs',
|
'widgets/player_bar.rs',
|
||||||
'widgets/poe_list.rs',
|
'widgets/poe_list.rs',
|
||||||
|
'widgets/screen.rs',
|
||||||
|
'widgets/section.rs',
|
||||||
'config.rs',
|
'config.rs',
|
||||||
'config.rs.in',
|
'config.rs.in',
|
||||||
'main.rs',
|
'main.rs',
|
||||||
|
|
|
||||||
|
|
@ -1,74 +1,72 @@
|
||||||
use super::*;
|
use super::RecordingScreen;
|
||||||
use crate::backend::*;
|
|
||||||
use crate::database::*;
|
use crate::backend::Backend;
|
||||||
|
use crate::database::{Ensemble, Recording};
|
||||||
use crate::editors::EnsembleEditor;
|
use crate::editors::EnsembleEditor;
|
||||||
use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow};
|
use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow, Screen, Section};
|
||||||
use gio::prelude::*;
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/// A screen for showing recordings with a ensemble.
|
||||||
pub struct EnsembleScreen {
|
pub struct EnsembleScreen {
|
||||||
backend: Rc<Backend>,
|
backend: Rc<Backend>,
|
||||||
ensemble: Ensemble,
|
ensemble: Ensemble,
|
||||||
widget: gtk::Box,
|
widget: Screen,
|
||||||
search_entry: gtk::SearchEntry,
|
|
||||||
stack: gtk::Stack,
|
|
||||||
recording_list: Rc<List>,
|
recording_list: Rc<List>,
|
||||||
recordings: RefCell<Vec<Recording>>,
|
recordings: RefCell<Vec<Recording>>,
|
||||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EnsembleScreen {
|
impl EnsembleScreen {
|
||||||
|
/// Create a new ensemble screen for the specified ensemble and load the
|
||||||
|
/// contents asynchronously.
|
||||||
pub fn new(backend: Rc<Backend>, ensemble: Ensemble) -> Rc<Self> {
|
pub fn new(backend: Rc<Backend>, ensemble: Ensemble) -> Rc<Self> {
|
||||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/ensemble_screen.ui");
|
let widget = Screen::new();
|
||||||
|
widget.set_title(&ensemble.name);
|
||||||
get_widget!(builder, gtk::Box, widget);
|
|
||||||
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);
|
|
||||||
|
|
||||||
title_label.set_label(&ensemble.name);
|
|
||||||
|
|
||||||
let edit_action = gio::SimpleAction::new("edit", None);
|
|
||||||
let delete_action = gio::SimpleAction::new("delete", None);
|
|
||||||
|
|
||||||
let actions = gio::SimpleActionGroup::new();
|
|
||||||
actions.add_action(&edit_action);
|
|
||||||
actions.add_action(&delete_action);
|
|
||||||
|
|
||||||
widget.insert_action_group("widget", Some(&actions));
|
|
||||||
|
|
||||||
let recording_list = List::new();
|
let recording_list = List::new();
|
||||||
recording_frame.set_child(Some(&recording_list.widget));
|
|
||||||
|
|
||||||
let this = Rc::new(Self {
|
let this = Rc::new(Self {
|
||||||
backend,
|
backend,
|
||||||
ensemble,
|
ensemble,
|
||||||
widget,
|
widget,
|
||||||
search_entry,
|
|
||||||
stack,
|
|
||||||
recording_list,
|
recording_list,
|
||||||
recordings: RefCell::new(Vec::new()),
|
recordings: RefCell::new(Vec::new()),
|
||||||
navigator: RefCell::new(None),
|
navigator: RefCell::new(None),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.search_entry.connect_search_changed(clone!(@strong this => move |_| {
|
this.widget.set_back_cb(clone!(@strong this => move || {
|
||||||
this.recording_list.invalidate_filter();
|
|
||||||
}));
|
|
||||||
|
|
||||||
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.widget.add_action(&gettext("Edit ensemble"), clone!(@strong this => move || {
|
||||||
|
let editor = EnsembleEditor::new(this.backend.clone(), Some(this.ensemble.clone()));
|
||||||
|
let window = NavigatorWindow::new(editor);
|
||||||
|
window.show();
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.widget.add_action(&gettext("Delete ensemble"), clone!(@strong this => move || {
|
||||||
|
let context = glib::MainContext::default();
|
||||||
|
let clone = this.clone();
|
||||||
|
context.spawn_local(async move {
|
||||||
|
clone.backend.db().delete_ensemble(&clone.ensemble.id).await.unwrap();
|
||||||
|
clone.backend.library_changed();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.widget.set_search_cb(clone!(@strong this => move || {
|
||||||
|
this.recording_list.invalidate_filter();
|
||||||
|
}));
|
||||||
|
|
||||||
this.recording_list.set_make_widget_cb(clone!(@strong this => move |index| {
|
this.recording_list.set_make_widget_cb(clone!(@strong this => move |index| {
|
||||||
let recording = &this.recordings.borrow()[index];
|
let recording = &this.recordings.borrow()[index];
|
||||||
|
|
||||||
|
|
@ -90,28 +88,16 @@ impl EnsembleScreen {
|
||||||
|
|
||||||
this.recording_list.set_filter_cb(clone!(@strong this => move |index| {
|
this.recording_list.set_filter_cb(clone!(@strong this => move |index| {
|
||||||
let recording = &this.recordings.borrow()[index];
|
let recording = &this.recordings.borrow()[index];
|
||||||
let search = this.search_entry.get_text().unwrap().to_string().to_lowercase();
|
let search = this.widget.get_search();
|
||||||
let text = recording.work.get_title() + &recording.get_performers();
|
let text = recording.work.get_title() + &recording.get_performers();
|
||||||
search.is_empty() || text.to_lowercase().contains(&search)
|
search.is_empty() || text.to_lowercase().contains(&search)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
edit_action.connect_activate(clone!(@strong this => move |_, _| {
|
// Load the content asynchronously.
|
||||||
let editor = EnsembleEditor::new(this.backend.clone(), Some(this.ensemble.clone()));
|
|
||||||
let window = NavigatorWindow::new(editor);
|
|
||||||
window.show();
|
|
||||||
}));
|
|
||||||
|
|
||||||
delete_action.connect_activate(clone!(@strong this => move |_, _| {
|
|
||||||
let context = glib::MainContext::default();
|
|
||||||
let clone = this.clone();
|
|
||||||
context.spawn_local(async move {
|
|
||||||
clone.backend.db().delete_ensemble(&clone.ensemble.id).await.unwrap();
|
|
||||||
clone.backend.library_changed();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
let context = glib::MainContext::default();
|
let context = glib::MainContext::default();
|
||||||
let clone = this.clone();
|
let clone = Rc::clone(&this);
|
||||||
|
|
||||||
context.spawn_local(async move {
|
context.spawn_local(async move {
|
||||||
let recordings = clone
|
let recordings = clone
|
||||||
.backend
|
.backend
|
||||||
|
|
@ -120,14 +106,16 @@ impl EnsembleScreen {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if recordings.is_empty() {
|
if !recordings.is_empty() {
|
||||||
clone.stack.set_visible_child_name("nothing");
|
|
||||||
} else {
|
|
||||||
let length = recordings.len();
|
let length = recordings.len();
|
||||||
clone.recordings.replace(recordings);
|
clone.recordings.replace(recordings);
|
||||||
clone.recording_list.update(length);
|
clone.recording_list.update(length);
|
||||||
clone.stack.set_visible_child_name("content");
|
|
||||||
|
let section = Section::new("Recordings", &clone.recording_list.widget);
|
||||||
|
clone.widget.add_content(§ion.widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clone.widget.ready();
|
||||||
});
|
});
|
||||||
|
|
||||||
this
|
this
|
||||||
|
|
@ -140,7 +128,7 @@ impl NavigatorScreen for EnsembleScreen {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_widget(&self) -> gtk::Widget {
|
fn get_widget(&self) -> gtk::Widget {
|
||||||
self.widget.clone().upcast()
|
self.widget.widget.clone().upcast()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn detach_navigator(&self) {
|
fn detach_navigator(&self) {
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
pub mod ensemble_screen;
|
pub mod ensemble;
|
||||||
pub use ensemble_screen::*;
|
pub use ensemble::*;
|
||||||
|
|
||||||
pub mod person_screen;
|
pub mod person;
|
||||||
pub use person_screen::*;
|
pub use person::*;
|
||||||
|
|
||||||
pub mod player_screen;
|
pub mod player_screen;
|
||||||
pub use player_screen::*;
|
pub use player_screen::*;
|
||||||
|
|
||||||
pub mod work_screen;
|
pub mod work;
|
||||||
pub use work_screen::*;
|
pub use work::*;
|
||||||
|
|
||||||
pub mod recording_screen;
|
pub mod recording;
|
||||||
pub use recording_screen::*;
|
pub use recording::*;
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,23 @@
|
||||||
use super::*;
|
use super::{WorkScreen, RecordingScreen};
|
||||||
use crate::backend::*;
|
|
||||||
use crate::database::*;
|
use crate::backend::Backend;
|
||||||
|
use crate::database::{Person, Recording, Work};
|
||||||
use crate::editors::PersonEditor;
|
use crate::editors::PersonEditor;
|
||||||
use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow};
|
use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow, Screen, Section};
|
||||||
use gio::prelude::*;
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/// A screen for showing works by and recordings with a person.
|
||||||
pub struct PersonScreen {
|
pub struct PersonScreen {
|
||||||
backend: Rc<Backend>,
|
backend: Rc<Backend>,
|
||||||
person: Person,
|
person: Person,
|
||||||
widget: gtk::Box,
|
widget: Screen,
|
||||||
stack: gtk::Stack,
|
|
||||||
search_entry: gtk::SearchEntry,
|
|
||||||
work_box: gtk::Box,
|
|
||||||
work_list: Rc<List>,
|
work_list: Rc<List>,
|
||||||
recording_box: gtk::Box,
|
|
||||||
recording_list: Rc<List>,
|
recording_list: Rc<List>,
|
||||||
works: RefCell<Vec<Work>>,
|
works: RefCell<Vec<Work>>,
|
||||||
recordings: RefCell<Vec<Recording>>,
|
recordings: RefCell<Vec<Recording>>,
|
||||||
|
|
@ -27,62 +25,54 @@ pub struct PersonScreen {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PersonScreen {
|
impl PersonScreen {
|
||||||
|
/// Create a new person screen for the specified person and load the
|
||||||
|
/// contents asynchronously.
|
||||||
pub fn new(backend: Rc<Backend>, person: Person) -> Rc<Self> {
|
pub fn new(backend: Rc<Backend>, person: Person) -> Rc<Self> {
|
||||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/person_screen.ui");
|
let widget = Screen::new();
|
||||||
|
widget.set_title(&person.name_fl());
|
||||||
get_widget!(builder, gtk::Box, widget);
|
|
||||||
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::Box, work_box);
|
|
||||||
get_widget!(builder, gtk::Frame, work_frame);
|
|
||||||
get_widget!(builder, gtk::Box, recording_box);
|
|
||||||
get_widget!(builder, gtk::Frame, recording_frame);
|
|
||||||
|
|
||||||
title_label.set_label(&person.name_fl());
|
|
||||||
|
|
||||||
let edit_action = gio::SimpleAction::new("edit", None);
|
|
||||||
let delete_action = gio::SimpleAction::new("delete", None);
|
|
||||||
|
|
||||||
let actions = gio::SimpleActionGroup::new();
|
|
||||||
actions.add_action(&edit_action);
|
|
||||||
actions.add_action(&delete_action);
|
|
||||||
|
|
||||||
widget.insert_action_group("widget", Some(&actions));
|
|
||||||
|
|
||||||
let work_list = List::new();
|
let work_list = List::new();
|
||||||
let recording_list = List::new();
|
let recording_list = List::new();
|
||||||
work_frame.set_child(Some(&work_list.widget));
|
|
||||||
recording_frame.set_child(Some(&recording_list.widget));
|
|
||||||
|
|
||||||
let this = Rc::new(Self {
|
let this = Rc::new(Self {
|
||||||
backend,
|
backend,
|
||||||
person,
|
person,
|
||||||
widget,
|
widget,
|
||||||
stack,
|
|
||||||
search_entry,
|
|
||||||
work_box,
|
|
||||||
work_list,
|
work_list,
|
||||||
recording_box,
|
|
||||||
recording_list,
|
recording_list,
|
||||||
works: RefCell::new(Vec::new()),
|
works: RefCell::new(Vec::new()),
|
||||||
recordings: RefCell::new(Vec::new()),
|
recordings: RefCell::new(Vec::new()),
|
||||||
navigator: RefCell::new(None),
|
navigator: RefCell::new(None),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.search_entry.connect_search_changed(clone!(@strong this => move |_| {
|
this.widget.set_back_cb(clone!(@strong this => move || {
|
||||||
this.work_list.invalidate_filter();
|
|
||||||
this.recording_list.invalidate_filter();
|
|
||||||
}));
|
|
||||||
|
|
||||||
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.clone().pop();
|
navigator.pop();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
this.widget.add_action(&gettext("Edit person"), clone!(@strong this => move || {
|
||||||
|
let editor = PersonEditor::new(this.backend.clone(), Some(this.person.clone()));
|
||||||
|
let window = NavigatorWindow::new(editor);
|
||||||
|
window.show();
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.widget.add_action(&gettext("Delete person"), clone!(@strong this => move || {
|
||||||
|
let context = glib::MainContext::default();
|
||||||
|
let clone = this.clone();
|
||||||
|
context.spawn_local(async move {
|
||||||
|
clone.backend.db().delete_person(&clone.person.id).await.unwrap();
|
||||||
|
clone.backend.library_changed();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.widget.set_search_cb(clone!(@strong this => move || {
|
||||||
|
this.work_list.invalidate_filter();
|
||||||
|
this.recording_list.invalidate_filter();
|
||||||
|
}));
|
||||||
|
|
||||||
this.work_list.set_make_widget_cb(clone!(@strong this => move |index| {
|
this.work_list.set_make_widget_cb(clone!(@strong this => move |index| {
|
||||||
let work = &this.works.borrow()[index];
|
let work = &this.works.borrow()[index];
|
||||||
|
|
||||||
|
|
@ -103,7 +93,7 @@ impl PersonScreen {
|
||||||
|
|
||||||
this.work_list.set_filter_cb(clone!(@strong this => move |index| {
|
this.work_list.set_filter_cb(clone!(@strong this => move |index| {
|
||||||
let work = &this.works.borrow()[index];
|
let work = &this.works.borrow()[index];
|
||||||
let search = this.search_entry.get_text().unwrap().to_string().to_lowercase();
|
let search = this.widget.get_search();
|
||||||
let title = work.title.to_lowercase();
|
let title = work.title.to_lowercase();
|
||||||
search.is_empty() || title.contains(&search)
|
search.is_empty() || title.contains(&search)
|
||||||
}));
|
}));
|
||||||
|
|
@ -129,28 +119,16 @@ impl PersonScreen {
|
||||||
|
|
||||||
this.recording_list.set_filter_cb(clone!(@strong this => move |index| {
|
this.recording_list.set_filter_cb(clone!(@strong this => move |index| {
|
||||||
let recording = &this.recordings.borrow()[index];
|
let recording = &this.recordings.borrow()[index];
|
||||||
let search = this.search_entry.get_text().unwrap().to_string().to_lowercase();
|
let search = this.widget.get_search();
|
||||||
let text = recording.work.get_title() + &recording.get_performers();
|
let text = recording.work.get_title() + &recording.get_performers();
|
||||||
search.is_empty() || text.contains(&search)
|
search.is_empty() || text.to_lowercase().contains(&search)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
edit_action.connect_activate(clone!(@strong this => move |_, _| {
|
// Load the content asynchronously.
|
||||||
let editor = PersonEditor::new(this.backend.clone(), Some(this.person.clone()));
|
|
||||||
let window = NavigatorWindow::new(editor);
|
|
||||||
window.show();
|
|
||||||
}));
|
|
||||||
|
|
||||||
delete_action.connect_activate(clone!(@strong this => move |_, _| {
|
|
||||||
let context = glib::MainContext::default();
|
|
||||||
let clone = this.clone();
|
|
||||||
context.spawn_local(async move {
|
|
||||||
clone.backend.db().delete_person(&clone.person.id).await.unwrap();
|
|
||||||
clone.backend.library_changed();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
let context = glib::MainContext::default();
|
let context = glib::MainContext::default();
|
||||||
let clone = this.clone();
|
let clone = Rc::clone(&this);
|
||||||
|
|
||||||
context.spawn_local(async move {
|
context.spawn_local(async move {
|
||||||
let works = clone
|
let works = clone
|
||||||
.backend
|
.backend
|
||||||
|
|
@ -166,27 +144,25 @@ impl PersonScreen {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if works.is_empty() && recordings.is_empty() {
|
if !works.is_empty() {
|
||||||
clone.stack.set_visible_child_name("nothing");
|
let length = works.len();
|
||||||
} else {
|
clone.works.replace(works);
|
||||||
if works.is_empty() {
|
clone.work_list.update(length);
|
||||||
clone.work_box.hide();
|
|
||||||
} else {
|
|
||||||
let length = works.len();
|
|
||||||
clone.works.replace(works);
|
|
||||||
clone.work_list.update(length);
|
|
||||||
}
|
|
||||||
|
|
||||||
if recordings.is_empty() {
|
let section = Section::new("Works", &clone.work_list.widget);
|
||||||
clone.recording_box.hide();
|
clone.widget.add_content(§ion.widget);
|
||||||
} else {
|
|
||||||
let length = recordings.len();
|
|
||||||
clone.recordings.replace(recordings);
|
|
||||||
clone.recording_list.update(length);
|
|
||||||
}
|
|
||||||
|
|
||||||
clone.stack.set_visible_child_name("content");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !recordings.is_empty() {
|
||||||
|
let length = recordings.len();
|
||||||
|
clone.recordings.replace(recordings);
|
||||||
|
clone.recording_list.update(length);
|
||||||
|
|
||||||
|
let section = Section::new("Recordings", &clone.recording_list.widget);
|
||||||
|
clone.widget.add_content(§ion.widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
clone.widget.ready();
|
||||||
});
|
});
|
||||||
|
|
||||||
this
|
this
|
||||||
|
|
@ -199,7 +175,7 @@ impl NavigatorScreen for PersonScreen {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_widget(&self) -> gtk::Widget {
|
fn get_widget(&self) -> gtk::Widget {
|
||||||
self.widget.clone().upcast()
|
self.widget.widget.clone().upcast()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn detach_navigator(&self) {
|
fn detach_navigator(&self) {
|
||||||
106
src/screens/recording.rs
Normal file
106
src/screens/recording.rs
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
use crate::backend::Backend;
|
||||||
|
use crate::database::Recording;
|
||||||
|
use crate::editors::RecordingEditor;
|
||||||
|
use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow, Screen, Section};
|
||||||
|
|
||||||
|
use gettextrs::gettext;
|
||||||
|
use glib::clone;
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use libadwaita::prelude::*;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
/// A screen for showing a recording.
|
||||||
|
pub struct RecordingScreen {
|
||||||
|
backend: Rc<Backend>,
|
||||||
|
recording: Recording,
|
||||||
|
widget: Screen,
|
||||||
|
track_list: Rc<List>,
|
||||||
|
recordings: RefCell<Vec<Recording>>,
|
||||||
|
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RecordingScreen {
|
||||||
|
/// Create a new recording screen for the specified recording and load the
|
||||||
|
/// contents asynchronously.
|
||||||
|
pub fn new(backend: Rc<Backend>, recording: Recording) -> Rc<Self> {
|
||||||
|
let widget = Screen::new();
|
||||||
|
widget.set_title(&recording.work.get_title());
|
||||||
|
widget.set_subtitle(&recording.get_performers());
|
||||||
|
|
||||||
|
let track_list = List::new();
|
||||||
|
|
||||||
|
let this = Rc::new(Self {
|
||||||
|
backend,
|
||||||
|
recording,
|
||||||
|
widget,
|
||||||
|
track_list,
|
||||||
|
recordings: RefCell::new(Vec::new()),
|
||||||
|
navigator: RefCell::new(None),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.widget.set_back_cb(clone!(@strong this => move || {
|
||||||
|
let navigator = this.navigator.borrow().clone();
|
||||||
|
if let Some(navigator) = navigator {
|
||||||
|
navigator.pop();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
this.widget.add_action(&gettext("Edit recording"), clone!(@strong this => move || {
|
||||||
|
let editor = RecordingEditor::new(this.backend.clone(), Some(this.recording.clone()));
|
||||||
|
let window = NavigatorWindow::new(editor);
|
||||||
|
window.show();
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.widget.add_action(&gettext("Delete recording"), clone!(@strong this => move || {
|
||||||
|
let context = glib::MainContext::default();
|
||||||
|
let clone = this.clone();
|
||||||
|
context.spawn_local(async move {
|
||||||
|
clone.backend.db().delete_recording(&clone.recording.id).await.unwrap();
|
||||||
|
clone.backend.library_changed();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.widget.set_search_cb(clone!(@strong this => move || {
|
||||||
|
this.track_list.invalidate_filter();
|
||||||
|
}));
|
||||||
|
|
||||||
|
// TODO: Implement.
|
||||||
|
// this.track_list.set_make_widget_cb(clone!(@strong this => move |index| {
|
||||||
|
// }));
|
||||||
|
|
||||||
|
this.track_list.set_filter_cb(clone!(@strong this => move |index| {
|
||||||
|
// TODO: Implement.
|
||||||
|
// search.is_empty() || text.to_lowercase().contains(&search)
|
||||||
|
true
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Load the content asynchronously.
|
||||||
|
|
||||||
|
let context = glib::MainContext::default();
|
||||||
|
let clone = Rc::clone(&this);
|
||||||
|
|
||||||
|
context.spawn_local(async move {
|
||||||
|
// TODO: Implement.
|
||||||
|
|
||||||
|
clone.widget.ready();
|
||||||
|
});
|
||||||
|
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NavigatorScreen for RecordingScreen {
|
||||||
|
fn attach_navigator(&self, navigator: Rc<Navigator>) {
|
||||||
|
self.navigator.replace(Some(navigator));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_widget(&self) -> gtk::Widget {
|
||||||
|
self.widget.widget.clone().upcast()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detach_navigator(&self) {
|
||||||
|
self.navigator.replace(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,213 +0,0 @@
|
||||||
use crate::backend::*;
|
|
||||||
use crate::database::*;
|
|
||||||
use crate::editors::RecordingEditor;
|
|
||||||
use crate::player::*;
|
|
||||||
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 libadwaita::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<Backend>,
|
|
||||||
recording: Recording,
|
|
||||||
widget: gtk::Box,
|
|
||||||
stack: gtk::Stack,
|
|
||||||
list: Rc<List>,
|
|
||||||
track_sets: RefCell<Vec<TrackSet>>,
|
|
||||||
items: RefCell<Vec<ListItem>>,
|
|
||||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RecordingScreen {
|
|
||||||
pub fn new(backend: Rc<Backend>, recording: Recording) -> Rc<Self> {
|
|
||||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_screen.ui");
|
|
||||||
|
|
||||||
get_widget!(builder, gtk::Box, widget);
|
|
||||||
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);
|
|
||||||
|
|
||||||
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);
|
|
||||||
let edit_tracks_action = gio::SimpleAction::new("edit-tracks", None);
|
|
||||||
let delete_tracks_action = gio::SimpleAction::new("delete-tracks", None);
|
|
||||||
|
|
||||||
let actions = gio::SimpleActionGroup::new();
|
|
||||||
actions.add_action(&edit_action);
|
|
||||||
actions.add_action(&delete_action);
|
|
||||||
actions.add_action(&edit_tracks_action);
|
|
||||||
actions.add_action(&delete_tracks_action);
|
|
||||||
|
|
||||||
widget.insert_action_group("widget", Some(&actions));
|
|
||||||
|
|
||||||
let list = List::new();
|
|
||||||
frame.set_child(Some(&list.widget));
|
|
||||||
|
|
||||||
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),
|
|
||||||
});
|
|
||||||
|
|
||||||
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];
|
|
||||||
|
|
||||||
let mut title_parts = Vec::<String>::new();
|
|
||||||
for part in &track.work_parts {
|
|
||||||
title_parts.push(this.recording.work.parts[*part].title.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let title = if title_parts.is_empty() {
|
|
||||||
gettext("Unknown")
|
|
||||||
} else {
|
|
||||||
title_parts.join(", ")
|
|
||||||
};
|
|
||||||
|
|
||||||
let row = libadwaita::ActionRow::new();
|
|
||||||
row.set_title(Some(&title));
|
|
||||||
|
|
||||||
row.upcast()
|
|
||||||
}
|
|
||||||
ListItem::Separator => {
|
|
||||||
let separator = gtk::Separator::new(gtk::Orientation::Horizontal);
|
|
||||||
separator.upcast()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
back_button.connect_clicked(clone!(@strong this => move |_| {
|
|
||||||
let navigator = this.navigator.borrow().clone();
|
|
||||||
if let Some(navigator) = navigator {
|
|
||||||
navigator.clone().pop();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
// 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 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 this => move |_, _| {
|
|
||||||
let context = glib::MainContext::default();
|
|
||||||
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 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 this => move |_, _| {
|
|
||||||
let context = glib::MainContext::default();
|
|
||||||
let clone = this.clone();
|
|
||||||
context.spawn_local(async move {
|
|
||||||
// clone.backend.db().delete_tracks(&clone.recording.id).await.unwrap();
|
|
||||||
// clone.backend.library_changed();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
let context = glib::MainContext::default();
|
|
||||||
let clone = this.clone();
|
|
||||||
context.spawn_local(async move {
|
|
||||||
let track_sets = clone
|
|
||||||
.backend
|
|
||||||
.db()
|
|
||||||
.get_track_sets(&clone.recording.id)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
clone.show_track_sets(track_sets);
|
|
||||||
clone.stack.set_visible_child_name("content");
|
|
||||||
});
|
|
||||||
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update the track sets variable as well as the user interface.
|
|
||||||
fn show_track_sets(&self, track_sets: Vec<TrackSet>) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NavigatorScreen for RecordingScreen {
|
|
||||||
fn attach_navigator(&self, navigator: Rc<Navigator>) {
|
|
||||||
self.navigator.replace(Some(navigator));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_widget(&self) -> gtk::Widget {
|
|
||||||
self.widget.clone().upcast()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn detach_navigator(&self) {
|
|
||||||
self.navigator.replace(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,76 +1,73 @@
|
||||||
use super::*;
|
use super::RecordingScreen;
|
||||||
use crate::backend::*;
|
|
||||||
use crate::database::*;
|
use crate::backend::Backend;
|
||||||
|
use crate::database::{Work, Recording};
|
||||||
use crate::editors::WorkEditor;
|
use crate::editors::WorkEditor;
|
||||||
use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow};
|
use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow, Screen, Section};
|
||||||
use gio::prelude::*;
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/// A screen for showing recordings of a work.
|
||||||
pub struct WorkScreen {
|
pub struct WorkScreen {
|
||||||
backend: Rc<Backend>,
|
backend: Rc<Backend>,
|
||||||
work: Work,
|
work: Work,
|
||||||
widget: gtk::Box,
|
widget: Screen,
|
||||||
stack: gtk::Stack,
|
|
||||||
search_entry: gtk::SearchEntry,
|
|
||||||
recording_list: Rc<List>,
|
recording_list: Rc<List>,
|
||||||
recordings: RefCell<Vec<Recording>>,
|
recordings: RefCell<Vec<Recording>>,
|
||||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorkScreen {
|
impl WorkScreen {
|
||||||
|
/// Create a new work screen for the specified work and load the
|
||||||
|
/// contents asynchronously.
|
||||||
pub fn new(backend: Rc<Backend>, work: Work) -> Rc<Self> {
|
pub fn new(backend: Rc<Backend>, work: Work) -> Rc<Self> {
|
||||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_screen.ui");
|
let widget = Screen::new();
|
||||||
|
widget.set_title(&work.title);
|
||||||
get_widget!(builder, gtk::Box, widget);
|
widget.set_subtitle(&work.composer.name_fl());
|
||||||
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);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
let actions = gio::SimpleActionGroup::new();
|
|
||||||
actions.add_action(&edit_action);
|
|
||||||
actions.add_action(&delete_action);
|
|
||||||
|
|
||||||
widget.insert_action_group("widget", Some(&actions));
|
|
||||||
|
|
||||||
let recording_list = List::new();
|
let recording_list = List::new();
|
||||||
recording_frame.set_child(Some(&recording_list.widget));
|
|
||||||
|
|
||||||
let this = Rc::new(Self {
|
let this = Rc::new(Self {
|
||||||
backend,
|
backend,
|
||||||
work,
|
work,
|
||||||
widget,
|
widget,
|
||||||
stack,
|
|
||||||
search_entry,
|
|
||||||
recording_list,
|
recording_list,
|
||||||
recordings: RefCell::new(Vec::new()),
|
recordings: RefCell::new(Vec::new()),
|
||||||
navigator: RefCell::new(None),
|
navigator: RefCell::new(None),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.search_entry.connect_search_changed(clone!(@strong this => move |_| {
|
this.widget.set_back_cb(clone!(@strong this => move || {
|
||||||
this.recording_list.invalidate_filter();
|
|
||||||
}));
|
|
||||||
|
|
||||||
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.clone().pop();
|
navigator.pop();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
this.widget.add_action(&gettext("Edit work"), clone!(@strong this => move || {
|
||||||
|
let editor = WorkEditor::new(this.backend.clone(), Some(this.work.clone()));
|
||||||
|
let window = NavigatorWindow::new(editor);
|
||||||
|
window.show();
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.widget.add_action(&gettext("Delete work"), clone!(@strong this => move || {
|
||||||
|
let context = glib::MainContext::default();
|
||||||
|
let clone = this.clone();
|
||||||
|
context.spawn_local(async move {
|
||||||
|
clone.backend.db().delete_work(&clone.work.id).await.unwrap();
|
||||||
|
clone.backend.library_changed();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.widget.set_search_cb(clone!(@strong this => move || {
|
||||||
|
this.recording_list.invalidate_filter();
|
||||||
|
}));
|
||||||
|
|
||||||
this.recording_list.set_make_widget_cb(clone!(@strong this => move |index| {
|
this.recording_list.set_make_widget_cb(clone!(@strong this => move |index| {
|
||||||
let recording = &this.recordings.borrow()[index];
|
let recording = &this.recordings.borrow()[index];
|
||||||
|
|
||||||
|
|
@ -92,28 +89,16 @@ impl WorkScreen {
|
||||||
|
|
||||||
this.recording_list.set_filter_cb(clone!(@strong this => move |index| {
|
this.recording_list.set_filter_cb(clone!(@strong this => move |index| {
|
||||||
let recording = &this.recordings.borrow()[index];
|
let recording = &this.recordings.borrow()[index];
|
||||||
let search = this.search_entry.get_text().unwrap().to_string().to_lowercase();
|
let search = this.widget.get_search();
|
||||||
let text = recording.work.get_title() + &recording.get_performers();
|
let text = recording.work.get_title() + &recording.get_performers();
|
||||||
search.is_empty() || text.to_lowercase().contains(&search)
|
search.is_empty() || text.to_lowercase().contains(&search)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
edit_action.connect_activate(clone!(@strong this => move |_, _| {
|
// Load the content asynchronously.
|
||||||
let editor = WorkEditor::new(this.backend.clone(), Some(this.work.clone()));
|
|
||||||
let window = NavigatorWindow::new(editor);
|
|
||||||
window.show();
|
|
||||||
}));
|
|
||||||
|
|
||||||
delete_action.connect_activate(clone!(@strong this => move |_, _| {
|
|
||||||
let context = glib::MainContext::default();
|
|
||||||
let clone = this.clone();
|
|
||||||
context.spawn_local(async move {
|
|
||||||
clone.backend.db().delete_work(&clone.work.id).await.unwrap();
|
|
||||||
clone.backend.library_changed();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
let context = glib::MainContext::default();
|
let context = glib::MainContext::default();
|
||||||
let clone = this.clone();
|
let clone = Rc::clone(&this);
|
||||||
|
|
||||||
context.spawn_local(async move {
|
context.spawn_local(async move {
|
||||||
let recordings = clone
|
let recordings = clone
|
||||||
.backend
|
.backend
|
||||||
|
|
@ -122,14 +107,16 @@ impl WorkScreen {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if recordings.is_empty() {
|
if !recordings.is_empty() {
|
||||||
clone.stack.set_visible_child_name("nothing");
|
|
||||||
} else {
|
|
||||||
let length = recordings.len();
|
let length = recordings.len();
|
||||||
clone.recordings.replace(recordings);
|
clone.recordings.replace(recordings);
|
||||||
clone.recording_list.update(length);
|
clone.recording_list.update(length);
|
||||||
clone.stack.set_visible_child_name("content");
|
|
||||||
|
let section = Section::new("Recordings", &clone.recording_list.widget);
|
||||||
|
clone.widget.add_content(§ion.widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clone.widget.ready();
|
||||||
});
|
});
|
||||||
|
|
||||||
this
|
this
|
||||||
|
|
@ -142,7 +129,7 @@ impl NavigatorScreen for WorkScreen {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_widget(&self) -> gtk::Widget {
|
fn get_widget(&self) -> gtk::Widget {
|
||||||
self.widget.clone().upcast()
|
self.widget.widget.clone().upcast()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn detach_navigator(&self) {
|
fn detach_navigator(&self) {
|
||||||
|
|
@ -13,4 +13,10 @@ pub use player_bar::*;
|
||||||
pub mod poe_list;
|
pub mod poe_list;
|
||||||
pub use poe_list::*;
|
pub use poe_list::*;
|
||||||
|
|
||||||
|
pub mod screen;
|
||||||
|
pub use screen::*;
|
||||||
|
|
||||||
|
pub mod section;
|
||||||
|
pub use section::*;
|
||||||
|
|
||||||
mod indexed_list_model;
|
mod indexed_list_model;
|
||||||
|
|
|
||||||
113
src/widgets/screen.rs
Normal file
113
src/widgets/screen.rs
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
use gio::prelude::*;
|
||||||
|
use glib::clone;
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use gtk_macros::get_widget;
|
||||||
|
|
||||||
|
/// A general framework for screens. Screens have a header bar with at least
|
||||||
|
/// a button to go back and a scrollable content area that clamps its content.
|
||||||
|
pub struct Screen {
|
||||||
|
/// The actual GTK widget.
|
||||||
|
pub widget: gtk::Box,
|
||||||
|
|
||||||
|
/// The button to switch to the previous screen.
|
||||||
|
back_button: gtk::Button,
|
||||||
|
|
||||||
|
/// The title widget within the header bar.
|
||||||
|
window_title: libadwaita::WindowTitle,
|
||||||
|
|
||||||
|
/// The action menu.
|
||||||
|
menu: gio::Menu,
|
||||||
|
|
||||||
|
/// The entry for searching.
|
||||||
|
search_entry: gtk::SearchEntry,
|
||||||
|
|
||||||
|
/// The stack to switch to the loading page.
|
||||||
|
stack: gtk::Stack,
|
||||||
|
|
||||||
|
/// The box containing the content.
|
||||||
|
content_box: gtk::Box,
|
||||||
|
|
||||||
|
/// The actions for the menu.
|
||||||
|
actions: gio::SimpleActionGroup,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Screen {
|
||||||
|
/// Create a new screen.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/screen.ui");
|
||||||
|
|
||||||
|
get_widget!(builder, gtk::Box, widget);
|
||||||
|
get_widget!(builder, gtk::Button, back_button);
|
||||||
|
get_widget!(builder, libadwaita::WindowTitle, window_title);
|
||||||
|
get_widget!(builder, gio::Menu, menu);
|
||||||
|
get_widget!(builder, gtk::ToggleButton, search_button);
|
||||||
|
get_widget!(builder, gtk::SearchEntry, search_entry);
|
||||||
|
get_widget!(builder, gtk::Stack, stack);
|
||||||
|
get_widget!(builder, gtk::Box, content_box);
|
||||||
|
|
||||||
|
let actions = gio::SimpleActionGroup::new();
|
||||||
|
widget.insert_action_group("widget", Some(&actions));
|
||||||
|
|
||||||
|
search_button.connect_toggled(clone!(@strong search_entry => move |search_button| {
|
||||||
|
if search_button.get_active() {
|
||||||
|
search_entry.grab_focus();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
widget,
|
||||||
|
back_button,
|
||||||
|
window_title,
|
||||||
|
menu,
|
||||||
|
search_entry,
|
||||||
|
stack,
|
||||||
|
content_box,
|
||||||
|
actions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a closure to be called when the back button is pressed.
|
||||||
|
pub fn set_back_cb<F: Fn() + 'static>(&self, cb: F) {
|
||||||
|
self.back_button.connect_clicked(move |_| cb());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show a title in the header bar.
|
||||||
|
pub fn set_title(&self, title: &str) {
|
||||||
|
self.window_title.set_title(Some(title));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show a subtitle in the header bar.
|
||||||
|
pub fn set_subtitle(&self, subtitle: &str) {
|
||||||
|
self.window_title.set_subtitle(Some(subtitle));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a new item to the action menu and register a callback for it.
|
||||||
|
pub fn add_action<F: Fn() + 'static>(&self, label: &str, cb: F) {
|
||||||
|
let name = rand::random::<u64>().to_string();
|
||||||
|
let action = gio::SimpleAction::new(&name, None);
|
||||||
|
action.connect_activate(move |_, _| cb());
|
||||||
|
|
||||||
|
self.actions.add_action(&action);
|
||||||
|
self.menu.append(Some(label), Some(&format!("widget.{}", name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the closure to be called when the search string has changed.
|
||||||
|
pub fn set_search_cb<F: Fn() + 'static>(&self, cb: F) {
|
||||||
|
self.search_entry.connect_search_changed(move |_| cb());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current search string.
|
||||||
|
pub fn get_search(&self) -> String {
|
||||||
|
self.search_entry.get_text().unwrap().to_string().to_lowercase()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hide the loading page and switch to the content.
|
||||||
|
pub fn ready(&self) {
|
||||||
|
self.stack.set_visible_child_name("content");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add content to the bottom of the content area.
|
||||||
|
pub fn add_content<W: IsA<gtk::Widget>>(&self, content: &W) {
|
||||||
|
self.content_box.append(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/widgets/section.rs
Normal file
48
src/widgets/section.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use gtk_macros::get_widget;
|
||||||
|
|
||||||
|
/// A widget displaying a title, a framed child widget and, if needed, some
|
||||||
|
/// actions.
|
||||||
|
pub struct Section {
|
||||||
|
/// The actual GTK widget.
|
||||||
|
pub widget: gtk::Box,
|
||||||
|
|
||||||
|
/// The box containing the title and action buttons.
|
||||||
|
title_box: gtk::Box,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Section {
|
||||||
|
/// Create a new section.
|
||||||
|
pub fn new<W: IsA<gtk::Widget>>(title: &str, content: &W) -> Self {
|
||||||
|
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/section.ui");
|
||||||
|
|
||||||
|
get_widget!(builder, gtk::Box, widget);
|
||||||
|
get_widget!(builder, gtk::Box, title_box);
|
||||||
|
get_widget!(builder, gtk::Label, title_label);
|
||||||
|
get_widget!(builder, gtk::Frame, frame);
|
||||||
|
|
||||||
|
title_label.set_label(title);
|
||||||
|
frame.set_child(Some(content));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
widget,
|
||||||
|
title_box,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add an action button. This should by definition be something that is
|
||||||
|
/// doing something with the child widget that is applicable in all
|
||||||
|
/// situations where the widget is visible. The new button will be packed
|
||||||
|
/// to the end of the title box.
|
||||||
|
pub fn add_action<F: Fn() + 'static>(&self, icon_name: &str, cb: F) {
|
||||||
|
let button = gtk::ButtonBuilder::new()
|
||||||
|
.has_frame(false)
|
||||||
|
.valign(gtk::Align::Center)
|
||||||
|
.icon_name(icon_name)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
button.connect_clicked(move |_| cb());
|
||||||
|
|
||||||
|
self.title_box.append(&button);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue