Convert dialogs to navigator screens

This commit is contained in:
Elias Projahn 2020-12-02 15:41:19 +01:00
parent 6a40921ac4
commit 0e7a2f1f3d
56 changed files with 2650 additions and 2888 deletions

View file

@ -7,7 +7,6 @@
<file preprocess="xml-stripblanks">ui/instrument_editor.ui</file> <file preprocess="xml-stripblanks">ui/instrument_editor.ui</file>
<file preprocess="xml-stripblanks">ui/instrument_selector.ui</file> <file preprocess="xml-stripblanks">ui/instrument_selector.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/part_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_list.ui</file> <file preprocess="xml-stripblanks">ui/person_list.ui</file>
@ -21,13 +20,15 @@
<file preprocess="xml-stripblanks">ui/recording_screen.ui</file> <file preprocess="xml-stripblanks">ui/recording_screen.ui</file>
<file preprocess="xml-stripblanks">ui/recording_selector.ui</file> <file preprocess="xml-stripblanks">ui/recording_selector.ui</file>
<file preprocess="xml-stripblanks">ui/recording_selector_screen.ui</file> <file preprocess="xml-stripblanks">ui/recording_selector_screen.ui</file>
<file preprocess="xml-stripblanks">ui/section_editor.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/tracks_editor.ui</file> <file preprocess="xml-stripblanks">ui/tracks_editor.ui</file>
<file preprocess="xml-stripblanks">ui/track_editor.ui</file> <file preprocess="xml-stripblanks">ui/track_editor.ui</file>
<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_screen.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_selector.ui</file> <file preprocess="xml-stripblanks">ui/work_selector.ui</file>
<file preprocess="xml-stripblanks">ui/work_selector_screen.ui</file> <file preprocess="xml-stripblanks">ui/work_selector_screen.ui</file>
</gresource> </gresource>

View file

@ -2,57 +2,84 @@
<!-- Generated with glade 3.38.1 --> <!-- Generated with glade 3.38.1 -->
<interface> <interface>
<requires lib="gtk+" version="3.22"/> <requires lib="gtk+" version="3.22"/>
<requires lib="libhandy" version="0.0"/> <requires lib="libhandy" version="1.0"/>
<object class="HdyWindow" id="window"> <object class="GtkStack" id="widget">
<property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="modal">True</property> <property name="transition-type">crossfade</property>
<property name="destroy-with-parent">True</property>
<property name="type-hint">dialog</property>
<child> <child>
<object class="GtkStack" id="stack"> <object class="GtkBox">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="transition-type">crossfade</property> <property name="orientation">vertical</property>
<child> <child>
<object class="GtkBox"> <object class="HdyHeaderBar">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="orientation">vertical</property> <property name="title" translatable="yes">Ensemble</property>
<child> <child>
<object class="HdyHeaderBar"> <object class="GtkButton" id="back_button">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">True</property>
<property name="title" translatable="yes">Ensemble</property> <property name="receives-default">True</property>
<child> <child>
<object class="GtkButton" id="cancel_button"> <object class="GtkImage">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">False</property>
<property name="receives-default">True</property> <property name="icon-name">go-previous-symbolic</property>
</object> </object>
</child> </child>
<child> </object>
<object class="GtkButton" id="save_button"> </child>
<property name="label" translatable="yes">Save</property> <child>
<property name="visible">True</property> <object class="GtkButton" id="save_button">
<property name="can-focus">True</property> <property name="visible">True</property>
<property name="receives-default">True</property> <property name="can-focus">True</property>
<style> <property name="receives-default">True</property>
<class name="suggested-action"/> <child>
</style> <object class="GtkImage">
</object> <property name="visible">True</property>
<packing> <property name="can-focus">False</property>
<property name="pack-type">end</property> <property name="icon-name">object-select-symbolic</property>
<property name="position">1</property> </object>
</packing> </child>
</child> <style>
<class name="suggested-action"/>
</style>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="pack-type">end</property>
<property name="fill">True</property> <property name="position">1</property>
<property name="position">0</property>
</packing> </packing>
</child> </child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkInfoBar" id="info_bar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="revealed">False</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="HdyClamp">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="maximum-size">500</property>
<property name="tightening-threshold">300</property>
<child> <child>
<!-- n-columns=2 n-rows=2 --> <!-- n-columns=2 n-rows=2 -->
<object class="GtkGrid"> <object class="GtkGrid">
@ -109,107 +136,54 @@
</packing> </packing>
</child> </child>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkInfoBar" id="info_bar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="revealed">False</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can-focus">False</property>
<property name="spacing">6</property>
<property name="layout-style">end</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox">
<property name="can-focus">False</property>
<property name="spacing">16</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Failed to save ensemble!</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
<packing> <packing>
<property name="name">content</property> <property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="name">content</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="HdyHeaderBar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="title" translatable="yes">Ensemble</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkBox"> <object class="GtkSpinner">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="orientation">vertical</property> <property name="vexpand">True</property>
<child> <property name="active">True</property>
<object class="HdyHeaderBar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="title" translatable="yes">Ensemble</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSpinner">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="vexpand">True</property>
<property name="active">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object> </object>
<packing> <packing>
<property name="name">loading</property> <property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
</object> </object>
<packing>
<property name="name">loading</property>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
</interface> </interface>

View file

@ -2,57 +2,84 @@
<!-- Generated with glade 3.38.1 --> <!-- Generated with glade 3.38.1 -->
<interface> <interface>
<requires lib="gtk+" version="3.22"/> <requires lib="gtk+" version="3.22"/>
<requires lib="libhandy" version="0.0"/> <requires lib="libhandy" version="1.0"/>
<object class="HdyWindow" id="window"> <object class="GtkStack" id="widget">
<property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="modal">True</property> <property name="transition-type">crossfade</property>
<property name="destroy-with-parent">True</property>
<property name="type-hint">dialog</property>
<child> <child>
<object class="GtkStack" id="stack"> <object class="GtkBox">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="transition-type">crossfade</property> <property name="orientation">vertical</property>
<child> <child>
<object class="GtkBox"> <object class="HdyHeaderBar">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="orientation">vertical</property> <property name="title" translatable="yes">Instrument</property>
<child> <child>
<object class="HdyHeaderBar"> <object class="GtkButton" id="back_button">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">True</property>
<property name="title" translatable="yes">Instrument</property> <property name="receives-default">True</property>
<child> <child>
<object class="GtkButton" id="cancel_button"> <object class="GtkImage">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">False</property>
<property name="receives-default">True</property> <property name="icon-name">go-previous-symbolic</property>
</object> </object>
</child> </child>
<child> </object>
<object class="GtkButton" id="save_button"> </child>
<property name="label" translatable="yes">Save</property> <child>
<property name="visible">True</property> <object class="GtkButton" id="save_button">
<property name="can-focus">True</property> <property name="visible">True</property>
<property name="receives-default">True</property> <property name="can-focus">True</property>
<style> <property name="receives-default">True</property>
<class name="suggested-action"/> <child>
</style> <object class="GtkImage">
</object> <property name="visible">True</property>
<packing> <property name="can-focus">False</property>
<property name="pack-type">end</property> <property name="icon-name">object-select-symbolic</property>
<property name="position">1</property> </object>
</packing> </child>
</child> <style>
<class name="suggested-action"/>
</style>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="pack-type">end</property>
<property name="fill">True</property> <property name="position">1</property>
<property name="position">0</property>
</packing> </packing>
</child> </child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkInfoBar" id="info_bar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="revealed">False</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="HdyClamp">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="maximum-size">500</property>
<property name="tightening-threshold">300</property>
<child> <child>
<!-- n-columns=2 n-rows=2 --> <!-- n-columns=2 n-rows=2 -->
<object class="GtkGrid"> <object class="GtkGrid">
@ -109,107 +136,54 @@
</packing> </packing>
</child> </child>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkInfoBar" id="info_bar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="revealed">False</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can-focus">False</property>
<property name="spacing">6</property>
<property name="layout-style">end</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox">
<property name="can-focus">False</property>
<property name="spacing">16</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Failed to save instrument!</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
<packing> <packing>
<property name="name">content</property> <property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="name">content</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="HdyHeaderBar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="title" translatable="yes">Instrument</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkBox"> <object class="GtkSpinner">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="orientation">vertical</property> <property name="vexpand">True</property>
<child> <property name="active">True</property>
<object class="HdyHeaderBar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="title" translatable="yes">Instrument</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSpinner">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="vexpand">True</property>
<property name="active">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object> </object>
<packing> <packing>
<property name="name">loading</property> <property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
</object> </object>
<packing>
<property name="name">loading</property>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
</interface> </interface>

View file

@ -2,53 +2,65 @@
<!-- Generated with glade 3.38.1 --> <!-- Generated with glade 3.38.1 -->
<interface> <interface>
<requires lib="gtk+" version="3.22"/> <requires lib="gtk+" version="3.22"/>
<requires lib="libhandy" version="0.0"/> <requires lib="libhandy" version="1.0"/>
<object class="HdyWindow" id="window"> <object class="GtkBox" id="widget">
<property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="modal">True</property> <property name="orientation">vertical</property>
<property name="destroy-with-parent">True</property>
<property name="type-hint">dialog</property>
<child> <child>
<object class="GtkBox"> <object class="HdyHeaderBar">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="orientation">vertical</property> <property name="title" translatable="yes">Performance</property>
<child> <child>
<object class="HdyHeaderBar"> <object class="GtkButton" id="back_button">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">True</property>
<property name="title" translatable="yes">Performance</property> <property name="receives-default">True</property>
<child> <child>
<object class="GtkButton" id="cancel_button"> <object class="GtkImage">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">False</property>
<property name="receives-default">True</property> <property name="icon-name">go-previous-symbolic</property>
</object> </object>
</child> </child>
<child> </object>
<object class="GtkButton" id="save_button"> </child>
<property name="label" translatable="yes">Save</property> <child>
<property name="visible">True</property> <object class="GtkButton" id="save_button">
<property name="sensitive">False</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="sensitive">False</property>
<property name="receives-default">True</property> <property name="can-focus">True</property>
<style> <property name="receives-default">True</property>
<class name="suggested-action"/> <child>
</style> <object class="GtkImage">
</object> <property name="visible">True</property>
<packing> <property name="can-focus">False</property>
<property name="pack-type">end</property> <property name="icon-name">object-select-symbolic</property>
<property name="position">1</property> </object>
</packing> </child>
</child> <style>
<class name="suggested-action"/>
</style>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="pack-type">end</property>
<property name="fill">True</property> <property name="position">1</property>
<property name="position">0</property>
</packing> </packing>
</child> </child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="HdyClamp">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="maximum-size">500</property>
<property name="tightening-threshold">300</property>
<child> <child>
<!-- n-columns=2 n-rows=3 --> <!-- n-columns=2 n-rows=3 -->
<object class="GtkGrid"> <object class="GtkGrid">
@ -249,13 +261,13 @@
</packing> </packing>
</child> </child>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
<object class="GtkSizeGroup"> <object class="GtkSizeGroup">

View file

@ -2,57 +2,84 @@
<!-- Generated with glade 3.38.1 --> <!-- Generated with glade 3.38.1 -->
<interface> <interface>
<requires lib="gtk+" version="3.22"/> <requires lib="gtk+" version="3.22"/>
<requires lib="libhandy" version="0.0"/> <requires lib="libhandy" version="1.0"/>
<object class="HdyWindow" id="window"> <object class="GtkStack" id="widget">
<property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="modal">True</property> <property name="transition-type">crossfade</property>
<property name="destroy-with-parent">True</property>
<property name="type-hint">dialog</property>
<child> <child>
<object class="GtkStack" id="stack"> <object class="GtkBox">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="transition-type">crossfade</property> <property name="orientation">vertical</property>
<child> <child>
<object class="GtkBox"> <object class="HdyHeaderBar">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="orientation">vertical</property> <property name="title" translatable="yes">Person</property>
<child> <child>
<object class="HdyHeaderBar"> <object class="GtkButton" id="back_button">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">True</property>
<property name="title" translatable="yes">Person</property> <property name="receives-default">True</property>
<child> <child>
<object class="GtkButton" id="cancel_button"> <object class="GtkImage">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">False</property>
<property name="receives-default">True</property> <property name="icon-name">go-previous-symbolic</property>
</object> </object>
</child> </child>
<child> </object>
<object class="GtkButton" id="save_button"> </child>
<property name="label" translatable="yes">Save</property> <child>
<property name="visible">True</property> <object class="GtkButton" id="save_button">
<property name="can-focus">True</property> <property name="visible">True</property>
<property name="receives-default">True</property> <property name="can-focus">True</property>
<style> <property name="receives-default">True</property>
<class name="suggested-action"/> <child>
</style> <object class="GtkImage">
</object> <property name="visible">True</property>
<packing> <property name="can-focus">False</property>
<property name="pack-type">end</property> <property name="icon-name">object-select-symbolic</property>
<property name="position">1</property> </object>
</packing> </child>
</child> <style>
<class name="suggested-action"/>
</style>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="pack-type">end</property>
<property name="fill">True</property> <property name="position">1</property>
<property name="position">0</property>
</packing> </packing>
</child> </child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkInfoBar" id="info_bar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="revealed">False</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="HdyClamp">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="maximum-size">500</property>
<property name="tightening-threshold">300</property>
<child> <child>
<!-- n-columns=2 n-rows=3 --> <!-- n-columns=2 n-rows=3 -->
<object class="GtkGrid"> <object class="GtkGrid">
@ -132,107 +159,54 @@
</packing> </packing>
</child> </child>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkInfoBar" id="info_bar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="revealed">False</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can-focus">False</property>
<property name="spacing">6</property>
<property name="layout-style">end</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox">
<property name="can-focus">False</property>
<property name="spacing">16</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Failed to save person!</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
<packing> <packing>
<property name="name">content</property> <property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="name">content</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="HdyHeaderBar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="title" translatable="yes">Person</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkBox"> <object class="GtkSpinner">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="orientation">vertical</property> <property name="vexpand">True</property>
<child> <property name="active">True</property>
<object class="HdyHeaderBar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="title" translatable="yes">Person</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSpinner">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="vexpand">True</property>
<property name="active">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object> </object>
<packing> <packing>
<property name="name">loading</property> <property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
</object> </object>
<packing>
<property name="name">loading</property>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
</interface> </interface>

View file

@ -99,7 +99,7 @@
</child> </child>
<child> <child>
<object class="GtkCheckButton" id="server_check_button"> <object class="GtkCheckButton" id="server_check_button">
<property name="label" translatable="yes">Show works from the server</property> <property name="label" translatable="yes">Show recordings from the server</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">True</property>
<property name="receives-default">False</property> <property name="receives-default">False</property>

264
musicus/res/ui/selector.ui Normal file
View file

@ -0,0 +1,264 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.1 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<requires lib="libhandy" version="1.0"/>
<object class="GtkBox" id="widget">
<property name="width-request">250</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">False</property>
<property name="orientation">vertical</property>
<child>
<object class="HdyHeaderBar" id="header">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkButton" id="back_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">go-previous-symbolic</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkButton" id="add_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">list-add-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="HdySearchBar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="search-mode-enabled">True</property>
<child>
<object class="HdyClamp">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="maximum-size">500</property>
<property name="tightening-threshold">300</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkSearchEntry" id="search_entry">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="primary-icon-name">edit-find-symbolic</property>
<property name="primary-icon-activatable">False</property>
<property name="primary-icon-sensitive">False</property>
<property name="placeholder-text" translatable="yes">Search …</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="server_check_button">
<property name="label" translatable="yes">Use the Musicus server</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="halign">start</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkStack" id="stack">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hhomogeneous">False</property>
<property name="vhomogeneous">False</property>
<property name="transition-type">crossfade</property>
<property name="interpolate-size">True</property>
<child>
<object class="GtkSpinner">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-top">12</property>
<property name="active">True</property>
</object>
<packing>
<property name="name">loading</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="height-request">200</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="shadow-type">none</property>
<child>
<object class="HdyClamp">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="maximum-size">500</property>
<property name="tightening-threshold">300</property>
<child>
<object class="GtkFrame" id="frame">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">start</property>
<property name="margin-start">6</property>
<property name="margin-end">6</property>
<property name="margin-top">12</property>
<property name="margin-bottom">6</property>
<property name="label-xalign">0</property>
<property name="shadow-type">in</property>
<child>
<placeholder/>
</child>
<child type="label_item">
<placeholder/>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="name">content</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="border-width">18</property>
<property name="orientation">vertical</property>
<property name="spacing">18</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="opacity">0.5</property>
<property name="pixel-size">80</property>
<property name="icon-name">network-error-symbolic</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="opacity">0.5</property>
<property name="label" translatable="yes">An error occured!</property>
<attributes>
<attribute name="size" value="16384"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="opacity">0.5</property>
<property name="label" translatable="yes">The server was not reachable or responded with an error. Please check your internet connection.</property>
<property name="justify">center</property>
<property name="wrap">True</property>
<property name="max-width-chars">40</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="try_again_button">
<property name="label" translatable="yes">Try again</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="halign">center</property>
<style>
<class name="suggested-action"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="name">error</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</interface>

View file

@ -3,85 +3,117 @@
<interface> <interface>
<requires lib="gtk+" version="3.24"/> <requires lib="gtk+" version="3.24"/>
<requires lib="libhandy" version="0.0"/> <requires lib="libhandy" version="0.0"/>
<object class="HdyWindow" id="window"> <object class="GtkBox" id="widget">
<property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="modal">True</property> <property name="orientation">vertical</property>
<property name="default-width">350</property>
<property name="default-height">200</property>
<property name="type-hint">dialog</property>
<child> <child>
<object class="GtkBox"> <object class="HdyHeaderBar">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="orientation">vertical</property> <property name="title" translatable="yes">Track</property>
<child> <child>
<object class="HdyHeaderBar"> <object class="GtkButton" id="back_button">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="title" translatable="yes">Track</property>
<child>
<object class="GtkButton" id="cancel_button">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
</object>
</child>
<child>
<object class="GtkButton" id="save_button">
<property name="label" translatable="yes">Save</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<style>
<class name="suggested-action"/>
</style>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">True</property>
<property name="receives-default">True</property>
<child> <child>
<object class="GtkViewport"> <object class="GtkImage">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="shadow-type">none</property> <property name="icon-name">go-previous-symbolic</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkButton" id="save_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">object-select-symbolic</property>
</object>
</child>
<style>
<class name="suggested-action"/>
</style>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can-focus">True</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="shadow-type">none</property>
<child>
<object class="HdyClamp">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="maximum-size">500</property>
<property name="tightening-threshold">300</property>
<child> <child>
<object class="GtkListBox" id="list"> <object class="GtkFrame">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="selection-mode">none</property> <property name="valign">start</property>
<child type="placeholder"> <property name="margin-start">6</property>
<object class="GtkLabel"> <property name="margin-end">6</property>
<property name="margin-top">12</property>
<property name="margin-bottom">6</property>
<property name="label-xalign">0</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkListBox" id="list">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="label" translatable="yes">Select a recording of a work with multiple parts.</property> <property name="selection-mode">none</property>
<child type="placeholder">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">6</property>
<property name="margin-end">6</property>
<property name="margin-top">6</property>
<property name="margin-bottom">6</property>
<property name="label" translatable="yes">Select a recording of a work with multiple parts.</property>
<property name="ellipsize">end</property>
</object>
</child>
</object> </object>
</child> </child>
<child type="label_item">
<placeholder/>
</child>
</object> </object>
</child> </child>
</object> </object>
</child> </child>
</object> </object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
</interface> </interface>

View file

@ -3,60 +3,73 @@
<interface> <interface>
<requires lib="gtk+" version="3.24"/> <requires lib="gtk+" version="3.24"/>
<requires lib="libhandy" version="0.0"/> <requires lib="libhandy" version="0.0"/>
<object class="HdyWindow" id="window"> <object class="GtkBox" id="widget">
<property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="modal">True</property> <property name="orientation">vertical</property>
<property name="default-width">400</property>
<property name="default-height">300</property>
<property name="type-hint">dialog</property>
<child> <child>
<object class="GtkBox"> <object class="HdyHeaderBar">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="orientation">vertical</property> <property name="title" translatable="yes">Tracks</property>
<child> <child>
<object class="HdyHeaderBar"> <object class="GtkButton" id="save_button">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="sensitive">False</property>
<property name="title" translatable="yes">Tracks</property> <property name="can-focus">True</property>
<property name="receives-default">True</property>
<child> <child>
<object class="GtkButton" id="save_button"> <object class="GtkImage">
<property name="label" translatable="yes">Save</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="sensitive">False</property> <property name="can-focus">False</property>
<property name="can-focus">True</property> <property name="icon-name">object-select-symbolic</property>
<property name="receives-default">True</property>
<style>
<class name="suggested-action"/>
</style>
</object> </object>
<packing>
<property name="pack-type">end</property>
</packing>
</child> </child>
<style>
<class name="suggested-action"/>
</style>
</object>
<packing>
<property name="pack-type">end</property>
</packing>
</child>
<child>
<object class="GtkButton" id="back_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child> <child>
<object class="GtkButton" id="cancel_button"> <object class="GtkImage">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">False</property>
<property name="receives-default">True</property> <property name="icon-name">go-previous-symbolic</property>
</object> </object>
<packing>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="position">1</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing> </packing>
</child> </child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="HdyClamp">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="vexpand">True</property>
<property name="maximum-size">800</property>
<property name="tightening-threshold">300</property>
<child> <child>
<!-- n-columns=2 n-rows=2 --> <!-- n-columns=2 n-rows=2 -->
<object class="GtkGrid"> <object class="GtkGrid">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="vexpand">True</property>
<property name="border-width">18</property> <property name="border-width">18</property>
<property name="row-spacing">12</property> <property name="row-spacing">12</property>
<property name="column-spacing">6</property> <property name="column-spacing">6</property>
@ -286,13 +299,13 @@
</packing> </packing>
</child> </child>
</object> </object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
</interface> </interface>

