mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 11:47: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>
|
||||
<gresource prefix="/de/johrpan/musicus">
|
||||
<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/login_dialog.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/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_screen.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/poe_list.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_screen.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/server_dialog.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/work_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>
|
||||
</gresource>
|
||||
</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_selector.rs',
|
||||
'import/track_set_editor.rs',
|
||||
'screens/ensemble_screen.rs',
|
||||
'screens/ensemble.rs',
|
||||
'screens/mod.rs',
|
||||
'screens/person_screen.rs',
|
||||
'screens/person.rs',
|
||||
'screens/player_screen.rs',
|
||||
'screens/recording_screen.rs',
|
||||
'screens/work_screen.rs',
|
||||
'screens/recording.rs',
|
||||
'screens/work.rs',
|
||||
'selectors/ensemble.rs',
|
||||
'selectors/instrument.rs',
|
||||
'selectors/mod.rs',
|
||||
|
|
@ -97,6 +97,8 @@ sources = files(
|
|||
'widgets/navigator_window.rs',
|
||||
'widgets/player_bar.rs',
|
||||
'widgets/poe_list.rs',
|
||||
'widgets/screen.rs',
|
||||
'widgets/section.rs',
|
||||
'config.rs',
|
||||
'config.rs.in',
|
||||
'main.rs',
|
||||
|
|
|
|||
|
|
@ -1,74 +1,72 @@
|
|||
use super::*;
|
||||
use crate::backend::*;
|
||||
use crate::database::*;
|
||||
use super::RecordingScreen;
|
||||
|
||||
use crate::backend::Backend;
|
||||
use crate::database::{Ensemble, Recording};
|
||||
use crate::editors::EnsembleEditor;
|
||||
use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow};
|
||||
use gio::prelude::*;
|
||||
use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow, Screen, Section};
|
||||
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use libadwaita::prelude::*;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A screen for showing recordings with a ensemble.
|
||||
pub struct EnsembleScreen {
|
||||
backend: Rc<Backend>,
|
||||
ensemble: Ensemble,
|
||||
widget: gtk::Box,
|
||||
search_entry: gtk::SearchEntry,
|
||||
stack: gtk::Stack,
|
||||
widget: Screen,
|
||||
recording_list: Rc<List>,
|
||||
recordings: RefCell<Vec<Recording>>,
|
||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||
}
|
||||
|
||||
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> {
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/ensemble_screen.ui");
|
||||
|
||||
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 widget = Screen::new();
|
||||
widget.set_title(&ensemble.name);
|
||||
|
||||
let recording_list = List::new();
|
||||
recording_frame.set_child(Some(&recording_list.widget));
|
||||
|
||||
let this = Rc::new(Self {
|
||||
backend,
|
||||
ensemble,
|
||||
widget,
|
||||
search_entry,
|
||||
stack,
|
||||
recording_list,
|
||||
recordings: RefCell::new(Vec::new()),
|
||||
navigator: RefCell::new(None),
|
||||
});
|
||||
|
||||
this.search_entry.connect_search_changed(clone!(@strong this => move |_| {
|
||||
this.recording_list.invalidate_filter();
|
||||
}));
|
||||
|
||||
back_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
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 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| {
|
||||
let recording = &this.recordings.borrow()[index];
|
||||
|
||||
|
|
@ -90,28 +88,16 @@ impl EnsembleScreen {
|
|||
|
||||
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 search = this.widget.get_search();
|
||||
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 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();
|
||||
});
|
||||
}));
|
||||
// Load the content asynchronously.
|
||||
|
||||
let context = glib::MainContext::default();
|
||||
let clone = this.clone();
|
||||
let clone = Rc::clone(&this);
|
||||
|
||||
context.spawn_local(async move {
|
||||
let recordings = clone
|
||||
.backend
|
||||
|
|
@ -120,14 +106,16 @@ impl EnsembleScreen {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
if recordings.is_empty() {
|
||||
clone.stack.set_visible_child_name("nothing");
|
||||
} else {
|
||||
if !recordings.is_empty() {
|
||||
let length = recordings.len();
|
||||
clone.recordings.replace(recordings);
|
||||
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
|
||||
|
|
@ -140,7 +128,7 @@ impl NavigatorScreen for EnsembleScreen {
|
|||
}
|
||||
|
||||
fn get_widget(&self) -> gtk::Widget {
|
||||
self.widget.clone().upcast()
|
||||
self.widget.widget.clone().upcast()
|
||||
}
|
||||
|
||||
fn detach_navigator(&self) {
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
pub mod ensemble_screen;
|
||||
pub use ensemble_screen::*;
|
||||
pub mod ensemble;
|
||||
pub use ensemble::*;
|
||||
|
||||
pub mod person_screen;
|
||||
pub use person_screen::*;
|
||||
pub mod person;
|
||||
pub use person::*;
|
||||
|
||||
pub mod player_screen;
|
||||
pub use player_screen::*;
|
||||
|
||||
pub mod work_screen;
|
||||
pub use work_screen::*;
|
||||
pub mod work;
|
||||
pub use work::*;
|
||||
|
||||
pub mod recording_screen;
|
||||
pub use recording_screen::*;
|
||||
pub mod recording;
|
||||
pub use recording::*;
|
||||
|
|
|
|||
|
|
@ -1,25 +1,23 @@
|
|||
use super::*;
|
||||
use crate::backend::*;
|
||||
use crate::database::*;
|
||||
use super::{WorkScreen, RecordingScreen};
|
||||
|
||||
use crate::backend::Backend;
|
||||
use crate::database::{Person, Recording, Work};
|
||||
use crate::editors::PersonEditor;
|
||||
use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow};
|
||||
use gio::prelude::*;
|
||||
use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow, Screen, Section};
|
||||
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use libadwaita::prelude::*;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A screen for showing works by and recordings with a person.
|
||||
pub struct PersonScreen {
|
||||
backend: Rc<Backend>,
|
||||
person: Person,
|
||||
widget: gtk::Box,
|
||||
stack: gtk::Stack,
|
||||
search_entry: gtk::SearchEntry,
|
||||
work_box: gtk::Box,
|
||||
widget: Screen,
|
||||
work_list: Rc<List>,
|
||||
recording_box: gtk::Box,
|
||||
recording_list: Rc<List>,
|
||||
works: RefCell<Vec<Work>>,
|
||||
recordings: RefCell<Vec<Recording>>,
|
||||
|
|
@ -27,62 +25,54 @@ pub struct 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> {
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/person_screen.ui");
|
||||
|
||||
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 widget = Screen::new();
|
||||
widget.set_title(&person.name_fl());
|
||||
|
||||
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));
|
||||
|
||||
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),
|
||||
});
|
||||
|
||||
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 this => move |_| {
|
||||
this.widget.set_back_cb(clone!(@strong this => move || {
|
||||
let navigator = this.navigator.borrow().clone();
|
||||
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| {
|
||||
let work = &this.works.borrow()[index];
|
||||
|
||||
|
|
@ -103,7 +93,7 @@ impl PersonScreen {
|
|||
|
||||
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 search = this.widget.get_search();
|
||||
let title = work.title.to_lowercase();
|
||||
search.is_empty() || title.contains(&search)
|
||||
}));
|
||||
|
|
@ -129,28 +119,16 @@ impl PersonScreen {
|
|||
|
||||
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 search = this.widget.get_search();
|
||||
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 |_, _| {
|
||||
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();
|
||||
});
|
||||
}));
|
||||
// Load the content asynchronously.
|
||||
|
||||
let context = glib::MainContext::default();
|
||||
let clone = this.clone();
|
||||
let clone = Rc::clone(&this);
|
||||
|
||||
context.spawn_local(async move {
|
||||
let works = clone
|
||||
.backend
|
||||
|
|
@ -166,27 +144,25 @@ impl PersonScreen {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
if works.is_empty() && recordings.is_empty() {
|
||||
clone.stack.set_visible_child_name("nothing");
|
||||
} else {
|
||||
if works.is_empty() {
|
||||
clone.work_box.hide();
|
||||
} else {
|
||||
let length = works.len();
|
||||
clone.works.replace(works);
|
||||
clone.work_list.update(length);
|
||||
}
|
||||
if !works.is_empty() {
|
||||
let length = works.len();
|
||||
clone.works.replace(works);
|
||||
clone.work_list.update(length);
|
||||
|
||||
if recordings.is_empty() {
|
||||
clone.recording_box.hide();
|
||||
} else {
|
||||
let length = recordings.len();
|
||||
clone.recordings.replace(recordings);
|
||||
clone.recording_list.update(length);
|
||||
}
|
||||
|
||||
clone.stack.set_visible_child_name("content");
|
||||
let section = Section::new("Works", &clone.work_list.widget);
|
||||
clone.widget.add_content(§ion.widget);
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -199,7 +175,7 @@ impl NavigatorScreen for PersonScreen {
|
|||
}
|
||||
|
||||
fn get_widget(&self) -> gtk::Widget {
|
||||
self.widget.clone().upcast()
|
||||
self.widget.widget.clone().upcast()
|
||||
}
|
||||
|
||||
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 crate::backend::*;
|
||||
use crate::database::*;
|
||||
use super::RecordingScreen;
|
||||
|
||||
use crate::backend::Backend;
|
||||
use crate::database::{Work, Recording};
|
||||
use crate::editors::WorkEditor;
|
||||
use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow};
|
||||
use gio::prelude::*;
|
||||
use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow, Screen, Section};
|
||||
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use libadwaita::prelude::*;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A screen for showing recordings of a work.
|
||||
pub struct WorkScreen {
|
||||
backend: Rc<Backend>,
|
||||
work: Work,
|
||||
widget: gtk::Box,
|
||||
stack: gtk::Stack,
|
||||
search_entry: gtk::SearchEntry,
|
||||
widget: Screen,
|
||||
recording_list: Rc<List>,
|
||||
recordings: RefCell<Vec<Recording>>,
|
||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||
}
|
||||
|
||||
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> {
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_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::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 widget = Screen::new();
|
||||
widget.set_title(&work.title);
|
||||
widget.set_subtitle(&work.composer.name_fl());
|
||||
|
||||
let recording_list = List::new();
|
||||
recording_frame.set_child(Some(&recording_list.widget));
|
||||
|
||||
let this = Rc::new(Self {
|
||||
backend,
|
||||
work,
|
||||
widget,
|
||||
stack,
|
||||
search_entry,
|
||||
recording_list,
|
||||
recordings: RefCell::new(Vec::new()),
|
||||
navigator: RefCell::new(None),
|
||||
});
|
||||
|
||||
this.search_entry.connect_search_changed(clone!(@strong this => move |_| {
|
||||
this.recording_list.invalidate_filter();
|
||||
}));
|
||||
|
||||
back_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
this.widget.set_back_cb(clone!(@strong this => move || {
|
||||
let navigator = this.navigator.borrow().clone();
|
||||
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| {
|
||||
let recording = &this.recordings.borrow()[index];
|
||||
|
||||
|
|
@ -92,28 +89,16 @@ impl WorkScreen {
|
|||
|
||||
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 search = this.widget.get_search();
|
||||
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 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();
|
||||
});
|
||||
}));
|
||||
// Load the content asynchronously.
|
||||
|
||||
let context = glib::MainContext::default();
|
||||
let clone = this.clone();
|
||||
let clone = Rc::clone(&this);
|
||||
|
||||
context.spawn_local(async move {
|
||||
let recordings = clone
|
||||
.backend
|
||||
|
|
@ -122,14 +107,16 @@ impl WorkScreen {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
if recordings.is_empty() {
|
||||
clone.stack.set_visible_child_name("nothing");
|
||||
} else {
|
||||
if !recordings.is_empty() {
|
||||
let length = recordings.len();
|
||||
clone.recordings.replace(recordings);
|
||||
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
|
||||
|
|
@ -142,7 +129,7 @@ impl NavigatorScreen for WorkScreen {
|
|||
}
|
||||
|
||||
fn get_widget(&self) -> gtk::Widget {
|
||||
self.widget.clone().upcast()
|
||||
self.widget.widget.clone().upcast()
|
||||
}
|
||||
|
||||
fn detach_navigator(&self) {
|
||||
|
|
@ -13,4 +13,10 @@ pub use player_bar::*;
|
|||
pub mod poe_list;
|
||||
pub use poe_list::*;
|
||||
|
||||
pub mod screen;
|
||||
pub use screen::*;
|
||||
|
||||
pub mod section;
|
||||
pub use section::*;
|
||||
|
||||
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