View file

@ -2,7 +2,7 @@
<!-- Generated with glade 3.38.1 --> <!-- Generated with glade 3.38.1 -->
<interface> <interface>
<requires lib="gtk+" version="3.22"/> <requires lib="gtk+" version="3.22"/>
<requires lib="libhandy" version="0.0"/> <requires lib="libhandy" version="1.0"/>
<object class="GtkStack" id="widget"> <object class="GtkStack" id="widget">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
@ -17,20 +17,32 @@
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="title" translatable="yes">Work</property> <property name="title" translatable="yes">Work</property>
<child> <child>
<object class="GtkButton" id="cancel_button"> <object class="GtkButton" id="back_button">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">True</property>
<property name="receives-default">True</property> <property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">go-previous-symbolic</property>
</object>
</child>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkButton" id="save_button"> <object class="GtkButton" id="save_button">
<property name="label" translatable="yes">Save</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="sensitive">False</property> <property name="sensitive">False</property>
<property name="can-focus">True</property> <property name="can-focus">True</property>
<property name="receives-default">True</property> <property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">object-select-symbolic</property>
</object>
</child>
<style> <style>
<class name="suggested-action"/> <class name="suggested-action"/>
</style> </style>
@ -88,91 +100,99 @@
<property name="can-focus">True</property> <property name="can-focus">True</property>
<property name="show-border">False</property> <property name="show-border">False</property>
<child> <child>
<!-- n-columns=2 n-rows=3 --> <object class="HdyClamp">
<object class="GtkGrid">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="border-width">18</property> <property name="maximum-size">500</property>
<property name="row-spacing">12</property> <property name="tightening-threshold">300</property>
<property name="column-spacing">6</property>
<child> <child>
<object class="GtkButton" id="composer_button"> <!-- n-columns=2 n-rows=3 -->
<object class="GtkGrid">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">False</property>
<property name="receives-default">True</property> <property name="border-width">18</property>
<property name="hexpand">True</property> <property name="row-spacing">12</property>
<property name="column-spacing">6</property>
<child> <child>
<object class="GtkLabel" id="composer_label"> <object class="GtkButton" id="composer_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="hexpand">True</property>
<child>
<object class="GtkLabel" id="composer_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Select …</property>
<property name="ellipsize">end</property>
</object>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="halign">start</property> <property name="halign">end</property>
<property name="label" translatable="yes">Select …</property> <property name="label" translatable="yes">Composer</property>
<property name="ellipsize">end</property>
</object> </object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="title_entry">
<property name="visible">True</property>
<property name="can-focus">True</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="composer_labe">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Title</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Publish</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="upload_switch">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="halign">start</property>
<property name="active">True</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">2</property>
</packing>
</child> </child>
</object> </object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Composer</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="title_entry">
<property name="visible">True</property>
<property name="can-focus">True</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="composer_labe">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Title</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Publish</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="upload_switch">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="halign">start</property>
<property name="active">True</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">2</property>
</packing>
</child> </child>
</object> </object>
</child> </child>
@ -187,63 +207,76 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkBox"> <object class="HdyClamp">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="border-width">18</property> <property name="maximum-size">800</property>
<property name="spacing">6</property> <property name="tightening-threshold">300</property>
<child>
<object class="GtkScrolledWindow" id="instruments_scroll">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child> <child>
<object class="GtkBox"> <object class="GtkBox">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="border-width">0</property> <property name="border-width">18</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property> <property name="spacing">6</property>
<child> <child>
<object class="GtkButton" id="add_instrument_button"> <object class="GtkScrolledWindow" id="instruments_scroll">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">True</property>
<property name="receives-default">True</property> <property name="shadow-type">in</property>
<child> <child>
<object class="GtkImage"> <placeholder/>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">list-add-symbolic</property>
</object>
</child> </child>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">True</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">0</property> <property name="position">0</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkButton" id="remove_instrument_button"> <object class="GtkBox">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">False</property>
<property name="receives-default">True</property> <property name="border-width">0</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child> <child>
<object class="GtkImage"> <object class="GtkButton" id="add_instrument_button">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">True</property>
<property name="icon-name">list-remove-symbolic</property> <property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">list-add-symbolic</property>
</object>
</child>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="remove_instrument_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">list-remove-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
<packing> <packing>
@ -253,11 +286,6 @@
</packing> </packing>
</child> </child>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
<packing> <packing>
@ -276,62 +304,153 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkBox"> <object class="HdyClamp">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="border-width">18</property> <property name="maximum-size">800</property>
<property name="spacing">6</property> <property name="tightening-threshold">300</property>
<child>
<object class="GtkScrolledWindow" id="structure_scroll">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child> <child>
<object class="GtkBox"> <object class="GtkBox">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="orientation">vertical</property> <property name="border-width">18</property>
<property name="spacing">6</property> <property name="spacing">6</property>
<child> <child>
<object class="GtkButton" id="add_part_button"> <object class="GtkScrolledWindow" id="structure_scroll">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">True</property>
<property name="receives-default">True</property> <property name="shadow-type">in</property>
<child> <child>
<object class="GtkImage"> <placeholder/>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">list-add-symbolic</property>
</object>
</child> </child>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">True</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">0</property> <property name="position">0</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkButton" id="add_section_button"> <object class="GtkBox">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">False</property>
<property name="receives-default">True</property> <property name="orientation">vertical</property>
<property name="spacing">6</property>
<child> <child>
<object class="GtkImage"> <object class="GtkButton" id="add_part_button">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">True</property>
<property name="icon-name">folder-new-symbolic</property> <property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">list-add-symbolic</property>
</object>
</child>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="add_section_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">folder-new-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="edit_part_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">edit-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="remove_part_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">list-remove-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkButton" id="move_part_down_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">go-down-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkButton" id="move_part_up_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">go-up-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">5</property>
</packing>
</child> </child>
</object> </object>
<packing> <packing>
@ -340,90 +459,7 @@
<property name="position">1</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkButton" id="edit_part_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">edit-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="remove_part_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">list-remove-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkButton" id="move_part_down_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">go-down-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkButton" id="move_part_up_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">go-up-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">5</property>
</packing>
</child>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
<packing> <packing>

View file

@ -2,53 +2,64 @@
<!-- Generated with glade 3.38.1 --> <!-- Generated with glade 3.38.1 -->
<interface> <interface>
<requires lib="gtk+" version="3.22"/> <requires lib="gtk+" version="3.22"/>
<requires lib="libhandy" version="0.0"/> <requires lib="libhandy" version="1.0"/>
<object class="HdyWindow" id="window"> <object class="GtkBox" id="widget">
<property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="modal">True</property> <property name="orientation">vertical</property>
<property name="default-width">350</property>
<property name="destroy-with-parent">True</property>
<property name="type-hint">dialog</property>
<child> <child>
<object class="GtkBox"> <object class="HdyHeaderBar">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="orientation">vertical</property> <property name="title" translatable="yes">Work part</property>
<child> <child>
<object class="HdyHeaderBar"> <object class="GtkButton" id="back_button">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">True</property>
<property name="title" translatable="yes">Work part</property> <property name="receives-default">True</property>
<child> <child>
<object class="GtkButton" id="cancel_button"> <object class="GtkImage">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">False</property>
<property name="receives-default">True</property> <property name="icon-name">go-previous-symbolic</property>
</object> </object>
</child> </child>
<child> </object>
<object class="GtkButton" id="save_button"> </child>
<property name="label" translatable="yes">Save</property> <child>
<property name="visible">True</property> <object class="GtkButton" id="save_button">
<property name="can-focus">True</property> <property name="visible">True</property>
<property name="receives-default">True</property> <property name="can-focus">True</property>
<style> <property name="receives-default">True</property>
<class name="suggested-action"/> <child>
</style> <object class="GtkImage">
</object> <property name="visible">True</property>
<packing> <property name="can-focus">False</property>
<property name="pack-type">end</property> <property name="icon-name">object-select-symbolic</property>
<property name="position">1</property> </object>
</packing> </child>
</child> <style>
<class name="suggested-action"/>
</style>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="pack-type">end</property>
<property name="fill">True</property> <property name="position">1</property>
<property name="position">0</property>
</packing> </packing>
</child> </child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="HdyClamp">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="maximum-size">500</property>
<property name="tightening-threshold">300</property>
<child> <child>
<!-- n-columns=2 n-rows=2 --> <!-- n-columns=2 n-rows=2 -->
<object class="GtkGrid"> <object class="GtkGrid">
@ -146,13 +157,13 @@
</packing> </packing>
</child> </child>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
</interface> </interface>

View file

@ -2,52 +2,64 @@
<!-- Generated with glade 3.38.1 --> <!-- Generated with glade 3.38.1 -->
<interface> <interface>
<requires lib="gtk+" version="3.22"/> <requires lib="gtk+" version="3.22"/>
<requires lib="libhandy" version="0.0"/> <requires lib="libhandy" version="1.0"/>
<object class="HdyWindow" id="window"> <object class="GtkBox" id="widget">
<property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="modal">True</property> <property name="orientation">vertical</property>
<property name="destroy-with-parent">True</property>
<property name="type-hint">dialog</property>
<child> <child>
<object class="GtkBox"> <object class="HdyHeaderBar">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="orientation">vertical</property> <property name="title" translatable="yes">Work section</property>
<child> <child>
<object class="HdyHeaderBar"> <object class="GtkButton" id="back_button">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">True</property>
<property name="title" translatable="yes">Work section</property> <property name="receives-default">True</property>
<child> <child>
<object class="GtkButton" id="cancel_button"> <object class="GtkImage">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">False</property>
<property name="receives-default">True</property> <property name="icon-name">go-previous-symbolic</property>
</object> </object>
</child> </child>
<child> </object>
<object class="GtkButton" id="save_button"> </child>
<property name="label" translatable="yes">Save</property> <child>
<property name="visible">True</property> <object class="GtkButton" id="save_button">
<property name="can-focus">True</property> <property name="visible">True</property>
<property name="receives-default">True</property> <property name="can-focus">True</property>
<style> <property name="receives-default">True</property>
<class name="suggested-action"/> <child>
</style> <object class="GtkImage">
</object> <property name="visible">True</property>
<packing> <property name="can-focus">False</property>
<property name="pack-type">end</property> <property name="icon-name">object-select-symbolic</property>
<property name="position">1</property> </object>
</packing> </child>
</child> <style>
<class name="suggested-action"/>
</style>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="pack-type">end</property>
<property name="fill">True</property> <property name="position">1</property>
<property name="position">0</property>
</packing> </packing>
</child> </child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="HdyClamp">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="maximum-size">500</property>
<property name="tightening-threshold">300</property>
<child> <child>
<!-- n-columns=2 n-rows=1 --> <!-- n-columns=2 n-rows=1 -->
<object class="GtkGrid"> <object class="GtkGrid">
@ -80,13 +92,13 @@
</packing> </packing>
</child> </child>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
</interface> </interface>

View file

@ -49,6 +49,20 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="title" translatable="yes">Select a work</property> <property name="title" translatable="yes">Select a work</property>
<child>
<object class="GtkButton" id="back_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">go-previous-symbolic</property>
</object>
</child>
</object>
</child>
<child> <child>
<object class="GtkButton" id="add_button"> <object class="GtkButton" id="add_button">
<property name="visible">True</property> <property name="visible">True</property>
@ -62,6 +76,10 @@
</object> </object>
</child> </child>
</object> </object>
<packing>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
<packing> <packing>

View file

@ -1,162 +0,0 @@
use super::EnsembleEditor;
use crate::backend::Backend;
use crate::database::Ensemble;
use crate::widgets::List;
use gettextrs::gettext;
use gio::prelude::*;
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
use std::cell::RefCell;
use std::rc::Rc;
/// A dialog for selecting a ensemble.
pub struct EnsembleSelector {
backend: Rc<Backend>,
window: libhandy::Window,
server_check_button: gtk::CheckButton,
stack: gtk::Stack,
list: Rc<List<Ensemble>>,
selected_cb: RefCell<Option<Box<dyn Fn(Ensemble) -> ()>>>,
}
impl EnsembleSelector {
pub fn new<P>(backend: Rc<Backend>, parent: &P) -> Rc<Self>
where
P: IsA<gtk::Window>,
{
// Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/ensemble_selector.ui");
get_widget!(builder, libhandy::Window, window);
get_widget!(builder, gtk::Button, add_button);
get_widget!(builder, gtk::CheckButton, server_check_button);
get_widget!(builder, gtk::SearchEntry, search_entry);
get_widget!(builder, gtk::Stack, stack);
get_widget!(builder, gtk::ScrolledWindow, scroll);
get_widget!(builder, gtk::Button, try_again_button);
window.set_transient_for(Some(parent));
let list = List::<Ensemble>::new(&gettext("No ensembles found."));
scroll.add(&list.widget);
let this = Rc::new(Self {
backend,
window,
server_check_button,
stack,
list,
selected_cb: RefCell::new(None),
});
// Connect signals and callbacks
add_button.connect_clicked(clone!(@strong this => move |_| {
let editor = EnsembleEditor::new(
this.backend.clone(),
&this.window,
None,
);
editor.set_saved_cb(clone!(@strong this => move |ensemble| {
if let Some(cb) = &*this.selected_cb.borrow() {
cb(ensemble);
}
this.window.close();
}));
editor.show();
}));
search_entry.connect_search_changed(clone!(@strong this => move |_| {
this.list.invalidate_filter();
}));
let load_online = Rc::new(clone!(@strong this => move || {
this.stack.set_visible_child_name("loading");
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
match clone.backend.get_ensembles().await {
Ok(ensembles) => {
clone.list.show_items(ensembles);
clone.stack.set_visible_child_name("content");
}
Err(_) => {
clone.list.show_items(Vec::new());
clone.stack.set_visible_child_name("error");
}
}
});
}));
let load_local = Rc::new(clone!(@strong this => move || {
this.stack.set_visible_child_name("loading");
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
let ensembles = clone.backend.db().get_ensembles().await.unwrap();
clone.list.show_items(ensembles);
clone.stack.set_visible_child_name("content");
});
}));
this.server_check_button.connect_toggled(
clone!(@strong this, @strong load_local, @strong load_online => move |_| {
if this.server_check_button.get_active() {
load_online();
} else {
load_local();
}
}),
);
this.list.set_make_widget(|ensemble: &Ensemble| {
let label = gtk::Label::new(Some(&ensemble.name));
label.set_halign(gtk::Align::Start);
label.set_margin_start(6);
label.set_margin_end(6);
label.set_margin_top(6);
label.set_margin_bottom(6);
label.upcast()
});
this.list
.set_filter(clone!(@strong search_entry => move |ensemble: &Ensemble| {
let search = search_entry.get_text().to_string().to_lowercase();
search.is_empty() || ensemble.name.contains(&search)
}));
this.list.set_selected(clone!(@strong this => move |work| {
if let Some(cb) = &*this.selected_cb.borrow() {
cb(work.clone());
}
this.window.close();
}));
try_again_button.connect_clicked(clone!(@strong load_online => move |_| {
load_online();
}));
// Initialize
load_online();
this
}
/// Set the closure to be called when the user has selected a ensemble.
pub fn set_selected_cb<F: Fn(Ensemble) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
/// Show the ensemble selector.
pub fn show(&self) {
self.window.show();
}
}

View file

@ -1,162 +0,0 @@
use super::InstrumentEditor;
use crate::backend::Backend;
use crate::database::Instrument;
use crate::widgets::List;
use gettextrs::gettext;
use gio::prelude::*;
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
use std::cell::RefCell;
use std::rc::Rc;
/// A dialog for selecting a instrument.
pub struct InstrumentSelector {
backend: Rc<Backend>,
window: libhandy::Window,
server_check_button: gtk::CheckButton,
stack: gtk::Stack,
list: Rc<List<Instrument>>,
selected_cb: RefCell<Option<Box<dyn Fn(Instrument) -> ()>>>,
}
impl InstrumentSelector {
pub fn new<P>(backend: Rc<Backend>, parent: &P) -> Rc<Self>
where
P: IsA<gtk::Window>,
{
// Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/instrument_selector.ui");
get_widget!(builder, libhandy::Window, window);
get_widget!(builder, gtk::Button, add_button);
get_widget!(builder, gtk::CheckButton, server_check_button);
get_widget!(builder, gtk::SearchEntry, search_entry);
get_widget!(builder, gtk::Stack, stack);
get_widget!(builder, gtk::ScrolledWindow, scroll);
get_widget!(builder, gtk::Button, try_again_button);
window.set_transient_for(Some(parent));
let list = List::<Instrument>::new(&gettext("No instruments found."));
scroll.add(&list.widget);
let this = Rc::new(Self {
backend,
window,
server_check_button,
stack,
list,
selected_cb: RefCell::new(None),
});
// Connect signals and callbacks
add_button.connect_clicked(clone!(@strong this => move |_| {
let editor = InstrumentEditor::new(
this.backend.clone(),
&this.window,
None,
);
editor.set_saved_cb(clone!(@strong this => move |instrument| {
if let Some(cb) = &*this.selected_cb.borrow() {
cb(instrument);
}
this.window.close();
}));
editor.show();
}));
search_entry.connect_search_changed(clone!(@strong this => move |_| {
this.list.invalidate_filter();
}));
let load_online = Rc::new(clone!(@strong this => move || {
this.stack.set_visible_child_name("loading");
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
match clone.backend.get_instruments().await {
Ok(instruments) => {
clone.list.show_items(instruments);
clone.stack.set_visible_child_name("content");
}
Err(_) => {
clone.list.show_items(Vec::new());
clone.stack.set_visible_child_name("error");
}
}
});
}));
let load_local = Rc::new(clone!(@strong this => move || {
this.stack.set_visible_child_name("loading");
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
let instruments = clone.backend.db().get_instruments().await.unwrap();
clone.list.show_items(instruments);
clone.stack.set_visible_child_name("content");
});
}));
this.server_check_button.connect_toggled(
clone!(@strong this, @strong load_local, @strong load_online => move |_| {
if this.server_check_button.get_active() {
load_online();
} else {
load_local();
}
}),
);
this.list.set_make_widget(|instrument: &Instrument| {
let label = gtk::Label::new(Some(&instrument.name));
label.set_halign(gtk::Align::Start);
label.set_margin_start(6);
label.set_margin_end(6);
label.set_margin_top(6);
label.set_margin_bottom(6);
label.upcast()
});
this.list
.set_filter(clone!(@strong search_entry => move |instrument: &Instrument| {
let search = search_entry.get_text().to_string().to_lowercase();
search.is_empty() || instrument.name.contains(&search)
}));
this.list.set_selected(clone!(@strong this => move |work| {
if let Some(cb) = &*this.selected_cb.borrow() {
cb(work.clone());
}
this.window.close();
}));
try_again_button.connect_clicked(clone!(@strong load_online => move |_| {
load_online();
}));
// Initialize
load_online();
this
}
/// Set the closure to be called when the user has selected a instrument.
pub fn set_selected_cb<F: Fn(Instrument) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
/// Show the instrument selector.
pub fn show(&self) {
self.window.show();
}
}

View file

@ -1,41 +1,11 @@
pub mod about; pub mod about;
pub use about::*; pub use about::*;
pub mod ensemble_editor;
pub use ensemble_editor::*;
pub mod ensemble_selector;
pub use ensemble_selector::*;
pub mod instrument_editor;
pub use instrument_editor::*;
pub mod instrument_selector;
pub use instrument_selector::*;
pub mod login_dialog; pub mod login_dialog;
pub use login_dialog::*; pub use login_dialog::*;
pub mod person_editor;
pub use person_editor::*;
pub mod person_selector;
pub use person_selector::*;
pub mod preferences; pub mod preferences;
pub use preferences::*; pub use preferences::*;
pub mod server_dialog; pub mod server_dialog;
pub use server_dialog::*; pub use server_dialog::*;
pub mod recording;
pub use recording::*;
pub mod track_editor;
pub use track_editor::*;
pub mod tracks_editor;
pub use tracks_editor::*;
pub mod work;
pub use work::*;

View file

@ -1,163 +0,0 @@
use super::PersonEditor;
use crate::backend::Backend;
use crate::database::Person;
use crate::widgets::List;
use gettextrs::gettext;
use gio::prelude::*;
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
use std::cell::RefCell;
use std::rc::Rc;
/// A dialog for selecting a person.
pub struct PersonSelector {
backend: Rc<Backend>,
window: libhandy::Window,
server_check_button: gtk::CheckButton,
stack: gtk::Stack,
list: Rc<List<Person>>,
selected_cb: RefCell<Option<Box<dyn Fn(Person) -> ()>>>,
}
impl PersonSelector {
pub fn new<P>(backend: Rc<Backend>, parent: &P) -> Rc<Self>
where
P: IsA<gtk::Window>,
{
// Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/person_selector.ui");
get_widget!(builder, libhandy::Window, window);
get_widget!(builder, gtk::Button, add_button);
get_widget!(builder, gtk::CheckButton, server_check_button);
get_widget!(builder, gtk::SearchEntry, search_entry);
get_widget!(builder, gtk::Stack, stack);
get_widget!(builder, gtk::ScrolledWindow, scroll);
get_widget!(builder, gtk::Button, try_again_button);
window.set_transient_for(Some(parent));
let list = List::<Person>::new(&gettext("No persons found."));
scroll.add(&list.widget);
let this = Rc::new(Self {
backend,
window,
server_check_button,
stack,
list,
selected_cb: RefCell::new(None),
});
// Connect signals and callbacks
add_button.connect_clicked(clone!(@strong this => move |_| {
let editor = PersonEditor::new(
this.backend.clone(),
&this.window,
None,
);
editor.set_saved_cb(clone!(@strong this => move |person| {
if let Some(cb) = &*this.selected_cb.borrow() {
cb(person);
}
this.window.close();
}));
editor.show();
}));
search_entry.connect_search_changed(clone!(@strong this => move |_| {
this.list.invalidate_filter();
}));
let load_online = Rc::new(clone!(@strong this => move || {
this.stack.set_visible_child_name("loading");
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
match clone.backend.get_persons().await {
Ok(persons) => {
clone.list.show_items(persons);
clone.stack.set_visible_child_name("content");
}
Err(_) => {
clone.list.show_items(Vec::new());
clone.stack.set_visible_child_name("error");
}
}
});
}));
let load_local = Rc::new(clone!(@strong this => move || {
this.stack.set_visible_child_name("loading");
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
let persons = clone.backend.db().get_persons().await.unwrap();
clone.list.show_items(persons);
clone.stack.set_visible_child_name("content");
});
}));
this.server_check_button.connect_toggled(
clone!(@strong this, @strong load_local, @strong load_online => move |_| {
if this.server_check_button.get_active() {
load_online();
} else {
load_local();
}
}),
);
this.list.set_make_widget(|person: &Person| {
let label = gtk::Label::new(Some(&person.name_lf()));
label.set_halign(gtk::Align::Start);
label.set_margin_start(6);
label.set_margin_end(6);
label.set_margin_top(6);
label.set_margin_bottom(6);
label.upcast()
});
this.list
.set_filter(clone!(@strong search_entry => move |person: &Person| {
let search = search_entry.get_text().to_string().to_lowercase();
let name = person.name_fl().to_lowercase();
search.is_empty() || name.contains(&search)
}));
this.list.set_selected(clone!(@strong this => move |person| {
if let Some(cb) = &*this.selected_cb.borrow() {
cb(person.clone());
}
this.window.close();
}));
try_again_button.connect_clicked(clone!(@strong load_online => move |_| {
load_online();
}));
// Initialize
load_online();
this
}
/// Set the closure to be called when the user has selected a person.
pub fn set_selected_cb<F: Fn(Person) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
/// Show the person selector.
pub fn show(&self) {
self.window.show();
}
}

View file

@ -1,11 +0,0 @@
pub mod recording_dialog;
pub use recording_dialog::*;
pub mod recording_editor_dialog;
pub use recording_editor_dialog::*;
mod performance_editor;
mod recording_editor;
mod recording_selector;
mod recording_selector_person_screen;
mod recording_selector_work_screen;

View file

@ -1,86 +0,0 @@
use super::recording_editor::*;
use super::recording_selector::*;
use crate::backend::*;
use crate::database::*;
use glib::clone;
use gtk::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;
/// A dialog for selecting and creating a recording.
pub struct RecordingDialog {
pub window: libhandy::Window,
stack: gtk::Stack,
selector: Rc<RecordingSelector>,
editor: Rc<RecordingEditor>,
selected_cb: RefCell<Option<Box<dyn Fn(Recording) -> ()>>>,
}
impl RecordingDialog {
/// Create a new recording dialog.
pub fn new<W: IsA<gtk::Window>>(backend: Rc<Backend>, parent: &W) -> Rc<Self> {
// Create UI
let window = libhandy::Window::new();
window.set_type_hint(gdk::WindowTypeHint::Dialog);
window.set_modal(true);
window.set_transient_for(Some(parent));
window.set_default_size(600, 424);
let selector = RecordingSelector::new(backend.clone());
let editor = RecordingEditor::new(backend.clone(), &window, None);
let stack = gtk::Stack::new();
stack.set_transition_type(gtk::StackTransitionType::Crossfade);
stack.add(&selector.widget);
stack.add(&editor.widget);
window.add(&stack);
window.show_all();
let this = Rc::new(Self {
window,
stack,
selector,
editor,
selected_cb: RefCell::new(None),
});
// Connect signals and callbacks
this.selector.set_add_cb(clone!(@strong this => move || {
this.stack.set_visible_child(&this.editor.widget);
}));
this.selector
.set_selected_cb(clone!(@strong this => move |recording| {
if let Some(cb) = &*this.selected_cb.borrow() {
cb(recording);
this.window.close();
}
}));
this.editor.set_back_cb(clone!(@strong this => move || {
this.stack.set_visible_child(&this.selector.widget);
}));
this.editor
.set_selected_cb(clone!(@strong this => move |recording| {
if let Some(cb) = &*this.selected_cb.borrow() {
cb(recording);
this.window.close();
}
}));
this
}
/// Set the closure to be called when the user has selected or created a recording.
pub fn set_selected_cb<F: Fn(Recording) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
/// Show the recording dialog.
pub fn show(&self) {
self.window.show();
}
}

View file

@ -1,64 +0,0 @@
use super::recording_editor::*;
use crate::backend::*;
use crate::database::*;
use glib::clone;
use gtk::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;
/// A dialog for creating or editing a recording.
pub struct RecordingEditorDialog {
pub window: libhandy::Window,
selected_cb: RefCell<Option<Box<dyn Fn(Recording) -> ()>>>,
}
impl RecordingEditorDialog {
/// Create a new recording editor dialog and optionally initialize it.
pub fn new<W: IsA<gtk::Window>>(
backend: Rc<Backend>,
parent: &W,
recording: Option<Recording>,
) -> Rc<Self> {
// Create UI
let window = libhandy::Window::new();
window.set_type_hint(gdk::WindowTypeHint::Dialog);
window.set_modal(true);
window.set_transient_for(Some(parent));
let editor = RecordingEditor::new(backend.clone(), &window, recording);
window.add(&editor.widget);
window.show_all();
let this = Rc::new(Self {
window,
selected_cb: RefCell::new(None),
});
// Connect signals and callbacks
editor.set_back_cb(clone!(@strong this => move || {
this.window.close();
}));
editor.set_selected_cb(clone!(@strong this => move |recording| {
if let Some(cb) = &*this.selected_cb.borrow() {
cb(recording);
}
this.window.close();
}));
this
}
/// Set the closure to be called when the user edited or created a recording.
pub fn set_selected_cb<F: Fn(Recording) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
/// Show the recording editor dialog.
pub fn show(&self) {
self.window.show();
}
}

View file

@ -1,174 +0,0 @@
use super::recording_selector_person_screen::*;
use crate::backend::Backend;
use crate::database::*;
use crate::widgets::*;
use gettextrs::gettext;
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
use libhandy::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;
/// A widget for selecting a recording from a list of existing ones.
pub struct RecordingSelector {
pub widget: libhandy::Leaflet,
backend: Rc<Backend>,
sidebar_box: gtk::Box,
server_check_button: gtk::CheckButton,
stack: gtk::Stack,
list: Rc<List<Person>>,
selected_cb: RefCell<Option<Box<dyn Fn(Recording) -> ()>>>,
add_cb: RefCell<Option<Box<dyn Fn() -> ()>>>,
navigator: Rc<Navigator>,
}
impl RecordingSelector {
/// Create a new recording selector.
pub fn new(backend: Rc<Backend>) -> Rc<Self> {
// Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_selector.ui");
get_widget!(builder, libhandy::Leaflet, widget);
get_widget!(builder, gtk::Button, add_button);
get_widget!(builder, gtk::Box, sidebar_box);
get_widget!(builder, gtk::CheckButton, server_check_button);
get_widget!(builder, gtk::SearchEntry, search_entry);
get_widget!(builder, gtk::Stack, stack);
get_widget!(builder, gtk::ScrolledWindow, scroll);
get_widget!(builder, gtk::Button, try_again_button);
get_widget!(builder, gtk::Box, empty_screen);
let list = List::<Person>::new(&gettext("No persons found."));
scroll.add(&list.widget);
let navigator = Navigator::new(&empty_screen);
widget.add(&navigator.widget);
let this = Rc::new(Self {
widget,
backend,
sidebar_box,
server_check_button,
stack,
list,
selected_cb: RefCell::new(None),
add_cb: RefCell::new(None),
navigator,
});
// Connect signals and callbacks
add_button.connect_clicked(clone!(@strong this => move |_| {
if let Some(cb) = &*this.add_cb.borrow() {
cb();
}
}));
search_entry.connect_search_changed(clone!(@strong this => move |_| {
this.list.invalidate_filter();
}));
let load_online = Rc::new(clone!(@strong this => move || {
this.stack.set_visible_child_name("loading");
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
match clone.backend.get_persons().await {
Ok(persons) => {
clone.list.show_items(persons);
clone.stack.set_visible_child_name("content");
}
Err(_) => {
clone.list.show_items(Vec::new());
clone.stack.set_visible_child_name("error");
}
}
});
}));
let load_local = Rc::new(clone!(@strong this => move || {
this.stack.set_visible_child_name("loading");
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
let persons = clone.backend.db().get_persons().await.unwrap();
clone.list.show_items(persons);
clone.stack.set_visible_child_name("content");
});
}));
this.server_check_button.connect_toggled(
clone!(@strong this, @strong load_local, @strong load_online => move |_| {
if this.server_check_button.get_active() {
load_online();
} else {
load_local();
}
}),
);
this.list.set_make_widget(|person: &Person| {
let label = gtk::Label::new(Some(&person.name_lf()));
label.set_halign(gtk::Align::Start);
label.set_margin_start(6);
label.set_margin_end(6);
label.set_margin_top(6);
label.set_margin_bottom(6);
label.upcast()
});
this.list
.set_filter(clone!(@strong search_entry => move |person: &Person| {
let search = search_entry.get_text().to_string().to_lowercase();
let name = person.name_fl().to_lowercase();
search.is_empty() || name.contains(&search)
}));
this.list
.set_selected(clone!(@strong this => move |person| {
let online = this.server_check_button.get_active();
let person_screen = RecordingSelectorPersonScreen::new(
this.backend.clone(),
person.clone(),
online,
);
person_screen.set_selected_cb(clone!(@strong this => move |work| {
if let Some(cb) = &*this.selected_cb.borrow() {
cb(work);
}
}));
this.navigator.clone().replace(person_screen);
this.widget.set_visible_child(&this.navigator.widget);
}));
try_again_button.connect_clicked(clone!(@strong load_online => move |_| {
load_online();
}));
this.navigator.set_back_cb(clone!(@strong this => move || {
this.widget.set_visible_child(&this.sidebar_box);
}));
// Initialize
load_online();
this
}
/// Set the closure to be called if the user wants to add a new recording.
pub fn set_add_cb<F: Fn() -> () + 'static>(&self, cb: F) {
self.add_cb.replace(Some(Box::new(cb)));
}
/// Set the closure to be called when the user has selected a recording.
pub fn set_selected_cb<F: Fn(Recording) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
}

View file

@ -1,161 +0,0 @@
use super::recording_selector_work_screen::*;
use crate::backend::*;
use crate::database::*;
use crate::widgets::*;
use gettextrs::gettext;
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
use libhandy::HeaderBarExt;
use std::cell::RefCell;
use std::rc::Rc;
/// A screen within the recording selector that presents a list of works and switches to a work
/// screen on selection.
pub struct RecordingSelectorPersonScreen {
backend: Rc<Backend>,
person: Person,
online: bool,
widget: gtk::Box,
stack: gtk::Stack,
work_list: Rc<List<Work>>,
selected_cb: RefCell<Option<Box<dyn Fn(Recording) -> ()>>>,
navigator: RefCell<Option<Rc<Navigator>>>,
}
impl RecordingSelectorPersonScreen {
/// Create a new recording selector person screen.
pub fn new(backend: Rc<Backend>, person: Person, online: bool) -> Rc<Self> {
// Create UI
let builder =
gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_selector_screen.ui");
get_widget!(builder, gtk::Box, widget);
get_widget!(builder, libhandy::HeaderBar, header);
get_widget!(builder, gtk::Button, back_button);
get_widget!(builder, gtk::Stack, stack);
get_widget!(builder, gtk::ScrolledWindow, scroll);
get_widget!(builder, gtk::Button, try_again_button);
header.set_title(Some(&person.name_fl()));
let work_list = List::new(&gettext("No works found."));
scroll.add(&work_list.widget);
let this = Rc::new(Self {
backend,
person,
online,
widget,
stack,
work_list,
selected_cb: RefCell::new(None),
navigator: RefCell::new(None),
});
// Connect signals and callbacks
back_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
}));
let load_online = Rc::new(clone!(@strong this => move || {
this.stack.set_visible_child_name("loading");
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
match clone.backend.get_works(&clone.person.id).await {
Ok(works) => {
clone.work_list.show_items(works);
clone.stack.set_visible_child_name("content");
}
Err(_) => {
clone.work_list.show_items(Vec::new());
clone.stack.set_visible_child_name("error");
}
}
});
}));
let load_local = Rc::new(clone!(@strong this => move || {
this.stack.set_visible_child_name("loading");
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
let works = clone.backend.db().get_works(&clone.person.id).await.unwrap();
clone.work_list.show_items(works);
clone.stack.set_visible_child_name("content");
});
}));
this.work_list.set_make_widget(|work: &Work| {
let label = gtk::Label::new(Some(&work.title));
label.set_ellipsize(pango::EllipsizeMode::End);
label.set_halign(gtk::Align::Start);
label.set_margin_start(6);
label.set_margin_end(6);
label.set_margin_top(6);
label.set_margin_bottom(6);
label.upcast()
});
this.work_list
.set_selected(clone!(@strong this => move |work| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let work_screen = RecordingSelectorWorkScreen::new(
this.backend.clone(),
work.clone(),
this.online,
);
work_screen.set_selected_cb(clone!(@strong this => move |recording| {
if let Some(cb) = &*this.selected_cb.borrow() {
cb(recording);
}
}));
navigator.push(work_screen);
}
}));
try_again_button.connect_clicked(clone!(@strong load_online => move |_| {
load_online();
}));
// Initialize
if this.online {
load_online();
} else {
load_local();
}
this
}
/// Sets a closure to be called when the user has selected a recording.
pub fn set_selected_cb<F: Fn(Recording) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
}
impl NavigatorScreen for RecordingSelectorPersonScreen {
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);
}
}

View file

@ -1,155 +0,0 @@
use crate::backend::*;
use crate::database::*;
use crate::widgets::*;
use gettextrs::gettext;
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
use libhandy::HeaderBarExt;
use std::cell::RefCell;
use std::rc::Rc;
/// A screen within the recording selector presenting a list of recordings for a work.
pub struct RecordingSelectorWorkScreen {
backend: Rc<Backend>,
work: Work,
online: bool,
widget: gtk::Box,
stack: gtk::Stack,
recording_list: Rc<List<Recording>>,
selected_cb: RefCell<Option<Box<dyn Fn(Recording) -> ()>>>,
navigator: RefCell<Option<Rc<Navigator>>>,
}
impl RecordingSelectorWorkScreen {
/// Create a new recording selector work screen.
pub fn new(backend: Rc<Backend>, work: Work, online: bool) -> Rc<Self> {
// Create UI
let builder =
gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_selector_screen.ui");
get_widget!(builder, gtk::Box, widget);
get_widget!(builder, libhandy::HeaderBar, header);
get_widget!(builder, gtk::Button, back_button);
get_widget!(builder, gtk::Stack, stack);
get_widget!(builder, gtk::ScrolledWindow, scroll);
get_widget!(builder, gtk::Button, try_again_button);
header.set_title(Some(&work.title));
header.set_subtitle(Some(&work.composer.name_fl()));
let recording_list = List::new(&gettext("No recordings found."));
scroll.add(&recording_list.widget);
let this = Rc::new(Self {
backend,
work,
online,
widget,
stack,
recording_list,
selected_cb: RefCell::new(None),
navigator: RefCell::new(None),
});
// Connect signals and callbacks
back_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
}));
let load_online = Rc::new(clone!(@strong this => move || {
this.stack.set_visible_child_name("loading");
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
match clone.backend.get_recordings_for_work(&clone.work.id).await {
Ok(recordings) => {
clone.recording_list.show_items(recordings);
clone.stack.set_visible_child_name("content");
}
Err(_) => {
clone.recording_list.show_items(Vec::new());
clone.stack.set_visible_child_name("error");
}
}
});
}));
let load_local = Rc::new(clone!(@strong this => move || {
this.stack.set_visible_child_name("loading");
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
let recordings = clone.backend.db().get_recordings_for_work(&clone.work.id).await.unwrap();
clone.recording_list.show_items(recordings);
clone.stack.set_visible_child_name("content");
});
}));
this.recording_list
.set_make_widget(|recording: &Recording| {
let work_label = gtk::Label::new(Some(&recording.work.get_title()));
work_label.set_ellipsize(pango::EllipsizeMode::End);
work_label.set_halign(gtk::Align::Start);
let performers_label = gtk::Label::new(Some(&recording.get_performers()));
performers_label.set_ellipsize(pango::EllipsizeMode::End);
performers_label.set_opacity(0.5);
performers_label.set_halign(gtk::Align::Start);
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
vbox.set_border_width(6);
vbox.add(&work_label);
vbox.add(&performers_label);
vbox.upcast()
});
this.recording_list
.set_selected(clone!(@strong this => move |recording| {
if let Some(cb) = &*this.selected_cb.borrow() {
cb(recording.clone());
}
}));
try_again_button.connect_clicked(clone!(@strong load_online => move |_| {
load_online();
}));
// Initialize
if this.online {
load_online();
} else {
load_local();
}
this
}
/// Sets a closure to be called when the user has selected a recording.
pub fn set_selected_cb<F: Fn(Recording) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
}
impl NavigatorScreen for RecordingSelectorWorkScreen {
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);
}
}

View file

@ -1,11 +0,0 @@
pub mod work_dialog;
pub use work_dialog::*;
pub mod work_editor_dialog;
pub use work_editor_dialog::*;
mod part_editor;
mod section_editor;
mod work_editor;
mod work_selector;
mod work_selector_person_screen;

View file

@ -1,86 +0,0 @@
use super::work_editor::*;
use super::work_selector::*;
use crate::backend::*;
use crate::database::*;
use glib::clone;
use gtk::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;
/// A dialog for selecting and creating a work.
pub struct WorkDialog {
pub window: libhandy::Window,
stack: gtk::Stack,
selector: Rc<WorkSelector>,
editor: Rc<WorkEditor>,
selected_cb: RefCell<Option<Box<dyn Fn(Work) -> ()>>>,
}
impl WorkDialog {
/// Create a new work dialog.
pub fn new<W: IsA<gtk::Window>>(backend: Rc<Backend>, parent: &W) -> Rc<Self> {
// Create UI
let window = libhandy::Window::new();
window.set_type_hint(gdk::WindowTypeHint::Dialog);
window.set_modal(true);
window.set_transient_for(Some(parent));
window.set_default_size(600, 424);
let selector = WorkSelector::new(backend.clone());
let editor = WorkEditor::new(backend.clone(), &window, None);
let stack = gtk::Stack::new();
stack.set_transition_type(gtk::StackTransitionType::Crossfade);
stack.add(&selector.widget);
stack.add(&editor.widget);
window.add(&stack);
window.show_all();
let this = Rc::new(Self {
window,
stack,
selector,
editor,
selected_cb: RefCell::new(None),
});
// Connect signals and callbacks
this.selector.set_add_cb(clone!(@strong this => move || {
this.stack.set_visible_child(&this.editor.widget);
}));
this.selector
.set_selected_cb(clone!(@strong this => move |work| {
if let Some(cb) = &*this.selected_cb.borrow() {
cb(work);
this.window.close();
}
}));
this.editor.set_cancel_cb(clone!(@strong this => move || {
this.stack.set_visible_child(&this.selector.widget);
}));
this.editor
.set_saved_cb(clone!(@strong this => move |work| {
if let Some(cb) = &*this.selected_cb.borrow() {
cb(work);
this.window.close();
}
}));
this
}
/// Set the closure to be called when the user has selected or created a work.
pub fn set_selected_cb<F: Fn(Work) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
/// Show the work dialog.
pub fn show(&self) {
self.window.show();
}
}

View file

@ -1,64 +0,0 @@
use super::work_editor::*;
use crate::backend::*;
use crate::database::*;
use glib::clone;
use gtk::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;
/// A dialog for creating or editing a work.
pub struct WorkEditorDialog {
pub window: libhandy::Window,
saved_cb: RefCell<Option<Box<dyn Fn(Work) -> ()>>>,
}
impl WorkEditorDialog {
/// Create a new work editor dialog and optionally initialize it.
pub fn new<W: IsA<gtk::Window>>(
backend: Rc<Backend>,
parent: &W,
work: Option<Work>,
) -> Rc<Self> {
// Create UI
let window = libhandy::Window::new();
window.set_type_hint(gdk::WindowTypeHint::Dialog);
window.set_modal(true);
window.set_transient_for(Some(parent));
let editor = WorkEditor::new(backend.clone(), &window, work);
window.add(&editor.widget);
window.show_all();
let this = Rc::new(Self {
window,
saved_cb: RefCell::new(None),
});
// Connect signals and callbacks
editor.set_cancel_cb(clone!(@strong this => move || {
this.window.close();
}));
editor.set_saved_cb(clone!(@strong this => move |work| {
if let Some(cb) = &*this.saved_cb.borrow() {
cb(work);
}
this.window.close();
}));
this
}
/// Set the closure to be called when the user edited or created a work.
pub fn set_saved_cb<F: Fn(Work) -> () + 'static>(&self, cb: F) {
self.saved_cb.replace(Some(Box::new(cb)));
}
/// Show the work editor dialog.
pub fn show(&self) {
self.window.show();
}
}

View file

@ -1,174 +0,0 @@
use super::work_selector_person_screen::*;
use crate::backend::Backend;
use crate::database::*;
use crate::widgets::*;
use gettextrs::gettext;
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
use libhandy::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;
/// A widget for selecting a work from a list of existing ones.
pub struct WorkSelector {
pub widget: libhandy::Leaflet,
backend: Rc<Backend>,
sidebar_box: gtk::Box,
server_check_button: gtk::CheckButton,
stack: gtk::Stack,
list: Rc<List<Person>>,
selected_cb: RefCell<Option<Box<dyn Fn(Work) -> ()>>>,
add_cb: RefCell<Option<Box<dyn Fn() -> ()>>>,
navigator: Rc<Navigator>,
}
impl WorkSelector {
/// Create a new work selector.
pub fn new(backend: Rc<Backend>) -> Rc<Self> {
// Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_selector.ui");
get_widget!(builder, libhandy::Leaflet, widget);
get_widget!(builder, gtk::Button, add_button);
get_widget!(builder, gtk::Box, sidebar_box);
get_widget!(builder, gtk::CheckButton, server_check_button);
get_widget!(builder, gtk::SearchEntry, search_entry);
get_widget!(builder, gtk::Stack, stack);
get_widget!(builder, gtk::ScrolledWindow, scroll);
get_widget!(builder, gtk::Button, try_again_button);
get_widget!(builder, gtk::Box, empty_screen);
let list = List::<Person>::new(&gettext("No persons found."));
scroll.add(&list.widget);
let navigator = Navigator::new(&empty_screen);
widget.add(&navigator.widget);
let this = Rc::new(Self {
widget,
backend,
sidebar_box,
server_check_button,
stack,
list,
selected_cb: RefCell::new(None),
add_cb: RefCell::new(None),
navigator,
});
// Connect signals and callbacks
add_button.connect_clicked(clone!(@strong this => move |_| {
if let Some(cb) = &*this.add_cb.borrow() {
cb();
}
}));
search_entry.connect_search_changed(clone!(@strong this => move |_| {
this.list.invalidate_filter();
}));
let load_online = Rc::new(clone!(@strong this => move || {
this.stack.set_visible_child_name("loading");
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
match clone.backend.get_persons().await {
Ok(persons) => {
clone.list.show_items(persons);
clone.stack.set_visible_child_name("content");
}
Err(_) => {
clone.list.show_items(Vec::new());
clone.stack.set_visible_child_name("error");
}
}
});
}));
let load_local = Rc::new(clone!(@strong this => move || {
this.stack.set_visible_child_name("loading");
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
let persons = clone.backend.db().get_persons().await.unwrap();
clone.list.show_items(persons);
clone.stack.set_visible_child_name("content");
});
}));
this.server_check_button.connect_toggled(
clone!(@strong this, @strong load_local, @strong load_online => move |_| {
if this.server_check_button.get_active() {
load_online();
} else {
load_local();
}
}),
);
this.list.set_make_widget(|person: &Person| {
let label = gtk::Label::new(Some(&person.name_lf()));
label.set_halign(gtk::Align::Start);
label.set_margin_start(6);
label.set_margin_end(6);
label.set_margin_top(6);
label.set_margin_bottom(6);
label.upcast()
});
this.list
.set_filter(clone!(@strong search_entry => move |person: &Person| {
let search = search_entry.get_text().to_string().to_lowercase();
let name = person.name_fl().to_lowercase();
search.is_empty() || name.contains(&search)
}));
this.list
.set_selected(clone!(@strong this => move |person| {
let online = this.server_check_button.get_active();
let person_screen = WorkSelectorPersonScreen::new(
this.backend.clone(),
person.clone(),
online,
);
person_screen.set_selected_cb(clone!(@strong this => move |work| {
if let Some(cb) = &*this.selected_cb.borrow() {
cb(work);
}
}));
this.navigator.clone().replace(person_screen);
this.widget.set_visible_child(&this.navigator.widget);
}));
try_again_button.connect_clicked(clone!(@strong load_online => move |_| {
load_online();
}));
this.navigator.set_back_cb(clone!(@strong this => move || {
this.widget.set_visible_child(&this.sidebar_box);
}));
// Initialize
load_online();
this
}
/// Set the closure to be called if the user wants to add a new work.
pub fn set_add_cb<F: Fn() -> () + 'static>(&self, cb: F) {
self.add_cb.replace(Some(Box::new(cb)));
}
/// Set the closure to be called when the user has selected a work.
pub fn set_selected_cb<F: Fn(Work) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
}

View file

@ -1,145 +0,0 @@
use crate::backend::*;
use crate::database::*;
use crate::widgets::*;
use gettextrs::gettext;
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
use libhandy::HeaderBarExt;
use std::cell::RefCell;
use std::rc::Rc;
/// A screen within the work selector that presents a list of works by a person.
pub struct WorkSelectorPersonScreen {
backend: Rc<Backend>,
person: Person,
online: bool,
widget: gtk::Box,
stack: gtk::Stack,
work_list: Rc<List<Work>>,
selected_cb: RefCell<Option<Box<dyn Fn(Work) -> ()>>>,
navigator: RefCell<Option<Rc<Navigator>>>,
}
impl WorkSelectorPersonScreen {
/// Create a new work selector person screen.
pub fn new(backend: Rc<Backend>, person: Person, online: bool) -> Rc<Self> {
// Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_selector_screen.ui");
get_widget!(builder, gtk::Box, widget);
get_widget!(builder, libhandy::HeaderBar, header);
get_widget!(builder, gtk::Button, back_button);
get_widget!(builder, gtk::Stack, stack);
get_widget!(builder, gtk::ScrolledWindow, scroll);
get_widget!(builder, gtk::Button, try_again_button);
header.set_title(Some(&person.name_fl()));
let work_list = List::new(&gettext("No works found."));
scroll.add(&work_list.widget);
let this = Rc::new(Self {
backend,
person,
online,
widget,
stack,
work_list,
selected_cb: RefCell::new(None),
navigator: RefCell::new(None),
});
// Connect signals and callbacks
back_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
}));
let load_online = Rc::new(clone!(@strong this => move || {
this.stack.set_visible_child_name("loading");
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
match clone.backend.get_works(&clone.person.id).await {
Ok(works) => {
clone.work_list.show_items(works);
clone.stack.set_visible_child_name("content");
}
Err(_) => {
clone.work_list.show_items(Vec::new());
clone.stack.set_visible_child_name("error");
}
}
});
}));
let load_local = Rc::new(clone!(@strong this => move || {
this.stack.set_visible_child_name("loading");
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
let works = clone.backend.db().get_works(&clone.person.id).await.unwrap();
clone.work_list.show_items(works);
clone.stack.set_visible_child_name("content");
});
}));
this.work_list.set_make_widget(|work: &Work| {
let label = gtk::Label::new(Some(&work.title));
label.set_ellipsize(pango::EllipsizeMode::End);
label.set_halign(gtk::Align::Start);
label.set_margin_start(6);
label.set_margin_end(6);
label.set_margin_top(6);
label.set_margin_bottom(6);
label.upcast()
});
this.work_list
.set_selected(clone!(@strong this => move |work| {
if let Some(cb) = &*this.selected_cb.borrow() {
cb(work.clone());
}
}));
try_again_button.connect_clicked(clone!(@strong load_online => move |_| {
load_online();
}));
// Initialize
if this.online {
load_online();
} else {
load_local();
}
this
}
/// Sets a closure to be called when the user has selected a work.
pub fn set_selected_cb<F: Fn(Work) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
}
impl NavigatorScreen for WorkSelectorPersonScreen {
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);
}
}

View file

@ -1,5 +1,6 @@
use crate::backend::Backend; use crate::backend::Backend;
use crate::database::*; use crate::database::*;
use crate::widgets::{Navigator, NavigatorScreen};
use anyhow::Result; use anyhow::Result;
use glib::clone; use glib::clone;
use gtk::prelude::*; use gtk::prelude::*;
@ -11,29 +12,24 @@ use std::rc::Rc;
pub struct EnsembleEditor { pub struct EnsembleEditor {
backend: Rc<Backend>, backend: Rc<Backend>,
id: String, id: String,
window: libhandy::Window, widget: gtk::Stack,
stack: gtk::Stack,
info_bar: gtk::InfoBar, info_bar: gtk::InfoBar,
name_entry: gtk::Entry, name_entry: gtk::Entry,
upload_switch: gtk::Switch, upload_switch: gtk::Switch,
saved_cb: RefCell<Option<Box<dyn Fn(Ensemble) -> ()>>>, saved_cb: RefCell<Option<Box<dyn Fn(Ensemble) -> ()>>>,
navigator: RefCell<Option<Rc<Navigator>>>,
} }
impl EnsembleEditor { impl EnsembleEditor {
/// Create a new ensemble editor and optionally initialize it. /// Create a new ensemble editor and optionally initialize it.
pub fn new<P: IsA<gtk::Window>>( pub fn new(backend: Rc<Backend>, ensemble: Option<Ensemble>) -> Rc<Self> {
backend: Rc<Backend>,
parent: &P,
ensemble: Option<Ensemble>,
) -> Rc<Self> {
// Create UI // Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/ensemble_editor.ui"); let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/ensemble_editor.ui");
get_widget!(builder, libhandy::Window, window); get_widget!(builder, gtk::Stack, widget);
get_widget!(builder, gtk::Button, cancel_button); get_widget!(builder, gtk::Button, back_button);
get_widget!(builder, gtk::Button, save_button); get_widget!(builder, gtk::Button, save_button);
get_widget!(builder, gtk::Stack, stack);
get_widget!(builder, gtk::InfoBar, info_bar); get_widget!(builder, gtk::InfoBar, info_bar);
get_widget!(builder, gtk::Entry, name_entry); get_widget!(builder, gtk::Entry, name_entry);
get_widget!(builder, gtk::Switch, upload_switch); get_widget!(builder, gtk::Switch, upload_switch);
@ -50,40 +46,44 @@ impl EnsembleEditor {
let this = Rc::new(Self { let this = Rc::new(Self {
backend, backend,
id, id,
window, widget,
stack,
info_bar, info_bar,
name_entry, name_entry,
upload_switch, upload_switch,
saved_cb: RefCell::new(None), saved_cb: RefCell::new(None),
navigator: RefCell::new(None),
}); });
// Connect signals and callbacks // Connect signals and callbacks
cancel_button.connect_clicked(clone!(@strong this => move |_| { back_button.connect_clicked(clone!(@strong this => move |_| {
this.window.close(); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
})); }));
save_button.connect_clicked(clone!(@strong this => move |_| { save_button.connect_clicked(clone!(@strong this => move |_| {
let context = glib::MainContext::default(); let context = glib::MainContext::default();
let clone = this.clone(); let clone = this.clone();
context.spawn_local(async move { context.spawn_local(async move {
clone.stack.set_visible_child_name("loading"); clone.widget.set_visible_child_name("loading");
match clone.clone().save().await { match clone.clone().save().await {
Ok(_) => { Ok(_) => {
clone.window.close(); let navigator = clone.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
} }
Err(_) => { Err(_) => {
clone.info_bar.set_revealed(true); clone.info_bar.set_revealed(true);
clone.stack.set_visible_child_name("content"); clone.widget.set_visible_child_name("content");
} }
} }
}); });
})); }));
this.window.set_transient_for(Some(parent));
this this
} }
@ -92,11 +92,6 @@ impl EnsembleEditor {
self.saved_cb.replace(Some(Box::new(cb))); self.saved_cb.replace(Some(Box::new(cb)));
} }
/// Show the ensemble editor.
pub fn show(&self) {
self.window.show();
}
/// Save the ensemble and possibly upload it to the server. /// Save the ensemble and possibly upload it to the server.
async fn save(self: Rc<Self>) -> Result<()> { async fn save(self: Rc<Self>) -> Result<()> {
let name = self.name_entry.get_text().to_string(); let name = self.name_entry.get_text().to_string();
@ -111,7 +106,10 @@ impl EnsembleEditor {
self.backend.post_ensemble(&ensemble).await?; self.backend.post_ensemble(&ensemble).await?;
} }
self.backend.db().update_ensemble(ensemble.clone()).await?; self.backend
.db()
.update_ensemble(ensemble.clone())
.await?;
self.backend.library_changed(); self.backend.library_changed();
if let Some(cb) = &*self.saved_cb.borrow() { if let Some(cb) = &*self.saved_cb.borrow() {
@ -121,3 +119,17 @@ impl EnsembleEditor {
Ok(()) Ok(())
} }
} }
impl NavigatorScreen for EnsembleEditor {
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);
}
}

View file

@ -1,5 +1,6 @@
use crate::backend::Backend; use crate::backend::Backend;
use crate::database::*; use crate::database::*;
use crate::widgets::{Navigator, NavigatorScreen};
use anyhow::Result; use anyhow::Result;
use glib::clone; use glib::clone;
use gtk::prelude::*; use gtk::prelude::*;
@ -11,29 +12,24 @@ use std::rc::Rc;
pub struct InstrumentEditor { pub struct InstrumentEditor {
backend: Rc<Backend>, backend: Rc<Backend>,
id: String, id: String,
window: libhandy::Window, widget: gtk::Stack,
stack: gtk::Stack,
info_bar: gtk::InfoBar, info_bar: gtk::InfoBar,
name_entry: gtk::Entry, name_entry: gtk::Entry,
upload_switch: gtk::Switch, upload_switch: gtk::Switch,
saved_cb: RefCell<Option<Box<dyn Fn(Instrument) -> ()>>>, saved_cb: RefCell<Option<Box<dyn Fn(Instrument) -> ()>>>,
navigator: RefCell<Option<Rc<Navigator>>>,
} }
impl InstrumentEditor { impl InstrumentEditor {
/// Create a new instrument editor and optionally initialize it. /// Create a new instrument editor and optionally initialize it.
pub fn new<P: IsA<gtk::Window>>( pub fn new(backend: Rc<Backend>, instrument: Option<Instrument>) -> Rc<Self> {
backend: Rc<Backend>,
parent: &P,
instrument: Option<Instrument>,
) -> Rc<Self> {
// Create UI // Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/instrument_editor.ui"); let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/instrument_editor.ui");
get_widget!(builder, libhandy::Window, window); get_widget!(builder, gtk::Stack, widget);
get_widget!(builder, gtk::Button, cancel_button); get_widget!(builder, gtk::Button, back_button);
get_widget!(builder, gtk::Button, save_button); get_widget!(builder, gtk::Button, save_button);
get_widget!(builder, gtk::Stack, stack);
get_widget!(builder, gtk::InfoBar, info_bar); get_widget!(builder, gtk::InfoBar, info_bar);
get_widget!(builder, gtk::Entry, name_entry); get_widget!(builder, gtk::Entry, name_entry);
get_widget!(builder, gtk::Switch, upload_switch); get_widget!(builder, gtk::Switch, upload_switch);
@ -50,40 +46,44 @@ impl InstrumentEditor {
let this = Rc::new(Self { let this = Rc::new(Self {
backend, backend,
id, id,
window, widget,
stack,
info_bar, info_bar,
name_entry, name_entry,
upload_switch, upload_switch,
saved_cb: RefCell::new(None), saved_cb: RefCell::new(None),
navigator: RefCell::new(None),
}); });
// Connect signals and callbacks // Connect signals and callbacks
cancel_button.connect_clicked(clone!(@strong this => move |_| { back_button.connect_clicked(clone!(@strong this => move |_| {
this.window.close(); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
})); }));
save_button.connect_clicked(clone!(@strong this => move |_| { save_button.connect_clicked(clone!(@strong this => move |_| {
let context = glib::MainContext::default(); let context = glib::MainContext::default();
let clone = this.clone(); let clone = this.clone();
context.spawn_local(async move { context.spawn_local(async move {
clone.stack.set_visible_child_name("loading"); clone.widget.set_visible_child_name("loading");
match clone.clone().save().await { match clone.clone().save().await {
Ok(_) => { Ok(_) => {
clone.window.close(); let navigator = clone.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
} }
Err(_) => { Err(_) => {
clone.info_bar.set_revealed(true); clone.info_bar.set_revealed(true);
clone.stack.set_visible_child_name("content"); clone.widget.set_visible_child_name("content");
} }
} }
}); });
})); }));
this.window.set_transient_for(Some(parent));
this this
} }
@ -92,11 +92,6 @@ impl InstrumentEditor {
self.saved_cb.replace(Some(Box::new(cb))); self.saved_cb.replace(Some(Box::new(cb)));
} }
/// Show the instrument editor.
pub fn show(&self) {
self.window.show();
}
/// Save the instrument and possibly upload it to the server. /// Save the instrument and possibly upload it to the server.
async fn save(self: Rc<Self>) -> Result<()> { async fn save(self: Rc<Self>) -> Result<()> {
let name = self.name_entry.get_text().to_string(); let name = self.name_entry.get_text().to_string();
@ -111,7 +106,10 @@ impl InstrumentEditor {
self.backend.post_instrument(&instrument).await?; self.backend.post_instrument(&instrument).await?;
} }
self.backend.db().update_instrument(instrument.clone()).await?; self.backend
.db()
.update_instrument(instrument.clone())
.await?;
self.backend.library_changed(); self.backend.library_changed();
if let Some(cb) = &*self.saved_cb.borrow() { if let Some(cb) = &*self.saved_cb.borrow() {
@ -121,3 +119,17 @@ impl InstrumentEditor {
Ok(()) Ok(())
} }
} }
impl NavigatorScreen for InstrumentEditor {
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);
}
}

View file

@ -0,0 +1,22 @@
pub mod ensemble;
pub use ensemble::*;
pub mod instrument;
pub use instrument::*;
pub mod person;
pub use person::*;
pub mod recording;
pub use recording::*;
pub mod tracks;
pub use tracks::*;
pub mod work;
pub use work::*;
mod performance;
mod track;
mod work_part;
mod work_section;

View file

@ -1,6 +1,7 @@
use crate::backend::*; use crate::backend::Backend;
use crate::database::*; use crate::database::*;
use crate::dialogs::*; use crate::selectors::{EnsembleSelector, InstrumentSelector, PersonSelector};
use crate::widgets::{Navigator, NavigatorScreen};
use gettextrs::gettext; use gettextrs::gettext;
use glib::clone; use glib::clone;
use gtk::prelude::*; use gtk::prelude::*;
@ -11,7 +12,7 @@ use std::rc::Rc;
/// A dialog for editing a performance within a recording. /// A dialog for editing a performance within a recording.
pub struct PerformanceEditor { pub struct PerformanceEditor {
backend: Rc<Backend>, backend: Rc<Backend>,
window: libhandy::Window, widget: gtk::Box,
save_button: gtk::Button, save_button: gtk::Button,
person_label: gtk::Label, person_label: gtk::Label,
ensemble_label: gtk::Label, ensemble_label: gtk::Label,
@ -21,21 +22,18 @@ pub struct PerformanceEditor {
ensemble: RefCell<Option<Ensemble>>, ensemble: RefCell<Option<Ensemble>>,
role: RefCell<Option<Instrument>>, role: RefCell<Option<Instrument>>,
selected_cb: RefCell<Option<Box<dyn Fn(Performance) -> ()>>>, selected_cb: RefCell<Option<Box<dyn Fn(Performance) -> ()>>>,
navigator: RefCell<Option<Rc<Navigator>>>,
} }
impl PerformanceEditor { impl PerformanceEditor {
/// Create a new performance editor. /// Create a new performance editor.
pub fn new<P: IsA<gtk::Window>>( pub fn new(backend: Rc<Backend>, performance: Option<Performance>) -> Rc<Self> {
backend: Rc<Backend>,
parent: &P,
performance: Option<Performance>,
) -> Rc<Self> {
// Create UI // Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/performance_editor.ui"); let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/performance_editor.ui");
get_widget!(builder, libhandy::Window, window); get_widget!(builder, gtk::Box, widget);
get_widget!(builder, gtk::Button, cancel_button); get_widget!(builder, gtk::Button, back_button);
get_widget!(builder, gtk::Button, save_button); get_widget!(builder, gtk::Button, save_button);
get_widget!(builder, gtk::Button, person_button); get_widget!(builder, gtk::Button, person_button);
get_widget!(builder, gtk::Button, ensemble_button); get_widget!(builder, gtk::Button, ensemble_button);
@ -45,11 +43,9 @@ impl PerformanceEditor {
get_widget!(builder, gtk::Label, ensemble_label); get_widget!(builder, gtk::Label, ensemble_label);
get_widget!(builder, gtk::Label, role_label); get_widget!(builder, gtk::Label, role_label);
window.set_transient_for(Some(parent));
let this = Rc::new(PerformanceEditor { let this = Rc::new(PerformanceEditor {
backend, backend,
window, widget,
save_button, save_button,
person_label, person_label,
ensemble_label, ensemble_label,
@ -59,12 +55,16 @@ impl PerformanceEditor {
ensemble: RefCell::new(None), ensemble: RefCell::new(None),
role: RefCell::new(None), role: RefCell::new(None),
selected_cb: RefCell::new(None), selected_cb: RefCell::new(None),
navigator: RefCell::new(None),
}); });
// Connect signals and callbacks // Connect signals and callbacks
cancel_button.connect_clicked(clone!(@strong this => move |_| { back_button.connect_clicked(clone!(@strong this => move |_| {
this.window.close(); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
})); }));
this.save_button this.save_button
@ -75,46 +75,61 @@ impl PerformanceEditor {
ensemble: this.ensemble.borrow().clone(), ensemble: this.ensemble.borrow().clone(),
role: this.role.borrow().clone(), role: this.role.borrow().clone(),
}); });
}
this.window.close(); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
} }
})); }));
person_button.connect_clicked(clone!(@strong this => move |_| { person_button.connect_clicked(clone!(@strong this => move |_| {
let dialog = PersonSelector::new(this.backend.clone(), &this.window); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let selector = PersonSelector::new(this.backend.clone());
dialog.set_selected_cb(clone!(@strong this => move |person| { selector.set_selected_cb(clone!(@strong this, @strong navigator => move |person| {
this.show_person(Some(&person)); this.show_person(Some(&person));
this.person.replace(Some(person)); this.person.replace(Some(person.clone()));
this.show_ensemble(None); this.show_ensemble(None);
this.ensemble.replace(None); this.ensemble.replace(None);
})); navigator.clone().pop();
}));
dialog.show(); navigator.push(selector);
}
})); }));
ensemble_button.connect_clicked(clone!(@strong this => move |_| { ensemble_button.connect_clicked(clone!(@strong this => move |_| {
let dialog = EnsembleSelector::new(this.backend.clone(), &this.window); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let selector = EnsembleSelector::new(this.backend.clone());
dialog.set_selected_cb(clone!(@strong this => move |ensemble| { selector.set_selected_cb(clone!(@strong this, @strong navigator => move |ensemble| {
this.show_person(None); this.show_person(None);
this.person.replace(None); this.person.replace(None);
this.show_ensemble(Some(&ensemble)); this.show_ensemble(Some(&ensemble));
this.ensemble.replace(Some(ensemble)); this.ensemble.replace(Some(ensemble.clone()));
})); navigator.clone().pop();
}));
dialog.show(); navigator.push(selector);
}
})); }));
role_button.connect_clicked(clone!(@strong this => move |_| { role_button.connect_clicked(clone!(@strong this => move |_| {
let dialog = InstrumentSelector::new(this.backend.clone(), &this.window); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let selector = InstrumentSelector::new(this.backend.clone());
dialog.set_selected_cb(clone!(@strong this => move |role| { selector.set_selected_cb(clone!(@strong this, @strong navigator => move |role| {
this.show_role(Some(&role)); this.show_role(Some(&role));
this.role.replace(Some(role)); this.role.replace(Some(role.clone()));
})); navigator.clone().pop();
}));
dialog.show(); navigator.push(selector);
}
})); }));
this.reset_role_button this.reset_role_button
@ -148,11 +163,6 @@ impl PerformanceEditor {
self.selected_cb.replace(Some(Box::new(cb))); self.selected_cb.replace(Some(Box::new(cb)));
} }
/// Show the performance editor.
pub fn show(&self) {
self.window.show();
}
/// Update the UI according to person. /// Update the UI according to person.
fn show_person(&self, person: Option<&Person>) { fn show_person(&self, person: Option<&Person>) {
if let Some(person) = person { if let Some(person) = person {
@ -184,3 +194,17 @@ impl PerformanceEditor {
} }
} }
} }
impl NavigatorScreen for PerformanceEditor {
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);
}
}

View file

@ -1,5 +1,6 @@
use crate::backend::Backend; use crate::backend::Backend;
use crate::database::*; use crate::database::*;
use crate::widgets::{Navigator, NavigatorScreen};
use anyhow::Result; use anyhow::Result;
use glib::clone; use glib::clone;
use gtk::prelude::*; use gtk::prelude::*;
@ -11,30 +12,25 @@ use std::rc::Rc;
pub struct PersonEditor { pub struct PersonEditor {
backend: Rc<Backend>, backend: Rc<Backend>,
id: String, id: String,
window: libhandy::Window, widget: gtk::Stack,
stack: gtk::Stack,
info_bar: gtk::InfoBar, info_bar: gtk::InfoBar,
first_name_entry: gtk::Entry, first_name_entry: gtk::Entry,
last_name_entry: gtk::Entry, last_name_entry: gtk::Entry,
upload_switch: gtk::Switch, upload_switch: gtk::Switch,
saved_cb: RefCell<Option<Box<dyn Fn(Person) -> ()>>>, saved_cb: RefCell<Option<Box<dyn Fn(Person) -> ()>>>,
navigator: RefCell<Option<Rc<Navigator>>>,
} }
impl PersonEditor { impl PersonEditor {
/// Create a new person editor and optionally initialize it. /// Create a new person editor and optionally initialize it.
pub fn new<P: IsA<gtk::Window>>( pub fn new(backend: Rc<Backend>, person: Option<Person>) -> Rc<Self> {
backend: Rc<Backend>,
parent: &P,
person: Option<Person>,
) -> Rc<Self> {
// Create UI // Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/person_editor.ui"); let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/person_editor.ui");
get_widget!(builder, libhandy::Window, window); get_widget!(builder, gtk::Stack, widget);
get_widget!(builder, gtk::Button, cancel_button); get_widget!(builder, gtk::Button, back_button);
get_widget!(builder, gtk::Button, save_button); get_widget!(builder, gtk::Button, save_button);
get_widget!(builder, gtk::Stack, stack);
get_widget!(builder, gtk::InfoBar, info_bar); get_widget!(builder, gtk::InfoBar, info_bar);
get_widget!(builder, gtk::Entry, first_name_entry); get_widget!(builder, gtk::Entry, first_name_entry);
get_widget!(builder, gtk::Entry, last_name_entry); get_widget!(builder, gtk::Entry, last_name_entry);
@ -53,41 +49,45 @@ impl PersonEditor {
let this = Rc::new(Self { let this = Rc::new(Self {
backend, backend,
id, id,
window, widget,
stack,
info_bar, info_bar,
first_name_entry, first_name_entry,
last_name_entry, last_name_entry,
upload_switch, upload_switch,
saved_cb: RefCell::new(None), saved_cb: RefCell::new(None),
navigator: RefCell::new(None),
}); });
// Connect signals and callbacks // Connect signals and callbacks
cancel_button.connect_clicked(clone!(@strong this => move |_| { back_button.connect_clicked(clone!(@strong this => move |_| {
this.window.close(); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
})); }));
save_button.connect_clicked(clone!(@strong this => move |_| { save_button.connect_clicked(clone!(@strong this => move |_| {
let context = glib::MainContext::default(); let context = glib::MainContext::default();
let clone = this.clone(); let clone = this.clone();
context.spawn_local(async move { context.spawn_local(async move {
clone.stack.set_visible_child_name("loading"); clone.widget.set_visible_child_name("loading");
match clone.clone().save().await { match clone.clone().save().await {
Ok(_) => { Ok(_) => {
clone.window.close(); let navigator = clone.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
} }
Err(_) => { Err(_) => {
clone.info_bar.set_revealed(true); clone.info_bar.set_revealed(true);
clone.stack.set_visible_child_name("content"); clone.widget.set_visible_child_name("content");
} }
} }
}); });
})); }));
this.window.set_transient_for(Some(parent));
this this
} }
@ -96,11 +96,6 @@ impl PersonEditor {
self.saved_cb.replace(Some(Box::new(cb))); self.saved_cb.replace(Some(Box::new(cb)));
} }
/// Show the person editor.
pub fn show(&self) {
self.window.show();
}
/// Save the person and possibly upload it to the server. /// Save the person and possibly upload it to the server.
async fn save(self: Rc<Self>) -> Result<()> { async fn save(self: Rc<Self>) -> Result<()> {
let first_name = self.first_name_entry.get_text().to_string(); let first_name = self.first_name_entry.get_text().to_string();
@ -127,3 +122,17 @@ impl PersonEditor {
Ok(()) Ok(())
} }
} }
impl NavigatorScreen for PersonEditor {
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);
}
}

View file

@ -1,8 +1,8 @@
use super::performance_editor::*; use super::performance::PerformanceEditor;
use crate::backend::*; use crate::backend::Backend;
use crate::database::*; use crate::database::*;
use crate::dialogs::*; use crate::selectors::{PersonSelector, WorkSelector};
use crate::widgets::*; use crate::widgets::{List, Navigator, NavigatorScreen};
use anyhow::Result; use anyhow::Result;
use gettextrs::gettext; use gettextrs::gettext;
use glib::clone; use glib::clone;
@ -16,7 +16,6 @@ use std::rc::Rc;
pub struct RecordingEditor { pub struct RecordingEditor {
pub widget: gtk::Stack, pub widget: gtk::Stack,
backend: Rc<Backend>, backend: Rc<Backend>,
parent: gtk::Window,
save_button: gtk::Button, save_button: gtk::Button,
info_bar: gtk::InfoBar, info_bar: gtk::InfoBar,
work_label: gtk::Label, work_label: gtk::Label,
@ -28,16 +27,12 @@ pub struct RecordingEditor {
performances: RefCell<Vec<Performance>>, performances: RefCell<Vec<Performance>>,
selected_cb: RefCell<Option<Box<dyn Fn(Recording) -> ()>>>, selected_cb: RefCell<Option<Box<dyn Fn(Recording) -> ()>>>,
back_cb: RefCell<Option<Box<dyn Fn() -> ()>>>, back_cb: RefCell<Option<Box<dyn Fn() -> ()>>>,
navigator: RefCell<Option<Rc<Navigator>>>,
} }
impl RecordingEditor { impl RecordingEditor {
/// Create a new recording editor widget and optionally initialize it. The parent window is /// Create a new recording editor widget and optionally initialize it.
/// used as the parent for newly created dialogs. pub fn new(backend: Rc<Backend>, recording: Option<Recording>) -> Rc<Self> {
pub fn new<W: IsA<gtk::Window>>(
backend: Rc<Backend>,
parent: &W,
recording: Option<Recording>,
) -> Rc<Self> {
// Create UI // Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_editor.ui"); let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_editor.ui");
@ -69,7 +64,6 @@ impl RecordingEditor {
let this = Rc::new(RecordingEditor { let this = Rc::new(RecordingEditor {
widget, widget,
backend, backend,
parent: parent.clone().upcast(),
save_button, save_button,
info_bar, info_bar,
work_label, work_label,
@ -81,6 +75,7 @@ impl RecordingEditor {
performances: RefCell::new(performances), performances: RefCell::new(performances),
selected_cb: RefCell::new(None), selected_cb: RefCell::new(None),
back_cb: RefCell::new(None), back_cb: RefCell::new(None),
navigator: RefCell::new(None),
}); });
// Connect signals and callbacks // Connect signals and callbacks
@ -89,6 +84,11 @@ impl RecordingEditor {
if let Some(cb) = &*this.back_cb.borrow() { if let Some(cb) = &*this.back_cb.borrow() {
cb(); cb();
} }
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.clone().pop();
}
})); }));
this.save_button this.save_button
@ -99,7 +99,10 @@ impl RecordingEditor {
clone.widget.set_visible_child_name("loading"); clone.widget.set_visible_child_name("loading");
match clone.clone().save().await { match clone.clone().save().await {
Ok(_) => { Ok(_) => {
// We already called the callback. let navigator = clone.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.clone().pop();
}
} }
Err(_) => { Err(_) => {
clone.info_bar.set_revealed(true); clone.info_bar.set_revealed(true);
@ -111,14 +114,26 @@ impl RecordingEditor {
})); }));
work_button.connect_clicked(clone!(@strong this => move |_| { work_button.connect_clicked(clone!(@strong this => move |_| {
let dialog = WorkDialog::new(this.backend.clone(), &this.parent); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let person_selector = PersonSelector::new(this.backend.clone());
dialog.set_selected_cb(clone!(@strong this => move |work| { person_selector.set_selected_cb(clone!(@strong this, @strong navigator => move |person| {
this.work_selected(&work); let work_selector = WorkSelector::new(this.backend.clone(), person.clone());
this.work.replace(Some(work));
}));
dialog.show(); work_selector.set_selected_cb(clone!(@strong this, @strong navigator => move |work| {
this.work_selected(&work);
this.work.replace(Some(work.clone()));
navigator.clone().pop();
navigator.clone().pop();
}));
navigator.clone().push(work_selector);
}));
navigator.push(person_selector);
}
})); }));
this.performance_list.set_make_widget(|performance| { this.performance_list.set_make_widget(|performance| {
@ -133,42 +148,50 @@ impl RecordingEditor {
}); });
add_performer_button.connect_clicked(clone!(@strong this => move |_| { add_performer_button.connect_clicked(clone!(@strong this => move |_| {
let editor = PerformanceEditor::new(this.backend.clone(), &this.parent, None); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let editor = PerformanceEditor::new(this.backend.clone(), None);
editor.set_selected_cb(clone!(@strong this => move |performance| { editor.set_selected_cb(clone!(@strong this, @strong navigator => move |performance| {
let mut performances = this.performances.borrow_mut(); let mut performances = this.performances.borrow_mut();
let index = match this.performance_list.get_selected_index() { let index = match this.performance_list.get_selected_index() {
Some(index) => index + 1, Some(index) => index + 1,
None => performances.len(), None => performances.len(),
}; };
performances.insert(index, performance); performances.insert(index, performance);
this.performance_list.show_items(performances.clone()); this.performance_list.show_items(performances.clone());
this.performance_list.select_index(index); this.performance_list.select_index(index);
}));
editor.show(); navigator.clone().pop();
}));
navigator.push(editor);
}
})); }));
edit_performer_button.connect_clicked(clone!(@strong this => move |_| { edit_performer_button.connect_clicked(clone!(@strong this => move |_| {
if let Some(index) = this.performance_list.get_selected_index() { let navigator = this.navigator.borrow().clone();
let performance = &this.performances.borrow()[index]; if let Some(navigator) = navigator {
if let Some(index) = this.performance_list.get_selected_index() {
let performance = &this.performances.borrow()[index];
let editor = PerformanceEditor::new( let editor = PerformanceEditor::new(
this.backend.clone(), this.backend.clone(),
&this.parent, Some(performance.clone()),
Some(performance.clone()), );
);
editor.set_selected_cb(clone!(@strong this => move |performance| { editor.set_selected_cb(clone!(@strong this, @strong navigator => move |performance| {
let mut performances = this.performances.borrow_mut(); let mut performances = this.performances.borrow_mut();
performances[index] = performance; performances[index] = performance;
this.performance_list.show_items(performances.clone()); this.performance_list.show_items(performances.clone());
this.performance_list.select_index(index); this.performance_list.select_index(index);
})); navigator.clone().pop();
}));
editor.show(); navigator.push(editor);
}
} }
})); }));
@ -240,6 +263,25 @@ impl RecordingEditor {
cb(recording.clone()); cb(recording.clone());
} }
let navigator = self.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.clone().pop();
}
Ok(()) Ok(())
} }
} }
impl NavigatorScreen for RecordingEditor {
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);
}
}

View file

@ -1,4 +1,5 @@
use crate::database::*; use crate::database::*;
use crate::widgets::{Navigator, NavigatorScreen};
use glib::clone; use glib::clone;
use gtk::prelude::*; use gtk::prelude::*;
use gtk_macros::get_widget; use gtk_macros::get_widget;
@ -6,43 +7,58 @@ use std::cell::RefCell;
use std::convert::TryInto; use std::convert::TryInto;
use std::rc::Rc; use std::rc::Rc;
/// A screen for editing a single track.
// TODO: Refactor.
pub struct TrackEditor { pub struct TrackEditor {
window: libhandy::Window, widget: gtk::Box,
ready_cb: RefCell<Option<Box<dyn Fn(Track) -> ()>>>,
navigator: RefCell<Option<Rc<Navigator>>>,
} }
impl TrackEditor { impl TrackEditor {
pub fn new<W, F>(parent: &W, track: Track, work: Work, callback: F) -> Self /// Create a new track editor.
where pub fn new(track: Track, work: Work) -> Rc<Self> {
W: IsA<gtk::Window>, // Create UI
F: Fn(Track) -> () + 'static,
{
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/track_editor.ui"); let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/track_editor.ui");
get_widget!(builder, libhandy::Window, window); get_widget!(builder, gtk::Box, widget);
get_widget!(builder, gtk::Button, cancel_button); get_widget!(builder, gtk::Button, back_button);
get_widget!(builder, gtk::Button, save_button); get_widget!(builder, gtk::Button, save_button);
get_widget!(builder, gtk::ListBox, list); get_widget!(builder, gtk::ListBox, list);
window.set_transient_for(Some(parent)); let this = Rc::new(Self {
widget,
ready_cb: RefCell::new(None),
navigator: RefCell::new(None),
});
cancel_button.connect_clicked(clone!(@strong window => move |_| { back_button.connect_clicked(clone!(@strong this => move |_| {
window.close(); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
})); }));
let work = Rc::new(work); let work = Rc::new(work);
let work_parts = Rc::new(RefCell::new(track.work_parts)); let work_parts = Rc::new(RefCell::new(track.work_parts));
let file_name = track.file_name; let file_name = track.file_name;
save_button.connect_clicked(clone!(@strong work_parts, @strong window => move |_| { save_button.connect_clicked(clone!(@strong this, @strong work_parts => move |_| {
let mut work_parts = work_parts.borrow_mut(); let mut work_parts = work_parts.borrow_mut();
work_parts.sort(); work_parts.sort();
callback(Track { if let Some(cb) = &*this.ready_cb.borrow() {
work_parts: work_parts.clone(), cb(Track {
file_name: file_name.clone(), work_parts: work_parts.clone(),
}); file_name: file_name.clone(),
});
}
window.close(); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
})); }));
for (index, part) in work.parts.iter().enumerate() { for (index, part) in work.parts.iter().enumerate() {
@ -108,10 +124,25 @@ impl TrackEditor {
section_count += 1; section_count += 1;
} }
Self { window } this
} }
pub fn show(&self) { /// Set the closure to be called when the track was edited.
self.window.show(); pub fn set_ready_cb<F: Fn(Track) -> () + 'static>(&self, cb: F) {
self.ready_cb.replace(Some(Box::new(cb)));
}
}
impl NavigatorScreen for TrackEditor {
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);
} }
} }

View file

@ -1,7 +1,8 @@
use super::*; use super::track::TrackEditor;
use crate::backend::*; use crate::backend::Backend;
use crate::database::*; use crate::database::*;
use crate::widgets::*; use crate::widgets::{List, Navigator, NavigatorScreen};
use crate::selectors::{PersonSelector, WorkSelector, RecordingSelector};
use gettextrs::gettext; use gettextrs::gettext;
use glib::clone; use glib::clone;
use gtk::prelude::*; use gtk::prelude::*;
@ -13,7 +14,7 @@ use std::rc::Rc;
// TODO: Disable buttons if no track is selected. // TODO: Disable buttons if no track is selected.
pub struct TracksEditor { pub struct TracksEditor {
backend: Rc<Backend>, backend: Rc<Backend>,
window: libhandy::Window, widget: gtk::Box,
save_button: gtk::Button, save_button: gtk::Button,
recording_stack: gtk::Stack, recording_stack: gtk::Stack,
work_label: gtk::Label, work_label: gtk::Label,
@ -22,14 +23,14 @@ pub struct TracksEditor {
recording: RefCell<Option<Recording>>, recording: RefCell<Option<Recording>>,
tracks: RefCell<Vec<Track>>, tracks: RefCell<Vec<Track>>,
callback: RefCell<Option<Box<dyn Fn() -> ()>>>, callback: RefCell<Option<Box<dyn Fn() -> ()>>>,
navigator: RefCell<Option<Rc<Navigator>>>,
} }
impl TracksEditor { impl TracksEditor {
/// Create a new track editor an optionally initialize it with a recording and a list of /// Create a new track editor an optionally initialize it with a recording and a list of
/// tracks. /// tracks.
pub fn new<P: IsA<gtk::Window>>( pub fn new(
backend: Rc<Backend>, backend: Rc<Backend>,
parent: &P,
recording: Option<Recording>, recording: Option<Recording>,
tracks: Vec<Track>, tracks: Vec<Track>,
) -> Rc<Self> { ) -> Rc<Self> {
@ -37,8 +38,8 @@ impl TracksEditor {
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/tracks_editor.ui"); let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/tracks_editor.ui");
get_widget!(builder, libhandy::Window, window); get_widget!(builder, gtk::Box, widget);
get_widget!(builder, gtk::Button, cancel_button); get_widget!(builder, gtk::Button, back_button);
get_widget!(builder, gtk::Button, save_button); get_widget!(builder, gtk::Button, save_button);
get_widget!(builder, gtk::Button, recording_button); get_widget!(builder, gtk::Button, recording_button);
get_widget!(builder, gtk::Stack, recording_stack); get_widget!(builder, gtk::Stack, recording_stack);
@ -51,18 +52,12 @@ impl TracksEditor {
get_widget!(builder, gtk::Button, move_track_up_button); get_widget!(builder, gtk::Button, move_track_up_button);
get_widget!(builder, gtk::Button, move_track_down_button); get_widget!(builder, gtk::Button, move_track_down_button);
window.set_transient_for(Some(parent));
cancel_button.connect_clicked(clone!(@strong window => move |_| {
window.close();
}));
let track_list = List::new(&gettext("Add some tracks.")); let track_list = List::new(&gettext("Add some tracks."));
scroll.add(&track_list.widget); scroll.add(&track_list.widget);
let this = Rc::new(Self { let this = Rc::new(Self {
backend, backend,
window, widget,
save_button, save_button,
recording_stack, recording_stack,
work_label, work_label,
@ -71,10 +66,18 @@ impl TracksEditor {
recording: RefCell::new(recording), recording: RefCell::new(recording),
tracks: RefCell::new(tracks), tracks: RefCell::new(tracks),
callback: RefCell::new(None), callback: RefCell::new(None),
navigator: RefCell::new(None),
}); });
// Signals and callbacks // Signals and callbacks
back_button.connect_clicked(clone!(@strong this => move |_| {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
}));
this.save_button this.save_button
.connect_clicked(clone!(@strong this => move |_| { .connect_clicked(clone!(@strong this => move |_| {
let context = glib::MainContext::default(); let context = glib::MainContext::default();
@ -99,22 +102,43 @@ impl TracksEditor {
callback(); callback();
} }
this.window.close(); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
}); });
})); }));
recording_button.connect_clicked(clone!(@strong this => move |_| { recording_button.connect_clicked(clone!(@strong this => move |_| {
let dialog = RecordingDialog::new(this.backend.clone(), &this.window); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let person_selector = PersonSelector::new(this.backend.clone());
dialog.set_selected_cb(clone!(@strong this => move |recording| { person_selector.set_selected_cb(clone!(@strong this, @strong navigator => move |person| {
this.recording_selected(&recording); let work_selector = WorkSelector::new(this.backend.clone(), person.clone());
this.recording.replace(Some(recording));
}));
dialog.show(); work_selector.set_selected_cb(clone!(@strong this, @strong navigator => move |work| {
let recording_selector = RecordingSelector::new(this.backend.clone(), work.clone());
recording_selector.set_selected_cb(clone!(@strong this, @strong navigator => move |recording| {
this.recording_selected(recording);
this.recording.replace(Some(recording.clone()));
navigator.clone().pop();
navigator.clone().pop();
navigator.clone().pop();
}));
navigator.clone().push(recording_selector);
}));
navigator.clone().push(work_selector);
}));
navigator.clone().push(person_selector);
} }
)); }));
this.track_list this.track_list
.set_make_widget(clone!(@strong this => move |track| { .set_make_widget(clone!(@strong this => move |track| {
@ -122,40 +146,43 @@ impl TracksEditor {
})); }));
add_track_button.connect_clicked(clone!(@strong this => move |_| { add_track_button.connect_clicked(clone!(@strong this => move |_| {
let music_library_path = this.backend.get_music_library_path().unwrap(); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let music_library_path = this.backend.get_music_library_path().unwrap();
let dialog = gtk::FileChooserNative::new( let dialog = gtk::FileChooserNative::new(
Some(&gettext("Select audio files")), Some(&gettext("Select audio files")),
Some(&this.window), Some(&navigator.window),
gtk::FileChooserAction::Open, gtk::FileChooserAction::Open,
None, None,
None, None,
); );
dialog.set_select_multiple(true); dialog.set_select_multiple(true);
dialog.set_current_folder(&music_library_path); dialog.set_current_folder(&music_library_path);
if let gtk::ResponseType::Accept = dialog.run() { if let gtk::ResponseType::Accept = dialog.run() {
let mut index = match this.track_list.get_selected_index() { let mut index = match this.track_list.get_selected_index() {
Some(index) => index + 1, Some(index) => index + 1,
None => this.tracks.borrow().len(), None => this.tracks.borrow().len(),
}; };
{ {
let mut tracks = this.tracks.borrow_mut(); let mut tracks = this.tracks.borrow_mut();
for file_name in dialog.get_filenames() { for file_name in dialog.get_filenames() {
let file_name = file_name.strip_prefix(&music_library_path).unwrap(); let file_name = file_name.strip_prefix(&music_library_path).unwrap();
tracks.insert(index, Track { tracks.insert(index, Track {
work_parts: Vec::new(), work_parts: Vec::new(),
file_name: String::from(file_name.to_str().unwrap()), file_name: String::from(file_name.to_str().unwrap()),
}); });
index += 1; index += 1;
}
} }
}
this.track_list.show_items(this.tracks.borrow().clone()); this.track_list.show_items(this.tracks.borrow().clone());
this.autofill_parts(); this.autofill_parts();
this.track_list.select_index(index); this.track_list.select_index(index);
}
} }
})); }));
@ -200,14 +227,21 @@ impl TracksEditor {
})); }));
edit_track_button.connect_clicked(clone!(@strong this => move |_| { edit_track_button.connect_clicked(clone!(@strong this => move |_| {
if let Some(index) = this.track_list.get_selected_index() { let navigator = this.navigator.borrow().clone();
if let Some(recording) = &*this.recording.borrow() { if let Some(navigator) = navigator {
TrackEditor::new(&this.window, this.tracks.borrow()[index].clone(), recording.work.clone(), clone!(@strong this => move |track| { if let Some(index) = this.track_list.get_selected_index() {
let mut tracks = this.tracks.borrow_mut(); if let Some(recording) = &*this.recording.borrow() {
tracks[index] = track; let editor = TrackEditor::new(this.tracks.borrow()[index].clone(), recording.work.clone());
this.track_list.show_items(tracks.clone());
this.track_list.select_index(index); editor.set_ready_cb(clone!(@strong this => move |track| {
})).show(); let mut tracks = this.tracks.borrow_mut();
tracks[index] = track;
this.track_list.show_items(tracks.clone());
this.track_list.select_index(index);
}));
navigator.push(editor);
}
} }
} }
})); }));
@ -228,11 +262,6 @@ impl TracksEditor {
self.callback.replace(Some(Box::new(cb))); self.callback.replace(Some(Box::new(cb)));
} }
/// Open the track editor.
pub fn show(&self) {
self.window.show();
}
/// Create a widget representing a track. /// Create a widget representing a track.
fn build_track_row(&self, track: &Track) -> gtk::Widget { fn build_track_row(&self, track: &Track) -> gtk::Widget {
let mut title_parts = Vec::<String>::new(); let mut title_parts = Vec::<String>::new();
@ -292,3 +321,17 @@ impl TracksEditor {
} }
} }
} }
impl NavigatorScreen for TracksEditor {
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);
}
}

View file

@ -1,9 +1,9 @@
use super::part_editor::*; use super::work_part::WorkPartEditor;
use super::section_editor::*; use super::work_section::WorkSectionEditor;
use crate::backend::*; use crate::backend::Backend;
use crate::database::*; use crate::database::*;
use crate::dialogs::*; use crate::selectors::{InstrumentSelector, PersonSelector};
use crate::widgets::*; use crate::widgets::{List, Navigator, NavigatorScreen};
use anyhow::Result; use anyhow::Result;
use gettextrs::gettext; use gettextrs::gettext;
use glib::clone; use glib::clone;
@ -22,9 +22,8 @@ enum PartOrSection {
/// A widget for editing and creating works. /// A widget for editing and creating works.
pub struct WorkEditor { pub struct WorkEditor {
pub widget: gtk::Stack, widget: gtk::Stack,
backend: Rc<Backend>, backend: Rc<Backend>,
parent: gtk::Window,
save_button: gtk::Button, save_button: gtk::Button,
title_entry: gtk::Entry, title_entry: gtk::Entry,
info_bar: gtk::InfoBar, info_bar: gtk::InfoBar,
@ -36,24 +35,19 @@ pub struct WorkEditor {
composer: RefCell<Option<Person>>, composer: RefCell<Option<Person>>,
instruments: RefCell<Vec<Instrument>>, instruments: RefCell<Vec<Instrument>>,
structure: RefCell<Vec<PartOrSection>>, structure: RefCell<Vec<PartOrSection>>,
cancel_cb: RefCell<Option<Box<dyn Fn() -> ()>>>,
saved_cb: RefCell<Option<Box<dyn Fn(Work) -> ()>>>, saved_cb: RefCell<Option<Box<dyn Fn(Work) -> ()>>>,
navigator: RefCell<Option<Rc<Navigator>>>,
} }
impl WorkEditor { impl WorkEditor {
/// Create a new work editor widget and optionally initialize it. The parent window is used /// Create a new work editor widget and optionally initialize it.
/// as the parent for newly created dialogs. pub fn new(backend: Rc<Backend>, work: Option<Work>) -> Rc<Self> {
pub fn new<P: IsA<gtk::Window>>(
backend: Rc<Backend>,
parent: &P,
work: Option<Work>,
) -> Rc<Self> {
// Create UI // Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_editor.ui"); let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_editor.ui");
get_widget!(builder, gtk::Stack, widget); get_widget!(builder, gtk::Stack, widget);
get_widget!(builder, gtk::Button, cancel_button); get_widget!(builder, gtk::Button, back_button);
get_widget!(builder, gtk::Button, save_button); get_widget!(builder, gtk::Button, save_button);
get_widget!(builder, gtk::InfoBar, info_bar); get_widget!(builder, gtk::InfoBar, info_bar);
get_widget!(builder, gtk::Entry, title_entry); get_widget!(builder, gtk::Entry, title_entry);
@ -102,7 +96,6 @@ impl WorkEditor {
let this = Rc::new(Self { let this = Rc::new(Self {
widget, widget,
backend, backend,
parent: parent.clone().upcast(),
save_button, save_button,
id, id,
info_bar, info_bar,
@ -114,15 +107,16 @@ impl WorkEditor {
composer: RefCell::new(composer), composer: RefCell::new(composer),
instruments: RefCell::new(instruments), instruments: RefCell::new(instruments),
structure: RefCell::new(structure), structure: RefCell::new(structure),
cancel_cb: RefCell::new(None),
saved_cb: RefCell::new(None), saved_cb: RefCell::new(None),
navigator: RefCell::new(None),
}); });
// Connect signals and callbacks // Connect signals and callbacks
cancel_button.connect_clicked(clone!(@strong this => move |_| { back_button.connect_clicked(clone!(@strong this => move |_| {
if let Some(cb) = &*this.cancel_cb.borrow() { let navigator = this.navigator.borrow().clone();
cb(); if let Some(navigator) = navigator {
navigator.pop();
} }
})); }));
@ -134,7 +128,10 @@ impl WorkEditor {
clone.widget.set_visible_child_name("loading"); clone.widget.set_visible_child_name("loading");
match clone.clone().save().await { match clone.clone().save().await {
Ok(_) => { Ok(_) => {
// We already called the callback. let navigator = clone.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
} }
Err(_) => { Err(_) => {
clone.info_bar.set_revealed(true); clone.info_bar.set_revealed(true);
@ -146,14 +143,18 @@ impl WorkEditor {
})); }));
composer_button.connect_clicked(clone!(@strong this => move |_| { composer_button.connect_clicked(clone!(@strong this => move |_| {
let dialog = PersonSelector::new(this.backend.clone(), &this.parent); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let selector = PersonSelector::new(this.backend.clone());
dialog.set_selected_cb(clone!(@strong this => move |person| { selector.set_selected_cb(clone!(@strong this, @strong navigator => move |person| {
this.show_composer(&person); this.show_composer(person);
this.composer.replace(Some(person)); this.composer.replace(Some(person.clone()));
})); navigator.clone().pop();
}));
dialog.show(); navigator.push(selector);
}
})); }));
this.instrument_list.set_make_widget(|instrument| { this.instrument_list.set_make_widget(|instrument| {
@ -168,22 +169,27 @@ impl WorkEditor {
}); });
add_instrument_button.connect_clicked(clone!(@strong this => move |_| { add_instrument_button.connect_clicked(clone!(@strong this => move |_| {
let dialog = InstrumentSelector::new(this.backend.clone(), &this.parent); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let selector = InstrumentSelector::new(this.backend.clone());
dialog.set_selected_cb(clone!(@strong this => move |instrument| { selector.set_selected_cb(clone!(@strong this, @strong navigator => move |instrument| {
let mut instruments = this.instruments.borrow_mut(); let mut instruments = this.instruments.borrow_mut();
let index = match this.instrument_list.get_selected_index() { let index = match this.instrument_list.get_selected_index() {
Some(index) => index + 1, Some(index) => index + 1,
None => instruments.len(), None => instruments.len(),
}; };
instruments.insert(index, instrument); instruments.insert(index, instrument.clone());
this.instrument_list.show_items(instruments.clone()); this.instrument_list.show_items(instruments.clone());
this.instrument_list.select_index(index); this.instrument_list.select_index(index);
}));
dialog.show(); navigator.clone().pop();
}));
navigator.push(selector);
}
})); }));
remove_instrument_button.connect_clicked(clone!(@strong this => move |_| { remove_instrument_button.connect_clicked(clone!(@strong this => move |_| {
@ -221,73 +227,84 @@ impl WorkEditor {
}); });
add_part_button.connect_clicked(clone!(@strong this => move |_| { add_part_button.connect_clicked(clone!(@strong this => move |_| {
let editor = PartEditor::new(this.backend.clone(), &this.parent, None); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let editor = WorkPartEditor::new(this.backend.clone(), None);
editor.set_ready_cb(clone!(@strong this => move |part| { editor.set_ready_cb(clone!(@strong this, @strong navigator => move |part| {
let mut structure = this.structure.borrow_mut(); let mut structure = this.structure.borrow_mut();
let index = match this.part_list.get_selected_index() { let index = match this.part_list.get_selected_index() {
Some(index) => index + 1, Some(index) => index + 1,
None => structure.len(), None => structure.len(),
}; };
structure.insert(index, PartOrSection::Part(part)); structure.insert(index, PartOrSection::Part(part));
this.part_list.show_items(structure.clone()); this.part_list.show_items(structure.clone());
this.part_list.select_index(index); this.part_list.select_index(index);
}));
editor.show(); navigator.clone().pop();
}));
navigator.push(editor);
}
})); }));
add_section_button.connect_clicked(clone!(@strong this => move |_| { add_section_button.connect_clicked(clone!(@strong this => move |_| {
let editor = SectionEditor::new(&this.parent, None); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let editor = WorkSectionEditor::new(None);
editor.set_ready_cb(clone!(@strong this => move |section| { editor.set_ready_cb(clone!(@strong this, @strong navigator => move |section| {
let mut structure = this.structure.borrow_mut(); let mut structure = this.structure.borrow_mut();
let index = match this.part_list.get_selected_index() { let index = match this.part_list.get_selected_index() {
Some(index) => index + 1, Some(index) => index + 1,
None => structure.len(), None => structure.len(),
}; };
structure.insert(index, PartOrSection::Section(section)); structure.insert(index, PartOrSection::Section(section));
this.part_list.show_items(structure.clone()); this.part_list.show_items(structure.clone());
this.part_list.select_index(index); this.part_list.select_index(index);
}));
editor.show(); navigator.clone().pop();
}));
navigator.push(editor);
}
})); }));
edit_part_button.connect_clicked(clone!(@strong this => move |_| { edit_part_button.connect_clicked(clone!(@strong this => move |_| {
if let Some(index) = this.part_list.get_selected_index() { let navigator = this.navigator.borrow().clone();
match this.structure.borrow()[index].clone() { if let Some(navigator) = navigator {
PartOrSection::Part(part) => { if let Some(index) = this.part_list.get_selected_index() {
let editor = PartEditor::new( match this.structure.borrow()[index].clone() {
this.backend.clone(), PartOrSection::Part(part) => {
&this.parent, let editor = WorkPartEditor::new(this.backend.clone(), Some(part));
Some(part),
);
editor.set_ready_cb(clone!(@strong this => move |part| { editor.set_ready_cb(clone!(@strong this, @strong navigator => move |part| {
let mut structure = this.structure.borrow_mut(); let mut structure = this.structure.borrow_mut();
structure[index] = PartOrSection::Part(part); structure[index] = PartOrSection::Part(part);
this.part_list.show_items(structure.clone()); this.part_list.show_items(structure.clone());
this.part_list.select_index(index); this.part_list.select_index(index);
})); navigator.clone().pop();
}));
editor.show(); navigator.push(editor);
} }
PartOrSection::Section(section) => { PartOrSection::Section(section) => {
let editor = SectionEditor::new(&this.parent, Some(section)); let editor = WorkSectionEditor::new(Some(section));
editor.set_ready_cb(clone!(@strong this => move |section| { editor.set_ready_cb(clone!(@strong this, @strong navigator => move |section| {
let mut structure = this.structure.borrow_mut(); let mut structure = this.structure.borrow_mut();
structure[index] = PartOrSection::Section(section); structure[index] = PartOrSection::Section(section);
this.part_list.show_items(structure.clone()); this.part_list.show_items(structure.clone());
this.part_list.select_index(index); this.part_list.select_index(index);
})); navigator.clone().pop();
}));
editor.show(); navigator.push(editor);
}
} }
} }
} }
@ -337,11 +354,6 @@ impl WorkEditor {
this this
} }
/// The closure to call when the editor is canceled.
pub fn set_cancel_cb<F: Fn() -> () + 'static>(&self, cb: F) {
self.cancel_cb.replace(Some(Box::new(cb)));
}
/// The closure to call when a work was created. /// The closure to call when a work was created.
pub fn set_saved_cb<F: Fn(Work) -> () + 'static>(&self, cb: F) { pub fn set_saved_cb<F: Fn(Work) -> () + 'static>(&self, cb: F) {
self.saved_cb.replace(Some(Box::new(cb))); self.saved_cb.replace(Some(Box::new(cb)));
@ -404,3 +416,17 @@ impl WorkEditor {
Ok(()) Ok(())
} }
} }
impl NavigatorScreen for WorkEditor {
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);
}
}

View file

@ -1,6 +1,7 @@
use crate::backend::*; use crate::backend::Backend;
use crate::database::*; use crate::database::*;
use crate::dialogs::*; use crate::selectors::PersonSelector;
use crate::widgets::{Navigator, NavigatorScreen};
use gettextrs::gettext; use gettextrs::gettext;
use glib::clone; use glib::clone;
use gtk::prelude::*; use gtk::prelude::*;
@ -9,37 +10,32 @@ use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
/// A dialog for creating or editing a work part. /// A dialog for creating or editing a work part.
pub struct PartEditor { pub struct WorkPartEditor {
backend: Rc<Backend>, backend: Rc<Backend>,
window: libhandy::Window, widget: gtk::Box,
title_entry: gtk::Entry, title_entry: gtk::Entry,
composer_label: gtk::Label, composer_label: gtk::Label,
reset_composer_button: gtk::Button, reset_composer_button: gtk::Button,
composer: RefCell<Option<Person>>, composer: RefCell<Option<Person>>,
ready_cb: RefCell<Option<Box<dyn Fn(WorkPart) -> ()>>>, ready_cb: RefCell<Option<Box<dyn Fn(WorkPart) -> ()>>>,
navigator: RefCell<Option<Rc<Navigator>>>,
} }
impl PartEditor { impl WorkPartEditor {
/// Create a new part editor and optionally initialize it. /// Create a new part editor and optionally initialize it.
pub fn new<P: IsA<gtk::Window>>( pub fn new(backend: Rc<Backend>, part: Option<WorkPart>) -> Rc<Self> {
backend: Rc<Backend>,
parent: &P,
part: Option<WorkPart>,
) -> Rc<Self> {
// Create UI // Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/part_editor.ui"); let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_part_editor.ui");
get_widget!(builder, libhandy::Window, window); get_widget!(builder, gtk::Box, widget);
get_widget!(builder, gtk::Button, cancel_button); get_widget!(builder, gtk::Button, back_button);
get_widget!(builder, gtk::Button, save_button); get_widget!(builder, gtk::Button, save_button);
get_widget!(builder, gtk::Entry, title_entry); get_widget!(builder, gtk::Entry, title_entry);
get_widget!(builder, gtk::Button, composer_button); get_widget!(builder, gtk::Button, composer_button);
get_widget!(builder, gtk::Label, composer_label); get_widget!(builder, gtk::Label, composer_label);
get_widget!(builder, gtk::Button, reset_composer_button); get_widget!(builder, gtk::Button, reset_composer_button);
window.set_transient_for(Some(parent));
let composer = match part { let composer = match part {
Some(part) => { Some(part) => {
title_entry.set_text(&part.title); title_entry.set_text(&part.title);
@ -50,18 +46,22 @@ impl PartEditor {
let this = Rc::new(Self { let this = Rc::new(Self {
backend, backend,
window, widget,
title_entry, title_entry,
composer_label, composer_label,
reset_composer_button, reset_composer_button,
composer: RefCell::new(composer), composer: RefCell::new(composer),
ready_cb: RefCell::new(None), ready_cb: RefCell::new(None),
navigator: RefCell::new(None),
}); });
// Connect signals and callbacks // Connect signals and callbacks
cancel_button.connect_clicked(clone!(@strong this => move |_| { back_button.connect_clicked(clone!(@strong this => move |_| {
this.window.close(); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
})); }));
save_button.connect_clicked(clone!(@strong this => move |_| { save_button.connect_clicked(clone!(@strong this => move |_| {
@ -72,18 +72,26 @@ impl PartEditor {
}); });
} }
this.window.close(); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
})); }));
composer_button.connect_clicked(clone!(@strong this => move |_| { composer_button.connect_clicked(clone!(@strong this => move |_| {
let dialog = PersonSelector::new(this.backend.clone(), &this.window); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let selector = PersonSelector::new(this.backend.clone());
dialog.set_selected_cb(clone!(@strong this => move |person| { selector.set_selected_cb(clone!(@strong this, @strong navigator => move |person| {
this.show_composer(Some(&person)); this.show_composer(Some(person));
this.composer.replace(Some(person)); this.composer.replace(Some(person.clone()));
})); navigator.clone().pop();
}));
navigator.push(selector);
}
dialog.show();
})); }));
this.reset_composer_button this.reset_composer_button
@ -106,11 +114,6 @@ impl PartEditor {
self.ready_cb.replace(Some(Box::new(cb))); self.ready_cb.replace(Some(Box::new(cb)));
} }
/// Show the part editor.
pub fn show(&self) {
self.window.show();
}
/// Update the UI according to person. /// Update the UI according to person.
fn show_composer(&self, person: Option<&Person>) { fn show_composer(&self, person: Option<&Person>) {
if let Some(person) = person { if let Some(person) = person {
@ -122,3 +125,17 @@ impl PartEditor {
} }
} }
} }
impl NavigatorScreen for WorkPartEditor {
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);
}
}

View file

@ -1,4 +1,5 @@
use crate::database::*; use crate::database::*;
use crate::widgets::{Navigator, NavigatorScreen};
use glib::clone; use glib::clone;
use gtk::prelude::*; use gtk::prelude::*;
use gtk_macros::get_widget; use gtk_macros::get_widget;
@ -6,40 +7,43 @@ use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
/// A dialog for creating or editing a work section. /// A dialog for creating or editing a work section.
pub struct SectionEditor { pub struct WorkSectionEditor {
window: libhandy::Window, widget: gtk::Box,
title_entry: gtk::Entry, title_entry: gtk::Entry,
ready_cb: RefCell<Option<Box<dyn Fn(WorkSection) -> ()>>>, ready_cb: RefCell<Option<Box<dyn Fn(WorkSection) -> ()>>>,
navigator: RefCell<Option<Rc<Navigator>>>,
} }
impl SectionEditor { impl WorkSectionEditor {
/// Create a new section editor and optionally initialize it. /// Create a new section editor and optionally initialize it.
pub fn new<P: IsA<gtk::Window>>(parent: &P, section: Option<WorkSection>) -> Rc<Self> { pub fn new(section: Option<WorkSection>) -> Rc<Self> {
// Create UI // Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/section_editor.ui"); let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_section_editor.ui");
get_widget!(builder, libhandy::Window, window); get_widget!(builder, gtk::Box, widget);
get_widget!(builder, gtk::Button, cancel_button); get_widget!(builder, gtk::Button, back_button);
get_widget!(builder, gtk::Button, save_button); get_widget!(builder, gtk::Button, save_button);
get_widget!(builder, gtk::Entry, title_entry); get_widget!(builder, gtk::Entry, title_entry);
window.set_transient_for(Some(parent));
if let Some(section) = section { if let Some(section) = section {
title_entry.set_text(&section.title); title_entry.set_text(&section.title);
} }
let this = Rc::new(Self { let this = Rc::new(Self {
window, widget,
title_entry, title_entry,
ready_cb: RefCell::new(None), ready_cb: RefCell::new(None),
navigator: RefCell::new(None),
}); });
// Connect signals and callbacks // Connect signals and callbacks
cancel_button.connect_clicked(clone!(@strong this => move |_| { back_button.connect_clicked(clone!(@strong this => move |_| {
this.window.close(); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
})); }));
save_button.connect_clicked(clone!(@strong this => move |_| { save_button.connect_clicked(clone!(@strong this => move |_| {
@ -50,7 +54,10 @@ impl SectionEditor {
}); });
} }
this.window.close(); let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
})); }));
this this
@ -62,9 +69,18 @@ impl SectionEditor {
pub fn set_ready_cb<F: Fn(WorkSection) -> () + 'static>(&self, cb: F) { pub fn set_ready_cb<F: Fn(WorkSection) -> () + 'static>(&self, cb: F) {
self.ready_cb.replace(Some(Box::new(cb))); self.ready_cb.replace(Some(Box::new(cb)));
} }
}
/// Show the section editor. impl NavigatorScreen for WorkSectionEditor {
pub fn show(&self) { fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.window.show(); self.navigator.replace(Some(navigator));
}
fn get_widget(&self) -> gtk::Widget {
self.widget.clone().upcast()
}
fn detach_navigator(&self) {
self.navigator.replace(None);
} }
} }

View file

@ -11,12 +11,14 @@ use glib::clone;
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
mod config;
mod backend; mod backend;
mod config;
mod database; mod database;
mod dialogs; mod dialogs;
mod editors;
mod player; mod player;
mod screens; mod screens;
mod selectors;
mod widgets; mod widgets;
mod window; mod window;
@ -33,11 +35,8 @@ fn main() {
libhandy::init(); libhandy::init();
resources::init().expect("Failed to initialize resources!"); resources::init().expect("Failed to initialize resources!");
let app = gtk::Application::new( let app = gtk::Application::new(Some("de.johrpan.musicus"), gio::ApplicationFlags::empty())
Some("de.johrpan.musicus"), .expect("Failed to initialize GTK application!");
gio::ApplicationFlags::empty(),
)
.expect("Failed to initialize GTK application!");
let window: RefCell<Option<Rc<Window>>> = RefCell::new(None); let window: RefCell<Option<Rc<Window>>> = RefCell::new(None);

View file

@ -52,43 +52,38 @@ sources = files(
'database/tracks.rs', 'database/tracks.rs',
'database/works.rs', 'database/works.rs',
'dialogs/about.rs', 'dialogs/about.rs',
'dialogs/ensemble_editor.rs',
'dialogs/ensemble_selector.rs',
'dialogs/instrument_editor.rs',
'dialogs/instrument_selector.rs',
'dialogs/login_dialog.rs', 'dialogs/login_dialog.rs',
'dialogs/mod.rs', 'dialogs/mod.rs',
'dialogs/person_editor.rs',
'dialogs/person_selector.rs',
'dialogs/preferences.rs', 'dialogs/preferences.rs',
'dialogs/server_dialog.rs', 'dialogs/server_dialog.rs',
'dialogs/recording/mod.rs', 'editors/ensemble.rs',
'dialogs/recording/performance_editor.rs', 'editors/instrument.rs',
'dialogs/recording/recording_dialog.rs', 'editors/mod.rs',
'dialogs/recording/recording_editor_dialog.rs', 'editors/performance.rs',
'dialogs/recording/recording_editor.rs', 'editors/person.rs',
'dialogs/recording/recording_selector_person_screen.rs', 'editors/recording.rs',
'dialogs/recording/recording_selector.rs', 'editors/track.rs',
'dialogs/recording/recording_selector_work_screen.rs', 'editors/tracks.rs',
'dialogs/track_editor.rs', 'editors/work.rs',
'dialogs/tracks_editor.rs', 'editors/work_part.rs',
'dialogs/work/mod.rs', 'editors/work_section.rs',
'dialogs/work/part_editor.rs',
'dialogs/work/section_editor.rs',
'dialogs/work/work_dialog.rs',
'dialogs/work/work_editor_dialog.rs',
'dialogs/work/work_editor.rs',
'dialogs/work/work_selector_person_screen.rs',
'dialogs/work/work_selector.rs',
'screens/ensemble_screen.rs', 'screens/ensemble_screen.rs',
'screens/mod.rs', 'screens/mod.rs',
'screens/person_screen.rs', 'screens/person_screen.rs',
'screens/player_screen.rs', 'screens/player_screen.rs',
'screens/recording_screen.rs', 'screens/recording_screen.rs',
'screens/work_screen.rs', 'screens/work_screen.rs',
'selectors/ensemble.rs',
'selectors/instrument.rs',
'selectors/mod.rs',
'selectors/person.rs',
'selectors/recording.rs',
'selectors/selector.rs',
'selectors/work.rs',
'widgets/list.rs', 'widgets/list.rs',
'widgets/mod.rs', 'widgets/mod.rs',
'widgets/navigator.rs', 'widgets/navigator.rs',
'widgets/navigator_window.rs',
'widgets/player_bar.rs', 'widgets/player_bar.rs',
'widgets/poe_list.rs', 'widgets/poe_list.rs',
'widgets/selector_row.rs', 'widgets/selector_row.rs',

View file

@ -1,8 +1,8 @@
use super::*; use super::*;
use crate::backend::*; use crate::backend::*;
use crate::database::*; use crate::database::*;
use crate::dialogs::EnsembleEditor; use crate::editors::EnsembleEditor;
use crate::widgets::*; use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow};
use gettextrs::gettext; use gettextrs::gettext;
use gio::prelude::*; use gio::prelude::*;
use glib::clone; use glib::clone;
@ -109,7 +109,9 @@ impl EnsembleScreen {
})); }));
edit_action.connect_activate(clone!(@strong result => move |_, _| { edit_action.connect_activate(clone!(@strong result => move |_, _| {
EnsembleEditor::new(result.backend.clone(), &result.window, Some(result.ensemble.clone())).show(); let editor = EnsembleEditor::new(result.backend.clone(), Some(result.ensemble.clone()));
let window = NavigatorWindow::new(editor);
window.show();
})); }));
delete_action.connect_activate(clone!(@strong result => move |_, _| { delete_action.connect_activate(clone!(@strong result => move |_, _| {

View file

@ -1,8 +1,8 @@
use super::*; use super::*;
use crate::backend::*; use crate::backend::*;
use crate::database::*; use crate::database::*;
use crate::dialogs::PersonEditor; use crate::editors::PersonEditor;
use crate::widgets::*; use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow};
use gettextrs::gettext; use gettextrs::gettext;
use gio::prelude::*; use gio::prelude::*;
use glib::clone; use glib::clone;
@ -145,7 +145,9 @@ impl PersonScreen {
})); }));
edit_action.connect_activate(clone!(@strong result => move |_, _| { edit_action.connect_activate(clone!(@strong result => move |_, _| {
PersonEditor::new(result.backend.clone(), &result.window, Some(result.person.clone())).show(); let editor = PersonEditor::new(result.backend.clone(), Some(result.person.clone()));
let window = NavigatorWindow::new(editor);
window.show();
})); }));
delete_action.connect_activate(clone!(@strong result => move |_, _| { delete_action.connect_activate(clone!(@strong result => move |_, _| {

View file

@ -1,8 +1,8 @@
use crate::backend::*; use crate::backend::*;
use crate::database::*; use crate::database::*;
use crate::dialogs::{RecordingEditorDialog, TracksEditor}; use crate::editors::{RecordingEditor, TracksEditor};
use crate::player::*; use crate::player::*;
use crate::widgets::*; use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow};
use gettextrs::gettext; use gettextrs::gettext;
use gio::prelude::*; use gio::prelude::*;
use glib::clone; use glib::clone;
@ -111,7 +111,9 @@ impl RecordingScreen {
})); }));
edit_action.connect_activate(clone!(@strong result => move |_, _| { edit_action.connect_activate(clone!(@strong result => move |_, _| {
RecordingEditorDialog::new(result.backend.clone(), &result.window, Some(result.recording.clone())).show(); let editor = RecordingEditor::new(result.backend.clone(), Some(result.recording.clone()));
let window = NavigatorWindow::new(editor);
window.show();
})); }));
delete_action.connect_activate(clone!(@strong result => move |_, _| { delete_action.connect_activate(clone!(@strong result => move |_, _| {
@ -124,7 +126,9 @@ impl RecordingScreen {
})); }));
edit_tracks_action.connect_activate(clone!(@strong result => move |_, _| { edit_tracks_action.connect_activate(clone!(@strong result => move |_, _| {
TracksEditor::new(result.backend.clone(), &result.window, Some(result.recording.clone()), result.tracks.borrow().clone()).show(); let editor = TracksEditor::new(result.backend.clone(), Some(result.recording.clone()), result.tracks.borrow().clone());
let window = NavigatorWindow::new(editor);
window.show();
})); }));
delete_tracks_action.connect_activate(clone!(@strong result => move |_, _| { delete_tracks_action.connect_activate(clone!(@strong result => move |_, _| {

View file

@ -1,8 +1,8 @@
use super::*; use super::*;
use crate::backend::*; use crate::backend::*;
use crate::database::*; use crate::database::*;
use crate::dialogs::WorkEditorDialog; use crate::editors::WorkEditor;
use crate::widgets::*; use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow};
use gettextrs::gettext; use gettextrs::gettext;
use gio::prelude::*; use gio::prelude::*;
use glib::clone; use glib::clone;
@ -108,7 +108,9 @@ impl WorkScreen {
})); }));
edit_action.connect_activate(clone!(@strong result => move |_, _| { edit_action.connect_activate(clone!(@strong result => move |_, _| {
WorkEditorDialog::new(result.backend.clone(), &result.window, Some(result.work.clone())).show(); let editor = WorkEditor::new(result.backend.clone(), Some(result.work.clone()));
let window = NavigatorWindow::new(editor);
window.show();
})); }));
delete_action.connect_activate(clone!(@strong result => move |_, _| { delete_action.connect_activate(clone!(@strong result => move |_, _| {

View file

@ -0,0 +1,111 @@
use super::selector::Selector;
use crate::backend::Backend;
use crate::database::Ensemble;
use crate::editors::EnsembleEditor;
use crate::widgets::{Navigator, NavigatorScreen};
use gettextrs::gettext;
use glib::clone;
use gtk::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;
/// A screen for selecting a ensemble.
pub struct EnsembleSelector {
backend: Rc<Backend>,
selector: Rc<Selector<Ensemble>>,
selected_cb: RefCell<Option<Box<dyn Fn(&Ensemble) -> ()>>>,
navigator: RefCell<Option<Rc<Navigator>>>,
}
impl EnsembleSelector {
/// Create a new ensemble selector.
pub fn new(backend: Rc<Backend>) -> Rc<Self> {
// Create UI
let selector = Selector::<Ensemble>::new();
selector.set_title(&gettext("Select ensemble"));
let this = Rc::new(Self {
backend,
selector,
selected_cb: RefCell::new(None),
navigator: RefCell::new(None),
});
// Connect signals and callbacks
this.selector.set_back_cb(clone!(@strong this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
}));
this.selector.set_add_cb(clone!(@strong this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let editor = EnsembleEditor::new(this.backend.clone(), None);
editor
.set_saved_cb(clone!(@strong this => move |ensemble| this.select(&ensemble)));
navigator.push(editor);
}
}));
this.selector
.set_load_online(clone!(@strong this => move || {
let clone = this.clone();
async move { clone.backend.get_ensembles().await }
}));
this.selector
.set_load_local(clone!(@strong this => move || {
let clone = this.clone();
async move { clone.backend.db().get_ensembles().await.unwrap() }
}));
this.selector.set_make_widget(|ensemble| {
let label = gtk::Label::new(Some(&ensemble.name));
label.set_halign(gtk::Align::Start);
label.set_margin_start(6);
label.set_margin_end(6);
label.set_margin_top(6);
label.set_margin_bottom(6);
label.upcast()
});
this.selector
.set_filter(|search, ensemble| ensemble.name.to_lowercase().contains(search));
this.selector
.set_selected_cb(clone!(@strong this => move |ensemble| this.select(ensemble)));
this
}
/// Set the closure to be called when an item is selected.
pub fn set_selected_cb<F: Fn(&Ensemble) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
/// Select a ensemble.
fn select(&self, ensemble: &Ensemble) {
if let Some(cb) = &*self.selected_cb.borrow() {
cb(&ensemble);
}
}
}
impl NavigatorScreen for EnsembleSelector {
fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.navigator.replace(Some(navigator));
}
fn get_widget(&self) -> gtk::Widget {
self.selector.widget.clone().upcast()
}
fn detach_navigator(&self) {
self.navigator.replace(None);
}
}

View file

@ -0,0 +1,110 @@
use super::selector::Selector;
use crate::backend::Backend;
use crate::database::Instrument;
use crate::editors::InstrumentEditor;
use crate::widgets::{Navigator, NavigatorScreen};
use gettextrs::gettext;
use glib::clone;
use gtk::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;
/// A screen for selecting a instrument.
pub struct InstrumentSelector {
backend: Rc<Backend>,
selector: Rc<Selector<Instrument>>,
selected_cb: RefCell<Option<Box<dyn Fn(&Instrument) -> ()>>>,
navigator: RefCell<Option<Rc<Navigator>>>,
}
impl InstrumentSelector {
/// Create a new instrument selector.
pub fn new(backend: Rc<Backend>) -> Rc<Self> {
// Create UI
let selector = Selector::<Instrument>::new();
selector.set_title(&gettext("Select instrument"));
let this = Rc::new(Self {
backend,
selector,
selected_cb: RefCell::new(None),
navigator: RefCell::new(None),
});
// Connect signals and callbacks
this.selector.set_back_cb(clone!(@strong this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
}));
this.selector.set_add_cb(clone!(@strong this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let editor = InstrumentEditor::new(this.backend.clone(), None);
editor
.set_saved_cb(clone!(@strong this => move |instrument| this.select(&instrument)));
navigator.push(editor);
}
}));
this.selector
.set_load_online(clone!(@strong this => move || {
let clone = this.clone();
async move { clone.backend.get_instruments().await }
}));
this.selector
.set_load_local(clone!(@strong this => move || {
let clone = this.clone();
async move { clone.backend.db().get_instruments().await.unwrap() }
}));
this.selector.set_make_widget(|instrument| {
let label = gtk::Label::new(Some(&instrument.name));
label.set_halign(gtk::Align::Start);
label.set_margin_start(6);
label.set_margin_end(6);
label.set_margin_top(6);
label.set_margin_bottom(6);
label.upcast()
});
this.selector
.set_filter(|search, instrument| instrument.name.to_lowercase().contains(search));
this.selector
.set_selected_cb(clone!(@strong this => move |instrument| this.select(instrument)));
this
}
/// Set the closure to be called when an item is selected.
pub fn set_selected_cb<F: Fn(&Instrument) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
/// Select a instrument.
fn select(&self, instrument: &Instrument) {
if let Some(cb) = &*self.selected_cb.borrow() {
cb(&instrument);
}
}
}
impl NavigatorScreen for InstrumentSelector {
fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.navigator.replace(Some(navigator));
}
fn get_widget(&self) -> gtk::Widget {
self.selector.widget.clone().upcast()
}
fn detach_navigator(&self) {
self.navigator.replace(None);
}
}

View file

@ -0,0 +1,16 @@
pub mod ensemble;
pub use ensemble::*;
pub mod instrument;
pub use instrument::*;
pub mod person;
pub use person::*;
pub mod recording;
pub use recording::*;
pub mod work;
pub use work::*;
mod selector;

View file

@ -0,0 +1,110 @@
use super::selector::Selector;
use crate::backend::Backend;
use crate::database::Person;
use crate::editors::PersonEditor;
use crate::widgets::{Navigator, NavigatorScreen};
use gettextrs::gettext;
use glib::clone;
use gtk::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;
/// A screen for selecting a person.
pub struct PersonSelector {
backend: Rc<Backend>,
selector: Rc<Selector<Person>>,
selected_cb: RefCell<Option<Box<dyn Fn(&Person) -> ()>>>,
navigator: RefCell<Option<Rc<Navigator>>>,
}
impl PersonSelector {
/// Create a new person selector.
pub fn new(backend: Rc<Backend>) -> Rc<Self> {
// Create UI
let selector = Selector::<Person>::new();
selector.set_title(&gettext("Select person"));
let this = Rc::new(Self {
backend,
selector,
selected_cb: RefCell::new(None),
navigator: RefCell::new(None),
});
// Connect signals and callbacks
this.selector.set_back_cb(clone!(@strong this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
}));
this.selector.set_add_cb(clone!(@strong this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let editor = PersonEditor::new(this.backend.clone(), None);
editor
.set_saved_cb(clone!(@strong this => move |person| this.select(&person)));
navigator.push(editor);
}
}));
this.selector
.set_load_online(clone!(@strong this => move || {
let clone = this.clone();
async move { clone.backend.get_persons().await }
}));
this.selector
.set_load_local(clone!(@strong this => move || {
let clone = this.clone();
async move { clone.backend.db().get_persons().await.unwrap() }
}));
this.selector.set_make_widget(|person| {
let label = gtk::Label::new(Some(&person.name_lf()));
label.set_halign(gtk::Align::Start);
label.set_margin_start(6);
label.set_margin_end(6);
label.set_margin_top(6);
label.set_margin_bottom(6);
label.upcast()
});
this.selector
.set_filter(|search, person| person.name_fl().to_lowercase().contains(search));
this.selector
.set_selected_cb(clone!(@strong this => move |person| this.select(person)));
this
}
/// Set the closure to be called when an item is selected.
pub fn set_selected_cb<F: Fn(&Person) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
/// Select a person.
fn select(&self, person: &Person) {
if let Some(cb) = &*self.selected_cb.borrow() {
cb(&person);
}
}
}
impl NavigatorScreen for PersonSelector {
fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.navigator.replace(Some(navigator));
}
fn get_widget(&self) -> gtk::Widget {
self.selector.widget.clone().upcast()
}
fn detach_navigator(&self) {
self.navigator.replace(None);
}
}

View file

@ -0,0 +1,114 @@
use super::selector::Selector;
use crate::backend::Backend;
use crate::database::{Recording, Work};
use crate::editors::RecordingEditor;
use crate::widgets::{Navigator, NavigatorScreen};
use gettextrs::gettext;
use glib::clone;
use gtk::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;
/// A screen for selecting a recording.
pub struct RecordingSelector {
backend: Rc<Backend>,
work: Work,
selector: Rc<Selector<Recording>>,
selected_cb: RefCell<Option<Box<dyn Fn(&Recording) -> ()>>>,
navigator: RefCell<Option<Rc<Navigator>>>,
}
impl RecordingSelector {
/// Create a new recording selector for recordings of a specific work.
pub fn new(backend: Rc<Backend>, work: Work) -> Rc<Self> {
// Create UI
let selector = Selector::<Recording>::new();
selector.set_title(&gettext("Select recording"));
selector.set_subtitle(&work.get_title());
let this = Rc::new(Self {
backend,
work,
selector,
selected_cb: RefCell::new(None),
navigator: RefCell::new(None),
});
// Connect signals and callbacks
this.selector.set_back_cb(clone!(@strong this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
}));
this.selector.set_add_cb(clone!(@strong this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let editor = RecordingEditor::new(this.backend.clone(), None);
editor
.set_selected_cb(clone!(@strong this => move |recording| this.select(&recording)));
navigator.push(editor);
}
}));
this.selector
.set_load_online(clone!(@strong this => move || {
let clone = this.clone();
async move { clone.backend.get_recordings_for_work(&clone.work.id).await }
}));
this.selector
.set_load_local(clone!(@strong this => move || {
let clone = this.clone();
async move { clone.backend.db().get_recordings_for_work(&clone.work.id).await.unwrap() }
}));
this.selector.set_make_widget(|recording| {
let label = gtk::Label::new(Some(&recording.get_performers()));
label.set_halign(gtk::Align::Start);
label.set_margin_start(6);
label.set_margin_end(6);
label.set_margin_top(6);
label.set_margin_bottom(6);
label.upcast()
});
this.selector.set_filter(|search, recording| {
recording.get_performers().to_lowercase().contains(search)
});
this.selector
.set_selected_cb(clone!(@strong this => move |recording| this.select(recording)));
this
}
/// Set the closure to be called when an item is selected.
pub fn set_selected_cb<F: Fn(&Recording) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
/// Select a recording.
fn select(&self, recording: &Recording) {
if let Some(cb) = &*self.selected_cb.borrow() {
cb(&recording);
}
}
}
impl NavigatorScreen for RecordingSelector {
fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.navigator.replace(Some(navigator));
}
fn get_widget(&self) -> gtk::Widget {
self.selector.widget.clone().upcast()
}
fn detach_navigator(&self) {
self.navigator.replace(None);
}
}

View file

@ -0,0 +1,201 @@
use crate::widgets::List;
use anyhow::Result;
use gettextrs::gettext;
use glib::clone;
use gtk::prelude::*;
use gtk_macros::get_widget;
use libhandy::HeaderBarExt;
use std::cell::RefCell;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
/// A screen that presents a list of items. It allows to switch between the server and the local
/// database and to search within the list.
pub struct Selector<T: 'static> {
pub widget: gtk::Box,
header: libhandy::HeaderBar,
server_check_button: gtk::CheckButton,
stack: gtk::Stack,
list: Rc<List<T>>,
back_cb: RefCell<Option<Box<dyn Fn() -> ()>>>,
add_cb: RefCell<Option<Box<dyn Fn() -> ()>>>,
load_online: RefCell<Option<Box<dyn Fn() -> Box<dyn Future<Output = Result<Vec<T>>>>>>>,
load_local: RefCell<Option<Box<dyn Fn() -> Box<dyn Future<Output = Vec<T>>>>>>,
filter: RefCell<Option<Box<dyn Fn(&str, &T) -> bool>>>,
}
impl<T> Selector<T> {
/// Create a new selector.
pub fn new() -> Rc<Self> {
// Create UI
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/selector.ui");
get_widget!(builder, gtk::Box, widget);
get_widget!(builder, libhandy::HeaderBar, header);
get_widget!(builder, gtk::Button, back_button);
get_widget!(builder, gtk::Button, add_button);
get_widget!(builder, gtk::SearchEntry, search_entry);
get_widget!(builder, gtk::CheckButton, server_check_button);
get_widget!(builder, gtk::Stack, stack);
get_widget!(builder, gtk::Frame, frame);
get_widget!(builder, gtk::Button, try_again_button);
let list = List::<T>::new(&gettext("Nothing found."));
frame.add(&list.widget);
let this = Rc::new(Self {
widget,
header,
server_check_button,
stack,
list,
back_cb: RefCell::new(None),
add_cb: RefCell::new(None),
load_online: RefCell::new(None),
load_local: RefCell::new(None),
filter: RefCell::new(None),
});
// Connect signals and callbacks
back_button.connect_clicked(clone!(@strong this => move |_| {
if let Some(cb) = &*this.back_cb.borrow() {
cb();
}
}));
add_button.connect_clicked(clone!(@strong this => move |_| {
if let Some(cb) = &*this.add_cb.borrow() {
cb();
}
}));
search_entry.connect_search_changed(clone!(@strong this => move |_| {
this.list.invalidate_filter();
}));
this.server_check_button
.connect_toggled(clone!(@strong this => move |_| {
if this.server_check_button.get_active() {
this.clone().load_online();
} else {
this.clone().load_local();
}
}));
this.list.set_filter(
clone!(@strong this, @strong search_entry => move |item: &T| {
match &*this.filter.borrow() {
Some(filter) => {
let search = search_entry.get_text().to_string().to_lowercase();
search.is_empty() || filter(&search, item)
}
None => true,
}
}),
);
try_again_button.connect_clicked(clone!(@strong this => move |_| {
this.clone().load_online();
}));
// Initialize
this.clone().load_online();
this
}
/// Set the title to be shown in the header.
pub fn set_title(&self, title: &str) {
self.header.set_title(Some(title));
}
/// Set the subtitle to be shown in the header.
pub fn set_subtitle(&self, subtitle: &str) {
self.header.set_subtitle(Some(subtitle));
}
/// Set the closure to be called when the user wants to go back.
pub fn set_back_cb<F: Fn() -> () + 'static>(&self, cb: F) {
self.back_cb.replace(Some(Box::new(cb)));
}
/// Set the closure to be called when the user wants to add an item.
pub fn set_add_cb<F: Fn() -> () + 'static>(&self, cb: F) {
self.add_cb.replace(Some(Box::new(cb)));
}
/// Set the async closure to be called to fetch items from the server. If that results in an
/// error, an error screen is shown allowing to try again.
pub fn set_load_online<F, R>(&self, cb: F)
where
F: (Fn() -> R) + 'static,
R: Future<Output = Result<Vec<T>>> + 'static,
{
self.load_online
.replace(Some(Box::new(move || Box::new(cb()))));
}
/// Set the async closure to be called to get local items.
pub fn set_load_local<F, R>(&self, cb: F)
where
F: (Fn() -> R) + 'static,
R: Future<Output = Vec<T>> + 'static,
{
self.load_local
.replace(Some(Box::new(move || Box::new(cb()))));
}
/// Set the closure to be called for creating a new list row.
pub fn set_make_widget<F: Fn(&T) -> gtk::Widget + 'static>(&self, make_widget: F) {
self.list.set_make_widget(make_widget);
}
/// Set a closure to call when deciding whether to show an item based on a search string. The
/// search string will be converted to lowercase.
pub fn set_filter<F: Fn(&str, &T) -> bool + 'static>(&self, filter: F) {
self.filter.replace(Some(Box::new(filter)));
}
/// Set the closure to be called when an item is selected.
pub fn set_selected_cb<F: Fn(&T) -> () + 'static>(&self, cb: F) {
self.list.set_selected(cb);
}
fn load_online(self: Rc<Self>) {
let context = glib::MainContext::default();
let clone = self.clone();
context.spawn_local(async move {
if let Some(cb) = &*self.load_online.borrow() {
self.stack.set_visible_child_name("loading");
match Pin::from(cb()).await {
Ok(items) => {
clone.list.show_items(items);
clone.stack.set_visible_child_name("content");
}
Err(_) => {
clone.list.show_items(Vec::new());
clone.stack.set_visible_child_name("error");
}
}
}
});
}
fn load_local(self: Rc<Self>) {
let context = glib::MainContext::default();
let clone = self.clone();
context.spawn_local(async move {
if let Some(cb) = &*self.load_local.borrow() {
self.stack.set_visible_child_name("loading");
let items = Pin::from(cb()).await;
clone.list.show_items(items);
clone.stack.set_visible_child_name("content");
}
});
}
}

View file

@ -0,0 +1,113 @@
use super::selector::Selector;
use crate::backend::Backend;
use crate::database::{Person, Work};
use crate::editors::WorkEditor;
use crate::widgets::{Navigator, NavigatorScreen};
use gettextrs::gettext;
use glib::clone;
use gtk::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;
/// A screen for selecting a work.
pub struct WorkSelector {
backend: Rc<Backend>,
person: Person,
selector: Rc<Selector<Work>>,
selected_cb: RefCell<Option<Box<dyn Fn(&Work) -> ()>>>,
navigator: RefCell<Option<Rc<Navigator>>>,
}
impl WorkSelector {
/// Create a new work selector for works by a specific composer.
pub fn new(backend: Rc<Backend>, person: Person) -> Rc<Self> {
// Create UI
let selector = Selector::<Work>::new();
selector.set_title(&gettext("Select work"));
selector.set_subtitle(&person.name_fl());
let this = Rc::new(Self {
backend,
person,
selector,
selected_cb: RefCell::new(None),
navigator: RefCell::new(None),
});
// Connect signals and callbacks
this.selector.set_back_cb(clone!(@strong this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
navigator.pop();
}
}));
this.selector.set_add_cb(clone!(@strong this => move || {
let navigator = this.navigator.borrow().clone();
if let Some(navigator) = navigator {
let editor = WorkEditor::new(this.backend.clone(), None);
editor
.set_saved_cb(clone!(@strong this => move |work| this.select(&work)));
navigator.push(editor);
}
}));
this.selector
.set_load_online(clone!(@strong this => move || {
let clone = this.clone();
async move { clone.backend.get_works(&clone.person.id).await }
}));
this.selector
.set_load_local(clone!(@strong this => move || {
let clone = this.clone();
async move { clone.backend.db().get_works(&clone.person.id).await.unwrap() }
}));
this.selector.set_make_widget(|work| {
let label = gtk::Label::new(Some(&work.title));
label.set_halign(gtk::Align::Start);
label.set_margin_start(6);
label.set_margin_end(6);
label.set_margin_top(6);
label.set_margin_bottom(6);
label.upcast()
});
this.selector
.set_filter(|search, work| work.title.to_lowercase().contains(search));
this.selector
.set_selected_cb(clone!(@strong this => move |work| this.select(work)));
this
}
/// Set the closure to be called when an item is selected.
pub fn set_selected_cb<F: Fn(&Work) -> () + 'static>(&self, cb: F) {
self.selected_cb.replace(Some(Box::new(cb)));
}
/// Select a work.
fn select(&self, work: &Work) {
if let Some(cb) = &*self.selected_cb.borrow() {
cb(&work);
}
}
}
impl NavigatorScreen for WorkSelector {
fn attach_navigator(&self, navigator: Rc<Navigator>) {
self.navigator.replace(Some(navigator));
}
fn get_widget(&self) -> gtk::Widget {
self.selector.widget.clone().upcast()
}
fn detach_navigator(&self) {
self.navigator.replace(None);
}
}

View file

@ -4,6 +4,9 @@ pub use list::*;
pub mod navigator; pub mod navigator;
pub use navigator::*; pub use navigator::*;
pub mod navigator_window;
pub use navigator_window::*;
pub mod player_bar; pub mod player_bar;
pub use player_bar::*; pub use player_bar::*;

View file

@ -10,6 +10,7 @@ pub trait NavigatorScreen {
} }
pub struct Navigator { pub struct Navigator {
pub window: gtk::Window,
pub widget: gtk::Stack, pub widget: gtk::Stack,
screens: RefCell<Vec<Rc<dyn NavigatorScreen>>>, screens: RefCell<Vec<Rc<dyn NavigatorScreen>>>,
old_screens: RefCell<Vec<Rc<dyn NavigatorScreen>>>, old_screens: RefCell<Vec<Rc<dyn NavigatorScreen>>>,
@ -17,17 +18,22 @@ pub struct Navigator {
} }
impl Navigator { impl Navigator {
pub fn new<W>(empty_screen: &W) -> Rc<Self> pub fn new<W, S>(window: &W, empty_screen: &S) -> Rc<Self>
where where
W: IsA<gtk::Widget>, W: IsA<gtk::Window>,
S: IsA<gtk::Widget>,
{ {
let widget = gtk::Stack::new(); let widget = gtk::Stack::new();
widget.set_hhomogeneous(false);
widget.set_vhomogeneous(false);
widget.set_interpolate_size(true);
widget.set_transition_type(gtk::StackTransitionType::Crossfade); widget.set_transition_type(gtk::StackTransitionType::Crossfade);
widget.set_hexpand(true); widget.set_hexpand(true);
widget.add_named(empty_screen, "empty_screen"); widget.add_named(empty_screen, "empty_screen");
widget.show(); widget.show();
let result = Rc::new(Self { let result = Rc::new(Self {
window: window.clone().upcast(),
widget, widget,
screens: RefCell::new(Vec::new()), screens: RefCell::new(Vec::new()),
old_screens: RefCell::new(Vec::new()), old_screens: RefCell::new(Vec::new()),
@ -48,7 +54,10 @@ impl Navigator {
result result
} }
pub fn set_back_cb<F>(&self, cb: F) where F: Fn() -> () + 'static { pub fn set_back_cb<F>(&self, cb: F)
where
F: Fn() -> () + 'static,
{
self.back_cb.replace(Some(Box::new(cb))); self.back_cb.replace(Some(Box::new(cb)));
} }

View file

@ -0,0 +1,42 @@
use crate::widgets::{Navigator, NavigatorScreen};
use glib::clone;
use gtk::prelude::*;
use std::rc::Rc;
/// A window hosting a navigator.
pub struct NavigatorWindow {
window: libhandy::Window,
navigator: Rc<Navigator>,
}
impl NavigatorWindow {
/// Create a new navigator window showing an initial screen.
pub fn new<S: NavigatorScreen + 'static>(initial_screen: Rc<S>) -> Rc<Self> {
// Create UI
let window = libhandy::Window::new();
window.set_default_size(600, 424);
let placeholder = gtk::Label::new(None);
let navigator = Navigator::new(&window, &placeholder);
window.add(&navigator.widget);
let this = Rc::new(Self { window, navigator });
// Connect signals and callbacks
this.navigator.set_back_cb(clone!(@strong this => move || {
this.window.close();
}));
// Initialize
this.navigator.clone().replace(initial_screen);
this
}
/// Show the navigator window.
pub fn show(&self) {
self.window.show();
}
}

View file

@ -1,5 +1,6 @@
use crate::backend::*; use crate::backend::*;
use crate::dialogs::*; use crate::dialogs::*;
use crate::editors::TracksEditor;
use crate::screens::*; use crate::screens::*;
use crate::widgets::*; use crate::widgets::*;
use futures::prelude::*; use futures::prelude::*;
@ -42,7 +43,7 @@ impl Window {
stack.add_named(&player_screen.widget, "player_screen"); stack.add_named(&player_screen.widget, "player_screen");
let poe_list = PoeList::new(backend.clone()); let poe_list = PoeList::new(backend.clone());
let navigator = Navigator::new(&empty_screen); let navigator = Navigator::new(&window, &empty_screen);
navigator.set_back_cb(clone!(@strong leaflet, @strong sidebar_box => move || { navigator.set_back_cb(clone!(@strong leaflet, @strong sidebar_box => move || {
leaflet.set_visible_child(&sidebar_box); leaflet.set_visible_child(&sidebar_box);
})); }));
@ -84,13 +85,14 @@ impl Window {
})); }));
add_button.connect_clicked(clone!(@strong result => move |_| { add_button.connect_clicked(clone!(@strong result => move |_| {
let editor = TracksEditor::new(result.backend.clone(), &result.window, None, Vec::new()); let editor = TracksEditor::new(result.backend.clone(), None, Vec::new());
editor.set_callback(clone!(@strong result => move || { editor.set_callback(clone!(@strong result => move || {
result.reload(); result.reload();
})); }));
editor.show(); let window = NavigatorWindow::new(editor);
window.show();
})); }));
result result