mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 19:57:25 +01:00
Move desktop app to subdirectory
This commit is contained in:
parent
ea3bd35ffd
commit
775f3ffe90
109 changed files with 64 additions and 53 deletions
9
musicus/.gitignore
vendored
Normal file
9
musicus/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/.flatpak-builder
|
||||
/build
|
||||
/builddir
|
||||
/flatpak
|
||||
/src/config.rs
|
||||
/src/resources.rs
|
||||
/target
|
||||
Cargo.lock
|
||||
test.sqlite
|
||||
27
musicus/Cargo.toml
Normal file
27
musicus/Cargo.toml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
[package]
|
||||
name = "musicus"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.33"
|
||||
diesel = { version = "1.4.5", features = ["sqlite"] }
|
||||
diesel_migrations = "1.4.0"
|
||||
fragile = "1.0.0"
|
||||
futures = "0.3.6"
|
||||
futures-channel = "0.3.5"
|
||||
gdk = "0.13.2"
|
||||
gettext-rs = "0.5.0"
|
||||
gio = "0.9.1"
|
||||
glib = "0.10.2"
|
||||
gtk = { version = "0.9.2", features = ["v3_24"] }
|
||||
gtk-macros = "0.2.0"
|
||||
gstreamer = "0.16.4"
|
||||
gstreamer-player = "0.16.3"
|
||||
isahc = "0.9.12"
|
||||
libhandy = "0.7.0"
|
||||
pango = "0.9.1"
|
||||
rand = "0.7.3"
|
||||
secret-service = "1.1.1"
|
||||
serde = { version = "1.0.117", features = ["derive"] }
|
||||
serde_json = "1.0.59"
|
||||
60
musicus/README.md
Normal file
60
musicus/README.md
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# Musicus
|
||||
|
||||
This is a desktop app for Musicus.
|
||||
|
||||
https://musicus.org
|
||||
|
||||
## Hacking
|
||||
|
||||
### Building
|
||||
|
||||
Musicus uses the [Meson build system](https://mesonbuild.com/). You can build
|
||||
it using the following commands:
|
||||
|
||||
```
|
||||
$ meson build
|
||||
$ ninja -C build
|
||||
```
|
||||
|
||||
Afterwards the resulting binary executable is under
|
||||
`build/target/debug/musicus`.
|
||||
|
||||
### Flatpak
|
||||
|
||||
There is a Flatpak manifest file called `de.johrpan.musicus.json`. To build a
|
||||
Flatpak you need the the latest Gnome SDK and the Freedesktop SDK with the Rust
|
||||
extension. You can install those using the following commands:
|
||||
|
||||
```
|
||||
$ flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
$ flatpak remote-add --user --if-not-exists gnome-nightly https://nightly.gnome.org/gnome-nightly.flatpakrepo
|
||||
$ flatpak install --user gnome-nightly org.gnome.Sdk org.gnome.Platform
|
||||
$ flatpak install --user flathub org.freedesktop.Sdk.Extension.rust-stable//19.08
|
||||
```
|
||||
|
||||
Afterwards, the following commands will build, install and run the application:
|
||||
|
||||
```
|
||||
$ rm -rf flatpak
|
||||
$ flatpak-builder --user --install flatpak de.johrpan.musicus.json
|
||||
$ flatpak run de.johrpan.musicus
|
||||
```
|
||||
|
||||
### Special requirements
|
||||
|
||||
This program uses [Diesel](https://diesel.rs) as its ORM. After installing
|
||||
the Diesel command line utility, you will be able to create a new schema
|
||||
migration using the following command:
|
||||
|
||||
```
|
||||
$ diesel migration generate [change_description]
|
||||
```
|
||||
|
||||
To update the `src/database/schema.rs` file, you should use the following
|
||||
command:
|
||||
|
||||
```
|
||||
$ diesel migration run --database-url test.sqlite
|
||||
```
|
||||
|
||||
This file should never be edited manually.
|
||||
24
musicus/build-aux/cargo.sh
Normal file
24
musicus/build-aux/cargo.sh
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#!/bin/sh
|
||||
|
||||
export MESON_BUILD_ROOT="$1"
|
||||
export MESON_SOURCE_ROOT="$2"
|
||||
export CARGO_TARGET_DIR="$MESON_BUILD_ROOT"/target
|
||||
export CARGO_HOME="$CARGO_TARGET_DIR"/cargo-home
|
||||
export OUTPUT="$3"
|
||||
export BUILDTYPE="$4"
|
||||
export APP_BIN="$5"
|
||||
|
||||
|
||||
if [ $BUILDTYPE = "release" ]
|
||||
then
|
||||
echo "RELEASE MODE"
|
||||
cargo build --manifest-path \
|
||||
"$MESON_SOURCE_ROOT"/Cargo.toml --release && \
|
||||
cp "$CARGO_TARGET_DIR"/release/"$APP_BIN" "$OUTPUT"
|
||||
else
|
||||
echo "DEBUG MODE"
|
||||
cargo build --manifest-path \
|
||||
"$MESON_SOURCE_ROOT"/Cargo.toml --verbose && \
|
||||
cp "$CARGO_TARGET_DIR"/debug/"$APP_BIN" "$OUTPUT"
|
||||
fi
|
||||
|
||||
21
musicus/build-aux/postinstall.py
Executable file
21
musicus/build-aux/postinstall.py
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from os import environ, path
|
||||
from subprocess import call
|
||||
|
||||
prefix = environ.get('MESON_INSTALL_PREFIX', '/usr/local')
|
||||
datadir = path.join(prefix, 'share')
|
||||
destdir = environ.get('DESTDIR', '')
|
||||
|
||||
# Package managers set this so we don't need to run
|
||||
if not destdir:
|
||||
print('Updating icon cache...')
|
||||
call(['gtk-update-icon-cache', '-qtf', path.join(datadir, 'icons', 'hicolor')])
|
||||
|
||||
print('Updating desktop database...')
|
||||
call(['update-desktop-database', '-q', path.join(datadir, 'applications')])
|
||||
|
||||
print('Compiling GSettings schemas...')
|
||||
call(['glib-compile-schemas', path.join(datadir, 'glib-2.0', 'schemas')])
|
||||
|
||||
|
||||
8
musicus/data/de.johrpan.musicus.desktop.in
Normal file
8
musicus/data/de.johrpan.musicus.desktop.in
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[Desktop Entry]
|
||||
Name=Musicus
|
||||
Icon=de.johrpan.musicus
|
||||
Exec=musicus
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=GTK;
|
||||
StartupNotify=true
|
||||
13
musicus/data/de.johrpan.musicus.gschema.xml
Normal file
13
musicus/data/de.johrpan.musicus.gschema.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<schemalist gettext-domain="musicus">
|
||||
<schema id="de.johrpan.musicus" path="/de/johrpan/musicus/">
|
||||
<key name="music-library-path" type="s">
|
||||
<default>""</default>
|
||||
<summary>Path to the music library folder</summary>
|
||||
</key>
|
||||
<key name="server-url" type="s">
|
||||
<default>"https://musicus.johrpan.de"</default>
|
||||
<summary>URL of the Musicus server to use</summary>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="128"
|
||||
height="128"
|
||||
viewBox="0 0 33.866666 33.866668"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"
|
||||
sodipodi:docname="musicus.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.6046466"
|
||||
inkscape:cx="31.109369"
|
||||
inkscape:cy="42.493912"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:document-rotation="0"
|
||||
showgrid="true"
|
||||
inkscape:window-width="1396"
|
||||
inkscape:window-height="1016"
|
||||
inkscape:window-x="50"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="0"
|
||||
units="px">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid851"
|
||||
spacingx="1.0583333"
|
||||
spacingy="1.0583333"
|
||||
empspacing="4" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Ebene 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<circle
|
||||
style="fill:#303030;fill-opacity:1;stroke:none;stroke-width:0.2;stroke-opacity:0.5;opacity:1"
|
||||
id="path853"
|
||||
cx="16.933332"
|
||||
cy="16.933332"
|
||||
r="15.874999" />
|
||||
<path
|
||||
d="M 13.553183,9.5568091 C 13.056762,12.40878 12.41526,15.765311 11.641666,19.579166 11.112514,19.117684 10.363554,18.82791 9.5303854,18.82791 c -1.6023905,0 -2.9013699,1.06575 -2.9013699,2.380533 0,1.314781 1.2989794,2.380531 2.9013699,2.380531 1.6023916,0 2.6414696,-0.72268 2.9013706,-2.380531 0.371458,-2.606532 0.693542,-4.484063 1.272743,-8.053734 h 0.08454 c 0.97349,2.045774 1.97525,4.277025 3.004977,6.691673 l 1.051893,-2.149423 c -1.146154,-2.433005 -2.36032,-5.134786 -3.650597,-8.1403239 z m 9.830992,0 c -1.922644,3.9360329 -4.028599,8.2312419 -5.872099,11.9996389 0.214174,0.478841 0.213832,0.473733 0.706271,0.806455 1.391414,-3.185731 2.821015,-6.23621 4.287978,-9.15126 l 0.07567,0.0096 c 0.497372,3.746054 0.852824,6.812033 1.066876,9.198452 0.195174,-0.03778 0.513002,-0.05658 0.953701,-0.05658 0.52257,0 0.881268,0.01883 1.076442,0.05658 -0.705145,-4.230869 -1.308981,-8.518284 -1.812675,-12.8625029 z"
|
||||
id="path4"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffc107;fill-opacity:1;stroke-width:0.349035" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3 KiB |
|
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 4.2333332 4.2333332"
|
||||
version="1.1"
|
||||
id="svg2197"
|
||||
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"
|
||||
sodipodi:docname="musicus_symbolic.svg">
|
||||
<defs
|
||||
id="defs2191" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="31.083523"
|
||||
inkscape:cx="9.8959183"
|
||||
inkscape:cy="9.0432761"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:document-rotation="0"
|
||||
showgrid="true"
|
||||
inkscape:window-width="1396"
|
||||
inkscape:window-height="1016"
|
||||
inkscape:window-x="236"
|
||||
inkscape:window-y="185"
|
||||
inkscape:window-maximized="0"
|
||||
units="px">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid2786"
|
||||
spacingx="0.52916665"
|
||||
spacingy="0.52916665"
|
||||
empspacing="2" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata2194">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Ebene 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
d="M 1.6109484,0.75244585 C 1.5144212,1.3069954 1.3896858,1.9596541 1.2392649,2.7012366 1.1363746,2.6115036 0.99074332,2.5551592 0.82873813,2.5551592 c -0.31157571,0 -0.56415494,0.207229 -0.56415494,0.4628812 0,0.2556517 0.25257923,0.4628809 0.56415494,0.4628809 0.31157607,0 0.51361887,-0.1405212 0.56415527,-0.4628809 C 1.4651224,2.511215 1.5277477,2.1461397 1.6403711,1.4520373 h 0.016433 c 0.1892954,0.397789 0.3840822,0.8316432 0.5843067,1.3011582 L 2.4456455,2.3352522 C 2.2227823,1.8621681 1.9866947,1.3368218 1.7358073,0.7524119 Z m 1.9115815,0 C 3.1486824,1.5177853 2.7391913,2.3529645 2.380733,3.0857081 2.42238,3.1788171 2.422311,3.1778251 2.5180621,3.2425184 2.7886154,2.6230712 3.0665935,2.0299227 3.3518361,1.4631079 l 0.014716,0.00186 c 0.09671,0.7283991 0.1658266,1.3245612 0.2074481,1.7885873 0.037951,-0.00736 0.09975,-0.011014 0.1854421,-0.011014 0.1016105,0 0.1713582,0.00368 0.2093077,0.011014 C 3.8316365,2.4308883 3.7142245,1.5972244 3.6162839,0.7525155 Z"
|
||||
id="path4"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#000000;fill-opacity:1;stroke-width:0.0678681" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
41
musicus/data/meson.build
Normal file
41
musicus/data/meson.build
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
datadir = get_option('datadir')
|
||||
|
||||
scalable_dir = join_paths('icons', 'hicolor', 'scalable', 'apps')
|
||||
install_data(
|
||||
join_paths(scalable_dir, 'de.johrpan.musicus.svg'),
|
||||
install_dir: join_paths(datadir, scalable_dir),
|
||||
)
|
||||
|
||||
symbolic_dir = join_paths('icons', 'hicolor', 'symbolic', 'apps')
|
||||
install_data(
|
||||
join_paths(symbolic_dir, 'de.johrpan.musicus-symbolic.svg'),
|
||||
install_dir: join_paths(datadir, symbolic_dir),
|
||||
)
|
||||
|
||||
|
||||
desktop_file = i18n.merge_file(
|
||||
input: 'de.johrpan.musicus.desktop.in',
|
||||
output: 'de.johrpan.musicus.desktop',
|
||||
type: 'desktop',
|
||||
po_dir: '../po',
|
||||
install: true,
|
||||
install_dir: join_paths(datadir, 'applications')
|
||||
)
|
||||
|
||||
desktop_utils = find_program('desktop-file-validate', required: false)
|
||||
if desktop_utils.found()
|
||||
test('Validate desktop file', desktop_utils,
|
||||
args: [desktop_file]
|
||||
)
|
||||
endif
|
||||
|
||||
install_data('de.johrpan.musicus.gschema.xml',
|
||||
install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas')
|
||||
)
|
||||
|
||||
compile_schemas = find_program('glib-compile-schemas', required: false)
|
||||
if compile_schemas.found()
|
||||
test('Validate schema file', compile_schemas,
|
||||
args: ['--strict', '--dry-run', meson.current_source_dir()]
|
||||
)
|
||||
endif
|
||||
53
musicus/de.johrpan.musicus.json
Normal file
53
musicus/de.johrpan.musicus.json
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"app-id" : "de.johrpan.musicus",
|
||||
"runtime" : "org.gnome.Platform",
|
||||
"runtime-version" : "master",
|
||||
"sdk" : "org.gnome.Sdk",
|
||||
"sdk-extensions" : [
|
||||
"org.freedesktop.Sdk.Extension.rust-stable"
|
||||
],
|
||||
"command" : "musicus",
|
||||
"finish-args" : [
|
||||
"--share=network",
|
||||
"--share=ipc",
|
||||
"--socket=x11",
|
||||
"--socket=wayland",
|
||||
"--socket=pulseaudio",
|
||||
"--filesystem=host"
|
||||
],
|
||||
"build-options" : {
|
||||
"append-path" : "/usr/lib/sdk/rust-stable/bin",
|
||||
"build-args" : [
|
||||
"--share=network"
|
||||
],
|
||||
"env" : {
|
||||
"CARGO_HOME" : "/run/build/musicus/cargo",
|
||||
"RUST_BACKTRACE" : "1",
|
||||
"RUST_LOG" : "musicus=debug"
|
||||
}
|
||||
},
|
||||
"cleanup" : [
|
||||
"/include",
|
||||
"/lib/pkgconfig",
|
||||
"/man",
|
||||
"/share/doc",
|
||||
"/share/gtk-doc",
|
||||
"/share/man",
|
||||
"/share/pkgconfig",
|
||||
"*.la",
|
||||
"*.a"
|
||||
],
|
||||
"modules" : [
|
||||
{
|
||||
"name" : "musicus",
|
||||
"builddir" : true,
|
||||
"buildsystem" : "meson",
|
||||
"sources" : [
|
||||
{
|
||||
"type" : "git",
|
||||
"url" : "."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
2
musicus/diesel.toml
Normal file
2
musicus/diesel.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[print_schema]
|
||||
file = "src/database/schema.rs"
|
||||
24
musicus/meson.build
Normal file
24
musicus/meson.build
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
project('musicus', 'rust',
|
||||
version: '0.1.0',
|
||||
meson_version: '>= 0.50.0',
|
||||
license: 'AGPLv3+',
|
||||
)
|
||||
|
||||
dependency('dbus-1', version: '>= 1.3')
|
||||
dependency('glib-2.0', version: '>= 2.56')
|
||||
dependency('gio-2.0', version: '>= 2.56')
|
||||
dependency('gstreamer-1.0', version: '>= 1.12')
|
||||
dependency('gtk+-3.0', version: '>= 3.24.7')
|
||||
dependency('libcurl', version: '>= 7.24.0')
|
||||
dependency('libhandy-1', version: '>= 1.0.0')
|
||||
dependency('pango', version: '>= 1.0')
|
||||
dependency('sqlite3', version: '>= 3.20')
|
||||
|
||||
i18n = import('i18n')
|
||||
|
||||
subdir('data')
|
||||
subdir('po')
|
||||
subdir('res')
|
||||
subdir('src')
|
||||
|
||||
meson.add_install_script('build-aux/postinstall.py')
|
||||
21
musicus/migrations/2020-09-27-201047_initial_schema/down.sql
Normal file
21
musicus/migrations/2020-09-27-201047_initial_schema/down.sql
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
DROP TABLE persons;
|
||||
|
||||
DROP TABLE instruments;
|
||||
|
||||
DROP TABLE works;
|
||||
|
||||
DROP TABLE instrumentations;
|
||||
|
||||
DROP TABLE work_parts;
|
||||
|
||||
DROP TABLE part_instrumentations;
|
||||
|
||||
DROP TABLE work_sections;
|
||||
|
||||
DROP TABLE ensembles;
|
||||
|
||||
DROP TABLE recordings;
|
||||
|
||||
DROP TABLE performances;
|
||||
|
||||
DROP TABLE tracks;
|
||||
70
musicus/migrations/2020-09-27-201047_initial_schema/up.sql
Normal file
70
musicus/migrations/2020-09-27-201047_initial_schema/up.sql
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
CREATE TABLE persons (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
first_name TEXT NOT NULL,
|
||||
last_name TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE instruments (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE works (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
composer BIGINT NOT NULL REFERENCES persons(id),
|
||||
title TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE instrumentations (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
work BIGINT NOT NULL REFERENCES works(id) ON DELETE CASCADE,
|
||||
instrument BIGINT NOT NULL REFERENCES instruments(id)
|
||||
);
|
||||
|
||||
CREATE TABLE work_parts (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
work BIGINT NOT NULL REFERENCES works(id) ON DELETE CASCADE,
|
||||
part_index BIGINT NOT NULL,
|
||||
composer BIGINT REFERENCES persons(id),
|
||||
title TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE part_instrumentations (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
work_part BIGINT NOT NULL REFERENCES works(id) ON DELETE CASCADE,
|
||||
instrument BIGINT NOT NULL REFERENCES instruments(id)
|
||||
);
|
||||
|
||||
CREATE TABLE work_sections (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
work BIGINT NOT NULL REFERENCES works(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL,
|
||||
before_index BIGINT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE ensembles (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE recordings (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
work BIGINT NOT NULL REFERENCES works(id),
|
||||
comment TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE performances (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
recording BIGINT NOT NULL REFERENCES recordings(id) ON DELETE CASCADE,
|
||||
person BIGINT REFERENCES persons(id),
|
||||
ensemble BIGINT REFERENCES ensembles(id),
|
||||
role BIGINT REFERENCES instruments(id)
|
||||
);
|
||||
|
||||
CREATE TABLE tracks (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
file_name TEXT NOT NULL,
|
||||
recording BIGINT NOT NULL REFERENCES recordings(id),
|
||||
track_index INTEGER NOT NULL,
|
||||
work_parts TEXT NOT NULL
|
||||
);
|
||||
1
musicus/po/LINGUAS
Normal file
1
musicus/po/LINGUAS
Normal file
|
|
@ -0,0 +1 @@
|
|||
de
|
||||
77
musicus/po/POTFILES.in
Normal file
77
musicus/po/POTFILES.in
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
res/ui/ensemble_editor.ui
|
||||
res/ui/ensemble_screen.ui
|
||||
res/ui/ensemble_selector.ui
|
||||
res/ui/instrument_editor.ui
|
||||
res/ui/instrument_selector.ui
|
||||
res/ui/part_editor.ui
|
||||
res/ui/performance_editor.ui
|
||||
res/ui/person_editor.ui
|
||||
res/ui/person_list.ui
|
||||
res/ui/person_screen.ui
|
||||
res/ui/person_selector.ui
|
||||
res/ui/player_bar.ui
|
||||
res/ui/player_screen.ui
|
||||
res/ui/poe_list.ui
|
||||
res/ui/preferences.ui
|
||||
res/ui/recording_editor.ui
|
||||
res/ui/recording_screen.ui
|
||||
res/ui/recording_selector_screen.ui
|
||||
res/ui/recording_selector.ui
|
||||
res/ui/section_editor.ui
|
||||
res/ui/track_editor.ui
|
||||
res/ui/tracks_editor.ui
|
||||
res/ui/window.ui
|
||||
res/ui/work_editor.ui
|
||||
res/ui/work_screen.ui
|
||||
res/ui/work_selector.ui
|
||||
res/ui/work_selector_screen.ui
|
||||
|
||||
src/database/database.rs
|
||||
src/database/models.rs
|
||||
src/database/mod.rs
|
||||
src/database/schema.rs
|
||||
src/database/tables.rs
|
||||
src/dialogs/about.rs
|
||||
src/dialogs/ensemble_editor.rs
|
||||
src/dialogs/ensemble_selector.rs
|
||||
src/dialogs/instrument_editor.rs
|
||||
src/dialogs/instrument_selector.rs
|
||||
src/dialogs/mod.rs
|
||||
src/dialogs/person_editor.rs
|
||||
src/dialogs/person_selector.rs
|
||||
src/dialogs/preferences.rs
|
||||
src/dialogs/recording/mod.rs
|
||||
src/dialogs/recording/performance_editor.rs
|
||||
src/dialogs/recording/recording_dialog.rs
|
||||
src/dialogs/recording/recording_editor_dialog.rs
|
||||
src/dialogs/recording/recording_editor.rs
|
||||
src/dialogs/recording/recording_selector_person_screen.rs
|
||||
src/dialogs/recording/recording_selector.rs
|
||||
src/dialogs/recording/recording_selector_work_screen.rs
|
||||
src/dialogs/track_editor.rs
|
||||
src/dialogs/tracks_editor.rs
|
||||
src/dialogs/work/mod.rs
|
||||
src/dialogs/work/part_editor.rs
|
||||
src/dialogs/work/section_editor.rs
|
||||
src/dialogs/work/work_dialog.rs
|
||||
src/dialogs/work/work_editor_dialog.rs
|
||||
src/dialogs/work/work_editor.rs
|
||||
src/dialogs/work/work_selector_person_screen.rs
|
||||
src/dialogs/work/work_selector.rs
|
||||
src/screens/ensemble_screen.rs
|
||||
src/screens/mod.rs
|
||||
src/screens/person_screen.rs
|
||||
src/screens/player_screen.rs
|
||||
src/screens/recording_screen.rs
|
||||
src/screens/work_screen.rs
|
||||
src/widgets/list.rs
|
||||
src/widgets/mod.rs
|
||||
src/widgets/navigator.rs
|
||||
src/widgets/person_list.rs
|
||||
src/widgets/player_bar.rs
|
||||
src/widgets/poe_list.rs
|
||||
src/widgets/selector_row.rs
|
||||
src/backend.rs
|
||||
src/main.rs
|
||||
src/player.rs
|
||||
src/window.rs
|
||||
387
musicus/po/de.po
Normal file
387
musicus/po/de.po
Normal file
|
|
@ -0,0 +1,387 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-11-08 20:12+0100\n"
|
||||
"PO-Revision-Date: 2020-11-08 20:13+0100\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: de\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.4.1\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: res/ui/ensemble_editor.ui:20 res/ui/performance_editor.ui:196
|
||||
#: res/ui/performance_editor.ui:227
|
||||
msgid "Ensemble"
|
||||
msgstr "Ensemble"
|
||||
|
||||
#: res/ui/ensemble_editor.ui:23 res/ui/instrument_editor.ui:23
|
||||
#: res/ui/part_editor.ui:25 res/ui/performance_editor.ui:23
|
||||
#: res/ui/person_editor.ui:23 res/ui/recording_editor.ui:17
|
||||
#: res/ui/section_editor.ui:23 res/ui/track_editor.ui:24
|
||||
#: res/ui/tracks_editor.ui:39 res/ui/work_editor.ui:17
|
||||
msgid "Cancel"
|
||||
msgstr "Abbrechen"
|
||||
|
||||
#: res/ui/ensemble_editor.ui:31 res/ui/instrument_editor.ui:31
|
||||
#: res/ui/part_editor.ui:33 res/ui/performance_editor.ui:31
|
||||
#: res/ui/person_editor.ui:31 res/ui/recording_editor.ui:25
|
||||
#: res/ui/section_editor.ui:31 res/ui/track_editor.ui:32
|
||||
#: res/ui/tracks_editor.ui:24 res/ui/work_editor.ui:25
|
||||
msgid "Save"
|
||||
msgstr "Speichern"
|
||||
|
||||
#: res/ui/ensemble_editor.ui:64 res/ui/instrument_editor.ui:64
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
#: res/ui/ensemble_screen.ui:90 res/ui/work_screen.ui:90
|
||||
msgid "Search recordings …"
|
||||
msgstr "Aufnahmen durchsuchen …"
|
||||
|
||||
#: res/ui/ensemble_screen.ui:145 res/ui/person_screen.ui:196
|
||||
#: res/ui/work_screen.ui:145
|
||||
msgid "Recordings"
|
||||
msgstr "Aufnahmen"
|
||||
|
||||
#: res/ui/ensemble_screen.ui:188 res/ui/work_screen.ui:188
|
||||
#: src/dialogs/recording/recording_selector_work_screen.rs:38
|
||||
#: src/screens/ensemble_screen.rs:53 src/screens/person_screen.rs:77
|
||||
#: src/screens/work_screen.rs:54
|
||||
msgid "No recordings found."
|
||||
msgstr "Keine Aufnahmen gefunden."
|
||||
|
||||
#: res/ui/ensemble_selector.ui:22
|
||||
msgid "Select ensemble"
|
||||
msgstr "Ensemble auswählen"
|
||||
|
||||
#: res/ui/ensemble_selector.ui:84
|
||||
msgid "No ensembles found."
|
||||
msgstr "Keine Ensembles gefunden."
|
||||
|
||||
#: res/ui/instrument_editor.ui:20
|
||||
msgid "Instrument"
|
||||
msgstr "Instrument"
|
||||
|
||||
#: res/ui/instrument_selector.ui:22
|
||||
msgid "Select instrument"
|
||||
msgstr "Instrument auswählen"
|
||||
|
||||
#: res/ui/instrument_selector.ui:84
|
||||
msgid "No instruments found."
|
||||
msgstr "Keine Instrumente gefunden."
|
||||
|
||||
#: res/ui/part_editor.ui:22
|
||||
msgid "Work part"
|
||||
msgstr "Werkabschnitt"
|
||||
|
||||
#: res/ui/part_editor.ui:70 res/ui/work_editor.ui:84
|
||||
msgid "Composer"
|
||||
msgstr "Komponist"
|
||||
|
||||
#: res/ui/part_editor.ui:93 res/ui/player_bar.ui:118
|
||||
#: res/ui/player_screen.ui:160 res/ui/section_editor.ui:64
|
||||
#: res/ui/work_editor.ui:106
|
||||
msgid "Title"
|
||||
msgstr "Titel"
|
||||
|
||||
#: res/ui/part_editor.ui:116 res/ui/performance_editor.ui:87
|
||||
#: res/ui/performance_editor.ui:170 res/ui/performance_editor.ui:214
|
||||
#: res/ui/recording_editor.ui:81 res/ui/tracks_editor.ui:93
|
||||
#: res/ui/work_editor.ui:69 src/dialogs/recording/performance_editor.rs:150
|
||||
#: src/dialogs/recording/performance_editor.rs:160
|
||||
#: src/dialogs/recording/performance_editor.rs:170
|
||||
#: src/dialogs/work/part_editor.rs:166
|
||||
msgid "Select …"
|
||||
msgstr "Auswählen …"
|
||||
|
||||
#: res/ui/part_editor.ui:159 res/ui/recording_editor.ui:119
|
||||
#: res/ui/work_editor.ui:119
|
||||
msgid "Overview"
|
||||
msgstr "Überblick"
|
||||
|
||||
#: res/ui/part_editor.ui:247 res/ui/work_editor.ui:207
|
||||
msgid "Instruments"
|
||||
msgstr "Instrumente"
|
||||
|
||||
#: res/ui/performance_editor.ui:20
|
||||
msgid "Performance"
|
||||
msgstr "Auftritt"
|
||||
|
||||
#: res/ui/performance_editor.ui:64
|
||||
msgid "Role"
|
||||
msgstr "Rolle"
|
||||
|
||||
#: res/ui/performance_editor.ui:128
|
||||
msgid "Type"
|
||||
msgstr "Typ"
|
||||
|
||||
#: res/ui/performance_editor.ui:151 res/ui/performance_editor.ui:183
|
||||
#: res/ui/person_editor.ui:20
|
||||
msgid "Person"
|
||||
msgstr "Person"
|
||||
|
||||
#: res/ui/person_editor.ui:64
|
||||
msgid "First name"
|
||||
msgstr "Vorname"
|
||||
|
||||
#: res/ui/person_editor.ui:87
|
||||
msgid "Last name"
|
||||
msgstr "Nachname"
|
||||
|
||||
#: res/ui/person_list.ui:28
|
||||
msgid "Search persons …"
|
||||
msgstr "Personen durchsuchen …"
|
||||
|
||||
#: res/ui/person_screen.ui:90
|
||||
msgid "Search works and recordings …"
|
||||
msgstr "Werke und Aufnahmen durchsuchen …"
|
||||
|
||||
#: res/ui/person_screen.ui:151
|
||||
msgid "Works"
|
||||
msgstr "Werke"
|
||||
|
||||
#: res/ui/person_screen.ui:246
|
||||
msgid "No works or recordings found."
|
||||
msgstr "Keine Werke oder Aufnahmen gefunden."
|
||||
|
||||
#: res/ui/person_selector.ui:22
|
||||
msgid "Select person"
|
||||
msgstr "Person auswählen"
|
||||
|
||||
#: res/ui/player_bar.ui:135 res/ui/player_screen.ui:177
|
||||
msgid "Subtitle"
|
||||
msgstr "Untertitel"
|
||||
|
||||
#: res/ui/player_bar.ui:181 res/ui/player_bar.ui:205
|
||||
#: res/ui/player_screen.ui:229 res/ui/player_screen.ui:257
|
||||
msgid "0:00"
|
||||
msgstr "0:00"
|
||||
|
||||
#: res/ui/player_bar.ui:193
|
||||
msgid "/"
|
||||
msgstr "/"
|
||||
|
||||
#: res/ui/player_screen.ui:24
|
||||
msgid "Player"
|
||||
msgstr "Wiedergabe"
|
||||
|
||||
#: res/ui/poe_list.ui:28
|
||||
msgid "Search persons and ensembles …"
|
||||
msgstr "Personen und Ensembles durchsuchen …"
|
||||
|
||||
#: res/ui/preferences.ui:16
|
||||
msgid "General"
|
||||
msgstr "Allgemein"
|
||||
|
||||
#: res/ui/preferences.ui:21
|
||||
msgid "Music library"
|
||||
msgstr "Musikbibliothek"
|
||||
|
||||
#: res/ui/preferences.ui:27
|
||||
msgid "Music library folder"
|
||||
msgstr "Ordner der Musikbibliothek"
|
||||
|
||||
#: res/ui/preferences.ui:29
|
||||
msgid "None selected"
|
||||
msgstr "Keiner ausgewählt"
|
||||
|
||||
#: res/ui/preferences.ui:32
|
||||
msgid "Select"
|
||||
msgstr "Auswählen"
|
||||
|
||||
#: res/ui/recording_editor.ui:14 res/ui/tracks_editor.ui:68
|
||||
msgid "Recording"
|
||||
msgstr "Aufnahme"
|
||||
|
||||
#: res/ui/recording_editor.ui:63
|
||||
msgid "Comment"
|
||||
msgstr "Kommentar"
|
||||
|
||||
#: res/ui/recording_editor.ui:106 res/ui/tracks_editor.ui:109
|
||||
#: res/ui/work_editor.ui:14
|
||||
msgid "Work"
|
||||
msgstr "Werk"
|
||||
|
||||
#: res/ui/recording_editor.ui:226 res/ui/tracks_editor.ui:127
|
||||
msgid "Performers"
|
||||
msgstr "Interpreten"
|
||||
|
||||
#: res/ui/recording_screen.ui:98 res/ui/tracks_editor.ui:21
|
||||
msgid "Tracks"
|
||||
msgstr "Tracks"
|
||||
|
||||
#: res/ui/recording_screen.ui:127
|
||||
msgid "Add to playlist"
|
||||
msgstr "Zur Wiedergabeliste hinzufügen"
|
||||
|
||||
#: res/ui/recording_selector.ui:27 res/ui/work_selector.ui:26
|
||||
msgid "Select a composer on the left."
|
||||
msgstr "Wählen Sie einen Komponisten aus."
|
||||
|
||||
#: res/ui/recording_selector.ui:51
|
||||
msgid "Select a recording"
|
||||
msgstr "Aufnahme auswählen"
|
||||
|
||||
#: res/ui/section_editor.ui:20
|
||||
msgid "Work section"
|
||||
msgstr "Werkteil"
|
||||
|
||||
#: res/ui/track_editor.ui:21
|
||||
msgid "Track"
|
||||
msgstr "Track"
|
||||
|
||||
#: res/ui/track_editor.ui:70
|
||||
msgid "Select a recording of a work with multiple parts."
|
||||
msgstr "Wählen Sie eine Aufnahme eines mehrteiligen Werks aus."
|
||||
|
||||
#: res/ui/window.ui:51 res/ui/window.ui:141
|
||||
msgid "Welcome to Musicus!"
|
||||
msgstr "Willkommen bei Musicus!"
|
||||
|
||||
#: res/ui/window.ui:67
|
||||
msgid ""
|
||||
"Get startet by selecting something from the sidebar or adding new things to "
|
||||
"your library using the button in the top left corner."
|
||||
msgstr ""
|
||||
"Legen Sie los, indem Sie etwas in der Seitenleiste auswählen oder fügen Sie "
|
||||
"mit dem Knopf oben links neue Aufnahmen zu Ihrer Musikbibliothek hinzu."
|
||||
|
||||
#: res/ui/window.ui:104 res/ui/window.ui:252 src/dialogs/about.rs:10
|
||||
msgid "Musicus"
|
||||
msgstr "Musicus"
|
||||
|
||||
#: res/ui/window.ui:157
|
||||
msgid ""
|
||||
"Get startet by selecting the folder containing your music files! Musicus "
|
||||
"will create a new database there or open one that already exists."
|
||||
msgstr ""
|
||||
"Wählen Sie als Erstes den Ordner aus, worin sich Ihre Musik befindet. "
|
||||
"Musicus wird dort eine neue Datenbank anlegen oder eine bereits existierende "
|
||||
"öffnen."
|
||||
|
||||
#: res/ui/window.ui:170
|
||||
msgid "Select folder"
|
||||
msgstr "Ordner auswählen"
|
||||
|
||||
#: res/ui/window.ui:331
|
||||
msgid "Preferences"
|
||||
msgstr "Einstellungen"
|
||||
|
||||
#: res/ui/window.ui:335
|
||||
msgid "About Musicus"
|
||||
msgstr "Über Musicus"
|
||||
|
||||
#: res/ui/work_editor.ui:373
|
||||
msgid "Structure"
|
||||
msgstr "Struktur"
|
||||
|
||||
#: res/ui/work_selector.ui:51
|
||||
msgid "Select a work"
|
||||
msgstr "Werk auswählen"
|
||||
|
||||
#: src/dialogs/about.rs:12
|
||||
msgid "The classical music player and organizer."
|
||||
msgstr "Das Programm zum Abspielen und Organisieren von Klassik."
|
||||
|
||||
#: src/dialogs/about.rs:14
|
||||
msgid "Further information and source code"
|
||||
msgstr "Weitere Informationen und Quellcode"
|
||||
|
||||
#: src/dialogs/preferences.rs:30 src/window.rs:70
|
||||
msgid "Select music library folder"
|
||||
msgstr "Ordner der Musikbibliothek auswählen"
|
||||
|
||||
#: src/dialogs/recording/recording_editor.rs:54
|
||||
msgid "No performers added."
|
||||
msgstr "Keine Interpreten hinzugefügt."
|
||||
|
||||
#: src/dialogs/recording/recording_selector_person_screen.rs:39
|
||||
#: src/dialogs/work/work_selector_person_screen.rs:36
|
||||
#: src/screens/person_screen.rs:57
|
||||
msgid "No works found."
|
||||
msgstr "Keine Werke gefunden."
|
||||
|
||||
#: src/dialogs/tracks_editor.rs:60
|
||||
msgid "Add some tracks."
|
||||
msgstr "Fügen Sie Tracks hinzu."
|
||||
|
||||
#: src/dialogs/tracks_editor.rs:118
|
||||
msgid "Select audio files"
|
||||
msgstr "Audiodateien auswählen"
|
||||
|
||||
#: src/dialogs/tracks_editor.rs:236 src/screens/player_screen.rs:230
|
||||
#: src/screens/recording_screen.rs:79
|
||||
msgid "Unknown"
|
||||
msgstr "Unbekannt"
|
||||
|
||||
#: src/dialogs/work/part_editor.rs:49 src/dialogs/work/work_editor.rs:69
|
||||
msgid "No instruments added."
|
||||
msgstr "Keine Instrumente hinzugefügt."
|
||||
|
||||
#: src/dialogs/work/work_editor.rs:72
|
||||
msgid "No work parts added."
|
||||
msgstr "Keine Werkabschnitte hinzugefügt."
|
||||
|
||||
#: src/screens/ensemble_screen.rs:35
|
||||
msgid "Edit ensemble"
|
||||
msgstr "Ensemble bearbeiten"
|
||||
|
||||
#: src/screens/ensemble_screen.rs:41
|
||||
msgid "Delete ensemble"
|
||||
msgstr "Ensemble löschen"
|
||||
|
||||
#: src/screens/person_screen.rs:39
|
||||
msgid "Edit person"
|
||||
msgstr "Person bearbeiten"
|
||||
|
||||
#: src/screens/person_screen.rs:45
|
||||
msgid "Delete person"
|
||||
msgstr "Person löschen"
|
||||
|
||||
#: src/screens/recording_screen.rs:36
|
||||
msgid "Edit recording"
|
||||
msgstr "Aufnahme bearbeiten"
|
||||
|
||||
#: src/screens/recording_screen.rs:42
|
||||
msgid "Delete recording"
|
||||
msgstr "Aufnahme löschen"
|
||||
|
||||
#: src/screens/recording_screen.rs:48
|
||||
msgid "Edit tracks"
|
||||
msgstr "Tracks bearbeiten"
|
||||
|
||||
#: src/screens/recording_screen.rs:54
|
||||
msgid "Delete tracks"
|
||||
msgstr "Tracks löschen"
|
||||
|
||||
#: src/screens/recording_screen.rs:69
|
||||
msgid "No tracks found."
|
||||
msgstr "Keine Tracks gefunden."
|
||||
|
||||
#: src/screens/work_screen.rs:36
|
||||
msgid "Edit work"
|
||||
msgstr "Werk bearbeiten"
|
||||
|
||||
#: src/screens/work_screen.rs:42
|
||||
msgid "Delete work"
|
||||
msgstr "Werk löschen"
|
||||
|
||||
#: src/widgets/person_list.rs:26
|
||||
msgid "No persons found."
|
||||
msgstr "Keine Personen gefunden."
|
||||
|
||||
#: src/widgets/poe_list.rs:41
|
||||
msgid "No persons or ensembles found."
|
||||
msgstr "Keine Personen oder Ensembles gefunden."
|
||||
|
||||
#~ msgid "Search works …"
|
||||
#~ msgstr "Werke durchsuchen …"
|
||||
1
musicus/po/meson.build
Normal file
1
musicus/po/meson.build
Normal file
|
|
@ -0,0 +1 @@
|
|||
i18n.gettext('musicus', preset: 'glib')
|
||||
378
musicus/po/musicus.pot
Normal file
378
musicus/po/musicus.pot
Normal file
|
|
@ -0,0 +1,378 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the musicus package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: musicus\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-11-08 20:12+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: res/ui/ensemble_editor.ui:20 res/ui/performance_editor.ui:196
|
||||
#: res/ui/performance_editor.ui:227
|
||||
msgid "Ensemble"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/ensemble_editor.ui:23 res/ui/instrument_editor.ui:23
|
||||
#: res/ui/part_editor.ui:25 res/ui/performance_editor.ui:23
|
||||
#: res/ui/person_editor.ui:23 res/ui/recording_editor.ui:17
|
||||
#: res/ui/section_editor.ui:23 res/ui/track_editor.ui:24
|
||||
#: res/ui/tracks_editor.ui:39 res/ui/work_editor.ui:17
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/ensemble_editor.ui:31 res/ui/instrument_editor.ui:31
|
||||
#: res/ui/part_editor.ui:33 res/ui/performance_editor.ui:31
|
||||
#: res/ui/person_editor.ui:31 res/ui/recording_editor.ui:25
|
||||
#: res/ui/section_editor.ui:31 res/ui/track_editor.ui:32
|
||||
#: res/ui/tracks_editor.ui:24 res/ui/work_editor.ui:25
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/ensemble_editor.ui:64 res/ui/instrument_editor.ui:64
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/ensemble_screen.ui:90 res/ui/work_screen.ui:90
|
||||
msgid "Search recordings …"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/ensemble_screen.ui:145 res/ui/person_screen.ui:196
|
||||
#: res/ui/work_screen.ui:145
|
||||
msgid "Recordings"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/ensemble_screen.ui:188 res/ui/work_screen.ui:188
|
||||
#: src/dialogs/recording/recording_selector_work_screen.rs:38
|
||||
#: src/screens/ensemble_screen.rs:53 src/screens/person_screen.rs:77
|
||||
#: src/screens/work_screen.rs:54
|
||||
msgid "No recordings found."
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/ensemble_selector.ui:22
|
||||
msgid "Select ensemble"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/ensemble_selector.ui:84
|
||||
msgid "No ensembles found."
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/instrument_editor.ui:20
|
||||
msgid "Instrument"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/instrument_selector.ui:22
|
||||
msgid "Select instrument"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/instrument_selector.ui:84
|
||||
msgid "No instruments found."
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/part_editor.ui:22
|
||||
msgid "Work part"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/part_editor.ui:70 res/ui/work_editor.ui:84
|
||||
msgid "Composer"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/part_editor.ui:93 res/ui/player_bar.ui:118
|
||||
#: res/ui/player_screen.ui:160 res/ui/section_editor.ui:64
|
||||
#: res/ui/work_editor.ui:106
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/part_editor.ui:116 res/ui/performance_editor.ui:87
|
||||
#: res/ui/performance_editor.ui:170 res/ui/performance_editor.ui:214
|
||||
#: res/ui/recording_editor.ui:81 res/ui/tracks_editor.ui:93
|
||||
#: res/ui/work_editor.ui:69 src/dialogs/recording/performance_editor.rs:150
|
||||
#: src/dialogs/recording/performance_editor.rs:160
|
||||
#: src/dialogs/recording/performance_editor.rs:170
|
||||
#: src/dialogs/work/part_editor.rs:166
|
||||
msgid "Select …"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/part_editor.ui:159 res/ui/recording_editor.ui:119
|
||||
#: res/ui/work_editor.ui:119
|
||||
msgid "Overview"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/part_editor.ui:247 res/ui/work_editor.ui:207
|
||||
msgid "Instruments"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/performance_editor.ui:20
|
||||
msgid "Performance"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/performance_editor.ui:64
|
||||
msgid "Role"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/performance_editor.ui:128
|
||||
msgid "Type"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/performance_editor.ui:151 res/ui/performance_editor.ui:183
|
||||
#: res/ui/person_editor.ui:20
|
||||
msgid "Person"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/person_editor.ui:64
|
||||
msgid "First name"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/person_editor.ui:87
|
||||
msgid "Last name"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/person_list.ui:28
|
||||
msgid "Search persons …"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/person_screen.ui:90
|
||||
msgid "Search works and recordings …"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/person_screen.ui:151
|
||||
msgid "Works"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/person_screen.ui:246
|
||||
msgid "No works or recordings found."
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/person_selector.ui:22
|
||||
msgid "Select person"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/player_bar.ui:135 res/ui/player_screen.ui:177
|
||||
msgid "Subtitle"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/player_bar.ui:181 res/ui/player_bar.ui:205
|
||||
#: res/ui/player_screen.ui:229 res/ui/player_screen.ui:257
|
||||
msgid "0:00"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/player_bar.ui:193
|
||||
msgid "/"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/player_screen.ui:24
|
||||
msgid "Player"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/poe_list.ui:28
|
||||
msgid "Search persons and ensembles …"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/preferences.ui:16
|
||||
msgid "General"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/preferences.ui:21
|
||||
msgid "Music library"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/preferences.ui:27
|
||||
msgid "Music library folder"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/preferences.ui:29
|
||||
msgid "None selected"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/preferences.ui:32
|
||||
msgid "Select"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/recording_editor.ui:14 res/ui/tracks_editor.ui:68
|
||||
msgid "Recording"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/recording_editor.ui:63
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/recording_editor.ui:106 res/ui/tracks_editor.ui:109
|
||||
#: res/ui/work_editor.ui:14
|
||||
msgid "Work"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/recording_editor.ui:226 res/ui/tracks_editor.ui:127
|
||||
msgid "Performers"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/recording_screen.ui:98 res/ui/tracks_editor.ui:21
|
||||
msgid "Tracks"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/recording_screen.ui:127
|
||||
msgid "Add to playlist"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/recording_selector.ui:27 res/ui/work_selector.ui:26
|
||||
msgid "Select a composer on the left."
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/recording_selector.ui:51
|
||||
msgid "Select a recording"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/section_editor.ui:20
|
||||
msgid "Work section"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/track_editor.ui:21
|
||||
msgid "Track"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/track_editor.ui:70
|
||||
msgid "Select a recording of a work with multiple parts."
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/window.ui:51 res/ui/window.ui:141
|
||||
msgid "Welcome to Musicus!"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/window.ui:67
|
||||
msgid ""
|
||||
"Get startet by selecting something from the sidebar or adding new things to "
|
||||
"your library using the button in the top left corner."
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/window.ui:104 res/ui/window.ui:252 src/dialogs/about.rs:10
|
||||
msgid "Musicus"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/window.ui:157
|
||||
msgid ""
|
||||
"Get startet by selecting the folder containing your music files! Musicus "
|
||||
"will create a new database there or open one that already exists."
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/window.ui:170
|
||||
msgid "Select folder"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/window.ui:331
|
||||
msgid "Preferences"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/window.ui:335
|
||||
msgid "About Musicus"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/work_editor.ui:373
|
||||
msgid "Structure"
|
||||
msgstr ""
|
||||
|
||||
#: res/ui/work_selector.ui:51
|
||||
msgid "Select a work"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs/about.rs:12
|
||||
msgid "The classical music player and organizer."
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs/about.rs:14
|
||||
msgid "Further information and source code"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs/preferences.rs:30 src/window.rs:70
|
||||
msgid "Select music library folder"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs/recording/recording_editor.rs:54
|
||||
msgid "No performers added."
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs/recording/recording_selector_person_screen.rs:39
|
||||
#: src/dialogs/work/work_selector_person_screen.rs:36
|
||||
#: src/screens/person_screen.rs:57
|
||||
msgid "No works found."
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs/tracks_editor.rs:60
|
||||
msgid "Add some tracks."
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs/tracks_editor.rs:118
|
||||
msgid "Select audio files"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs/tracks_editor.rs:236 src/screens/player_screen.rs:230
|
||||
#: src/screens/recording_screen.rs:79
|
||||
msgid "Unknown"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs/work/part_editor.rs:49 src/dialogs/work/work_editor.rs:69
|
||||
msgid "No instruments added."
|
||||
msgstr ""
|
||||
|
||||
#: src/dialogs/work/work_editor.rs:72
|
||||
msgid "No work parts added."
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/ensemble_screen.rs:35
|
||||
msgid "Edit ensemble"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/ensemble_screen.rs:41
|
||||
msgid "Delete ensemble"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/person_screen.rs:39
|
||||
msgid "Edit person"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/person_screen.rs:45
|
||||
msgid "Delete person"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/recording_screen.rs:36
|
||||
msgid "Edit recording"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/recording_screen.rs:42
|
||||
msgid "Delete recording"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/recording_screen.rs:48
|
||||
msgid "Edit tracks"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/recording_screen.rs:54
|
||||
msgid "Delete tracks"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/recording_screen.rs:69
|
||||
msgid "No tracks found."
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/work_screen.rs:36
|
||||
msgid "Edit work"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/work_screen.rs:42
|
||||
msgid "Delete work"
|
||||
msgstr ""
|
||||
|
||||
#: src/widgets/person_list.rs:26
|
||||
msgid "No persons found."
|
||||
msgstr ""
|
||||
|
||||
#: src/widgets/poe_list.rs:41
|
||||
msgid "No persons or ensembles found."
|
||||
msgstr ""
|
||||
9
musicus/res/meson.build
Normal file
9
musicus/res/meson.build
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())
|
||||
gnome = import('gnome')
|
||||
|
||||
resources = gnome.compile_resources('musicus',
|
||||
'musicus.gresource.xml',
|
||||
gresource_bundle: true,
|
||||
install: true,
|
||||
install_dir: pkgdatadir,
|
||||
)
|
||||
34
musicus/res/musicus.gresource.xml
Normal file
34
musicus/res/musicus.gresource.xml
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/de/johrpan/musicus">
|
||||
<file preprocess="xml-stripblanks">ui/ensemble_editor.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/ensemble_selector.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/ensemble_screen.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/instrument_editor.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/instrument_selector.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/person_editor.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/person_list.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/person_screen.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/person_selector.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/player_bar.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/player_screen.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/poe_list.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/preferences.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/recording_editor.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/recording_screen.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/recording_selector.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/server_dialog.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/window.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/work_editor.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/work_screen.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/work_selector.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/work_selector_screen.ui</file>
|
||||
</gresource>
|
||||
</gresources>
|
||||
92
musicus/res/ui/ensemble_editor.ui
Normal file
92
musicus/res/ui/ensemble_editor.ui
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<requires lib="libhandy" version="0.0"/>
|
||||
<object class="HdyWindow" id="window">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="destroy-with-parent">True</property>
|
||||
<property name="type-hint">dialog</property>
|
||||
<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>
|
||||
<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">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<!-- n-columns=2 n-rows=1 -->
|
||||
<object class="GtkGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">18</property>
|
||||
<property name="row-spacing">12</property>
|
||||
<property name="column-spacing">6</property>
|
||||
<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">Name</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="name_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
203
musicus/res/ui/ensemble_screen.ui
Normal file
203
musicus/res/ui/ensemble_screen.ui
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
<?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="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="HdyHeaderBar" id="header">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="show-close-button">True</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="GtkMenuButton" id="menu_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</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">view-more-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="search_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-find-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" bind-source="search_button" bind-property="active" bind-flags="bidirectional|sync-create">False</property>
|
||||
<child>
|
||||
<object class="HdyClamp">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="maximum-size">400</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 recordings …</property>
|
||||
</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>
|
||||
<child>
|
||||
<object class="GtkSpinner">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="active">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">loading</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="margin-start">12</property>
|
||||
<property name="margin-end">12</property>
|
||||
<property name="margin-top">18</property>
|
||||
<property name="margin-bottom">12</property>
|
||||
<property name="maximum-size">800</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="recording_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Recordings</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="recording_frame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label-xalign">0</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">content</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="label" translatable="yes">No recordings found.</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">nothing</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>
|
||||
101
musicus/res/ui/ensemble_selector.ui
Normal file
101
musicus/res/ui/ensemble_selector.ui
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<requires lib="libhandy" version="0.0"/>
|
||||
<object class="HdyWindow" id="window">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="default-width">350</property>
|
||||
<property name="default-height">300</property>
|
||||
<property name="destroy-with-parent">True</property>
|
||||
<property name="type-hint">dialog</property>
|
||||
<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">Select ensemble</property>
|
||||
<property name="show-close-button">True</property>
|
||||
<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>
|
||||
</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="GtkSearchEntry" id="search_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="has-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>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</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="GtkListBox" id="list">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child type="placeholder">
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">No ensembles found.</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
92
musicus/res/ui/instrument_editor.ui
Normal file
92
musicus/res/ui/instrument_editor.ui
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<requires lib="libhandy" version="0.0"/>
|
||||
<object class="HdyWindow" id="window">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="destroy-with-parent">True</property>
|
||||
<property name="type-hint">dialog</property>
|
||||
<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>
|
||||
<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>
|
||||
<!-- n-columns=2 n-rows=1 -->
|
||||
<object class="GtkGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">18</property>
|
||||
<property name="row-spacing">12</property>
|
||||
<property name="column-spacing">6</property>
|
||||
<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">Name</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="name_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
101
musicus/res/ui/instrument_selector.ui
Normal file
101
musicus/res/ui/instrument_selector.ui
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<requires lib="libhandy" version="0.0"/>
|
||||
<object class="HdyWindow" id="window">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="default-width">350</property>
|
||||
<property name="default-height">300</property>
|
||||
<property name="destroy-with-parent">True</property>
|
||||
<property name="type-hint">dialog</property>
|
||||
<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">Select instrument</property>
|
||||
<property name="show-close-button">True</property>
|
||||
<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>
|
||||
</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="GtkSearchEntry" id="search_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="has-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>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkViewport">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="list">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child type="placeholder">
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">No instruments found.</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
219
musicus/res/ui/login_dialog.ui
Normal file
219
musicus/res/ui/login_dialog.ui
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.24"/>
|
||||
<requires lib="libhandy" version="0.0"/>
|
||||
<object class="HdyWindow" id="window">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="default-width">350</property>
|
||||
<property name="type-hint">dialog</property>
|
||||
<child>
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="transition-type">crossfade</property>
|
||||
<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">Login</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="login_button">
|
||||
<property name="label" translatable="yes">Login</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="can-default">True</property>
|
||||
<property name="has-default">True</property>
|
||||
<property name="receives-default">False</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="GtkInfoBar" id="info_bar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="message-type">error</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">The login credentials were wrong!</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>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<!-- n-columns=2 n-rows=2 -->
|
||||
<object class="GtkGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">18</property>
|
||||
<property name="row-spacing">12</property>
|
||||
<property name="column-spacing">6</property>
|
||||
<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">Username</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">Password</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="username_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="has-focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="password_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="visibility">False</property>
|
||||
<property name="activates-default">True</property>
|
||||
<property name="input-purpose">password</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<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">Login</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="active">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">loading</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
264
musicus/res/ui/part_editor.ui
Normal file
264
musicus/res/ui/part_editor.ui
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<requires lib="libhandy" version="0.0"/>
|
||||
<object class="HdyWindow" id="window">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="default-width">450</property>
|
||||
<property name="default-height">300</property>
|
||||
<property name="destroy-with-parent">True</property>
|
||||
<property name="type-hint">dialog</property>
|
||||
<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">Work part</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="GtkNotebook">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<child>
|
||||
<!-- n-columns=2 n-rows=2 -->
|
||||
<object class="GtkGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">18</property>
|
||||
<property name="row-spacing">12</property>
|
||||
<property name="column-spacing">6</property>
|
||||
<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>
|
||||
<property name="hexpand">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="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<child>
|
||||
<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>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="reset_composer_button">
|
||||
<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">user-trash-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="linked"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Overview</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="tab-fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">18</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="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>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">0</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="add_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-add-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</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>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Instruments</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
<property name="tab-fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
269
musicus/res/ui/performance_editor.ui
Normal file
269
musicus/res/ui/performance_editor.ui
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<requires lib="libhandy" version="0.0"/>
|
||||
<object class="HdyWindow" id="window">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="destroy-with-parent">True</property>
|
||||
<property name="type-hint">dialog</property>
|
||||
<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">Performance</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="sensitive">False</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>
|
||||
<!-- n-columns=2 n-rows=3 -->
|
||||
<object class="GtkGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">18</property>
|
||||
<property name="row-spacing">12</property>
|
||||
<property name="column-spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label4">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Role</property>
|
||||
<property name="xalign">1</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="role_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="role_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Select …</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="reset_role_button">
|
||||
<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">user-trash-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="linked"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Type</property>
|
||||
<property name="justify">right</property>
|
||||
<property name="xalign">1</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="transition-type">crossfade</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Person</property>
|
||||
<property name="xalign">1</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="person_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="person_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Select …</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">person</property>
|
||||
<property name="title" translatable="yes">Person</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="label" 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="GtkButton" id="ensemble_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="ensemble_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Select …</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">ensemble</property>
|
||||
<property name="title" translatable="yes">Ensemble</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">1</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackSwitcher">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<property name="stack">stack</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkSizeGroup">
|
||||
<widgets>
|
||||
<widget name="label1"/>
|
||||
<widget name="label2"/>
|
||||
<widget name="label3"/>
|
||||
<widget name="label4"/>
|
||||
</widgets>
|
||||
</object>
|
||||
</interface>
|
||||
115
musicus/res/ui/person_editor.ui
Normal file
115
musicus/res/ui/person_editor.ui
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<requires lib="libhandy" version="0.0"/>
|
||||
<object class="HdyWindow" id="window">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="destroy-with-parent">True</property>
|
||||
<property name="type-hint">dialog</property>
|
||||
<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>
|
||||
<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>
|
||||
<!-- n-columns=2 n-rows=2 -->
|
||||
<object class="GtkGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">18</property>
|
||||
<property name="row-spacing">12</property>
|
||||
<property name="column-spacing">6</property>
|
||||
<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">First name</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="first_name_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</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">Last name</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="last_name_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
75
musicus/res/ui/person_list.ui
Normal file
75
musicus/res/ui/person_list.ui
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<?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="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<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">400</property>
|
||||
<property name="tightening-threshold">300</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 persons …</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkSpinner">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="active">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">loading</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">content</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
261
musicus/res/ui/person_screen.ui
Normal file
261
musicus/res/ui/person_screen.ui
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
<?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="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="HdyHeaderBar" id="header">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="show-close-button">True</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="GtkMenuButton" id="menu_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</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">view-more-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="search_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-find-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" bind-source="search_button" bind-property="active" bind-flags="bidirectional|sync-create">False</property>
|
||||
<child>
|
||||
<object class="HdyClamp">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="maximum-size">400</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 works and recordings …</property>
|
||||
</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>
|
||||
<child>
|
||||
<object class="GtkSpinner">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="active">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">loading</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="margin-start">12</property>
|
||||
<property name="margin-end">12</property>
|
||||
<property name="margin-top">18</property>
|
||||
<property name="margin-bottom">12</property>
|
||||
<property name="maximum-size">800</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">18</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="work_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Works</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="work_frame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label-xalign">0</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</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="GtkBox" id="recording_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Recordings</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="recording_frame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label-xalign">0</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">content</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="label" translatable="yes">No works or recordings found.</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">nothing</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>
|
||||
51
musicus/res/ui/person_selector.ui
Normal file
51
musicus/res/ui/person_selector.ui
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<requires lib="libhandy" version="0.0"/>
|
||||
<object class="HdyWindow" id="window">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="default-width">350</property>
|
||||
<property name="default-height">300</property>
|
||||
<property name="destroy-with-parent">True</property>
|
||||
<property name="type-hint">dialog</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="vbox">
|
||||
<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">Select person</property>
|
||||
<property name="show-close-button">True</property>
|
||||
<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>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
230
musicus/res/ui/player_bar.ui
Normal file
230
musicus/res/ui/player_bar.ui
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.24"/>
|
||||
<object class="GtkImage" id="play_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">media-playback-start-symbolic</property>
|
||||
</object>
|
||||
<object class="GtkRevealer" id="widget">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="transition-type">slide-up</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkSeparator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">6</property>
|
||||
<property name="spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="previous_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</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">media-skip-backward-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="play_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="pause_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">media-playback-pause-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="next_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</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">media-skip-forward-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</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="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="title_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Title</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="subtitle_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Subtitle</property>
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="playlist_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="valign">center</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">view-list-bullet-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">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="position_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">0:00</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="label" translatable="yes">/</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="duration_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">0:00</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
305
musicus/res/ui/player_screen.ui
Normal file
305
musicus/res/ui/player_screen.ui
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
<?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="GtkImage" id="play_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">media-playback-start-symbolic</property>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="position">
|
||||
<property name="upper">1</property>
|
||||
<property name="step-increment">0.01</property>
|
||||
<property name="page-increment">0.05</property>
|
||||
</object>
|
||||
<object class="GtkBox" id="widget">
|
||||
<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">Player</property>
|
||||
<property name="show-close-button">True</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>
|
||||
</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="margin-left">12</property>
|
||||
<property name="margin-right">12</property>
|
||||
<property name="margin-start">12</property>
|
||||
<property name="margin-end">12</property>
|
||||
<property name="margin-top">18</property>
|
||||
<property name="margin-bottom">12</property>
|
||||
<property name="maximum-size">800</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="previous_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</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">media-skip-backward-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="play_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="pause_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">media-playback-pause-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="next_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</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">media-skip-forward-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</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="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="title_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Title</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="subtitle_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Subtitle</property>
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="stop_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="valign">center</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">media-playback-stop-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</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="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="position_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">0:00</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScale" id="position_scale">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="events">GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="adjustment">position</property>
|
||||
<property name="upper-stepper-sensitivity">off</property>
|
||||
<property name="round-digits">1</property>
|
||||
<property name="draw-value">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="duration_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">0:00</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="frame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label-xalign">0</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child type="label_item">
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
75
musicus/res/ui/poe_list.ui
Normal file
75
musicus/res/ui/poe_list.ui
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<?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="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<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">400</property>
|
||||
<property name="tightening-threshold">300</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 persons and ensembles …</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkSpinner">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="active">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">loading</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">content</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
98
musicus/res/ui/preferences.ui
Normal file
98
musicus/res/ui/preferences.ui
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.24"/>
|
||||
<requires lib="libhandy" version="0.0"/>
|
||||
<object class="HdyPreferencesWindow" id="window">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="default-width">400</property>
|
||||
<property name="default-height">400</property>
|
||||
<property name="type-hint">dialog</property>
|
||||
<child>
|
||||
<object class="HdyPreferencesPage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="title" translatable="yes">General</property>
|
||||
<child>
|
||||
<object class="HdyPreferencesGroup">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="title" translatable="yes">Music library</property>
|
||||
<child>
|
||||
<object class="HdyActionRow" id="music_library_path_row">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="selectable">False</property>
|
||||
<property name="title" translatable="yes">Music library folder</property>
|
||||
<property name="activatable-widget">select_music_library_path_button</property>
|
||||
<property name="subtitle" translatable="yes">None selected</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="select_music_library_path_button">
|
||||
<property name="label" translatable="yes">Select</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="HdyPreferencesGroup">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="title" translatable="yes">Server connection</property>
|
||||
<child>
|
||||
<object class="HdyActionRow" id="url_row">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="selectable">False</property>
|
||||
<property name="title" translatable="yes">Server URL</property>
|
||||
<property name="activatable-widget">url_button</property>
|
||||
<property name="subtitle" translatable="yes">Not set</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="url_button">
|
||||
<property name="label" translatable="yes">Change</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="HdyActionRow" id="login_row">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="selectable">False</property>
|
||||
<property name="title" translatable="yes">Login credentials</property>
|
||||
<property name="activatable-widget">login_button</property>
|
||||
<property name="subtitle" translatable="yes">Not logged in</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="login_button">
|
||||
<property name="label" translatable="yes">Change</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkSizeGroup">
|
||||
<widgets>
|
||||
<widget name="select_music_library_path_button"/>
|
||||
<widget name="url_button"/>
|
||||
<widget name="login_button"/>
|
||||
</widgets>
|
||||
</object>
|
||||
</interface>
|
||||
241
musicus/res/ui/recording_editor.ui
Normal file
241
musicus/res/ui/recording_editor.ui
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<requires lib="libhandy" version="0.0"/>
|
||||
<object class="GtkBox" id="widget">
|
||||
<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">Recording</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="sensitive">False</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="GtkNotebook">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<child>
|
||||
<!-- n-columns=2 n-rows=2 -->
|
||||
<object class="GtkGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">18</property>
|
||||
<property name="row-spacing">12</property>
|
||||
<property name="column-spacing">6</property>
|
||||
<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">Comment</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="work_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="work_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">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="comment_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
</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">Work</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Overview</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="tab-fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">18</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="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>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">0</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="add_performer_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="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="edit_performer_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">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="remove_performer_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">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Performers</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
<property name="tab-fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
162
musicus/res/ui/recording_screen.ui
Normal file
162
musicus/res/ui/recording_screen.ui
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
<?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="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="HdyHeaderBar" id="header">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="show-close-button">True</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="GtkMenuButton" id="menu_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</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">view-more-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="GtkStack" id="stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkSpinner">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="active">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">loading</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="margin-start">12</property>
|
||||
<property name="margin-end">12</property>
|
||||
<property name="margin-top">18</property>
|
||||
<property name="margin-bottom">12</property>
|
||||
<property name="maximum-size">800</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Tracks</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="frame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label-xalign">0</property>
|
||||
<property name="shadow-type">in</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="GtkButton" id="add_to_playlist_button">
|
||||
<property name="label" translatable="yes">Add to playlist</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="halign">end</property>
|
||||
<style>
|
||||
<class name="suggested-action"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">content</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
93
musicus/res/ui/recording_selector.ui
Normal file
93
musicus/res/ui/recording_selector.ui
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<requires lib="libhandy" version="1.0"/>
|
||||
<object class="GtkBox" id="empty_screen">
|
||||
<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="hexpand">True</property>
|
||||
<property name="show-close-button">True</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="label" translatable="yes">Select a composer on the left.</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<object class="HdyLeaflet" id="widget">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="visible-child">sidebar_box</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="sidebar_box">
|
||||
<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">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="title" translatable="yes">Select a recording</property>
|
||||
<property name="show-close-button" bind-source="widget" bind-property="folded" bind-flags="sync-create">False</property>
|
||||
<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>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">sidebar</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<style>
|
||||
<class name="sidebar"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="navigatable">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
59
musicus/res/ui/recording_selector_screen.ui
Normal file
59
musicus/res/ui/recording_selector_screen.ui
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.24"/>
|
||||
<requires lib="libhandy" version="0.0"/>
|
||||
<object class="GtkBox" id="widget">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="HdyHeaderBar" id="header">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="show-close-button">True</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>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkSpinner">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="active">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">loading</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
92
musicus/res/ui/section_editor.ui
Normal file
92
musicus/res/ui/section_editor.ui
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<requires lib="libhandy" version="0.0"/>
|
||||
<object class="HdyWindow" id="window">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="destroy-with-parent">True</property>
|
||||
<property name="type-hint">dialog</property>
|
||||
<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">Work section</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>
|
||||
<!-- n-columns=2 n-rows=1 -->
|
||||
<object class="GtkGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">18</property>
|
||||
<property name="row-spacing">12</property>
|
||||
<property name="column-spacing">6</property>
|
||||
<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">Title</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="title_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
96
musicus/res/ui/server_dialog.ui
Normal file
96
musicus/res/ui/server_dialog.ui
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.24"/>
|
||||
<requires lib="libhandy" version="0.0"/>
|
||||
<object class="HdyWindow" id="window">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="destroy-with-parent">True</property>
|
||||
<property name="type-hint">dialog</property>
|
||||
<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">Server</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="set_button">
|
||||
<property name="label" translatable="yes">Set</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="can-default">True</property>
|
||||
<property name="has-default">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>
|
||||
<!-- n-columns=2 n-rows=1 -->
|
||||
<object class="GtkGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">18</property>
|
||||
<property name="row-spacing">12</property>
|
||||
<property name="column-spacing">6</property>
|
||||
<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">URL</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="url_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="has-focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="activates-default">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
87
musicus/res/ui/track_editor.ui
Normal file
87
musicus/res/ui/track_editor.ui
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.24"/>
|
||||
<requires lib="libhandy" version="0.0"/>
|
||||
<object class="HdyWindow" id="window">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="default-width">350</property>
|
||||
<property name="default-height">200</property>
|
||||
<property name="type-hint">dialog</property>
|
||||
<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">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="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="GtkListBox" id="list">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</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="label" translatable="yes">Select a recording of a work with multiple parts.</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
298
musicus/res/ui/tracks_editor.ui
Normal file
298
musicus/res/ui/tracks_editor.ui
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.24"/>
|
||||
<requires lib="libhandy" version="0.0"/>
|
||||
<object class="HdyWindow" id="window">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="default-width">400</property>
|
||||
<property name="default-height">300</property>
|
||||
<property name="type-hint">dialog</property>
|
||||
<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">Tracks</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="save_button">
|
||||
<property name="label" translatable="yes">Save</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</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>
|
||||
</packing>
|
||||
</child>
|
||||
<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>
|
||||
<packing>
|
||||
<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>
|
||||
<!-- n-columns=2 n-rows=2 -->
|
||||
<object class="GtkGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">18</property>
|
||||
<property name="row-spacing">12</property>
|
||||
<property name="column-spacing">6</property>
|
||||
<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">Recording</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="recording_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="GtkStack" id="recording_stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="vhomogeneous">False</property>
|
||||
<property name="transition-type">crossfade</property>
|
||||
<property name="interpolate-size">True</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Select …</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">unselected</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="GtkLabel" id="work_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Work</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="performers_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="opacity">0.5</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Performers</property>
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">selected</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="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>
|
||||
<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="GtkButton" id="add_track_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="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="edit_track_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_track_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_track_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_track_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>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">1</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
340
musicus/res/ui/window.ui
Normal file
340
musicus/res/ui/window.ui
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.22" />
|
||||
<requires lib="libhandy" version="1.0" />
|
||||
<object class="GtkBox" id="empty_screen">
|
||||
<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="hexpand">True</property>
|
||||
<property name="show-close-button">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</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">folder-music-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">Welcome to Musicus!</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">Get startet by selecting something from the sidebar or adding new things to your library using the button in the top left corner.</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>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<object class="HdyApplicationWindow" id="window">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="default-width">800</property>
|
||||
<property name="default-height">566</property>
|
||||
<child>
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="transition-type">crossfade</property>
|
||||
<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">Musicus</property>
|
||||
<property name="show-close-button">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</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.5019607843137255</property>
|
||||
<property name="pixel-size">80</property>
|
||||
<property name="icon-name">folder-music-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.5019607843137255</property>
|
||||
<property name="label" translatable="yes">Welcome to Musicus!</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.5019607843137255</property>
|
||||
<property name="label" translatable="yes">Get startet by selecting the folder containing your music files! Musicus will create a new database there or open one that already exists.</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="select_music_library_path_button">
|
||||
<property name="label" translatable="yes">Select folder</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="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">empty</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="show-close-button">True</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="active">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">loading</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="content_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="HdyLeaflet" id="leaflet">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="sidebar_box">
|
||||
<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">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="title" translatable="yes">Musicus</property>
|
||||
<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>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="menu-model">menu</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">open-menu-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>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">sidebar</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<style>
|
||||
<class name="sidebar" />
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="navigatable">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">content</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<menu id="menu">
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Preferences</attribute>
|
||||
<attribute name="action">win.preferences</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">About Musicus</attribute>
|
||||
<attribute name="action">win.about</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</menu>
|
||||
</interface>
|
||||
388
musicus/res/ui/work_editor.ui
Normal file
388
musicus/res/ui/work_editor.ui
Normal file
|
|
@ -0,0 +1,388 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<requires lib="libhandy" version="0.0"/>
|
||||
<object class="GtkBox" id="widget">
|
||||
<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">Work</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="sensitive">False</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="GtkNotebook">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<child>
|
||||
<!-- n-columns=2 n-rows=2 -->
|
||||
<object class="GtkGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">18</property>
|
||||
<property name="row-spacing">12</property>
|
||||
<property name="column-spacing">6</property>
|
||||
<child>
|
||||
<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="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>
|
||||
</object>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Overview</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="tab-fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">18</property>
|
||||
<property name="spacing">6</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>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">0</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="add_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-add-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</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>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Instruments</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
<property name="tab-fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">18</property>
|
||||
<property name="spacing">6</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>
|
||||
<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="GtkButton" id="add_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-add-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</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>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Structure</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">2</property>
|
||||
<property name="tab-fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
203
musicus/res/ui/work_screen.ui
Normal file
203
musicus/res/ui/work_screen.ui
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
<?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="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="HdyHeaderBar" id="header">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="show-close-button">True</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="GtkMenuButton" id="menu_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</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">view-more-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="search_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-find-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" bind-source="search_button" bind-property="active" bind-flags="bidirectional|sync-create">False</property>
|
||||
<child>
|
||||
<object class="HdyClamp">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="maximum-size">400</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 recordings …</property>
|
||||
</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>
|
||||
<child>
|
||||
<object class="GtkSpinner">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="active">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">loading</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="margin-start">12</property>
|
||||
<property name="margin-end">12</property>
|
||||
<property name="margin-top">18</property>
|
||||
<property name="margin-bottom">12</property>
|
||||
<property name="maximum-size">800</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="recording_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Recordings</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="recording_frame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label-xalign">0</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">content</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="label" translatable="yes">No recordings found.</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">nothing</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>
|
||||
92
musicus/res/ui/work_selector.ui
Normal file
92
musicus/res/ui/work_selector.ui
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<requires lib="libhandy" version="1.0"/>
|
||||
<object class="GtkBox" id="empty_screen">
|
||||
<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="hexpand">True</property>
|
||||
<property name="show-close-button">True</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="label" translatable="yes">Select a composer on the left.</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<object class="HdyLeaflet" id="widget">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="visible-child">sidebar_box</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="sidebar_box">
|
||||
<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">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="title" translatable="yes">Select a work</property>
|
||||
<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>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">sidebar</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<style>
|
||||
<class name="sidebar"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="navigatable">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
59
musicus/res/ui/work_selector_screen.ui
Normal file
59
musicus/res/ui/work_selector_screen.ui
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.24"/>
|
||||
<requires lib="libhandy" version="0.0"/>
|
||||
<object class="GtkBox" id="widget">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="HdyHeaderBar" id="header">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="show-close-button">True</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>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkSpinner">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="active">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">loading</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
528
musicus/src/backend/backend.rs
Normal file
528
musicus/src/backend/backend.rs
Normal file
|
|
@ -0,0 +1,528 @@
|
|||
use super::secure;
|
||||
use crate::database::*;
|
||||
use crate::player::*;
|
||||
use anyhow::{anyhow, Result};
|
||||
use futures_channel::oneshot::Sender;
|
||||
use futures_channel::{mpsc, oneshot};
|
||||
use gio::prelude::*;
|
||||
use serde::Serialize;
|
||||
use std::cell::RefCell;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Credentials used for login.
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LoginData {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
pub enum BackendState {
|
||||
NoMusicLibrary,
|
||||
Loading,
|
||||
Ready,
|
||||
}
|
||||
|
||||
enum BackendAction {
|
||||
UpdatePerson(Person, Sender<Result<()>>),
|
||||
GetPerson(i64, Sender<Result<Person>>),
|
||||
DeletePerson(i64, Sender<Result<()>>),
|
||||
GetPersons(Sender<Result<Vec<Person>>>),
|
||||
UpdateInstrument(Instrument, Sender<Result<()>>),
|
||||
GetInstrument(i64, Sender<Result<Instrument>>),
|
||||
DeleteInstrument(i64, Sender<Result<()>>),
|
||||
GetInstruments(Sender<Result<Vec<Instrument>>>),
|
||||
UpdateWork(WorkInsertion, Sender<Result<()>>),
|
||||
GetWorkDescription(i64, Sender<Result<WorkDescription>>),
|
||||
DeleteWork(i64, Sender<Result<()>>),
|
||||
GetWorkDescriptions(i64, Sender<Result<Vec<WorkDescription>>>),
|
||||
UpdateEnsemble(Ensemble, Sender<Result<()>>),
|
||||
GetEnsemble(i64, Sender<Result<Ensemble>>),
|
||||
DeleteEnsemble(i64, Sender<Result<()>>),
|
||||
GetEnsembles(Sender<Result<Vec<Ensemble>>>),
|
||||
UpdateRecording(RecordingInsertion, Sender<Result<()>>),
|
||||
GetRecordingDescription(i64, Sender<Result<RecordingDescription>>),
|
||||
DeleteRecording(i64, Sender<Result<()>>),
|
||||
GetRecordingsForPerson(i64, Sender<Result<Vec<RecordingDescription>>>),
|
||||
GetRecordingsForEnsemble(i64, Sender<Result<Vec<RecordingDescription>>>),
|
||||
GetRecordingsForWork(i64, Sender<Result<Vec<RecordingDescription>>>),
|
||||
UpdateTracks(i64, Vec<TrackDescription>, Sender<Result<()>>),
|
||||
DeleteTracks(i64, Sender<Result<()>>),
|
||||
GetTracks(i64, Sender<Result<Vec<TrackDescription>>>),
|
||||
Stop,
|
||||
}
|
||||
|
||||
use BackendAction::*;
|
||||
|
||||
pub struct Backend {
|
||||
pub state_stream: RefCell<mpsc::Receiver<BackendState>>,
|
||||
state_sender: RefCell<mpsc::Sender<BackendState>>,
|
||||
action_sender: RefCell<Option<std::sync::mpsc::Sender<BackendAction>>>,
|
||||
settings: gio::Settings,
|
||||
secrets: secret_service::SecretService,
|
||||
server_url: RefCell<Option<String>>,
|
||||
login_data: RefCell<Option<LoginData>>,
|
||||
token: RefCell<Option<String>>,
|
||||
music_library_path: RefCell<Option<PathBuf>>,
|
||||
player: RefCell<Option<Rc<Player>>>,
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
pub fn new() -> Self {
|
||||
let (state_sender, state_stream) = mpsc::channel(1024);
|
||||
let secrets = secret_service::SecretService::new(secret_service::EncryptionType::Dh)
|
||||
.expect("Failed to connect to SecretsService!");
|
||||
|
||||
Backend {
|
||||
state_stream: RefCell::new(state_stream),
|
||||
state_sender: RefCell::new(state_sender),
|
||||
action_sender: RefCell::new(None),
|
||||
settings: gio::Settings::new("de.johrpan.musicus"),
|
||||
secrets,
|
||||
music_library_path: RefCell::new(None),
|
||||
server_url: RefCell::new(None),
|
||||
login_data: RefCell::new(None),
|
||||
token: RefCell::new(None),
|
||||
player: RefCell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(self: Rc<Backend>) {
|
||||
if let Some(path) = self.settings.get_string("music-library-path") {
|
||||
if !path.is_empty() {
|
||||
let context = glib::MainContext::default();
|
||||
let clone = self.clone();
|
||||
context.spawn_local(async move {
|
||||
clone
|
||||
.set_music_library_path_priv(PathBuf::from(path.to_string()))
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(data) = secure::load_login_data().unwrap() {
|
||||
self.login_data.replace(Some(data));
|
||||
}
|
||||
|
||||
if let Some(url) = self.settings.get_string("server-url") {
|
||||
if !url.is_empty() {
|
||||
self.server_url.replace(Some(url.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update_person(&self, person: Person) -> Result<()> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?
|
||||
.send(UpdatePerson(person, sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn get_person(&self, id: i64) -> Result<Person> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?.send(GetPerson(id, sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn delete_person(&self, id: i64) -> Result<()> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?
|
||||
.send(DeletePerson(id, sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn get_persons(&self) -> Result<Vec<Person>> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?.send(GetPersons(sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn update_instrument(&self, instrument: Instrument) -> Result<()> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?
|
||||
.send(UpdateInstrument(instrument, sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn get_instrument(&self, id: i64) -> Result<Instrument> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?
|
||||
.send(GetInstrument(id, sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn delete_instrument(&self, id: i64) -> Result<()> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?
|
||||
.send(DeleteInstrument(id, sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn get_instruments(&self) -> Result<Vec<Instrument>> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?.send(GetInstruments(sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn update_work(&self, work_insertion: WorkInsertion) -> Result<()> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?
|
||||
.send(UpdateWork(work_insertion, sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn get_work_description(&self, id: i64) -> Result<WorkDescription> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?
|
||||
.send(GetWorkDescription(id, sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn delete_work(&self, id: i64) -> Result<()> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?.send(DeleteWork(id, sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn get_work_descriptions(&self, person_id: i64) -> Result<Vec<WorkDescription>> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?
|
||||
.send(GetWorkDescriptions(person_id, sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn update_ensemble(&self, ensemble: Ensemble) -> Result<()> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?
|
||||
.send(UpdateEnsemble(ensemble, sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn get_ensemble(&self, id: i64) -> Result<Ensemble> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?.send(GetEnsemble(id, sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn delete_ensemble(&self, id: i64) -> Result<()> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?
|
||||
.send(DeleteEnsemble(id, sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn get_ensembles(&self) -> Result<Vec<Ensemble>> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?.send(GetEnsembles(sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn update_recording(&self, recording_insertion: RecordingInsertion) -> Result<()> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?
|
||||
.send(UpdateRecording(recording_insertion, sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn get_recording_description(&self, id: i64) -> Result<RecordingDescription> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?
|
||||
.send(GetRecordingDescription(id, sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn delete_recording(&self, id: i64) -> Result<()> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?
|
||||
.send(DeleteRecording(id, sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn get_recordings_for_person(
|
||||
&self,
|
||||
person_id: i64,
|
||||
) -> Result<Vec<RecordingDescription>> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?
|
||||
.send(GetRecordingsForPerson(person_id, sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn get_recordings_for_ensemble(
|
||||
&self,
|
||||
ensemble_id: i64,
|
||||
) -> Result<Vec<RecordingDescription>> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?
|
||||
.send(GetRecordingsForEnsemble(ensemble_id, sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn get_recordings_for_work(&self, work_id: i64) -> Result<Vec<RecordingDescription>> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?
|
||||
.send(GetRecordingsForWork(work_id, sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn update_tracks(
|
||||
&self,
|
||||
recording_id: i64,
|
||||
tracks: Vec<TrackDescription>,
|
||||
) -> Result<()> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?
|
||||
.send(UpdateTracks(recording_id, tracks, sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn delete_tracks(&self, recording_id: i64) -> Result<()> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?
|
||||
.send(DeleteTracks(recording_id, sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn get_tracks(&self, recording_id: i64) -> Result<Vec<TrackDescription>> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.unwrap_action_sender()?
|
||||
.send(GetTracks(recording_id, sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
pub async fn set_music_library_path(&self, path: PathBuf) -> Result<()> {
|
||||
self.settings
|
||||
.set_string("music-library-path", path.to_str().unwrap())?;
|
||||
self.set_music_library_path_priv(path).await
|
||||
}
|
||||
|
||||
pub fn get_music_library_path(&self) -> Option<PathBuf> {
|
||||
self.music_library_path.borrow().clone()
|
||||
}
|
||||
|
||||
/// Get the currently stored login credentials.
|
||||
pub fn get_login_data(&self) -> Option<LoginData> {
|
||||
self.login_data.borrow().clone()
|
||||
}
|
||||
|
||||
/// Set the URL of the Musicus server to connect to.
|
||||
pub fn set_server_url(&self, url: &str) -> Result<()> {
|
||||
self.settings.set_string("server-url", url)?;
|
||||
self.server_url.replace(Some(url.to_string()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the currently used login token.
|
||||
pub fn get_token(&self) -> Option<String> {
|
||||
self.token.borrow().clone()
|
||||
}
|
||||
|
||||
/// Set the login token to use. This will be done automatically by the login method.
|
||||
pub fn set_token(&self, token: &str) {
|
||||
self.token.replace(Some(token.to_string()));
|
||||
}
|
||||
|
||||
/// Get the currently set server URL.
|
||||
pub fn get_server_url(&self) -> Option<String> {
|
||||
self.server_url.borrow().clone()
|
||||
}
|
||||
|
||||
/// Set the user credentials to use.
|
||||
pub async fn set_login_data(&self, data: LoginData) -> Result<()> {
|
||||
secure::store_login_data(data.clone()).await?;
|
||||
self.login_data.replace(Some(data));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_player(&self) -> Option<Rc<Player>> {
|
||||
self.player.borrow().clone()
|
||||
}
|
||||
|
||||
async fn set_music_library_path_priv(&self, path: PathBuf) -> Result<()> {
|
||||
self.set_state(BackendState::Loading);
|
||||
|
||||
if let Some(player) = &*self.player.borrow() {
|
||||
player.clear();
|
||||
}
|
||||
|
||||
self.music_library_path.replace(Some(path.clone()));
|
||||
self.player.replace(Some(Player::new(path.clone())));
|
||||
|
||||
if let Some(action_sender) = self.action_sender.borrow_mut().take() {
|
||||
action_sender.send(Stop)?;
|
||||
}
|
||||
|
||||
let mut db_path = path.clone();
|
||||
db_path.push("musicus.db");
|
||||
|
||||
self.start_db_thread(String::from(db_path.to_str().unwrap()))
|
||||
.await?;
|
||||
|
||||
self.set_state(BackendState::Ready);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_state(&self, state: BackendState) {
|
||||
self.state_sender.borrow_mut().try_send(state).unwrap();
|
||||
}
|
||||
|
||||
fn unwrap_action_sender(&self) -> Result<std::sync::mpsc::Sender<BackendAction>> {
|
||||
match &*self.action_sender.borrow() {
|
||||
Some(action_sender) => Ok(action_sender.clone()),
|
||||
None => Err(anyhow!("Database thread is not running!")),
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_db_thread(&self, url: String) -> Result<()> {
|
||||
let (ready_sender, ready_receiver) = oneshot::channel();
|
||||
let (action_sender, action_receiver) = std::sync::mpsc::channel::<BackendAction>();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let db = Database::new(&url).expect("Failed to open database!");
|
||||
|
||||
ready_sender
|
||||
.send(())
|
||||
.expect("Failed to communicate to main thread!");
|
||||
|
||||
for action in action_receiver {
|
||||
match action {
|
||||
UpdatePerson(person, sender) => {
|
||||
sender
|
||||
.send(db.update_person(person))
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
GetPerson(id, sender) => {
|
||||
sender
|
||||
.send(db.get_person(id))
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
DeletePerson(id, sender) => {
|
||||
sender
|
||||
.send(db.delete_person(id))
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
GetPersons(sender) => {
|
||||
sender
|
||||
.send(db.get_persons())
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
UpdateInstrument(instrument, sender) => {
|
||||
sender
|
||||
.send(db.update_instrument(instrument))
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
GetInstrument(id, sender) => {
|
||||
sender
|
||||
.send(db.get_instrument(id))
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
DeleteInstrument(id, sender) => {
|
||||
sender
|
||||
.send(db.delete_instrument(id))
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
GetInstruments(sender) => {
|
||||
sender
|
||||
.send(db.get_instruments())
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
UpdateWork(work, sender) => {
|
||||
sender
|
||||
.send(db.update_work(work))
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
GetWorkDescription(id, sender) => {
|
||||
sender
|
||||
.send(db.get_work_description(id))
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
DeleteWork(id, sender) => {
|
||||
sender
|
||||
.send(db.delete_work(id))
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
GetWorkDescriptions(id, sender) => {
|
||||
sender
|
||||
.send(db.get_work_descriptions(id))
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
UpdateEnsemble(ensemble, sender) => {
|
||||
sender
|
||||
.send(db.update_ensemble(ensemble))
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
GetEnsemble(id, sender) => {
|
||||
sender
|
||||
.send(db.get_ensemble(id))
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
DeleteEnsemble(id, sender) => {
|
||||
sender
|
||||
.send(db.delete_ensemble(id))
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
GetEnsembles(sender) => {
|
||||
sender
|
||||
.send(db.get_ensembles())
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
UpdateRecording(recording, sender) => {
|
||||
sender
|
||||
.send(db.update_recording(recording))
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
GetRecordingDescription(id, sender) => {
|
||||
sender
|
||||
.send(db.get_recording_description(id))
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
DeleteRecording(id, sender) => {
|
||||
sender
|
||||
.send(db.delete_recording(id))
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
GetRecordingsForPerson(id, sender) => {
|
||||
sender
|
||||
.send(db.get_recordings_for_person(id))
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
GetRecordingsForEnsemble(id, sender) => {
|
||||
sender
|
||||
.send(db.get_recordings_for_ensemble(id))
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
GetRecordingsForWork(id, sender) => {
|
||||
sender
|
||||
.send(db.get_recordings_for_work(id))
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
UpdateTracks(recording_id, tracks, sender) => {
|
||||
sender
|
||||
.send(db.update_tracks(recording_id, tracks))
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
DeleteTracks(recording_id, sender) => {
|
||||
sender
|
||||
.send(db.delete_tracks(recording_id))
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
GetTracks(recording_id, sender) => {
|
||||
sender
|
||||
.send(db.get_tracks(recording_id))
|
||||
.expect("Failed to send result from database thread!");
|
||||
}
|
||||
Stop => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ready_receiver.await?;
|
||||
self.action_sender.replace(Some(action_sender));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
31
musicus/src/backend/client.rs
Normal file
31
musicus/src/backend/client.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
use super::Backend;
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use isahc::http::StatusCode;
|
||||
use isahc::prelude::*;
|
||||
|
||||
impl Backend {
|
||||
/// Try to login a user with the provided credentials and return, wether the login suceeded.
|
||||
pub async fn login(&self) -> Result<bool> {
|
||||
let server_url = self.get_server_url().ok_or(anyhow!("No server URL set!"))?;
|
||||
let data = self.get_login_data().ok_or(anyhow!("No login data set!"))?;
|
||||
|
||||
let request = Request::post(format!("{}/login", server_url))
|
||||
.header("Content-Type", "application/json")
|
||||
.body(serde_json::to_string(&data)?)?;
|
||||
|
||||
let mut response = isahc::send_async(request).await?;
|
||||
|
||||
let success = match response.status() {
|
||||
StatusCode::OK => {
|
||||
let token = response.text_async().await?;
|
||||
self.set_token(&token);
|
||||
println!("{}", &token);
|
||||
true
|
||||
}
|
||||
StatusCode::UNAUTHORIZED => false,
|
||||
_ => bail!("Unexpected response status!"),
|
||||
};
|
||||
|
||||
Ok(success)
|
||||
}
|
||||
}
|
||||
7
musicus/src/backend/mod.rs
Normal file
7
musicus/src/backend/mod.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
pub mod backend;
|
||||
pub use backend::*;
|
||||
|
||||
pub mod client;
|
||||
pub use client::*;
|
||||
|
||||
mod secure;
|
||||
108
musicus/src/backend/secure.rs
Normal file
108
musicus/src/backend/secure.rs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
use super::LoginData;
|
||||
use anyhow::{anyhow, Result};
|
||||
use futures_channel::oneshot;
|
||||
use secret_service::{Collection, EncryptionType, SecretService};
|
||||
|
||||
/// Savely store the user's current login credentials.
|
||||
pub async fn store_login_data(data: LoginData) -> Result<()> {
|
||||
let (sender, receiver) = oneshot::channel::<Result<()>>();
|
||||
std::thread::spawn(move || sender.send(store_login_data_priv(data)));
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
/// Savely store the user's current login credentials.
|
||||
fn store_login_data_priv(data: LoginData) -> Result<()> {
|
||||
let ss = get_ss()?;
|
||||
let collection = get_collection(&ss)?;
|
||||
|
||||
let key = "musicus-login-data";
|
||||
delete_secrets(&collection, key)?;
|
||||
|
||||
collection
|
||||
.create_item(
|
||||
key,
|
||||
vec![("username", &data.username)],
|
||||
data.password.as_bytes(),
|
||||
true,
|
||||
"text/plain",
|
||||
)
|
||||
.or(Err(anyhow!(
|
||||
"Failed to save login data using SecretService!"
|
||||
)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the login credentials from secret storage.
|
||||
pub fn load_login_data() -> Result<Option<LoginData>> {
|
||||
let ss = get_ss()?;
|
||||
let collection = get_collection(&ss)?;
|
||||
|
||||
let items = collection.get_all_items().or(Err(anyhow!(
|
||||
"Failed to get items from SecretService collection!"
|
||||
)))?;
|
||||
|
||||
let key = "musicus-login-data";
|
||||
let item = items
|
||||
.iter()
|
||||
.find(|item| item.get_label().unwrap_or_default() == key);
|
||||
|
||||
Ok(match item {
|
||||
Some(item) => {
|
||||
let attrs = item.get_attributes().or(Err(anyhow!(
|
||||
"Failed to get attributes for ScretService item!"
|
||||
)))?;
|
||||
|
||||
let username = attrs
|
||||
.iter()
|
||||
.find(|attr| attr.0 == "username")
|
||||
.ok_or(anyhow!("No username in login data!"))?
|
||||
.1
|
||||
.clone();
|
||||
|
||||
let password = std::str::from_utf8(
|
||||
&item
|
||||
.get_secret()
|
||||
.or(Err(anyhow!("Failed to get secret from SecretService!")))?,
|
||||
)?
|
||||
.to_string();
|
||||
|
||||
Some(LoginData { username, password })
|
||||
}
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Delete all stored secrets for the provided key.
|
||||
fn delete_secrets(collection: &Collection, key: &str) -> Result<()> {
|
||||
let items = collection.get_all_items().or(Err(anyhow!(
|
||||
"Failed to get items from SecretService collection!"
|
||||
)))?;
|
||||
|
||||
for item in items {
|
||||
if item.get_label().unwrap_or_default() == key {
|
||||
item.delete()
|
||||
.or(Err(anyhow!("Failed to delete SecretService item!")))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the SecretService interface.
|
||||
fn get_ss() -> Result<SecretService> {
|
||||
SecretService::new(EncryptionType::Dh).or(Err(anyhow!("Failed to get SecretService!")))
|
||||
}
|
||||
|
||||
/// Get the default SecretService collection and unlock it.
|
||||
fn get_collection(ss: &SecretService) -> Result<Collection> {
|
||||
let collection = ss
|
||||
.get_default_collection()
|
||||
.or(Err(anyhow!("Failed to get SecretService connection!")))?;
|
||||
|
||||
collection
|
||||
.unlock()
|
||||
.or(Err(anyhow!("Failed to unclock SecretService collection!")))?;
|
||||
|
||||
Ok(collection)
|
||||
}
|
||||
2
musicus/src/config.rs.in
Normal file
2
musicus/src/config.rs.in
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pub static VERSION: &str = @VERSION@;
|
||||
pub static LOCALEDIR: &str = @LOCALEDIR@;
|
||||
448
musicus/src/database/database.rs
Normal file
448
musicus/src/database/database.rs
Normal file
|
|
@ -0,0 +1,448 @@
|
|||
use super::models::*;
|
||||
use super::schema::*;
|
||||
use super::tables::*;
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
use diesel::prelude::*;
|
||||
use std::convert::TryInto;
|
||||
|
||||
embed_migrations!();
|
||||
|
||||
pub struct Database {
|
||||
c: SqliteConnection,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
pub fn new(path: &str) -> Result<Database> {
|
||||
let c = SqliteConnection::establish(path)?;
|
||||
|
||||
diesel::sql_query("PRAGMA foreign_keys = ON;").execute(&c)?;
|
||||
embedded_migrations::run(&c)?;
|
||||
|
||||
Ok(Database { c: c })
|
||||
}
|
||||
|
||||
pub fn update_person(&self, person: Person) -> Result<()> {
|
||||
self.defer_foreign_keys();
|
||||
self.c.transaction(|| {
|
||||
diesel::replace_into(persons::table)
|
||||
.values(person)
|
||||
.execute(&self.c)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_person(&self, id: i64) -> Result<Person> {
|
||||
persons::table
|
||||
.filter(persons::id.eq(id))
|
||||
.load::<Person>(&self.c)?
|
||||
.first()
|
||||
.cloned()
|
||||
.ok_or(anyhow!("No person with ID: {}", id))
|
||||
}
|
||||
|
||||
pub fn delete_person(&self, id: i64) -> Result<()> {
|
||||
diesel::delete(persons::table.filter(persons::id.eq(id))).execute(&self.c)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_persons(&self) -> Result<Vec<Person>> {
|
||||
let persons = persons::table.load::<Person>(&self.c)?;
|
||||
Ok(persons)
|
||||
}
|
||||
|
||||
pub fn update_instrument(&self, instrument: Instrument) -> Result<()> {
|
||||
self.defer_foreign_keys();
|
||||
self.c.transaction(|| {
|
||||
diesel::replace_into(instruments::table)
|
||||
.values(instrument)
|
||||
.execute(&self.c)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_instrument(&self, id: i64) -> Result<Instrument> {
|
||||
instruments::table
|
||||
.filter(instruments::id.eq(id))
|
||||
.load::<Instrument>(&self.c)?
|
||||
.first()
|
||||
.cloned()
|
||||
.ok_or(anyhow!("No instrument with ID: {}", id))
|
||||
}
|
||||
|
||||
pub fn delete_instrument(&self, id: i64) -> Result<()> {
|
||||
diesel::delete(instruments::table.filter(instruments::id.eq(id))).execute(&self.c)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_instruments(&self) -> Result<Vec<Instrument>> {
|
||||
let instruments = instruments::table.load::<Instrument>(&self.c)?;
|
||||
Ok(instruments)
|
||||
}
|
||||
|
||||
pub fn update_work(&self, work_insertion: WorkInsertion) -> Result<()> {
|
||||
let id = work_insertion.work.id;
|
||||
|
||||
self.defer_foreign_keys();
|
||||
self.c.transaction::<(), Error, _>(|| {
|
||||
self.delete_work(id)?;
|
||||
|
||||
diesel::insert_into(works::table)
|
||||
.values(work_insertion.work)
|
||||
.execute(&self.c)?;
|
||||
|
||||
for instrument_id in work_insertion.instrument_ids {
|
||||
diesel::insert_into(instrumentations::table)
|
||||
.values(Instrumentation {
|
||||
id: rand::random(),
|
||||
work: id,
|
||||
instrument: instrument_id,
|
||||
})
|
||||
.execute(&self.c)?;
|
||||
}
|
||||
|
||||
for part_insertion in work_insertion.parts {
|
||||
let part_id = part_insertion.part.id;
|
||||
|
||||
diesel::insert_into(work_parts::table)
|
||||
.values(part_insertion.part)
|
||||
.execute(&self.c)?;
|
||||
|
||||
for instrument_id in part_insertion.instrument_ids {
|
||||
diesel::insert_into(part_instrumentations::table)
|
||||
.values(PartInstrumentation {
|
||||
id: rand::random(),
|
||||
work_part: part_id,
|
||||
instrument: instrument_id,
|
||||
})
|
||||
.execute(&self.c)?;
|
||||
}
|
||||
}
|
||||
|
||||
for section in work_insertion.sections {
|
||||
diesel::insert_into(work_sections::table)
|
||||
.values(section)
|
||||
.execute(&self.c)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_work(&self, id: i64) -> Result<Work> {
|
||||
works::table
|
||||
.filter(works::id.eq(id))
|
||||
.load::<Work>(&self.c)?
|
||||
.first()
|
||||
.cloned()
|
||||
.ok_or(anyhow!("No work with ID: {}", id))
|
||||
}
|
||||
|
||||
pub fn get_work_description_for_work(&self, work: &Work) -> Result<WorkDescription> {
|
||||
let mut instruments: Vec<Instrument> = Vec::new();
|
||||
|
||||
let instrumentations = instrumentations::table
|
||||
.filter(instrumentations::work.eq(work.id))
|
||||
.load::<Instrumentation>(&self.c)?;
|
||||
|
||||
for instrumentation in instrumentations {
|
||||
instruments.push(self.get_instrument(instrumentation.instrument)?);
|
||||
}
|
||||
|
||||
let mut part_descriptions: Vec<WorkPartDescription> = Vec::new();
|
||||
|
||||
let work_parts = work_parts::table
|
||||
.filter(work_parts::work.eq(work.id))
|
||||
.load::<WorkPart>(&self.c)?;
|
||||
|
||||
for work_part in work_parts {
|
||||
let mut part_instruments: Vec<Instrument> = Vec::new();
|
||||
|
||||
let part_instrumentations = part_instrumentations::table
|
||||
.filter(part_instrumentations::work_part.eq(work_part.id))
|
||||
.load::<PartInstrumentation>(&self.c)?;
|
||||
|
||||
for part_instrumentation in part_instrumentations {
|
||||
part_instruments.push(self.get_instrument(part_instrumentation.instrument)?);
|
||||
}
|
||||
|
||||
part_descriptions.push(WorkPartDescription {
|
||||
composer: match work_part.composer {
|
||||
Some(composer) => Some(self.get_person(composer)?),
|
||||
None => None,
|
||||
},
|
||||
title: work_part.title.clone(),
|
||||
instruments: part_instruments,
|
||||
});
|
||||
}
|
||||
|
||||
let mut section_descriptions: Vec<WorkSectionDescription> = Vec::new();
|
||||
|
||||
let sections = work_sections::table
|
||||
.filter(work_sections::work.eq(work.id))
|
||||
.load::<WorkSection>(&self.c)?;
|
||||
|
||||
for section in sections {
|
||||
section_descriptions.push(WorkSectionDescription {
|
||||
title: section.title.clone(),
|
||||
before_index: section.before_index,
|
||||
});
|
||||
}
|
||||
|
||||
let work_description = WorkDescription {
|
||||
id: work.id,
|
||||
composer: self.get_person(work.composer)?,
|
||||
title: work.title.clone(),
|
||||
instruments: instruments,
|
||||
parts: part_descriptions,
|
||||
sections: section_descriptions,
|
||||
};
|
||||
|
||||
Ok(work_description)
|
||||
}
|
||||
|
||||
pub fn get_work_description(&self, id: i64) -> Result<WorkDescription> {
|
||||
let work = self.get_work(id)?;
|
||||
let work_description = self.get_work_description_for_work(&work)?;
|
||||
Ok(work_description)
|
||||
}
|
||||
|
||||
pub fn delete_work(&self, id: i64) -> Result<()> {
|
||||
diesel::delete(works::table.filter(works::id.eq(id))).execute(&self.c)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_works(&self, composer_id: i64) -> Result<Vec<Work>> {
|
||||
let works = works::table
|
||||
.filter(works::composer.eq(composer_id))
|
||||
.load::<Work>(&self.c)?;
|
||||
|
||||
Ok(works)
|
||||
}
|
||||
|
||||
pub fn get_work_descriptions(&self, composer_id: i64) -> Result<Vec<WorkDescription>> {
|
||||
let mut work_descriptions: Vec<WorkDescription> = Vec::new();
|
||||
|
||||
let works = self.get_works(composer_id)?;
|
||||
for work in works {
|
||||
work_descriptions.push(self.get_work_description_for_work(&work)?);
|
||||
}
|
||||
|
||||
Ok(work_descriptions)
|
||||
}
|
||||
|
||||
pub fn update_ensemble(&self, ensemble: Ensemble) -> Result<()> {
|
||||
self.defer_foreign_keys();
|
||||
self.c.transaction(|| {
|
||||
diesel::replace_into(ensembles::table)
|
||||
.values(ensemble)
|
||||
.execute(&self.c)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_ensemble(&self, id: i64) -> Result<Ensemble> {
|
||||
ensembles::table
|
||||
.filter(ensembles::id.eq(id))
|
||||
.load::<Ensemble>(&self.c)?
|
||||
.first()
|
||||
.cloned()
|
||||
.ok_or(anyhow!("No ensemble with ID: {}", id))
|
||||
}
|
||||
|
||||
pub fn delete_ensemble(&self, id: i64) -> Result<()> {
|
||||
diesel::delete(ensembles::table.filter(ensembles::id.eq(id))).execute(&self.c)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_ensembles(&self) -> Result<Vec<Ensemble>> {
|
||||
let ensembles = ensembles::table.load::<Ensemble>(&self.c)?;
|
||||
Ok(ensembles)
|
||||
}
|
||||
|
||||
pub fn update_recording(&self, recording_insertion: RecordingInsertion) -> Result<()> {
|
||||
let id = recording_insertion.recording.id;
|
||||
|
||||
self.defer_foreign_keys();
|
||||
self.c.transaction::<(), Error, _>(|| {
|
||||
self.delete_recording(id)?;
|
||||
|
||||
diesel::insert_into(recordings::table)
|
||||
.values(recording_insertion.recording)
|
||||
.execute(&self.c)?;
|
||||
|
||||
for performance in recording_insertion.performances {
|
||||
diesel::insert_into(performances::table)
|
||||
.values(performance)
|
||||
.execute(&self.c)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_recording(&self, id: i64) -> Result<Recording> {
|
||||
recordings::table
|
||||
.filter(recordings::id.eq(id))
|
||||
.load::<Recording>(&self.c)?
|
||||
.first()
|
||||
.cloned()
|
||||
.ok_or(anyhow!("No recording with ID: {}", id))
|
||||
}
|
||||
|
||||
pub fn get_recording_description_for_recording(
|
||||
&self,
|
||||
recording: &Recording,
|
||||
) -> Result<RecordingDescription> {
|
||||
let mut performance_descriptions: Vec<PerformanceDescription> = Vec::new();
|
||||
|
||||
let performances = performances::table
|
||||
.filter(performances::recording.eq(recording.id))
|
||||
.load::<Performance>(&self.c)?;
|
||||
|
||||
for performance in performances {
|
||||
performance_descriptions.push(PerformanceDescription {
|
||||
person: match performance.person {
|
||||
Some(id) => Some(self.get_person(id)?),
|
||||
None => None,
|
||||
},
|
||||
ensemble: match performance.ensemble {
|
||||
Some(id) => Some(self.get_ensemble(id)?),
|
||||
None => None,
|
||||
},
|
||||
role: match performance.role {
|
||||
Some(id) => Some(self.get_instrument(id)?),
|
||||
None => None,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Ok(RecordingDescription {
|
||||
id: recording.id,
|
||||
work: self.get_work_description(recording.work)?,
|
||||
comment: recording.comment.clone(),
|
||||
performances: performance_descriptions,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_recording_description(&self, id: i64) -> Result<RecordingDescription> {
|
||||
let recording = self.get_recording(id)?;
|
||||
let recording_description = self.get_recording_description_for_recording(&recording)?;
|
||||
Ok(recording_description)
|
||||
}
|
||||
|
||||
pub fn get_recordings_for_person(&self, id: i64) -> Result<Vec<RecordingDescription>> {
|
||||
let mut recording_descriptions: Vec<RecordingDescription> = Vec::new();
|
||||
|
||||
let recordings = recordings::table
|
||||
.inner_join(performances::table.on(performances::recording.eq(recordings::id)))
|
||||
.inner_join(persons::table.on(persons::id.nullable().eq(performances::person)))
|
||||
.filter(persons::id.eq(id))
|
||||
.select(recordings::table::all_columns())
|
||||
.load::<Recording>(&self.c)?;
|
||||
|
||||
for recording in recordings {
|
||||
recording_descriptions.push(self.get_recording_description_for_recording(&recording)?);
|
||||
}
|
||||
|
||||
Ok(recording_descriptions)
|
||||
}
|
||||
|
||||
pub fn get_recordings_for_ensemble(&self, id: i64) -> Result<Vec<RecordingDescription>> {
|
||||
let mut recording_descriptions: Vec<RecordingDescription> = Vec::new();
|
||||
|
||||
let recordings = recordings::table
|
||||
.inner_join(performances::table.on(performances::recording.eq(recordings::id)))
|
||||
.inner_join(ensembles::table.on(ensembles::id.nullable().eq(performances::ensemble)))
|
||||
.filter(ensembles::id.eq(id))
|
||||
.select(recordings::table::all_columns())
|
||||
.load::<Recording>(&self.c)?;
|
||||
|
||||
for recording in recordings {
|
||||
recording_descriptions.push(self.get_recording_description_for_recording(&recording)?);
|
||||
}
|
||||
|
||||
Ok(recording_descriptions)
|
||||
}
|
||||
|
||||
pub fn get_recordings_for_work(&self, id: i64) -> Result<Vec<RecordingDescription>> {
|
||||
let mut recording_descriptions: Vec<RecordingDescription> = Vec::new();
|
||||
|
||||
let recordings = recordings::table
|
||||
.inner_join(works::table.on(works::id.eq(recordings::work)))
|
||||
.filter(works::id.eq(id))
|
||||
.select(recordings::table::all_columns())
|
||||
.load::<Recording>(&self.c)?;
|
||||
|
||||
for recording in recordings {
|
||||
recording_descriptions.push(self.get_recording_description_for_recording(&recording)?);
|
||||
}
|
||||
|
||||
Ok(recording_descriptions)
|
||||
}
|
||||
|
||||
pub fn delete_recording(&self, id: i64) -> Result<()> {
|
||||
self.delete_tracks(id)?;
|
||||
diesel::delete(recordings::table.filter(recordings::id.eq(id))).execute(&self.c)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_recordings(&self, work_id: i64) -> Result<Vec<Recording>> {
|
||||
let recordings = recordings::table
|
||||
.filter(recordings::work.eq(work_id))
|
||||
.load::<Recording>(&self.c)?;
|
||||
|
||||
Ok(recordings)
|
||||
}
|
||||
|
||||
pub fn update_tracks(&self, recording_id: i64, tracks: Vec<TrackDescription>) -> Result<()> {
|
||||
self.delete_tracks(recording_id)?;
|
||||
|
||||
for (index, track_description) in tracks.iter().enumerate() {
|
||||
let track = Track {
|
||||
id: rand::random(),
|
||||
file_name: track_description.file_name.clone(),
|
||||
recording: recording_id,
|
||||
track_index: index.try_into().unwrap(),
|
||||
work_parts: track_description
|
||||
.work_parts
|
||||
.iter()
|
||||
.map(|i| i.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(","),
|
||||
};
|
||||
|
||||
diesel::insert_into(tracks::table)
|
||||
.values(track)
|
||||
.execute(&self.c)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_tracks(&self, recording_id: i64) -> Result<()> {
|
||||
diesel::delete(tracks::table.filter(tracks::recording.eq(recording_id))).execute(&self.c)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_tracks(&self, recording_id: i64) -> Result<Vec<TrackDescription>> {
|
||||
let tracks = tracks::table
|
||||
.filter(tracks::recording.eq(recording_id))
|
||||
.order_by(tracks::track_index)
|
||||
.load::<Track>(&self.c)?;
|
||||
|
||||
Ok(tracks.iter().map(|track| track.clone().into()).collect())
|
||||
}
|
||||
|
||||
fn defer_foreign_keys(&self) {
|
||||
diesel::sql_query("PRAGMA defer_foreign_keys = ON;")
|
||||
.execute(&self.c)
|
||||
.expect("Failed to enable defer_foreign_keys_pragma!");
|
||||
}
|
||||
}
|
||||
10
musicus/src/database/mod.rs
Normal file
10
musicus/src/database/mod.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
pub mod database;
|
||||
pub use database::*;
|
||||
|
||||
pub mod models;
|
||||
pub use models::*;
|
||||
|
||||
pub mod schema;
|
||||
|
||||
pub mod tables;
|
||||
pub use tables::*;
|
||||
205
musicus/src/database/models.rs
Normal file
205
musicus/src/database/models.rs
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
use super::tables::*;
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WorkPartDescription {
|
||||
pub title: String,
|
||||
pub composer: Option<Person>,
|
||||
pub instruments: Vec<Instrument>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WorkSectionDescription {
|
||||
pub title: String,
|
||||
pub before_index: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WorkDescription {
|
||||
pub id: i64,
|
||||
pub title: String,
|
||||
pub composer: Person,
|
||||
pub instruments: Vec<Instrument>,
|
||||
pub parts: Vec<WorkPartDescription>,
|
||||
pub sections: Vec<WorkSectionDescription>,
|
||||
}
|
||||
|
||||
impl WorkDescription {
|
||||
pub fn get_title(&self) -> String {
|
||||
format!("{}: {}", self.composer.name_fl(), self.title)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WorkPartInsertion {
|
||||
pub part: WorkPart,
|
||||
pub instrument_ids: Vec<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WorkInsertion {
|
||||
pub work: Work,
|
||||
pub instrument_ids: Vec<i64>,
|
||||
pub parts: Vec<WorkPartInsertion>,
|
||||
pub sections: Vec<WorkSection>,
|
||||
}
|
||||
|
||||
impl From<WorkDescription> for WorkInsertion {
|
||||
fn from(description: WorkDescription) -> Self {
|
||||
WorkInsertion {
|
||||
work: Work {
|
||||
id: description.id,
|
||||
composer: description.composer.id,
|
||||
title: description.title.clone(),
|
||||
},
|
||||
instrument_ids: description
|
||||
.instruments
|
||||
.iter()
|
||||
.map(|instrument| instrument.id)
|
||||
.collect(),
|
||||
parts: description
|
||||
.parts
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, part)| WorkPartInsertion {
|
||||
part: WorkPart {
|
||||
id: rand::random(),
|
||||
work: description.id,
|
||||
part_index: index.try_into().expect("Part index didn't fit into u32!"),
|
||||
composer: part.composer.as_ref().map(|person| person.id),
|
||||
title: part.title.clone(),
|
||||
},
|
||||
instrument_ids: part
|
||||
.instruments
|
||||
.iter()
|
||||
.map(|instrument| instrument.id)
|
||||
.collect(),
|
||||
})
|
||||
.collect(),
|
||||
sections: description
|
||||
.sections
|
||||
.iter()
|
||||
.map(|section| WorkSection {
|
||||
id: rand::random(),
|
||||
work: description.id,
|
||||
title: section.title.clone(),
|
||||
before_index: section.before_index,
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PerformanceDescription {
|
||||
pub person: Option<Person>,
|
||||
pub ensemble: Option<Ensemble>,
|
||||
pub role: Option<Instrument>,
|
||||
}
|
||||
|
||||
impl PerformanceDescription {
|
||||
pub fn get_title(&self) -> String {
|
||||
let mut text = String::from(if self.is_person() {
|
||||
self.unwrap_person().name_fl()
|
||||
} else {
|
||||
self.unwrap_ensemble().name
|
||||
});
|
||||
|
||||
if self.has_role() {
|
||||
text = text + " (" + &self.unwrap_role().name + ")";
|
||||
}
|
||||
|
||||
text
|
||||
}
|
||||
|
||||
pub fn is_person(&self) -> bool {
|
||||
self.person.is_some()
|
||||
}
|
||||
|
||||
pub fn unwrap_person(&self) -> Person {
|
||||
self.person.clone().unwrap()
|
||||
}
|
||||
|
||||
pub fn unwrap_ensemble(&self) -> Ensemble {
|
||||
self.ensemble.clone().unwrap()
|
||||
}
|
||||
|
||||
pub fn has_role(&self) -> bool {
|
||||
self.role.clone().is_some()
|
||||
}
|
||||
|
||||
pub fn unwrap_role(&self) -> Instrument {
|
||||
self.role.clone().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RecordingDescription {
|
||||
pub id: i64,
|
||||
pub work: WorkDescription,
|
||||
pub comment: String,
|
||||
pub performances: Vec<PerformanceDescription>,
|
||||
}
|
||||
|
||||
impl RecordingDescription {
|
||||
pub fn get_performers(&self) -> String {
|
||||
let texts: Vec<String> = self
|
||||
.performances
|
||||
.iter()
|
||||
.map(|performance| performance.get_title())
|
||||
.collect();
|
||||
|
||||
texts.join(", ")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RecordingInsertion {
|
||||
pub recording: Recording,
|
||||
pub performances: Vec<Performance>,
|
||||
}
|
||||
|
||||
impl From<RecordingDescription> for RecordingInsertion {
|
||||
fn from(description: RecordingDescription) -> Self {
|
||||
RecordingInsertion {
|
||||
recording: Recording {
|
||||
id: description.id,
|
||||
work: description.work.id,
|
||||
comment: description.comment.clone(),
|
||||
},
|
||||
performances: description
|
||||
.performances
|
||||
.iter()
|
||||
.map(|performance| Performance {
|
||||
id: rand::random(),
|
||||
recording: description.id,
|
||||
person: performance.person.as_ref().map(|person| person.id),
|
||||
ensemble: performance.ensemble.as_ref().map(|ensemble| ensemble.id),
|
||||
role: performance.role.as_ref().map(|role| role.id),
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TrackDescription {
|
||||
pub work_parts: Vec<usize>,
|
||||
pub file_name: String,
|
||||
}
|
||||
|
||||
impl From<Track> for TrackDescription {
|
||||
fn from(track: Track) -> Self {
|
||||
let mut work_parts = Vec::<usize>::new();
|
||||
for part in track.work_parts.split(",") {
|
||||
if !part.is_empty() {
|
||||
work_parts.push(part.parse().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
TrackDescription {
|
||||
work_parts,
|
||||
file_name: track.file_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
121
musicus/src/database/schema.rs
Normal file
121
musicus/src/database/schema.rs
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
table! {
|
||||
ensembles (id) {
|
||||
id -> BigInt,
|
||||
name -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
instrumentations (id) {
|
||||
id -> BigInt,
|
||||
work -> BigInt,
|
||||
instrument -> BigInt,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
instruments (id) {
|
||||
id -> BigInt,
|
||||
name -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
part_instrumentations (id) {
|
||||
id -> BigInt,
|
||||
work_part -> BigInt,
|
||||
instrument -> BigInt,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
performances (id) {
|
||||
id -> BigInt,
|
||||
recording -> BigInt,
|
||||
person -> Nullable<BigInt>,
|
||||
ensemble -> Nullable<BigInt>,
|
||||
role -> Nullable<BigInt>,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
persons (id) {
|
||||
id -> BigInt,
|
||||
first_name -> Text,
|
||||
last_name -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
recordings (id) {
|
||||
id -> BigInt,
|
||||
work -> BigInt,
|
||||
comment -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
tracks (id) {
|
||||
id -> BigInt,
|
||||
file_name -> Text,
|
||||
recording -> BigInt,
|
||||
track_index -> Integer,
|
||||
work_parts -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
work_parts (id) {
|
||||
id -> BigInt,
|
||||
work -> BigInt,
|
||||
part_index -> BigInt,
|
||||
composer -> Nullable<BigInt>,
|
||||
title -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
work_sections (id) {
|
||||
id -> BigInt,
|
||||
work -> BigInt,
|
||||
title -> Text,
|
||||
before_index -> BigInt,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
works (id) {
|
||||
id -> BigInt,
|
||||
composer -> BigInt,
|
||||
title -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
joinable!(instrumentations -> instruments (instrument));
|
||||
joinable!(instrumentations -> works (work));
|
||||
joinable!(part_instrumentations -> instruments (instrument));
|
||||
joinable!(part_instrumentations -> works (work_part));
|
||||
joinable!(performances -> ensembles (ensemble));
|
||||
joinable!(performances -> instruments (role));
|
||||
joinable!(performances -> persons (person));
|
||||
joinable!(performances -> recordings (recording));
|
||||
joinable!(recordings -> works (work));
|
||||
joinable!(tracks -> recordings (recording));
|
||||
joinable!(work_parts -> persons (composer));
|
||||
joinable!(work_parts -> works (work));
|
||||
joinable!(work_sections -> works (work));
|
||||
joinable!(works -> persons (composer));
|
||||
|
||||
allow_tables_to_appear_in_same_query!(
|
||||
ensembles,
|
||||
instrumentations,
|
||||
instruments,
|
||||
part_instrumentations,
|
||||
performances,
|
||||
persons,
|
||||
recordings,
|
||||
tracks,
|
||||
work_parts,
|
||||
work_sections,
|
||||
works,
|
||||
);
|
||||
94
musicus/src/database/tables.rs
Normal file
94
musicus/src/database/tables.rs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
use super::schema::*;
|
||||
use diesel::Queryable;
|
||||
|
||||
#[derive(Insertable, Queryable, Debug, Clone)]
|
||||
pub struct Person {
|
||||
pub id: i64,
|
||||
pub first_name: String,
|
||||
pub last_name: String,
|
||||
}
|
||||
|
||||
impl Person {
|
||||
pub fn name_fl(&self) -> String {
|
||||
format!("{} {}", self.first_name, self.last_name)
|
||||
}
|
||||
|
||||
pub fn name_lf(&self) -> String {
|
||||
format!("{}, {}", self.last_name, self.first_name)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Insertable, Queryable, Debug, Clone)]
|
||||
pub struct Instrument {
|
||||
pub id: i64,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Queryable, Debug, Clone)]
|
||||
pub struct Work {
|
||||
pub id: i64,
|
||||
pub composer: i64,
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Queryable, Debug, Clone)]
|
||||
pub struct Instrumentation {
|
||||
pub id: i64,
|
||||
pub work: i64,
|
||||
pub instrument: i64,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Queryable, Debug, Clone)]
|
||||
pub struct WorkPart {
|
||||
pub id: i64,
|
||||
pub work: i64,
|
||||
pub part_index: i64,
|
||||
pub composer: Option<i64>,
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Queryable, Debug, Clone)]
|
||||
pub struct PartInstrumentation {
|
||||
pub id: i64,
|
||||
pub work_part: i64,
|
||||
pub instrument: i64,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Queryable, Debug, Clone)]
|
||||
pub struct WorkSection {
|
||||
pub id: i64,
|
||||
pub work: i64,
|
||||
pub title: String,
|
||||
pub before_index: i64,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Queryable, Debug, Clone)]
|
||||
pub struct Ensemble {
|
||||
pub id: i64,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Queryable, Debug, Clone)]
|
||||
pub struct Recording {
|
||||
pub id: i64,
|
||||
pub work: i64,
|
||||
pub comment: String,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Queryable, Debug, Clone)]
|
||||
pub struct Performance {
|
||||
pub id: i64,
|
||||
pub recording: i64,
|
||||
pub person: Option<i64>,
|
||||
pub ensemble: Option<i64>,
|
||||
pub role: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Queryable, Debug, Clone)]
|
||||
pub struct Track {
|
||||
pub id: i64,
|
||||
pub file_name: String,
|
||||
pub recording: i64,
|
||||
pub track_index: i32,
|
||||
pub work_parts: String,
|
||||
}
|
||||
22
musicus/src/dialogs/about.rs
Normal file
22
musicus/src/dialogs/about.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use crate::config;
|
||||
use gettextrs::gettext;
|
||||
use gtk::prelude::*;
|
||||
|
||||
pub fn show_about_dialog<W: IsA<gtk::Window>>(parent: &W) {
|
||||
let dialog = gtk::AboutDialogBuilder::new()
|
||||
.transient_for(parent)
|
||||
.modal(true)
|
||||
.logo_icon_name("de.johrpan.musicus")
|
||||
.program_name(&gettext("Musicus"))
|
||||
.version(config::VERSION)
|
||||
.comments(&gettext("The classical music player and organizer."))
|
||||
.website("https://github.com/johrpan/musicus")
|
||||
.website_label(&gettext("Further information and source code"))
|
||||
.copyright("© 2020 Elias Projahn")
|
||||
.license_type(gtk::License::Agpl30)
|
||||
.authors(vec![String::from("Elias Projahn <johrpan@gmail.com>")])
|
||||
.build();
|
||||
|
||||
dialog.connect_response(|dialog, _| dialog.close());
|
||||
dialog.show();
|
||||
}
|
||||
80
musicus/src/dialogs/ensemble_editor.rs
Normal file
80
musicus/src/dialogs/ensemble_editor.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
use crate::backend::*;
|
||||
use crate::database::*;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct EnsembleEditor<F>
|
||||
where
|
||||
F: Fn(Ensemble) -> () + 'static,
|
||||
{
|
||||
backend: Rc<Backend>,
|
||||
window: libhandy::Window,
|
||||
callback: F,
|
||||
id: i64,
|
||||
name_entry: gtk::Entry,
|
||||
}
|
||||
|
||||
impl<F> EnsembleEditor<F>
|
||||
where
|
||||
F: Fn(Ensemble) -> () + 'static,
|
||||
{
|
||||
pub fn new<P: IsA<gtk::Window>>(
|
||||
backend: Rc<Backend>,
|
||||
parent: &P,
|
||||
ensemble: Option<Ensemble>,
|
||||
callback: F,
|
||||
) -> Rc<Self> {
|
||||
let builder =
|
||||
gtk::Builder::from_resource("/de/johrpan/musicus/ui/ensemble_editor.ui");
|
||||
|
||||
get_widget!(builder, libhandy::Window, window);
|
||||
get_widget!(builder, gtk::Button, cancel_button);
|
||||
get_widget!(builder, gtk::Button, save_button);
|
||||
get_widget!(builder, gtk::Entry, name_entry);
|
||||
|
||||
let id = match ensemble {
|
||||
Some(ensemble) => {
|
||||
name_entry.set_text(&ensemble.name);
|
||||
ensemble.id
|
||||
}
|
||||
None => rand::random::<u32>().into(),
|
||||
};
|
||||
|
||||
let result = Rc::new(EnsembleEditor {
|
||||
backend: backend,
|
||||
window: window,
|
||||
callback: callback,
|
||||
id: id,
|
||||
name_entry: name_entry,
|
||||
});
|
||||
|
||||
cancel_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
result.window.close();
|
||||
}));
|
||||
|
||||
save_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
let ensemble = Ensemble {
|
||||
id: result.id,
|
||||
name: result.name_entry.get_text().to_string(),
|
||||
};
|
||||
|
||||
let clone = result.clone();
|
||||
let c = glib::MainContext::default();
|
||||
c.spawn_local(async move {
|
||||
clone.backend.update_ensemble(ensemble.clone()).await.unwrap();
|
||||
clone.window.close();
|
||||
(clone.callback)(ensemble.clone());
|
||||
});
|
||||
}));
|
||||
|
||||
result.window.set_transient_for(Some(parent));
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn show(&self) {
|
||||
self.window.show();
|
||||
}
|
||||
}
|
||||
112
musicus/src/dialogs/ensemble_selector.rs
Normal file
112
musicus/src/dialogs/ensemble_selector.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
use super::EnsembleEditor;
|
||||
use crate::backend::Backend;
|
||||
use crate::database::*;
|
||||
use crate::widgets::*;
|
||||
use gio::prelude::*;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use std::convert::TryInto;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct EnsembleSelector<F>
|
||||
where
|
||||
F: Fn(Ensemble) -> () + 'static,
|
||||
{
|
||||
backend: Rc<Backend>,
|
||||
window: libhandy::Window,
|
||||
callback: F,
|
||||
list: gtk::ListBox,
|
||||
search_entry: gtk::SearchEntry,
|
||||
}
|
||||
|
||||
impl<F> EnsembleSelector<F>
|
||||
where
|
||||
F: Fn(Ensemble) -> () + 'static,
|
||||
{
|
||||
pub fn new<P: IsA<gtk::Window>>(backend: Rc<Backend>, parent: &P, callback: F) -> Rc<Self> {
|
||||
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::SearchEntry, search_entry);
|
||||
get_widget!(builder, gtk::ListBox, list);
|
||||
|
||||
let result = Rc::new(EnsembleSelector {
|
||||
backend: backend,
|
||||
window: window,
|
||||
callback: callback,
|
||||
search_entry: search_entry,
|
||||
list: list,
|
||||
});
|
||||
|
||||
let c = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
c.spawn_local(async move {
|
||||
let ensembles = clone.backend.get_ensembles().await.unwrap();
|
||||
|
||||
for (index, ensemble) in ensembles.iter().enumerate() {
|
||||
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);
|
||||
|
||||
let row = SelectorRow::new(index.try_into().unwrap(), &label);
|
||||
row.show_all();
|
||||
clone.list.insert(&row, -1);
|
||||
}
|
||||
|
||||
clone.list.connect_row_activated(
|
||||
clone!(@strong clone, @strong ensembles => move |_, row| {
|
||||
clone.window.close();
|
||||
let row = row.get_child().unwrap().downcast::<SelectorRow>().unwrap();
|
||||
let index: usize = row.get_index().try_into().unwrap();
|
||||
(clone.callback)(ensembles[index].clone());
|
||||
}),
|
||||
);
|
||||
|
||||
clone
|
||||
.list
|
||||
.set_filter_func(Some(Box::new(clone!(@strong clone => move |row| {
|
||||
let row = row.get_child().unwrap().downcast::<SelectorRow>().unwrap();
|
||||
let index: usize = row.get_index().try_into().unwrap();
|
||||
let search = clone.search_entry.get_text().to_string().to_lowercase();
|
||||
search.is_empty() || ensembles[index]
|
||||
.name
|
||||
.to_lowercase()
|
||||
.contains(&search)
|
||||
}))));
|
||||
});
|
||||
|
||||
result
|
||||
.search_entry
|
||||
.connect_search_changed(clone!(@strong result => move |_| {
|
||||
result.list.invalidate_filter();
|
||||
}));
|
||||
|
||||
add_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
let editor = EnsembleEditor::new(
|
||||
result.backend.clone(),
|
||||
&result.window,
|
||||
None,
|
||||
clone!(@strong result => move |ensemble| {
|
||||
result.window.close();
|
||||
(result.callback)(ensemble);
|
||||
}),
|
||||
);
|
||||
|
||||
editor.show();
|
||||
}));
|
||||
|
||||
result.window.set_transient_for(Some(parent));
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn show(&self) {
|
||||
self.window.show();
|
||||
}
|
||||
}
|
||||
80
musicus/src/dialogs/instrument_editor.rs
Normal file
80
musicus/src/dialogs/instrument_editor.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
use crate::backend::*;
|
||||
use crate::database::*;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct InstrumentEditor<F>
|
||||
where
|
||||
F: Fn(Instrument) -> () + 'static,
|
||||
{
|
||||
backend: Rc<Backend>,
|
||||
window: libhandy::Window,
|
||||
callback: F,
|
||||
id: i64,
|
||||
name_entry: gtk::Entry,
|
||||
}
|
||||
|
||||
impl<F> InstrumentEditor<F>
|
||||
where
|
||||
F: Fn(Instrument) -> () + 'static,
|
||||
{
|
||||
pub fn new<P: IsA<gtk::Window>>(
|
||||
backend: Rc<Backend>,
|
||||
parent: &P,
|
||||
instrument: Option<Instrument>,
|
||||
callback: F,
|
||||
) -> Rc<Self> {
|
||||
let builder =
|
||||
gtk::Builder::from_resource("/de/johrpan/musicus/ui/instrument_editor.ui");
|
||||
|
||||
get_widget!(builder, libhandy::Window, window);
|
||||
get_widget!(builder, gtk::Button, cancel_button);
|
||||
get_widget!(builder, gtk::Button, save_button);
|
||||
get_widget!(builder, gtk::Entry, name_entry);
|
||||
|
||||
let id = match instrument {
|
||||
Some(instrument) => {
|
||||
name_entry.set_text(&instrument.name);
|
||||
instrument.id
|
||||
}
|
||||
None => rand::random::<u32>().into(),
|
||||
};
|
||||
|
||||
let result = Rc::new(InstrumentEditor {
|
||||
backend: backend,
|
||||
window: window,
|
||||
callback: callback,
|
||||
id: id,
|
||||
name_entry: name_entry,
|
||||
});
|
||||
|
||||
cancel_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
result.window.close();
|
||||
}));
|
||||
|
||||
save_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
let instrument = Instrument {
|
||||
id: result.id,
|
||||
name: result.name_entry.get_text().to_string(),
|
||||
};
|
||||
|
||||
let c = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
c.spawn_local(async move {
|
||||
clone.backend.update_instrument(instrument.clone()).await.unwrap();
|
||||
clone.window.close();
|
||||
(clone.callback)(instrument.clone());
|
||||
});
|
||||
}));
|
||||
|
||||
result.window.set_transient_for(Some(parent));
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn show(&self) {
|
||||
self.window.show();
|
||||
}
|
||||
}
|
||||
113
musicus/src/dialogs/instrument_selector.rs
Normal file
113
musicus/src/dialogs/instrument_selector.rs
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
use super::InstrumentEditor;
|
||||
use crate::backend::Backend;
|
||||
use crate::database::*;
|
||||
use crate::widgets::*;
|
||||
use gio::prelude::*;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use std::convert::TryInto;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct InstrumentSelector<F>
|
||||
where
|
||||
F: Fn(Instrument) -> () + 'static,
|
||||
{
|
||||
backend: Rc<Backend>,
|
||||
window: libhandy::Window,
|
||||
callback: F,
|
||||
list: gtk::ListBox,
|
||||
search_entry: gtk::SearchEntry,
|
||||
}
|
||||
|
||||
impl<F> InstrumentSelector<F>
|
||||
where
|
||||
F: Fn(Instrument) -> () + 'static,
|
||||
{
|
||||
pub fn new<P: IsA<gtk::Window>>(backend: Rc<Backend>, parent: &P, callback: F) -> Rc<Self> {
|
||||
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::SearchEntry, search_entry);
|
||||
get_widget!(builder, gtk::ListBox, list);
|
||||
|
||||
let result = Rc::new(InstrumentSelector {
|
||||
backend: backend,
|
||||
window: window,
|
||||
callback: callback,
|
||||
search_entry: search_entry,
|
||||
list: list,
|
||||
});
|
||||
|
||||
let c = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
c.spawn_local(async move {
|
||||
let instruments = clone.backend.get_instruments().await.unwrap();
|
||||
|
||||
for (index, instrument) in instruments.iter().enumerate() {
|
||||
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);
|
||||
|
||||
let row = SelectorRow::new(index.try_into().unwrap(), &label);
|
||||
row.show_all();
|
||||
clone.list.insert(&row, -1);
|
||||
}
|
||||
|
||||
clone.list.connect_row_activated(
|
||||
clone!(@strong clone, @strong instruments => move |_, row| {
|
||||
clone.window.close();
|
||||
let row = row.get_child().unwrap().downcast::<SelectorRow>().unwrap();
|
||||
let index: usize = row.get_index().try_into().unwrap();
|
||||
(clone.callback)(instruments[index].clone());
|
||||
}),
|
||||
);
|
||||
|
||||
clone
|
||||
.list
|
||||
.set_filter_func(Some(Box::new(clone!(@strong clone => move |row| {
|
||||
let row = row.get_child().unwrap().downcast::<SelectorRow>().unwrap();
|
||||
let index: usize = row.get_index().try_into().unwrap();
|
||||
let search = clone.search_entry.get_text().to_string();
|
||||
|
||||
search.is_empty() || instruments[index]
|
||||
.name
|
||||
.to_lowercase()
|
||||
.contains(&clone.search_entry.get_text().to_string().to_lowercase())
|
||||
}))));
|
||||
});
|
||||
|
||||
result
|
||||
.search_entry
|
||||
.connect_search_changed(clone!(@strong result => move |_| {
|
||||
result.list.invalidate_filter();
|
||||
}));
|
||||
|
||||
add_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
let editor = InstrumentEditor::new(
|
||||
result.backend.clone(),
|
||||
&result.window,
|
||||
None,
|
||||
clone!(@strong result => move |instrument| {
|
||||
result.window.close();
|
||||
(result.callback)(instrument);
|
||||
}),
|
||||
);
|
||||
|
||||
editor.show();
|
||||
}));
|
||||
|
||||
result.window.set_transient_for(Some(parent));
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn show(&self) {
|
||||
self.window.show();
|
||||
}
|
||||
}
|
||||
88
musicus/src/dialogs/login_dialog.rs
Normal file
88
musicus/src/dialogs/login_dialog.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
use crate::backend::{Backend, LoginData};
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A dialog for entering login credentials.
|
||||
pub struct LoginDialog {
|
||||
backend: Rc<Backend>,
|
||||
window: libhandy::Window,
|
||||
stack: gtk::Stack,
|
||||
info_bar: gtk::InfoBar,
|
||||
username_entry: gtk::Entry,
|
||||
password_entry: gtk::Entry,
|
||||
selected_cb: RefCell<Option<Box<dyn Fn(LoginData) -> ()>>>,
|
||||
}
|
||||
|
||||
impl LoginDialog {
|
||||
/// Create a new login dialog.
|
||||
pub fn new<P: IsA<gtk::Window>>(backend: Rc<Backend>, parent: &P) -> Rc<Self> {
|
||||
// Create UI
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/login_dialog.ui");
|
||||
|
||||
get_widget!(builder, libhandy::Window, window);
|
||||
get_widget!(builder, gtk::Stack, stack);
|
||||
get_widget!(builder, gtk::InfoBar, info_bar);
|
||||
get_widget!(builder, gtk::Button, cancel_button);
|
||||
get_widget!(builder, gtk::Button, login_button);
|
||||
get_widget!(builder, gtk::Entry, username_entry);
|
||||
get_widget!(builder, gtk::Entry, password_entry);
|
||||
|
||||
window.set_transient_for(Some(parent));
|
||||
|
||||
let this = Rc::new(Self {
|
||||
backend,
|
||||
window,
|
||||
stack,
|
||||
info_bar,
|
||||
username_entry,
|
||||
password_entry,
|
||||
selected_cb: RefCell::new(None),
|
||||
});
|
||||
|
||||
// Connect signals and callbacks
|
||||
|
||||
cancel_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
this.window.close();
|
||||
}));
|
||||
|
||||
login_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
this.stack.set_visible_child_name("loading");
|
||||
|
||||
let data = LoginData {
|
||||
username: this.username_entry.get_text().to_string(),
|
||||
password: this.password_entry.get_text().to_string(),
|
||||
};
|
||||
|
||||
let c = glib::MainContext::default();
|
||||
let clone = this.clone();
|
||||
c.spawn_local(async move {
|
||||
clone.backend.set_login_data(data.clone()).await.unwrap();
|
||||
if clone.backend.login().await.unwrap() {
|
||||
if let Some(cb) = &*clone.selected_cb.borrow() {
|
||||
cb(data);
|
||||
}
|
||||
|
||||
clone.window.close();
|
||||
} else {
|
||||
clone.stack.set_visible_child_name("content");
|
||||
clone.info_bar.set_revealed(true);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// The closure to call when the login succeded.
|
||||
pub fn set_selected_cb<F: Fn(LoginData) -> () + 'static>(&self, cb: F) {
|
||||
self.selected_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
|
||||
/// Show the login dialog.
|
||||
pub fn show(&self) {
|
||||
self.window.show();
|
||||
}
|
||||
}
|
||||
41
musicus/src/dialogs/mod.rs
Normal file
41
musicus/src/dialogs/mod.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
pub mod 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 use login_dialog::*;
|
||||
|
||||
pub mod person_editor;
|
||||
pub use person_editor::*;
|
||||
|
||||
pub mod person_selector;
|
||||
pub use person_selector::*;
|
||||
|
||||
pub mod preferences;
|
||||
pub use preferences::*;
|
||||
|
||||
pub mod 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::*;
|
||||
84
musicus/src/dialogs/person_editor.rs
Normal file
84
musicus/src/dialogs/person_editor.rs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
use crate::backend::Backend;
|
||||
use crate::database::*;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct PersonEditor<F>
|
||||
where
|
||||
F: Fn(Person) -> () + 'static,
|
||||
{
|
||||
backend: Rc<Backend>,
|
||||
window: libhandy::Window,
|
||||
callback: F,
|
||||
id: i64,
|
||||
first_name_entry: gtk::Entry,
|
||||
last_name_entry: gtk::Entry,
|
||||
}
|
||||
|
||||
impl<F> PersonEditor<F>
|
||||
where
|
||||
F: Fn(Person) -> () + 'static,
|
||||
{
|
||||
pub fn new<P: IsA<gtk::Window>>(
|
||||
backend: Rc<Backend>,
|
||||
parent: &P,
|
||||
person: Option<Person>,
|
||||
callback: F,
|
||||
) -> Rc<Self> {
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/person_editor.ui");
|
||||
|
||||
get_widget!(builder, libhandy::Window, window);
|
||||
get_widget!(builder, gtk::Button, cancel_button);
|
||||
get_widget!(builder, gtk::Button, save_button);
|
||||
get_widget!(builder, gtk::Entry, first_name_entry);
|
||||
get_widget!(builder, gtk::Entry, last_name_entry);
|
||||
|
||||
let id = match person {
|
||||
Some(person) => {
|
||||
first_name_entry.set_text(&person.first_name);
|
||||
last_name_entry.set_text(&person.last_name);
|
||||
person.id
|
||||
}
|
||||
None => rand::random::<u32>().into(),
|
||||
};
|
||||
|
||||
let result = Rc::new(PersonEditor {
|
||||
backend: backend,
|
||||
window: window,
|
||||
callback: callback,
|
||||
id: id,
|
||||
first_name_entry: first_name_entry,
|
||||
last_name_entry: last_name_entry,
|
||||
});
|
||||
|
||||
cancel_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
result.window.close();
|
||||
}));
|
||||
|
||||
save_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
let person = Person {
|
||||
id: result.id,
|
||||
first_name: result.first_name_entry.get_text().to_string(),
|
||||
last_name: result.last_name_entry.get_text().to_string(),
|
||||
};
|
||||
|
||||
let c = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
c.spawn_local(async move {
|
||||
clone.backend.update_person(person.clone()).await.unwrap();
|
||||
clone.window.close();
|
||||
(clone.callback)(person.clone());
|
||||
});
|
||||
}));
|
||||
|
||||
result.window.set_transient_for(Some(parent));
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn show(&self) {
|
||||
self.window.show();
|
||||
}
|
||||
}
|
||||
61
musicus/src/dialogs/person_selector.rs
Normal file
61
musicus/src/dialogs/person_selector.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
use super::PersonEditor;
|
||||
use crate::backend::Backend;
|
||||
use crate::database::*;
|
||||
use crate::widgets::*;
|
||||
use gio::prelude::*;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct PersonSelector {
|
||||
window: libhandy::Window,
|
||||
}
|
||||
|
||||
impl PersonSelector {
|
||||
pub fn new<P, F>(backend: Rc<Backend>, parent: &P, callback: F) -> Self
|
||||
where
|
||||
P: IsA<gtk::Window>,
|
||||
F: Fn(Person) -> () + 'static,
|
||||
{
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/person_selector.ui");
|
||||
|
||||
get_widget!(builder, libhandy::Window, window);
|
||||
get_widget!(builder, gtk::Box, vbox);
|
||||
get_widget!(builder, gtk::Button, add_button);
|
||||
|
||||
let callback = Rc::new(callback);
|
||||
|
||||
let list = PersonList::new(backend.clone());
|
||||
|
||||
list.set_selected(clone!(@strong window, @strong callback => move |person| {
|
||||
window.close();
|
||||
callback(person.clone());
|
||||
}));
|
||||
|
||||
vbox.pack_start(&list.widget, true, true, 0);
|
||||
window.set_transient_for(Some(parent));
|
||||
|
||||
add_button.connect_clicked(
|
||||
clone!(@strong backend, @strong window, @strong callback => move |_| {
|
||||
let editor = PersonEditor::new(
|
||||
backend.clone(),
|
||||
&window,
|
||||
None,
|
||||
clone!(@strong window, @strong callback => move |person| {
|
||||
window.close();
|
||||
callback(person);
|
||||
}),
|
||||
);
|
||||
|
||||
editor.show();
|
||||
}),
|
||||
);
|
||||
|
||||
Self { window }
|
||||
}
|
||||
|
||||
pub fn show(&self) {
|
||||
self.window.show();
|
||||
}
|
||||
}
|
||||
105
musicus/src/dialogs/preferences.rs
Normal file
105
musicus/src/dialogs/preferences.rs
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
use super::{LoginDialog, ServerDialog};
|
||||
use crate::backend::Backend;
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use libhandy::prelude::*;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A dialog for configuring the app.
|
||||
pub struct Preferences {
|
||||
backend: Rc<Backend>,
|
||||
window: libhandy::Window,
|
||||
music_library_path_row: libhandy::ActionRow,
|
||||
url_row: libhandy::ActionRow,
|
||||
login_row: libhandy::ActionRow,
|
||||
}
|
||||
|
||||
impl Preferences {
|
||||
/// Create a new preferences dialog.
|
||||
pub fn new<P: IsA<gtk::Window>>(backend: Rc<Backend>, parent: &P) -> Rc<Self> {
|
||||
// Create UI
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/preferences.ui");
|
||||
|
||||
get_widget!(builder, libhandy::Window, window);
|
||||
get_widget!(builder, libhandy::ActionRow, music_library_path_row);
|
||||
get_widget!(builder, gtk::Button, select_music_library_path_button);
|
||||
get_widget!(builder, libhandy::ActionRow, url_row);
|
||||
get_widget!(builder, gtk::Button, url_button);
|
||||
get_widget!(builder, libhandy::ActionRow, login_row);
|
||||
get_widget!(builder, gtk::Button, login_button);
|
||||
|
||||
window.set_transient_for(Some(parent));
|
||||
|
||||
let this = Rc::new(Self {
|
||||
backend,
|
||||
window,
|
||||
music_library_path_row,
|
||||
url_row,
|
||||
login_row,
|
||||
});
|
||||
|
||||
// Connect signals and callbacks
|
||||
|
||||
select_music_library_path_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
let dialog = gtk::FileChooserNative::new(
|
||||
Some(&gettext("Select music library folder")),
|
||||
Some(&this.window), gtk::FileChooserAction::SelectFolder,None, None);
|
||||
|
||||
if let gtk::ResponseType::Accept = dialog.run() {
|
||||
if let Some(path) = dialog.get_filename() {
|
||||
this.music_library_path_row.set_subtitle(Some(path.to_str().unwrap()));
|
||||
|
||||
let context = glib::MainContext::default();
|
||||
let backend = this.backend.clone();
|
||||
context.spawn_local(async move {
|
||||
backend.set_music_library_path(path).await.unwrap();
|
||||
});
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
url_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
let dialog = ServerDialog::new(this.backend.clone(), &this.window);
|
||||
|
||||
dialog.set_selected_cb(clone!(@strong this => move |url| {
|
||||
this.url_row.set_subtitle(Some(&url));
|
||||
}));
|
||||
|
||||
dialog.show();
|
||||
}));
|
||||
|
||||
login_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
let dialog = LoginDialog::new(this.backend.clone(), &this.window);
|
||||
|
||||
dialog.set_selected_cb(clone!(@strong this => move |data| {
|
||||
this.login_row.set_subtitle(Some(&data.username));
|
||||
}));
|
||||
|
||||
dialog.show();
|
||||
}));
|
||||
|
||||
// Initialize
|
||||
|
||||
if let Some(path) = this.backend.get_music_library_path() {
|
||||
this.music_library_path_row
|
||||
.set_subtitle(Some(path.to_str().unwrap()));
|
||||
}
|
||||
|
||||
if let Some(url) = this.backend.get_server_url() {
|
||||
this.url_row.set_subtitle(Some(&url));
|
||||
}
|
||||
|
||||
if let Some(data) = this.backend.get_login_data() {
|
||||
this.login_row.set_subtitle(Some(&data.username));
|
||||
}
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// Show the preferences dialog.
|
||||
pub fn show(&self) {
|
||||
self.window.show();
|
||||
}
|
||||
}
|
||||
11
musicus/src/dialogs/recording/mod.rs
Normal file
11
musicus/src/dialogs/recording/mod.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
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;
|
||||
174
musicus/src/dialogs/recording/performance_editor.rs
Normal file
174
musicus/src/dialogs/recording/performance_editor.rs
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
use crate::backend::*;
|
||||
use crate::database::*;
|
||||
use crate::dialogs::*;
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A dialog for editing a performance within a recording.
|
||||
pub struct PerformanceEditor {
|
||||
backend: Rc<Backend>,
|
||||
window: libhandy::Window,
|
||||
save_button: gtk::Button,
|
||||
person_label: gtk::Label,
|
||||
ensemble_label: gtk::Label,
|
||||
role_label: gtk::Label,
|
||||
reset_role_button: gtk::Button,
|
||||
person: RefCell<Option<Person>>,
|
||||
ensemble: RefCell<Option<Ensemble>>,
|
||||
role: RefCell<Option<Instrument>>,
|
||||
selected_cb: RefCell<Option<Box<dyn Fn(PerformanceDescription) -> ()>>>,
|
||||
}
|
||||
|
||||
impl PerformanceEditor {
|
||||
/// Create a new performance editor.
|
||||
pub fn new<P: IsA<gtk::Window>>(
|
||||
backend: Rc<Backend>,
|
||||
parent: &P,
|
||||
performance: Option<PerformanceDescription>,
|
||||
) -> Rc<Self> {
|
||||
// Create UI
|
||||
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/performance_editor.ui");
|
||||
|
||||
get_widget!(builder, libhandy::Window, window);
|
||||
get_widget!(builder, gtk::Button, cancel_button);
|
||||
get_widget!(builder, gtk::Button, save_button);
|
||||
get_widget!(builder, gtk::Button, person_button);
|
||||
get_widget!(builder, gtk::Button, ensemble_button);
|
||||
get_widget!(builder, gtk::Button, role_button);
|
||||
get_widget!(builder, gtk::Button, reset_role_button);
|
||||
get_widget!(builder, gtk::Label, person_label);
|
||||
get_widget!(builder, gtk::Label, ensemble_label);
|
||||
get_widget!(builder, gtk::Label, role_label);
|
||||
|
||||
window.set_transient_for(Some(parent));
|
||||
|
||||
let this = Rc::new(PerformanceEditor {
|
||||
backend,
|
||||
window,
|
||||
save_button,
|
||||
person_label,
|
||||
ensemble_label,
|
||||
role_label,
|
||||
reset_role_button,
|
||||
person: RefCell::new(None),
|
||||
ensemble: RefCell::new(None),
|
||||
role: RefCell::new(None),
|
||||
selected_cb: RefCell::new(None),
|
||||
});
|
||||
|
||||
// Connect signals and callbacks
|
||||
|
||||
cancel_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
this.window.close();
|
||||
}));
|
||||
|
||||
this.save_button
|
||||
.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(cb) = &*this.selected_cb.borrow() {
|
||||
cb(PerformanceDescription {
|
||||
person: this.person.borrow().clone(),
|
||||
ensemble: this.ensemble.borrow().clone(),
|
||||
role: this.role.borrow().clone(),
|
||||
});
|
||||
|
||||
this.window.close();
|
||||
}
|
||||
}));
|
||||
|
||||
person_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
PersonSelector::new(this.backend.clone(), &this.window, clone!(@strong this => move |person| {
|
||||
this.show_person(Some(&person));
|
||||
this.person.replace(Some(person));
|
||||
this.show_ensemble(None);
|
||||
this.ensemble.replace(None);
|
||||
})).show();
|
||||
}));
|
||||
|
||||
ensemble_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
EnsembleSelector::new(this.backend.clone(), &this.window, clone!(@strong this => move |ensemble| {
|
||||
this.show_person(None);
|
||||
this.person.replace(None);
|
||||
this.show_ensemble(Some(&ensemble));
|
||||
this.ensemble.replace(Some(ensemble));
|
||||
})).show();
|
||||
}));
|
||||
|
||||
role_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
InstrumentSelector::new(this.backend.clone(), &this.window, clone!(@strong this => move |role| {
|
||||
this.show_role(Some(&role));
|
||||
this.role.replace(Some(role));
|
||||
})).show();
|
||||
}));
|
||||
|
||||
this.reset_role_button
|
||||
.connect_clicked(clone!(@strong this => move |_| {
|
||||
this.show_role(None);
|
||||
this.role.replace(None);
|
||||
}));
|
||||
|
||||
// Initialize
|
||||
|
||||
if let Some(performance) = performance {
|
||||
if let Some(person) = performance.person {
|
||||
this.show_person(Some(&person));
|
||||
this.person.replace(Some(person));
|
||||
} else if let Some(ensemble) = performance.ensemble {
|
||||
this.show_ensemble(Some(&ensemble));
|
||||
this.ensemble.replace(Some(ensemble));
|
||||
}
|
||||
|
||||
if let Some(role) = performance.role {
|
||||
this.show_role(Some(&role));
|
||||
this.role.replace(Some(role));
|
||||
}
|
||||
}
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// Set a closure to be called when the user has chosen to save the performance.
|
||||
pub fn set_selected_cb<F: Fn(PerformanceDescription) -> () + 'static>(&self, cb: F) {
|
||||
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.
|
||||
fn show_person(&self, person: Option<&Person>) {
|
||||
if let Some(person) = person {
|
||||
self.person_label.set_text(&person.name_fl());
|
||||
self.save_button.set_sensitive(true);
|
||||
} else {
|
||||
self.person_label.set_text(&gettext("Select …"));
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the UI according to ensemble.
|
||||
fn show_ensemble(&self, ensemble: Option<&Ensemble>) {
|
||||
if let Some(ensemble) = ensemble {
|
||||
self.ensemble_label.set_text(&ensemble.name);
|
||||
self.save_button.set_sensitive(true);
|
||||
} else {
|
||||
self.ensemble_label.set_text(&gettext("Select …"));
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the UI according to role.
|
||||
fn show_role(&self, role: Option<&Instrument>) {
|
||||
if let Some(role) = role {
|
||||
self.role_label.set_text(&role.name);
|
||||
self.reset_role_button.show();
|
||||
} else {
|
||||
self.role_label.set_text(&gettext("Select …"));
|
||||
self.reset_role_button.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
86
musicus/src/dialogs/recording/recording_dialog.rs
Normal file
86
musicus/src/dialogs/recording/recording_dialog.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
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(RecordingDescription) -> ()>>>,
|
||||
}
|
||||
|
||||
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(RecordingDescription) -> () + 'static>(&self, cb: F) {
|
||||
self.selected_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
|
||||
/// Show the recording dialog.
|
||||
pub fn show(&self) {
|
||||
self.window.show();
|
||||
}
|
||||
}
|
||||
204
musicus/src/dialogs/recording/recording_editor.rs
Normal file
204
musicus/src/dialogs/recording/recording_editor.rs
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
use super::performance_editor::*;
|
||||
use crate::backend::*;
|
||||
use crate::database::*;
|
||||
use crate::dialogs::*;
|
||||
use crate::widgets::*;
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A widget for creating or editing a recording.
|
||||
// TODO: Disable buttons if no performance is selected.
|
||||
pub struct RecordingEditor {
|
||||
pub widget: gtk::Box,
|
||||
backend: Rc<Backend>,
|
||||
parent: gtk::Window,
|
||||
save_button: gtk::Button,
|
||||
work_label: gtk::Label,
|
||||
comment_entry: gtk::Entry,
|
||||
performance_list: Rc<List<PerformanceDescription>>,
|
||||
id: i64,
|
||||
work: RefCell<Option<WorkDescription>>,
|
||||
performances: RefCell<Vec<PerformanceDescription>>,
|
||||
selected_cb: RefCell<Option<Box<dyn Fn(RecordingDescription) -> ()>>>,
|
||||
back_cb: RefCell<Option<Box<dyn Fn() -> ()>>>,
|
||||
}
|
||||
|
||||
impl RecordingEditor {
|
||||
/// Create a new recording editor widget and optionally initialize it. The parent window is
|
||||
/// used as the parent for newly created dialogs.
|
||||
pub fn new<W: IsA<gtk::Window>>(
|
||||
backend: Rc<Backend>,
|
||||
parent: &W,
|
||||
recording: Option<RecordingDescription>,
|
||||
) -> Rc<Self> {
|
||||
// Create UI
|
||||
|
||||
let builder =
|
||||
gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_editor.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
get_widget!(builder, gtk::Button, cancel_button);
|
||||
get_widget!(builder, gtk::Button, save_button);
|
||||
get_widget!(builder, gtk::Button, work_button);
|
||||
get_widget!(builder, gtk::Label, work_label);
|
||||
get_widget!(builder, gtk::Entry, comment_entry);
|
||||
get_widget!(builder, gtk::ScrolledWindow, scroll);
|
||||
get_widget!(builder, gtk::Button, add_performer_button);
|
||||
get_widget!(builder, gtk::Button, edit_performer_button);
|
||||
get_widget!(builder, gtk::Button, remove_performer_button);
|
||||
|
||||
let performance_list = List::new(&gettext("No performers added."));
|
||||
scroll.add(&performance_list.widget);
|
||||
|
||||
let (id, work, performances) = match recording {
|
||||
Some(recording) => {
|
||||
comment_entry.set_text(&recording.comment);
|
||||
(recording.id, Some(recording.work), recording.performances)
|
||||
}
|
||||
None => (rand::random::<u32>().into(), None, Vec::new()),
|
||||
};
|
||||
|
||||
let this = Rc::new(RecordingEditor {
|
||||
widget,
|
||||
backend,
|
||||
parent: parent.clone().upcast(),
|
||||
save_button,
|
||||
work_label,
|
||||
comment_entry,
|
||||
performance_list,
|
||||
id,
|
||||
work: RefCell::new(work),
|
||||
performances: RefCell::new(performances),
|
||||
selected_cb: RefCell::new(None),
|
||||
back_cb: RefCell::new(None),
|
||||
});
|
||||
|
||||
// Connect signals and callbacks
|
||||
|
||||
cancel_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(cb) = &*this.back_cb.borrow() {
|
||||
cb();
|
||||
}
|
||||
}));
|
||||
|
||||
this.save_button
|
||||
.connect_clicked(clone!(@strong this => move |_| {
|
||||
let recording = RecordingDescription {
|
||||
id: this.id,
|
||||
work: this.work.borrow().clone().expect("Tried to create recording without work!"),
|
||||
comment: this.comment_entry.get_text().to_string(),
|
||||
performances: this.performances.borrow().clone(),
|
||||
};
|
||||
|
||||
let c = glib::MainContext::default();
|
||||
let clone = this.clone();
|
||||
c.spawn_local(async move {
|
||||
clone.backend.update_recording(recording.clone().into()).await.unwrap();
|
||||
if let Some(cb) = &*clone.selected_cb.borrow() {
|
||||
cb(recording.clone());
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
work_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
let dialog = WorkDialog::new(this.backend.clone(), &this.parent);
|
||||
|
||||
dialog.set_selected_cb(clone!(@strong this => move |work| {
|
||||
this.work_selected(&work);
|
||||
this.work.replace(Some(work));
|
||||
}));
|
||||
|
||||
dialog.show();
|
||||
}));
|
||||
|
||||
this.performance_list.set_make_widget(|performance| {
|
||||
let label = gtk::Label::new(Some(&performance.get_title()));
|
||||
label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
label.set_halign(gtk::Align::Start);
|
||||
label.set_margin_start(6);
|
||||
label.set_margin_end(6);
|
||||
label.set_margin_top(6);
|
||||
label.set_margin_bottom(6);
|
||||
label.upcast()
|
||||
});
|
||||
|
||||
add_performer_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
let editor = PerformanceEditor::new(this.backend.clone(), &this.parent, None);
|
||||
|
||||
editor.set_selected_cb(clone!(@strong this => move |performance| {
|
||||
let mut performances = this.performances.borrow_mut();
|
||||
|
||||
let index = match this.performance_list.get_selected_index() {
|
||||
Some(index) => index + 1,
|
||||
None => performances.len(),
|
||||
};
|
||||
|
||||
performances.insert(index, performance);
|
||||
this.performance_list.show_items(performances.clone());
|
||||
this.performance_list.select_index(index);
|
||||
}));
|
||||
|
||||
editor.show();
|
||||
}));
|
||||
|
||||
edit_performer_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(index) = this.performance_list.get_selected_index() {
|
||||
let performance = &this.performances.borrow()[index];
|
||||
|
||||
let editor = PerformanceEditor::new(
|
||||
this.backend.clone(),
|
||||
&this.parent,
|
||||
Some(performance.clone()),
|
||||
);
|
||||
|
||||
editor.set_selected_cb(clone!(@strong this => move |performance| {
|
||||
let mut performances = this.performances.borrow_mut();
|
||||
performances[index] = performance;
|
||||
this.performance_list.show_items(performances.clone());
|
||||
this.performance_list.select_index(index);
|
||||
}));
|
||||
|
||||
editor.show();
|
||||
}
|
||||
}));
|
||||
|
||||
remove_performer_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(index) = this.performance_list.get_selected_index() {
|
||||
let mut performances = this.performances.borrow_mut();
|
||||
performances.remove(index);
|
||||
this.performance_list.show_items(performances.clone());
|
||||
this.performance_list.select_index(index);
|
||||
}
|
||||
}));
|
||||
|
||||
// Initialize
|
||||
|
||||
if let Some(work) = &*this.work.borrow() {
|
||||
this.work_selected(work);
|
||||
}
|
||||
|
||||
this.performance_list.show_items(this.performances.borrow().clone());
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// Set the closure to be called if the editor is canceled.
|
||||
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 if the recording was created.
|
||||
pub fn set_selected_cb<F: Fn(RecordingDescription) -> () + 'static>(&self, cb: F) {
|
||||
self.selected_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
|
||||
/// Update the UI according to work.
|
||||
fn work_selected(&self, work: &WorkDescription) {
|
||||
self.work_label.set_text(&format!("{}: {}", work.composer.name_fl(), work.title));
|
||||
self.save_button.set_sensitive(true);
|
||||
}
|
||||
}
|
||||
63
musicus/src/dialogs/recording/recording_editor_dialog.rs
Normal file
63
musicus/src/dialogs/recording/recording_editor_dialog.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
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(RecordingDescription) -> ()>>>,
|
||||
}
|
||||
|
||||
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<RecordingDescription>,
|
||||
) -> 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(RecordingDescription) -> () + 'static>(&self, cb: F) {
|
||||
self.selected_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
|
||||
/// Show the recording editor dialog.
|
||||
pub fn show(&self) {
|
||||
self.window.show();
|
||||
}
|
||||
}
|
||||
89
musicus/src/dialogs/recording/recording_selector.rs
Normal file
89
musicus/src/dialogs/recording/recording_selector.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
use super::recording_selector_person_screen::*;
|
||||
use crate::backend::Backend;
|
||||
use crate::database::*;
|
||||
use crate::widgets::*;
|
||||
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,
|
||||
selected_cb: RefCell<Option<Box<dyn Fn(RecordingDescription) -> ()>>>,
|
||||
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::Box, empty_screen);
|
||||
|
||||
let person_list = PersonList::new(backend.clone());
|
||||
sidebar_box.pack_start(&person_list.widget, true, true, 0);
|
||||
|
||||
let navigator = Navigator::new(&empty_screen);
|
||||
widget.add(&navigator.widget);
|
||||
|
||||
let this = Rc::new(Self {
|
||||
widget,
|
||||
backend,
|
||||
sidebar_box,
|
||||
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();
|
||||
}
|
||||
}));
|
||||
|
||||
person_list.set_selected(clone!(@strong this => move |person| {
|
||||
let person_screen = RecordingSelectorPersonScreen::new(
|
||||
this.backend.clone(),
|
||||
person.clone(),
|
||||
);
|
||||
|
||||
person_screen.set_selected_cb(clone!(@strong this => move |recording| {
|
||||
if let Some(cb) = &*this.selected_cb.borrow() {
|
||||
cb(recording);
|
||||
}
|
||||
}));
|
||||
|
||||
this.navigator.clone().push(person_screen);
|
||||
this.widget.set_visible_child(&this.navigator.widget);
|
||||
}));
|
||||
|
||||
this.navigator.set_back_cb(clone!(@strong this => move || {
|
||||
this.widget.set_visible_child(&this.sidebar_box);
|
||||
}));
|
||||
|
||||
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(RecordingDescription) -> () + 'static>(&self, cb: F) {
|
||||
self.selected_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
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>,
|
||||
widget: gtk::Box,
|
||||
stack: gtk::Stack,
|
||||
work_list: Rc<List<WorkDescription>>,
|
||||
selected_cb: RefCell<Option<Box<dyn Fn(RecordingDescription) -> ()>>>,
|
||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||
}
|
||||
|
||||
impl RecordingSelectorPersonScreen {
|
||||
/// Create a new recording selector person screen.
|
||||
pub fn new(backend: Rc<Backend>, person: Person) -> 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);
|
||||
|
||||
header.set_title(Some(&person.name_fl()));
|
||||
|
||||
let work_list = List::new(&gettext("No works found."));
|
||||
stack.add_named(&work_list.widget, "content");
|
||||
|
||||
let this = Rc::new(Self {
|
||||
backend,
|
||||
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();
|
||||
}
|
||||
}));
|
||||
|
||||
this.work_list.set_make_widget(|work: &WorkDescription| {
|
||||
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(),
|
||||
);
|
||||
|
||||
work_screen.set_selected_cb(clone!(@strong this => move |recording| {
|
||||
if let Some(cb) = &*this.selected_cb.borrow() {
|
||||
cb(recording);
|
||||
}
|
||||
}));
|
||||
|
||||
navigator.push(work_screen);
|
||||
}
|
||||
}));
|
||||
|
||||
// Initialize
|
||||
|
||||
let context = glib::MainContext::default();
|
||||
let clone = this.clone();
|
||||
context.spawn_local(async move {
|
||||
let works = clone
|
||||
.backend
|
||||
.get_work_descriptions(person.id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
clone.work_list.show_items(works);
|
||||
clone.stack.set_visible_child_name("content");
|
||||
});
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// Sets a closure to be called when the user has selected a recording.
|
||||
pub fn set_selected_cb<F: Fn(RecordingDescription) -> () + '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);
|
||||
}
|
||||
}
|
||||
120
musicus/src/dialogs/recording/recording_selector_work_screen.rs
Normal file
120
musicus/src/dialogs/recording/recording_selector_work_screen.rs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
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>,
|
||||
widget: gtk::Box,
|
||||
stack: gtk::Stack,
|
||||
recording_list: Rc<List<RecordingDescription>>,
|
||||
selected_cb: RefCell<Option<Box<dyn Fn(RecordingDescription) -> ()>>>,
|
||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||
}
|
||||
|
||||
impl RecordingSelectorWorkScreen {
|
||||
/// Create a new recording selector work screen.
|
||||
pub fn new(backend: Rc<Backend>, work: WorkDescription) -> 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);
|
||||
|
||||
header.set_title(Some(&work.title));
|
||||
header.set_subtitle(Some(&work.composer.name_fl()));
|
||||
|
||||
let recording_list = List::new(&gettext("No recordings found."));
|
||||
stack.add_named(&recording_list.widget, "content");
|
||||
|
||||
let this = Rc::new(Self {
|
||||
backend,
|
||||
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();
|
||||
}
|
||||
}));
|
||||
|
||||
this.recording_list.set_make_widget(|recording: &RecordingDescription| {
|
||||
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());
|
||||
}
|
||||
}));
|
||||
|
||||
// Initialize
|
||||
|
||||
let context = glib::MainContext::default();
|
||||
let clone = this.clone();
|
||||
context.spawn_local(async move {
|
||||
let recordings = clone
|
||||
.backend
|
||||
.get_recordings_for_work(work.id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
clone.recording_list.show_items(recordings);
|
||||
clone.stack.set_visible_child_name("content");
|
||||
});
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// Sets a closure to be called when the user has selected a recording.
|
||||
pub fn set_selected_cb<F: Fn(RecordingDescription) -> () + '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);
|
||||
}
|
||||
}
|
||||
65
musicus/src/dialogs/server_dialog.rs
Normal file
65
musicus/src/dialogs/server_dialog.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
use crate::backend::Backend;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A dialog for setting up the server.
|
||||
pub struct ServerDialog {
|
||||
backend: Rc<Backend>,
|
||||
window: libhandy::Window,
|
||||
url_entry: gtk::Entry,
|
||||
selected_cb: RefCell<Option<Box<dyn Fn(String) -> ()>>>,
|
||||
}
|
||||
|
||||
impl ServerDialog {
|
||||
/// Create a new server dialog.
|
||||
pub fn new<P: IsA<gtk::Window>>(backend: Rc<Backend>, parent: &P) -> Rc<Self> {
|
||||
// Create UI
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/server_dialog.ui");
|
||||
|
||||
get_widget!(builder, libhandy::Window, window);
|
||||
get_widget!(builder, gtk::Button, cancel_button);
|
||||
get_widget!(builder, gtk::Button, set_button);
|
||||
get_widget!(builder, gtk::Entry, url_entry);
|
||||
|
||||
window.set_transient_for(Some(parent));
|
||||
|
||||
let this = Rc::new(Self {
|
||||
backend,
|
||||
window,
|
||||
url_entry,
|
||||
selected_cb: RefCell::new(None),
|
||||
});
|
||||
|
||||
// Connect signals and callbacks
|
||||
|
||||
cancel_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
this.window.close();
|
||||
}));
|
||||
|
||||
set_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
let url = this.url_entry.get_text().to_string();
|
||||
this.backend.set_server_url(&url).unwrap();
|
||||
|
||||
if let Some(cb) = &*this.selected_cb.borrow() {
|
||||
cb(url);
|
||||
}
|
||||
|
||||
this.window.close();
|
||||
}));
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// The closure to call when the server was set.
|
||||
pub fn set_selected_cb<F: Fn(String) -> () + 'static>(&self, cb: F) {
|
||||
self.selected_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
|
||||
/// Show the server dialog.
|
||||
pub fn show(&self) {
|
||||
self.window.show();
|
||||
}
|
||||
}
|
||||
117
musicus/src/dialogs/track_editor.rs
Normal file
117
musicus/src/dialogs/track_editor.rs
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
use crate::database::*;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use std::cell::RefCell;
|
||||
use std::convert::TryInto;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct TrackEditor {
|
||||
window: libhandy::Window,
|
||||
}
|
||||
|
||||
impl TrackEditor {
|
||||
pub fn new<W, F>(parent: &W, track: TrackDescription, work: WorkDescription, callback: F) -> Self
|
||||
where
|
||||
W: IsA<gtk::Window>,
|
||||
F: Fn(TrackDescription) -> () + 'static,
|
||||
{
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/track_editor.ui");
|
||||
|
||||
get_widget!(builder, libhandy::Window, window);
|
||||
get_widget!(builder, gtk::Button, cancel_button);
|
||||
get_widget!(builder, gtk::Button, save_button);
|
||||
get_widget!(builder, gtk::ListBox, list);
|
||||
|
||||
window.set_transient_for(Some(parent));
|
||||
|
||||
cancel_button.connect_clicked(clone!(@strong window => move |_| {
|
||||
window.close();
|
||||
}));
|
||||
|
||||
let work = Rc::new(work);
|
||||
let work_parts = Rc::new(RefCell::new(track.work_parts));
|
||||
let file_name = track.file_name;
|
||||
|
||||
save_button.connect_clicked(clone!(@strong work_parts, @strong window => move |_| {
|
||||
let mut work_parts = work_parts.borrow_mut();
|
||||
work_parts.sort();
|
||||
|
||||
callback(TrackDescription {
|
||||
work_parts: work_parts.clone(),
|
||||
file_name: file_name.clone(),
|
||||
});
|
||||
|
||||
window.close();
|
||||
}));
|
||||
|
||||
for (index, part) in work.parts.iter().enumerate() {
|
||||
let check = gtk::CheckButton::new();
|
||||
check.set_active(work_parts.borrow().contains(&index));
|
||||
check.connect_toggled(clone!(@strong check, @strong work_parts => move |_| {
|
||||
if check.get_active() {
|
||||
let mut work_parts = work_parts.borrow_mut();
|
||||
work_parts.push(index);
|
||||
} else {
|
||||
let mut work_parts = work_parts.borrow_mut();
|
||||
if let Some(pos) = work_parts.iter().position(|part| *part == index) {
|
||||
work_parts.remove(pos);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
let label = gtk::Label::new(Some(&part.title));
|
||||
label.set_halign(gtk::Align::Start);
|
||||
label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
|
||||
let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 6);
|
||||
hbox.set_border_width(6);
|
||||
hbox.add(&check);
|
||||
hbox.add(&label);
|
||||
|
||||
let row = gtk::ListBoxRow::new();
|
||||
row.add(&hbox);
|
||||
row.show_all();
|
||||
|
||||
list.add(&row);
|
||||
list.connect_row_activated(
|
||||
clone!(@strong row, @strong check => move |_, activated_row| {
|
||||
if *activated_row == row {
|
||||
check.activate();
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
let mut section_count = 0;
|
||||
for section in &work.sections {
|
||||
let attributes = pango::AttrList::new();
|
||||
attributes.insert(pango::Attribute::new_weight(pango::Weight::Bold).unwrap());
|
||||
|
||||
let label = gtk::Label::new(Some(§ion.title));
|
||||
label.set_halign(gtk::Align::Start);
|
||||
label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
label.set_attributes(Some(&attributes));
|
||||
let wrap = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
wrap.set_border_width(6);
|
||||
wrap.add(&label);
|
||||
|
||||
let row = gtk::ListBoxRow::new();
|
||||
row.set_activatable(false);
|
||||
row.add(&wrap);
|
||||
row.show_all();
|
||||
|
||||
list.insert(
|
||||
&row,
|
||||
(section.before_index + section_count).try_into().unwrap(),
|
||||
);
|
||||
section_count += 1;
|
||||
}
|
||||
|
||||
Self { window }
|
||||
}
|
||||
|
||||
pub fn show(&self) {
|
||||
self.window.show();
|
||||
}
|
||||
}
|
||||
284
musicus/src/dialogs/tracks_editor.rs
Normal file
284
musicus/src/dialogs/tracks_editor.rs
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
use super::*;
|
||||
use crate::backend::*;
|
||||
use crate::database::*;
|
||||
use crate::widgets::*;
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A dialog for editing a set of tracks.
|
||||
// TODO: Disable buttons if no track is selected.
|
||||
pub struct TracksEditor {
|
||||
backend: Rc<Backend>,
|
||||
window: libhandy::Window,
|
||||
save_button: gtk::Button,
|
||||
recording_stack: gtk::Stack,
|
||||
work_label: gtk::Label,
|
||||
performers_label: gtk::Label,
|
||||
track_list: Rc<List<TrackDescription>>,
|
||||
recording: RefCell<Option<RecordingDescription>>,
|
||||
tracks: RefCell<Vec<TrackDescription>>,
|
||||
callback: RefCell<Option<Box<dyn Fn() -> ()>>>,
|
||||
}
|
||||
|
||||
impl TracksEditor {
|
||||
/// Create a new track editor an optionally initialize it with a recording and a list of
|
||||
/// tracks.
|
||||
pub fn new<P: IsA<gtk::Window>>(
|
||||
backend: Rc<Backend>,
|
||||
parent: &P,
|
||||
recording: Option<RecordingDescription>,
|
||||
tracks: Vec<TrackDescription>,
|
||||
) -> Rc<Self> {
|
||||
// UI setup
|
||||
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/tracks_editor.ui");
|
||||
|
||||
get_widget!(builder, libhandy::Window, window);
|
||||
get_widget!(builder, gtk::Button, cancel_button);
|
||||
get_widget!(builder, gtk::Button, save_button);
|
||||
get_widget!(builder, gtk::Button, recording_button);
|
||||
get_widget!(builder, gtk::Stack, recording_stack);
|
||||
get_widget!(builder, gtk::Label, work_label);
|
||||
get_widget!(builder, gtk::Label, performers_label);
|
||||
get_widget!(builder, gtk::ScrolledWindow, scroll);
|
||||
get_widget!(builder, gtk::Button, add_track_button);
|
||||
get_widget!(builder, gtk::Button, edit_track_button);
|
||||
get_widget!(builder, gtk::Button, remove_track_button);
|
||||
get_widget!(builder, gtk::Button, move_track_up_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."));
|
||||
scroll.add(&track_list.widget);
|
||||
|
||||
let this = Rc::new(Self {
|
||||
backend,
|
||||
window,
|
||||
save_button,
|
||||
recording_stack,
|
||||
work_label,
|
||||
performers_label,
|
||||
track_list,
|
||||
recording: RefCell::new(recording),
|
||||
tracks: RefCell::new(tracks),
|
||||
callback: RefCell::new(None),
|
||||
});
|
||||
|
||||
// Signals and callbacks
|
||||
|
||||
this.save_button
|
||||
.connect_clicked(clone!(@strong this => move |_| {
|
||||
let context = glib::MainContext::default();
|
||||
let this = this.clone();
|
||||
context.spawn_local(async move {
|
||||
this.backend.update_tracks(
|
||||
this.recording.borrow().as_ref().unwrap().id,
|
||||
this.tracks.borrow().clone(),
|
||||
).await.unwrap();
|
||||
|
||||
if let Some(callback) = &*this.callback.borrow() {
|
||||
callback();
|
||||
}
|
||||
|
||||
this.window.close();
|
||||
});
|
||||
|
||||
}));
|
||||
|
||||
recording_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
let dialog = RecordingDialog::new(this.backend.clone(), &this.window);
|
||||
|
||||
dialog.set_selected_cb(clone!(@strong this => move |recording| {
|
||||
this.recording_selected(&recording);
|
||||
this.recording.replace(Some(recording));
|
||||
}));
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
));
|
||||
|
||||
this.track_list
|
||||
.set_make_widget(clone!(@strong this => move |track| {
|
||||
this.build_track_row(track)
|
||||
}));
|
||||
|
||||
add_track_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
let music_library_path = this.backend.get_music_library_path().unwrap();
|
||||
|
||||
let dialog = gtk::FileChooserNative::new(
|
||||
Some(&gettext("Select audio files")),
|
||||
Some(&this.window),
|
||||
gtk::FileChooserAction::Open,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
dialog.set_select_multiple(true);
|
||||
dialog.set_current_folder(&music_library_path);
|
||||
|
||||
if let gtk::ResponseType::Accept = dialog.run() {
|
||||
let mut index = match this.track_list.get_selected_index() {
|
||||
Some(index) => index + 1,
|
||||
None => this.tracks.borrow().len(),
|
||||
};
|
||||
|
||||
{
|
||||
let mut tracks = this.tracks.borrow_mut();
|
||||
for file_name in dialog.get_filenames() {
|
||||
let file_name = file_name.strip_prefix(&music_library_path).unwrap();
|
||||
tracks.insert(index, TrackDescription {
|
||||
work_parts: Vec::new(),
|
||||
file_name: String::from(file_name.to_str().unwrap()),
|
||||
});
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
this.track_list.show_items(this.tracks.borrow().clone());
|
||||
this.autofill_parts();
|
||||
this.track_list.select_index(index);
|
||||
}
|
||||
}));
|
||||
|
||||
remove_track_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
match this.track_list.get_selected_index() {
|
||||
Some(index) => {
|
||||
let mut tracks = this.tracks.borrow_mut();
|
||||
tracks.remove(index);
|
||||
this.track_list.show_items(tracks.clone());
|
||||
this.track_list.select_index(index);
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}));
|
||||
|
||||
move_track_up_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
match this.track_list.get_selected_index() {
|
||||
Some(index) => {
|
||||
if index > 0 {
|
||||
let mut tracks = this.tracks.borrow_mut();
|
||||
tracks.swap(index - 1, index);
|
||||
this.track_list.show_items(tracks.clone());
|
||||
this.track_list.select_index(index - 1);
|
||||
}
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}));
|
||||
|
||||
move_track_down_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
match this.track_list.get_selected_index() {
|
||||
Some(index) => {
|
||||
let mut tracks = this.tracks.borrow_mut();
|
||||
if index < tracks.len() - 1 {
|
||||
tracks.swap(index, index + 1);
|
||||
this.track_list.show_items(tracks.clone());
|
||||
this.track_list.select_index(index + 1);
|
||||
}
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}));
|
||||
|
||||
edit_track_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(index) = this.track_list.get_selected_index() {
|
||||
if let Some(recording) = &*this.recording.borrow() {
|
||||
TrackEditor::new(&this.window, this.tracks.borrow()[index].clone(), recording.work.clone(), clone!(@strong this => move |track| {
|
||||
let mut tracks = this.tracks.borrow_mut();
|
||||
tracks[index] = track;
|
||||
this.track_list.show_items(tracks.clone());
|
||||
this.track_list.select_index(index);
|
||||
})).show();
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Initialization
|
||||
|
||||
if let Some(recording) = &*this.recording.borrow() {
|
||||
this.recording_selected(recording);
|
||||
}
|
||||
|
||||
this.track_list.show_items(this.tracks.borrow().clone());
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// Set a callback to be called when the tracks are saved.
|
||||
pub fn set_callback<F: Fn() -> () + 'static>(&self, cb: F) {
|
||||
self.callback.replace(Some(Box::new(cb)));
|
||||
}
|
||||
|
||||
/// Open the track editor.
|
||||
pub fn show(&self) {
|
||||
self.window.show();
|
||||
}
|
||||
|
||||
/// Create a widget representing a track.
|
||||
fn build_track_row(&self, track: &TrackDescription) -> gtk::Widget {
|
||||
let mut title_parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
if let Some(recording) = &*self.recording.borrow() {
|
||||
title_parts.push(recording.work.parts[*part].title.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let title = if title_parts.is_empty() {
|
||||
gettext("Unknown")
|
||||
} else {
|
||||
title_parts.join(", ")
|
||||
};
|
||||
|
||||
let title_label = gtk::Label::new(Some(&title));
|
||||
title_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
title_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let file_name_label = gtk::Label::new(Some(&track.file_name));
|
||||
file_name_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
file_name_label.set_opacity(0.5);
|
||||
file_name_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
vbox.set_border_width(6);
|
||||
vbox.add(&title_label);
|
||||
vbox.add(&file_name_label);
|
||||
|
||||
vbox.upcast()
|
||||
}
|
||||
|
||||
/// Set everything up after selecting a recording.
|
||||
fn recording_selected(&self, recording: &RecordingDescription) {
|
||||
self.work_label.set_text(&recording.work.get_title());
|
||||
self.performers_label.set_text(&recording.get_performers());
|
||||
self.recording_stack.set_visible_child_name("selected");
|
||||
self.save_button.set_sensitive(true);
|
||||
self.autofill_parts();
|
||||
}
|
||||
|
||||
/// Automatically try to put work part information from the selected recording into the
|
||||
/// selected tracks.
|
||||
fn autofill_parts(&self) {
|
||||
if let Some(recording) = &*self.recording.borrow() {
|
||||
let mut tracks = self.tracks.borrow_mut();
|
||||
|
||||
for (index, _) in recording.work.parts.iter().enumerate() {
|
||||
if let Some(mut track) = tracks.get_mut(index) {
|
||||
track.work_parts = vec![index];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
self.track_list.show_items(tracks.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
11
musicus/src/dialogs/work/mod.rs
Normal file
11
musicus/src/dialogs/work/mod.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
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;
|
||||
170
musicus/src/dialogs/work/part_editor.rs
Normal file
170
musicus/src/dialogs/work/part_editor.rs
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
use crate::backend::*;
|
||||
use crate::database::*;
|
||||
use crate::dialogs::*;
|
||||
use crate::widgets::*;
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A dialog for creating or editing a work part.
|
||||
pub struct PartEditor {
|
||||
backend: Rc<Backend>,
|
||||
window: libhandy::Window,
|
||||
title_entry: gtk::Entry,
|
||||
composer_label: gtk::Label,
|
||||
reset_composer_button: gtk::Button,
|
||||
instrument_list: Rc<List<Instrument>>,
|
||||
composer: RefCell<Option<Person>>,
|
||||
instruments: RefCell<Vec<Instrument>>,
|
||||
ready_cb: RefCell<Option<Box<dyn Fn(WorkPartDescription) -> ()>>>,
|
||||
}
|
||||
|
||||
impl PartEditor {
|
||||
/// Create a new part editor and optionally initialize it.
|
||||
pub fn new<P: IsA<gtk::Window>>(
|
||||
backend: Rc<Backend>,
|
||||
parent: &P,
|
||||
part: Option<WorkPartDescription>,
|
||||
) -> Rc<Self> {
|
||||
// Create UI
|
||||
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/part_editor.ui");
|
||||
|
||||
get_widget!(builder, libhandy::Window, window);
|
||||
get_widget!(builder, gtk::Button, cancel_button);
|
||||
get_widget!(builder, gtk::Button, save_button);
|
||||
get_widget!(builder, gtk::Entry, title_entry);
|
||||
get_widget!(builder, gtk::Button, composer_button);
|
||||
get_widget!(builder, gtk::Label, composer_label);
|
||||
get_widget!(builder, gtk::Button, reset_composer_button);
|
||||
get_widget!(builder, gtk::ScrolledWindow, scroll);
|
||||
get_widget!(builder, gtk::Button, add_instrument_button);
|
||||
get_widget!(builder, gtk::Button, remove_instrument_button);
|
||||
|
||||
window.set_transient_for(Some(parent));
|
||||
|
||||
let instrument_list = List::new(&gettext("No instruments added."));
|
||||
scroll.add(&instrument_list.widget);
|
||||
|
||||
let (composer, instruments) = match part {
|
||||
Some(part) => {
|
||||
title_entry.set_text(&part.title);
|
||||
(part.composer, part.instruments)
|
||||
}
|
||||
None => (None, Vec::new()),
|
||||
};
|
||||
|
||||
let this = Rc::new(Self {
|
||||
backend,
|
||||
window,
|
||||
title_entry,
|
||||
composer_label,
|
||||
reset_composer_button,
|
||||
instrument_list,
|
||||
composer: RefCell::new(composer),
|
||||
instruments: RefCell::new(instruments),
|
||||
ready_cb: RefCell::new(None),
|
||||
});
|
||||
|
||||
// Connect signals and callbacks
|
||||
|
||||
cancel_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
this.window.close();
|
||||
}));
|
||||
|
||||
save_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(cb) = &*this.ready_cb.borrow() {
|
||||
cb(WorkPartDescription {
|
||||
title: this.title_entry.get_text().to_string(),
|
||||
composer: this.composer.borrow().clone(),
|
||||
instruments: this.instruments.borrow().clone(),
|
||||
});
|
||||
}
|
||||
|
||||
this.window.close();
|
||||
}));
|
||||
|
||||
composer_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
PersonSelector::new(this.backend.clone(), &this.window, clone!(@strong this => move |person| {
|
||||
this.show_composer(Some(&person));
|
||||
this.composer.replace(Some(person));
|
||||
})).show();
|
||||
}));
|
||||
|
||||
this.reset_composer_button
|
||||
.connect_clicked(clone!(@strong this => move |_| {
|
||||
this.composer.replace(None);
|
||||
this.show_composer(None);
|
||||
}));
|
||||
|
||||
this.instrument_list.set_make_widget(|instrument| {
|
||||
let label = gtk::Label::new(Some(&instrument.name));
|
||||
label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
label.set_halign(gtk::Align::Start);
|
||||
label.set_margin_start(6);
|
||||
label.set_margin_end(6);
|
||||
label.set_margin_top(6);
|
||||
label.set_margin_bottom(6);
|
||||
label.upcast()
|
||||
});
|
||||
|
||||
add_instrument_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
InstrumentSelector::new(this.backend.clone(), &this.window, clone!(@strong this => move |instrument| {
|
||||
let mut instruments = this.instruments.borrow_mut();
|
||||
|
||||
let index = match this.instrument_list.get_selected_index() {
|
||||
Some(index) => index + 1,
|
||||
None => instruments.len(),
|
||||
};
|
||||
|
||||
instruments.insert(index, instrument);
|
||||
this.instrument_list.show_items(instruments.clone());
|
||||
this.instrument_list.select_index(index);
|
||||
})).show();
|
||||
}));
|
||||
|
||||
remove_instrument_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(index) = this.instrument_list.get_selected_index() {
|
||||
let mut instruments = this.instruments.borrow_mut();
|
||||
instruments.remove(index);
|
||||
this.instrument_list.show_items(instruments.clone());
|
||||
this.instrument_list.select_index(index);
|
||||
}
|
||||
}));
|
||||
|
||||
// Initialize
|
||||
|
||||
if let Some(composer) = &*this.composer.borrow() {
|
||||
this.show_composer(Some(composer));
|
||||
}
|
||||
|
||||
this.instrument_list
|
||||
.show_items(this.instruments.borrow().clone());
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// Set the closure to be called when the user wants to save the part.
|
||||
pub fn set_ready_cb<F: Fn(WorkPartDescription) -> () + 'static>(&self, cb: F) {
|
||||
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.
|
||||
fn show_composer(&self, person: Option<&Person>) {
|
||||
if let Some(person) = person {
|
||||
self.composer_label.set_text(&person.name_fl());
|
||||
self.reset_composer_button.show();
|
||||
} else {
|
||||
self.composer_label.set_text(&gettext("Select …"));
|
||||
self.reset_composer_button.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
73
musicus/src/dialogs/work/section_editor.rs
Normal file
73
musicus/src/dialogs/work/section_editor.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
use crate::database::*;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A dialog for creating or editing a work section.
|
||||
pub struct SectionEditor {
|
||||
window: libhandy::Window,
|
||||
title_entry: gtk::Entry,
|
||||
ready_cb: RefCell<Option<Box<dyn Fn(WorkSectionDescription) -> ()>>>,
|
||||
}
|
||||
|
||||
impl SectionEditor {
|
||||
/// Create a new section editor and optionally initialize it.
|
||||
pub fn new<P: IsA<gtk::Window>>(
|
||||
parent: &P,
|
||||
section: Option<WorkSectionDescription>,
|
||||
) -> Rc<Self> {
|
||||
// Create UI
|
||||
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/section_editor.ui");
|
||||
|
||||
get_widget!(builder, libhandy::Window, window);
|
||||
get_widget!(builder, gtk::Button, cancel_button);
|
||||
get_widget!(builder, gtk::Button, save_button);
|
||||
get_widget!(builder, gtk::Entry, title_entry);
|
||||
|
||||
window.set_transient_for(Some(parent));
|
||||
|
||||
if let Some(section) = section {
|
||||
title_entry.set_text(§ion.title);
|
||||
}
|
||||
|
||||
let this = Rc::new(Self {
|
||||
window,
|
||||
title_entry,
|
||||
ready_cb: RefCell::new(None),
|
||||
});
|
||||
|
||||
// Connect signals and callbacks
|
||||
|
||||
cancel_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
this.window.close();
|
||||
}));
|
||||
|
||||
save_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(cb) = &*this.ready_cb.borrow() {
|
||||
cb(WorkSectionDescription {
|
||||
before_index: 0,
|
||||
title: this.title_entry.get_text().to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
this.window.close();
|
||||
}));
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// Set the closure to be called when the user wants to save the section. Note that the
|
||||
/// resulting object will always have `before_index` set to 0. The caller is expected to
|
||||
/// change that later before adding the section to the database.
|
||||
pub fn set_ready_cb<F: Fn(WorkSectionDescription) -> () + 'static>(&self, cb: F) {
|
||||
self.ready_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
|
||||
/// Show the section editor.
|
||||
pub fn show(&self) {
|
||||
self.window.show();
|
||||
}
|
||||
}
|
||||
86
musicus/src/dialogs/work/work_dialog.rs
Normal file
86
musicus/src/dialogs/work/work_dialog.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
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(WorkDescription) -> ()>>>,
|
||||
}
|
||||
|
||||
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(WorkDescription) -> () + 'static>(&self, cb: F) {
|
||||
self.selected_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
|
||||
/// Show the work dialog.
|
||||
pub fn show(&self) {
|
||||
self.window.show();
|
||||
}
|
||||
}
|
||||
357
musicus/src/dialogs/work/work_editor.rs
Normal file
357
musicus/src/dialogs/work/work_editor.rs
Normal file
|
|
@ -0,0 +1,357 @@
|
|||
use super::part_editor::*;
|
||||
use super::section_editor::*;
|
||||
use crate::backend::*;
|
||||
use crate::database::*;
|
||||
use crate::dialogs::*;
|
||||
use crate::widgets::*;
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use std::cell::RefCell;
|
||||
use std::convert::TryInto;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Either a work part or a work section.
|
||||
#[derive(Clone)]
|
||||
enum PartOrSection {
|
||||
Part(WorkPartDescription),
|
||||
Section(WorkSectionDescription),
|
||||
}
|
||||
|
||||
/// A widget for editing and creating works.
|
||||
pub struct WorkEditor {
|
||||
pub widget: gtk::Box,
|
||||
backend: Rc<Backend>,
|
||||
parent: gtk::Window,
|
||||
save_button: gtk::Button,
|
||||
title_entry: gtk::Entry,
|
||||
composer_label: gtk::Label,
|
||||
instrument_list: Rc<List<Instrument>>,
|
||||
part_list: Rc<List<PartOrSection>>,
|
||||
id: i64,
|
||||
composer: RefCell<Option<Person>>,
|
||||
instruments: RefCell<Vec<Instrument>>,
|
||||
structure: RefCell<Vec<PartOrSection>>,
|
||||
cancel_cb: RefCell<Option<Box<dyn Fn() -> ()>>>,
|
||||
saved_cb: RefCell<Option<Box<dyn Fn(WorkDescription) -> ()>>>,
|
||||
}
|
||||
|
||||
impl WorkEditor {
|
||||
/// Create a new work editor widget and optionally initialize it. The parent window is used
|
||||
/// as the parent for newly created dialogs.
|
||||
pub fn new<P: IsA<gtk::Window>>(
|
||||
backend: Rc<Backend>,
|
||||
parent: &P,
|
||||
work: Option<WorkDescription>,
|
||||
) -> Rc<Self> {
|
||||
// Create UI
|
||||
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_editor.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
get_widget!(builder, gtk::Button, cancel_button);
|
||||
get_widget!(builder, gtk::Button, save_button);
|
||||
get_widget!(builder, gtk::Entry, title_entry);
|
||||
get_widget!(builder, gtk::Button, composer_button);
|
||||
get_widget!(builder, gtk::Label, composer_label);
|
||||
get_widget!(builder, gtk::ScrolledWindow, instruments_scroll);
|
||||
get_widget!(builder, gtk::Button, add_instrument_button);
|
||||
get_widget!(builder, gtk::Button, remove_instrument_button);
|
||||
get_widget!(builder, gtk::ScrolledWindow, structure_scroll);
|
||||
get_widget!(builder, gtk::Button, add_part_button);
|
||||
get_widget!(builder, gtk::Button, remove_part_button);
|
||||
get_widget!(builder, gtk::Button, add_section_button);
|
||||
get_widget!(builder, gtk::Button, edit_part_button);
|
||||
get_widget!(builder, gtk::Button, move_part_up_button);
|
||||
get_widget!(builder, gtk::Button, move_part_down_button);
|
||||
|
||||
let instrument_list = List::new(&gettext("No instruments added."));
|
||||
instruments_scroll.add(&instrument_list.widget);
|
||||
|
||||
let part_list = List::new(&gettext("No work parts added."));
|
||||
structure_scroll.add(&part_list.widget);
|
||||
|
||||
let (id, composer, instruments, structure) = match work {
|
||||
Some(work) => {
|
||||
title_entry.set_text(&work.title);
|
||||
|
||||
let mut structure = Vec::new();
|
||||
|
||||
for part in work.parts {
|
||||
structure.push(PartOrSection::Part(part));
|
||||
}
|
||||
|
||||
for section in work.sections {
|
||||
structure.insert(
|
||||
section.before_index.try_into().unwrap(),
|
||||
PartOrSection::Section(section),
|
||||
);
|
||||
}
|
||||
|
||||
(work.id, Some(work.composer), work.instruments, structure)
|
||||
}
|
||||
None => (rand::random::<u32>().into(), None, Vec::new(), Vec::new()),
|
||||
};
|
||||
|
||||
let this = Rc::new(Self {
|
||||
widget,
|
||||
backend,
|
||||
parent: parent.clone().upcast(),
|
||||
save_button,
|
||||
id,
|
||||
title_entry,
|
||||
composer_label,
|
||||
instrument_list,
|
||||
part_list,
|
||||
composer: RefCell::new(composer),
|
||||
instruments: RefCell::new(instruments),
|
||||
structure: RefCell::new(structure),
|
||||
cancel_cb: RefCell::new(None),
|
||||
saved_cb: RefCell::new(None),
|
||||
});
|
||||
|
||||
// Connect signals and callbacks
|
||||
|
||||
cancel_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(cb) = &*this.cancel_cb.borrow() {
|
||||
cb();
|
||||
}
|
||||
}));
|
||||
|
||||
this.save_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
let mut section_count = 0;
|
||||
let mut parts = Vec::new();
|
||||
let mut sections = Vec::new();
|
||||
|
||||
for (index, pos) in this.structure.borrow().iter().enumerate() {
|
||||
match pos {
|
||||
PartOrSection::Part(part) => parts.push(part.clone()),
|
||||
PartOrSection::Section(section) => {
|
||||
let mut section = section.clone();
|
||||
let index: i64 = index.try_into().unwrap();
|
||||
section.before_index = index - section_count;
|
||||
sections.push(section);
|
||||
section_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let work = WorkDescription {
|
||||
id: this.id,
|
||||
title: this.title_entry.get_text().to_string(),
|
||||
composer: this.composer.borrow().clone().expect("Tried to create work without composer!"),
|
||||
instruments: this.instruments.borrow().clone(),
|
||||
parts: parts,
|
||||
sections: sections,
|
||||
};
|
||||
|
||||
let c = glib::MainContext::default();
|
||||
let clone = this.clone();
|
||||
c.spawn_local(async move {
|
||||
clone.backend.update_work(work.clone().into()).await.unwrap();
|
||||
if let Some(cb) = &*clone.saved_cb.borrow() {
|
||||
cb(work);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
composer_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
PersonSelector::new(this.backend.clone(), &this.parent, clone!(@strong this => move |person| {
|
||||
this.show_composer(&person);
|
||||
this.composer.replace(Some(person));
|
||||
})).show();
|
||||
}));
|
||||
|
||||
this.instrument_list.set_make_widget(|instrument| {
|
||||
let label = gtk::Label::new(Some(&instrument.name));
|
||||
label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
label.set_halign(gtk::Align::Start);
|
||||
label.set_margin_start(6);
|
||||
label.set_margin_end(6);
|
||||
label.set_margin_top(6);
|
||||
label.set_margin_bottom(6);
|
||||
label.upcast()
|
||||
});
|
||||
|
||||
add_instrument_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
InstrumentSelector::new(this.backend.clone(), &this.parent, clone!(@strong this => move |instrument| {
|
||||
let mut instruments = this.instruments.borrow_mut();
|
||||
|
||||
let index = match this.instrument_list.get_selected_index() {
|
||||
Some(index) => index + 1,
|
||||
None => instruments.len(),
|
||||
};
|
||||
|
||||
instruments.insert(index, instrument);
|
||||
this.instrument_list.show_items(instruments.clone());
|
||||
this.instrument_list.select_index(index);
|
||||
})).show();
|
||||
}));
|
||||
|
||||
remove_instrument_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(index) = this.instrument_list.get_selected_index() {
|
||||
let mut instruments = this.instruments.borrow_mut();
|
||||
instruments.remove(index);
|
||||
this.instrument_list.show_items(instruments.clone());
|
||||
this.instrument_list.select_index(index);
|
||||
}
|
||||
}));
|
||||
|
||||
this.part_list.set_make_widget(|pos| {
|
||||
let label = gtk::Label::new(None);
|
||||
label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
label.set_halign(gtk::Align::Start);
|
||||
label.set_margin_end(6);
|
||||
label.set_margin_top(6);
|
||||
label.set_margin_bottom(6);
|
||||
|
||||
match pos {
|
||||
PartOrSection::Part(part) => {
|
||||
label.set_text(&part.title);
|
||||
label.set_margin_start(12);
|
||||
}
|
||||
PartOrSection::Section(section) => {
|
||||
let attrs = pango::AttrList::new();
|
||||
attrs.insert(pango::Attribute::new_weight(pango::Weight::Bold).unwrap());
|
||||
label.set_attributes(Some(&attrs));
|
||||
label.set_text(§ion.title);
|
||||
label.set_margin_start(6);
|
||||
}
|
||||
}
|
||||
|
||||
label.upcast()
|
||||
});
|
||||
|
||||
add_part_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
let editor = PartEditor::new(this.backend.clone(), &this.parent, None);
|
||||
|
||||
editor.set_ready_cb(clone!(@strong this => move |part| {
|
||||
let mut structure = this.structure.borrow_mut();
|
||||
|
||||
let index = match this.part_list.get_selected_index() {
|
||||
Some(index) => index + 1,
|
||||
None => structure.len(),
|
||||
};
|
||||
|
||||
structure.insert(index, PartOrSection::Part(part));
|
||||
this.part_list.show_items(structure.clone());
|
||||
this.part_list.select_index(index);
|
||||
}));
|
||||
|
||||
editor.show();
|
||||
}));
|
||||
|
||||
add_section_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
let editor = SectionEditor::new(&this.parent, None);
|
||||
|
||||
editor.set_ready_cb(clone!(@strong this => move |section| {
|
||||
let mut structure = this.structure.borrow_mut();
|
||||
|
||||
let index = match this.part_list.get_selected_index() {
|
||||
Some(index) => index + 1,
|
||||
None => structure.len(),
|
||||
};
|
||||
|
||||
structure.insert(index, PartOrSection::Section(section));
|
||||
this.part_list.show_items(structure.clone());
|
||||
this.part_list.select_index(index);
|
||||
}));
|
||||
|
||||
editor.show();
|
||||
}));
|
||||
|
||||
edit_part_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(index) = this.part_list.get_selected_index() {
|
||||
match this.structure.borrow()[index].clone() {
|
||||
PartOrSection::Part(part) => {
|
||||
let editor = PartEditor::new(
|
||||
this.backend.clone(),
|
||||
&this.parent,
|
||||
Some(part),
|
||||
);
|
||||
|
||||
editor.set_ready_cb(clone!(@strong this => move |part| {
|
||||
let mut structure = this.structure.borrow_mut();
|
||||
structure[index] = PartOrSection::Part(part);
|
||||
this.part_list.show_items(structure.clone());
|
||||
this.part_list.select_index(index);
|
||||
}));
|
||||
|
||||
editor.show();
|
||||
}
|
||||
PartOrSection::Section(section) => {
|
||||
let editor = SectionEditor::new(&this.parent, Some(section));
|
||||
|
||||
editor.set_ready_cb(clone!(@strong this => move |section| {
|
||||
let mut structure = this.structure.borrow_mut();
|
||||
structure[index] = PartOrSection::Section(section);
|
||||
this.part_list.show_items(structure.clone());
|
||||
this.part_list.select_index(index);
|
||||
}));
|
||||
|
||||
editor.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
remove_part_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(index) = this.part_list.get_selected_index() {
|
||||
let mut structure = this.structure.borrow_mut();
|
||||
structure.remove(index);
|
||||
this.part_list.show_items(structure.clone());
|
||||
this.part_list.select_index(index);
|
||||
}
|
||||
}));
|
||||
|
||||
move_part_up_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(index) = this.part_list.get_selected_index() {
|
||||
if index > 0 {
|
||||
let mut structure = this.structure.borrow_mut();
|
||||
structure.swap(index - 1, index);
|
||||
this.part_list.show_items(structure.clone());
|
||||
this.part_list.select_index(index - 1);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
move_part_down_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(index) = this.part_list.get_selected_index() {
|
||||
let mut structure = this.structure.borrow_mut();
|
||||
if index < structure.len() - 1 {
|
||||
structure.swap(index, index + 1);
|
||||
this.part_list.show_items(structure.clone());
|
||||
this.part_list.select_index(index + 1);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Initialization
|
||||
|
||||
if let Some(composer) = &*this.composer.borrow() {
|
||||
this.show_composer(composer);
|
||||
}
|
||||
|
||||
this.instrument_list.show_items(this.instruments.borrow().clone());
|
||||
this.part_list.show_items(this.structure.borrow().clone());
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn set_saved_cb<F: Fn(WorkDescription) -> () + 'static>(&self, cb: F) {
|
||||
self.saved_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
|
||||
/// Update the UI according to person.
|
||||
fn show_composer(&self, person: &Person) {
|
||||
self.composer_label.set_text(&person.name_fl());
|
||||
self.save_button.set_sensitive(true);
|
||||
}
|
||||
}
|
||||
63
musicus/src/dialogs/work/work_editor_dialog.rs
Normal file
63
musicus/src/dialogs/work/work_editor_dialog.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
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(WorkDescription) -> ()>>>,
|
||||
}
|
||||
|
||||
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<WorkDescription>,
|
||||
) -> 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(WorkDescription) -> () + 'static>(&self, cb: F) {
|
||||
self.saved_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
|
||||
/// Show the work editor dialog.
|
||||
pub fn show(&self) {
|
||||
self.window.show();
|
||||
}
|
||||
}
|
||||
89
musicus/src/dialogs/work/work_selector.rs
Normal file
89
musicus/src/dialogs/work/work_selector.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
use super::work_selector_person_screen::*;
|
||||
use crate::backend::Backend;
|
||||
use crate::database::*;
|
||||
use crate::widgets::*;
|
||||
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,
|
||||
selected_cb: RefCell<Option<Box<dyn Fn(WorkDescription) -> ()>>>,
|
||||
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::Box, empty_screen);
|
||||
|
||||
let person_list = PersonList::new(backend.clone());
|
||||
sidebar_box.pack_start(&person_list.widget, true, true, 0);
|
||||
|
||||
let navigator = Navigator::new(&empty_screen);
|
||||
widget.add(&navigator.widget);
|
||||
|
||||
let this = Rc::new(Self {
|
||||
widget,
|
||||
backend,
|
||||
sidebar_box,
|
||||
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();
|
||||
}
|
||||
}));
|
||||
|
||||
person_list.set_selected(clone!(@strong this => move |person| {
|
||||
let person_screen = WorkSelectorPersonScreen::new(
|
||||
this.backend.clone(),
|
||||
person.clone(),
|
||||
);
|
||||
|
||||
person_screen.set_selected_cb(clone!(@strong this => move |work| {
|
||||
if let Some(cb) = &*this.selected_cb.borrow() {
|
||||
cb(work);
|
||||
}
|
||||
}));
|
||||
|
||||
this.navigator.clone().push(person_screen);
|
||||
this.widget.set_visible_child(&this.navigator.widget);
|
||||
}));
|
||||
|
||||
this.navigator.set_back_cb(clone!(@strong this => move || {
|
||||
this.widget.set_visible_child(&this.sidebar_box);
|
||||
}));
|
||||
|
||||
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(WorkDescription) -> () + 'static>(&self, cb: F) {
|
||||
self.selected_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
}
|
||||
114
musicus/src/dialogs/work/work_selector_person_screen.rs
Normal file
114
musicus/src/dialogs/work/work_selector_person_screen.rs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
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>,
|
||||
widget: gtk::Box,
|
||||
stack: gtk::Stack,
|
||||
work_list: Rc<List<WorkDescription>>,
|
||||
selected_cb: RefCell<Option<Box<dyn Fn(WorkDescription) -> ()>>>,
|
||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||
}
|
||||
|
||||
impl WorkSelectorPersonScreen {
|
||||
/// Create a new work selector person screen.
|
||||
pub fn new(backend: Rc<Backend>, person: Person) -> 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);
|
||||
|
||||
header.set_title(Some(&person.name_fl()));
|
||||
|
||||
let work_list = List::new(&gettext("No works found."));
|
||||
stack.add_named(&work_list.widget, "content");
|
||||
|
||||
let this = Rc::new(Self {
|
||||
backend,
|
||||
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();
|
||||
}
|
||||
}));
|
||||
|
||||
this.work_list.set_make_widget(|work: &WorkDescription| {
|
||||
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 {
|
||||
if let Some(cb) = &*this.selected_cb.borrow() {
|
||||
cb(work.clone());
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Initialize
|
||||
|
||||
let context = glib::MainContext::default();
|
||||
let clone = this.clone();
|
||||
context.spawn_local(async move {
|
||||
let works = clone
|
||||
.backend
|
||||
.get_work_descriptions(person.id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
clone.work_list.show_items(works);
|
||||
clone.stack.set_visible_child_name("content");
|
||||
});
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// Sets a closure to be called when the user has selected a work.
|
||||
pub fn set_selected_cb<F: Fn(WorkDescription) -> () + '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);
|
||||
}
|
||||
}
|
||||
54
musicus/src/main.rs
Normal file
54
musicus/src/main.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// Required for database/schema.rs
|
||||
#[macro_use]
|
||||
extern crate diesel;
|
||||
|
||||
// Required for embed_migrations macro in database/database.rs
|
||||
#[macro_use]
|
||||
extern crate diesel_migrations;
|
||||
|
||||
use gio::prelude::*;
|
||||
use glib::clone;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
mod config;
|
||||
mod backend;
|
||||
mod database;
|
||||
mod dialogs;
|
||||
mod player;
|
||||
mod screens;
|
||||
mod widgets;
|
||||
|
||||
mod window;
|
||||
use window::Window;
|
||||
|
||||
mod resources;
|
||||
|
||||
fn main() {
|
||||
gettextrs::setlocale(gettextrs::LocaleCategory::LcAll, "");
|
||||
gettextrs::bindtextdomain("musicus", config::LOCALEDIR);
|
||||
gettextrs::textdomain("musicus");
|
||||
|
||||
gtk::init().expect("Failed to initialize GTK!");
|
||||
libhandy::init();
|
||||
resources::init().expect("Failed to initialize resources!");
|
||||
|
||||
let app = gtk::Application::new(
|
||||
Some("de.johrpan.musicus"),
|
||||
gio::ApplicationFlags::empty(),
|
||||
)
|
||||
.expect("Failed to initialize GTK application!");
|
||||
|
||||
let window: RefCell<Option<Rc<Window>>> = RefCell::new(None);
|
||||
|
||||
app.connect_activate(clone!(@strong app => move |_| {
|
||||
let mut window = window.borrow_mut();
|
||||
if window.is_none() {
|
||||
window.replace(Window::new(&app));
|
||||
}
|
||||
window.as_ref().unwrap().present();
|
||||
}));
|
||||
|
||||
let args = std::env::args().collect::<Vec<String>>();
|
||||
app.run(&args);
|
||||
}
|
||||
114
musicus/src/meson.build
Normal file
114
musicus/src/meson.build
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
prefix = get_option('prefix')
|
||||
localedir = join_paths(prefix, get_option('localedir'))
|
||||
|
||||
global_conf = configuration_data()
|
||||
global_conf.set_quoted('LOCALEDIR', localedir)
|
||||
global_conf.set_quoted('VERSION', meson.project_version())
|
||||
config_rs = configure_file(
|
||||
input: 'config.rs.in',
|
||||
output: 'config.rs',
|
||||
configuration: global_conf
|
||||
)
|
||||
|
||||
run_command(
|
||||
'cp',
|
||||
config_rs,
|
||||
meson.current_source_dir(),
|
||||
check: true
|
||||
)
|
||||
|
||||
resource_conf = configuration_data()
|
||||
resource_conf.set_quoted('RESOURCEFILE', resources.full_path())
|
||||
resource_rs = configure_file(
|
||||
input: 'resources.rs.in',
|
||||
output: 'resources.rs',
|
||||
configuration: resource_conf
|
||||
)
|
||||
|
||||
run_command(
|
||||
'cp',
|
||||
resource_rs,
|
||||
meson.current_source_dir(),
|
||||
check: true
|
||||
)
|
||||
|
||||
sources = files(
|
||||
'backend/backend.rs',
|
||||
'backend/client.rs',
|
||||
'backend/mod.rs',
|
||||
'backend/secure.rs',
|
||||
'database/database.rs',
|
||||
'database/mod.rs',
|
||||
'database/models.rs',
|
||||
'database/schema.rs',
|
||||
'database/tables.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/mod.rs',
|
||||
'dialogs/person_editor.rs',
|
||||
'dialogs/person_selector.rs',
|
||||
'dialogs/preferences.rs',
|
||||
'dialogs/server_dialog.rs',
|
||||
'dialogs/recording/mod.rs',
|
||||
'dialogs/recording/performance_editor.rs',
|
||||
'dialogs/recording/recording_dialog.rs',
|
||||
'dialogs/recording/recording_editor_dialog.rs',
|
||||
'dialogs/recording/recording_editor.rs',
|
||||
'dialogs/recording/recording_selector_person_screen.rs',
|
||||
'dialogs/recording/recording_selector.rs',
|
||||
'dialogs/recording/recording_selector_work_screen.rs',
|
||||
'dialogs/track_editor.rs',
|
||||
'dialogs/tracks_editor.rs',
|
||||
'dialogs/work/mod.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/mod.rs',
|
||||
'screens/person_screen.rs',
|
||||
'screens/player_screen.rs',
|
||||
'screens/recording_screen.rs',
|
||||
'screens/work_screen.rs',
|
||||
'widgets/list.rs',
|
||||
'widgets/mod.rs',
|
||||
'widgets/navigator.rs',
|
||||
'widgets/person_list.rs',
|
||||
'widgets/player_bar.rs',
|
||||
'widgets/poe_list.rs',
|
||||
'widgets/selector_row.rs',
|
||||
'config.rs',
|
||||
'config.rs.in',
|
||||
'main.rs',
|
||||
'player.rs',
|
||||
'resources.rs',
|
||||
'resources.rs.in',
|
||||
'window.rs',
|
||||
)
|
||||
|
||||
cargo_script = find_program(join_paths(meson.source_root(), 'build-aux/cargo.sh'))
|
||||
cargo_release = custom_target(
|
||||
'cargo-build',
|
||||
build_by_default: true,
|
||||
input: sources,
|
||||
depends: resources,
|
||||
output: meson.project_name(),
|
||||
console: true,
|
||||
install: true,
|
||||
install_dir: get_option('bindir'),
|
||||
command: [
|
||||
cargo_script,
|
||||
meson.build_root(),
|
||||
meson.source_root(),
|
||||
'@OUTPUT@',
|
||||
get_option('buildtype'),
|
||||
meson.project_name(),
|
||||
]
|
||||
)
|
||||
295
musicus/src/player.rs
Normal file
295
musicus/src/player.rs
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
use crate::database::*;
|
||||
use anyhow::anyhow;
|
||||
use anyhow::Result;
|
||||
use gstreamer_player::prelude::*;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PlaylistItem {
|
||||
pub recording: RecordingDescription,
|
||||
pub tracks: Vec<TrackDescription>,
|
||||
}
|
||||
|
||||
pub struct Player {
|
||||
music_library_path: PathBuf,
|
||||
player: gstreamer_player::Player,
|
||||
playlist: RefCell<Vec<PlaylistItem>>,
|
||||
current_item: Cell<Option<usize>>,
|
||||
current_track: Cell<Option<usize>>,
|
||||
playing: Cell<bool>,
|
||||
playlist_cbs: RefCell<Vec<Box<dyn Fn(Vec<PlaylistItem>) -> ()>>>,
|
||||
track_cbs: RefCell<Vec<Box<dyn Fn(usize, usize) -> ()>>>,
|
||||
duration_cbs: RefCell<Vec<Box<dyn Fn(u64) -> ()>>>,
|
||||
playing_cbs: RefCell<Vec<Box<dyn Fn(bool) -> ()>>>,
|
||||
position_cbs: RefCell<Vec<Box<dyn Fn(u64) -> ()>>>,
|
||||
}
|
||||
|
||||
impl Player {
|
||||
pub fn new(music_library_path: PathBuf) -> Rc<Self> {
|
||||
let dispatcher = gstreamer_player::PlayerGMainContextSignalDispatcher::new(None);
|
||||
let player = gstreamer_player::Player::new(None, Some(&dispatcher.upcast()));
|
||||
let mut config = player.get_config();
|
||||
config.set_position_update_interval(250);
|
||||
player.set_config(config).unwrap();
|
||||
player.set_video_track_enabled(false);
|
||||
|
||||
let result = Rc::new(Self {
|
||||
music_library_path,
|
||||
player: player.clone(),
|
||||
playlist: RefCell::new(Vec::new()),
|
||||
current_item: Cell::new(None),
|
||||
current_track: Cell::new(None),
|
||||
playing: Cell::new(false),
|
||||
playlist_cbs: RefCell::new(Vec::new()),
|
||||
track_cbs: RefCell::new(Vec::new()),
|
||||
duration_cbs: RefCell::new(Vec::new()),
|
||||
playing_cbs: RefCell::new(Vec::new()),
|
||||
position_cbs: RefCell::new(Vec::new()),
|
||||
});
|
||||
|
||||
let clone = fragile::Fragile::new(result.clone());
|
||||
player.connect_end_of_stream(move |_| {
|
||||
let clone = clone.get();
|
||||
if clone.has_next() {
|
||||
clone.next().unwrap();
|
||||
} else {
|
||||
clone.player.stop();
|
||||
clone.playing.replace(false);
|
||||
for cb in &*clone.playing_cbs.borrow() {
|
||||
cb(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let clone = fragile::Fragile::new(result.clone());
|
||||
player.connect_position_updated(move |_, position| {
|
||||
for cb in &*clone.get().position_cbs.borrow() {
|
||||
cb(position.mseconds().unwrap());
|
||||
}
|
||||
});
|
||||
|
||||
let clone = fragile::Fragile::new(result.clone());
|
||||
player.connect_duration_changed(move |_, duration| {
|
||||
for cb in &*clone.get().duration_cbs.borrow() {
|
||||
cb(duration.mseconds().unwrap());
|
||||
}
|
||||
});
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn add_playlist_cb<F: Fn(Vec<PlaylistItem>) -> () + 'static>(&self, cb: F) {
|
||||
self.playlist_cbs.borrow_mut().push(Box::new(cb));
|
||||
}
|
||||
|
||||
pub fn add_track_cb<F: Fn(usize, usize) -> () + 'static>(&self, cb: F) {
|
||||
self.track_cbs.borrow_mut().push(Box::new(cb));
|
||||
}
|
||||
|
||||
pub fn add_duration_cb<F: Fn(u64) -> () + 'static>(&self, cb: F) {
|
||||
self.duration_cbs.borrow_mut().push(Box::new(cb));
|
||||
}
|
||||
|
||||
pub fn add_playing_cb<F: Fn(bool) -> () + 'static>(&self, cb: F) {
|
||||
self.playing_cbs.borrow_mut().push(Box::new(cb));
|
||||
}
|
||||
|
||||
pub fn add_position_cb<F: Fn(u64) -> () + 'static>(&self, cb: F) {
|
||||
self.position_cbs.borrow_mut().push(Box::new(cb));
|
||||
}
|
||||
|
||||
pub fn get_playlist(&self) -> Vec<PlaylistItem> {
|
||||
self.playlist.borrow().clone()
|
||||
}
|
||||
|
||||
pub fn get_current_item(&self) -> Option<usize> {
|
||||
self.current_item.get()
|
||||
}
|
||||
|
||||
pub fn get_current_track(&self) -> Option<usize> {
|
||||
self.current_track.get()
|
||||
}
|
||||
|
||||
pub fn get_duration(&self) -> gstreamer::ClockTime {
|
||||
self.player.get_duration()
|
||||
}
|
||||
|
||||
pub fn is_playing(&self) -> bool {
|
||||
self.playing.get()
|
||||
}
|
||||
|
||||
pub fn add_item(&self, item: PlaylistItem) -> Result<()> {
|
||||
if item.tracks.is_empty() {
|
||||
Err(anyhow!(
|
||||
"Tried to add playlist item without tracks to playlist!"
|
||||
))
|
||||
} else {
|
||||
let was_empty = {
|
||||
let mut playlist = self.playlist.borrow_mut();
|
||||
let was_empty = playlist.is_empty();
|
||||
|
||||
playlist.push(item);
|
||||
|
||||
was_empty
|
||||
};
|
||||
|
||||
for cb in &*self.playlist_cbs.borrow() {
|
||||
cb(self.playlist.borrow().clone());
|
||||
}
|
||||
|
||||
if was_empty {
|
||||
self.set_track(0, 0)?;
|
||||
self.player.play();
|
||||
self.playing.set(true);
|
||||
|
||||
for cb in &*self.playing_cbs.borrow() {
|
||||
cb(true);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play_pause(&self) {
|
||||
if self.is_playing() {
|
||||
self.player.pause();
|
||||
self.playing.set(false);
|
||||
|
||||
for cb in &*self.playing_cbs.borrow() {
|
||||
cb(false);
|
||||
}
|
||||
} else {
|
||||
self.player.play();
|
||||
self.playing.set(true);
|
||||
|
||||
for cb in &*self.playing_cbs.borrow() {
|
||||
cb(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn seek(&self, ms: u64) {
|
||||
self.player.seek(gstreamer::ClockTime::from_mseconds(ms));
|
||||
}
|
||||
|
||||
pub fn has_previous(&self) -> bool {
|
||||
if let Some(current_item) = self.current_item.get() {
|
||||
if let Some(current_track) = self.current_track.get() {
|
||||
current_track > 0 || current_item > 0
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn previous(&self) -> Result<()> {
|
||||
let mut current_item = self.current_item.get().ok_or(anyhow!("No current item!"))?;
|
||||
let mut current_track = self
|
||||
.current_track
|
||||
.get()
|
||||
.ok_or(anyhow!("No current track!"))?;
|
||||
|
||||
let playlist = self.playlist.borrow();
|
||||
if current_track > 0 {
|
||||
current_track -= 1;
|
||||
} else if current_item > 0 {
|
||||
current_item -= 1;
|
||||
current_track = playlist[current_item].tracks.len() - 1;
|
||||
} else {
|
||||
return Err(anyhow!("No previous track!"));
|
||||
}
|
||||
|
||||
self.set_track(current_item, current_track)
|
||||
}
|
||||
|
||||
pub fn has_next(&self) -> bool {
|
||||
if let Some(current_item) = self.current_item.get() {
|
||||
if let Some(current_track) = self.current_track.get() {
|
||||
let playlist = self.playlist.borrow();
|
||||
let item = &playlist[current_item];
|
||||
|
||||
current_track + 1 < item.tracks.len() || current_item + 1 < playlist.len()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&self) -> Result<()> {
|
||||
let mut current_item = self.current_item.get().ok_or(anyhow!("No current item!"))?;
|
||||
let mut current_track = self
|
||||
.current_track
|
||||
.get()
|
||||
.ok_or(anyhow!("No current track!"))?;
|
||||
|
||||
let playlist = self.playlist.borrow();
|
||||
let item = &playlist[current_item];
|
||||
if current_track + 1 < item.tracks.len() {
|
||||
current_track += 1;
|
||||
} else if current_item + 1 < playlist.len() {
|
||||
current_item += 1;
|
||||
current_track = 0;
|
||||
} else {
|
||||
return Err(anyhow!("No next track!"));
|
||||
}
|
||||
|
||||
self.set_track(current_item, current_track)
|
||||
}
|
||||
|
||||
pub fn set_track(&self, current_item: usize, current_track: usize) -> Result<()> {
|
||||
let uri = format!(
|
||||
"file://{}",
|
||||
self.music_library_path
|
||||
.join(
|
||||
self.playlist
|
||||
.borrow()
|
||||
.get(current_item)
|
||||
.ok_or(anyhow!("Playlist item doesn't exist!"))?
|
||||
.tracks
|
||||
.get(current_track)
|
||||
.ok_or(anyhow!("Track doesn't exist!"))?
|
||||
.file_name
|
||||
.clone(),
|
||||
)
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
self.player.set_uri(&uri);
|
||||
if self.is_playing() {
|
||||
self.player.play();
|
||||
}
|
||||
|
||||
self.current_item.set(Some(current_item));
|
||||
self.current_track.set(Some(current_track));
|
||||
|
||||
for cb in &*self.track_cbs.borrow() {
|
||||
cb(current_item, current_track);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn clear(&self) {
|
||||
self.player.stop();
|
||||
self.playing.set(false);
|
||||
self.current_item.set(None);
|
||||
self.current_track.set(None);
|
||||
self.playlist.replace(Vec::new());
|
||||
|
||||
for cb in &*self.playing_cbs.borrow() {
|
||||
cb(false);
|
||||
}
|
||||
|
||||
for cb in &*self.playlist_cbs.borrow() {
|
||||
cb(Vec::new());
|
||||
}
|
||||
}
|
||||
}
|
||||
9
musicus/src/resources.rs.in
Normal file
9
musicus/src/resources.rs.in
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
use anyhow::Result;
|
||||
|
||||
pub fn init() -> Result<()> {
|
||||
let bytes = glib::Bytes::from(include_bytes!(@RESOURCEFILE@).as_ref());
|
||||
let resource = gio::Resource::from_data(&bytes)?;
|
||||
gio::resources_register(&resource);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
145
musicus/src/screens/ensemble_screen.rs
Normal file
145
musicus/src/screens/ensemble_screen.rs
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
use super::*;
|
||||
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;
|
||||
|
||||
pub struct EnsembleScreen {
|
||||
backend: Rc<Backend>,
|
||||
widget: gtk::Box,
|
||||
stack: gtk::Stack,
|
||||
recording_list: Rc<List<RecordingDescription>>,
|
||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||
}
|
||||
|
||||
impl EnsembleScreen {
|
||||
pub fn new(backend: Rc<Backend>, ensemble: Ensemble) -> Rc<Self> {
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/ensemble_screen.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
get_widget!(builder, libhandy::HeaderBar, header);
|
||||
get_widget!(builder, gtk::Button, back_button);
|
||||
get_widget!(builder, gtk::MenuButton, menu_button);
|
||||
get_widget!(builder, gtk::SearchEntry, search_entry);
|
||||
get_widget!(builder, gtk::Stack, stack);
|
||||
get_widget!(builder, gtk::Frame, recording_frame);
|
||||
|
||||
header.set_title(Some(&ensemble.name));
|
||||
|
||||
let edit_menu_item = gio::MenuItem::new(Some(&gettext("Edit ensemble")), None);
|
||||
edit_menu_item.set_action_and_target_value(
|
||||
Some("win.edit-ensemble"),
|
||||
Some(&glib::Variant::from(ensemble.id)),
|
||||
);
|
||||
|
||||
let delete_menu_item = gio::MenuItem::new(Some(&gettext("Delete ensemble")), None);
|
||||
delete_menu_item.set_action_and_target_value(
|
||||
Some("win.delete-ensemble"),
|
||||
Some(&glib::Variant::from(ensemble.id)),
|
||||
);
|
||||
|
||||
let menu = gio::Menu::new();
|
||||
menu.append_item(&edit_menu_item);
|
||||
menu.append_item(&delete_menu_item);
|
||||
|
||||
menu_button.set_menu_model(Some(&menu));
|
||||
|
||||
let recording_list = List::new(&gettext("No recordings found."));
|
||||
|
||||
recording_list.set_make_widget(|recording: &RecordingDescription| {
|
||||
let work_label = gtk::Label::new(Some(&recording.work.get_title()));
|
||||
|
||||
work_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
work_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let performers_label = gtk::Label::new(Some(&recording.get_performers()));
|
||||
performers_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
performers_label.set_opacity(0.5);
|
||||
performers_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
vbox.set_border_width(6);
|
||||
vbox.add(&work_label);
|
||||
vbox.add(&performers_label);
|
||||
|
||||
vbox.upcast()
|
||||
});
|
||||
|
||||
recording_list.set_filter(
|
||||
clone!(@strong search_entry => move |recording: &RecordingDescription| {
|
||||
let search = search_entry.get_text().to_string().to_lowercase();
|
||||
let text = recording.work.get_title() + &recording.get_performers();
|
||||
search.is_empty() || text.contains(&search)
|
||||
}),
|
||||
);
|
||||
|
||||
recording_frame.add(&recording_list.widget.clone());
|
||||
|
||||
let result = Rc::new(Self {
|
||||
backend,
|
||||
widget,
|
||||
stack,
|
||||
recording_list,
|
||||
navigator: RefCell::new(None),
|
||||
});
|
||||
|
||||
search_entry.connect_search_changed(clone!(@strong result => move |_| {
|
||||
result.recording_list.invalidate_filter();
|
||||
}));
|
||||
|
||||
back_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.pop();
|
||||
}
|
||||
}));
|
||||
|
||||
result
|
||||
.recording_list
|
||||
.set_selected(clone!(@strong result => move |recording| {
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.push(RecordingScreen::new(result.backend.clone(), recording.clone()));
|
||||
}
|
||||
}));
|
||||
|
||||
let context = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
context.spawn_local(async move {
|
||||
let recordings = clone
|
||||
.backend
|
||||
.get_recordings_for_ensemble(ensemble.id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if recordings.is_empty() {
|
||||
clone.stack.set_visible_child_name("nothing");
|
||||
} else {
|
||||
clone.recording_list.show_items(recordings);
|
||||
clone.stack.set_visible_child_name("content");
|
||||
}
|
||||
});
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl NavigatorScreen for EnsembleScreen {
|
||||
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);
|
||||
}
|
||||
}
|
||||
14
musicus/src/screens/mod.rs
Normal file
14
musicus/src/screens/mod.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
pub mod ensemble_screen;
|
||||
pub use ensemble_screen::*;
|
||||
|
||||
pub mod person_screen;
|
||||
pub use person_screen::*;
|
||||
|
||||
pub mod player_screen;
|
||||
pub use player_screen::*;
|
||||
|
||||
pub mod work_screen;
|
||||
pub use work_screen::*;
|
||||
|
||||
pub mod recording_screen;
|
||||
pub use recording_screen::*;
|
||||
199
musicus/src/screens/person_screen.rs
Normal file
199
musicus/src/screens/person_screen.rs
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
use super::*;
|
||||
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;
|
||||
|
||||
pub struct PersonScreen {
|
||||
backend: Rc<Backend>,
|
||||
widget: gtk::Box,
|
||||
stack: gtk::Stack,
|
||||
work_list: Rc<List<WorkDescription>>,
|
||||
recording_list: Rc<List<RecordingDescription>>,
|
||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||
}
|
||||
|
||||
impl PersonScreen {
|
||||
pub fn new(backend: Rc<Backend>, person: Person) -> Rc<Self> {
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/person_screen.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
get_widget!(builder, libhandy::HeaderBar, header);
|
||||
get_widget!(builder, gtk::Button, back_button);
|
||||
get_widget!(builder, gtk::MenuButton, menu_button);
|
||||
get_widget!(builder, gtk::SearchEntry, search_entry);
|
||||
get_widget!(builder, gtk::Stack, stack);
|
||||
get_widget!(builder, gtk::Box, work_box);
|
||||
get_widget!(builder, gtk::Frame, work_frame);
|
||||
get_widget!(builder, gtk::Box, recording_box);
|
||||
get_widget!(builder, gtk::Frame, recording_frame);
|
||||
|
||||
header.set_title(Some(&person.name_fl()));
|
||||
|
||||
let edit_menu_item = gio::MenuItem::new(Some(&gettext("Edit person")), None);
|
||||
edit_menu_item.set_action_and_target_value(
|
||||
Some("win.edit-person"),
|
||||
Some(&glib::Variant::from(person.id)),
|
||||
);
|
||||
|
||||
let delete_menu_item = gio::MenuItem::new(Some(&gettext("Delete person")), None);
|
||||
delete_menu_item.set_action_and_target_value(
|
||||
Some("win.delete-person"),
|
||||
Some(&glib::Variant::from(person.id)),
|
||||
);
|
||||
|
||||
let menu = gio::Menu::new();
|
||||
menu.append_item(&edit_menu_item);
|
||||
menu.append_item(&delete_menu_item);
|
||||
|
||||
menu_button.set_menu_model(Some(&menu));
|
||||
|
||||
let work_list = List::new(&gettext("No works found."));
|
||||
|
||||
work_list.set_make_widget(|work: &WorkDescription| {
|
||||
let label = gtk::Label::new(Some(&work.title));
|
||||
label.set_halign(gtk::Align::Start);
|
||||
label.set_margin_start(6);
|
||||
label.set_margin_end(6);
|
||||
label.set_margin_top(6);
|
||||
label.set_margin_bottom(6);
|
||||
label.upcast()
|
||||
});
|
||||
|
||||
work_list.set_filter(
|
||||
clone!(@strong search_entry => move |work: &WorkDescription| {
|
||||
let search = search_entry.get_text().to_string().to_lowercase();
|
||||
let title = work.title.to_lowercase();
|
||||
search.is_empty() || title.contains(&search)
|
||||
}),
|
||||
);
|
||||
|
||||
let recording_list = List::new(&gettext("No recordings found."));
|
||||
|
||||
recording_list.set_make_widget(|recording: &RecordingDescription| {
|
||||
let work_label = gtk::Label::new(Some(&recording.work.get_title()));
|
||||
|
||||
work_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
work_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let performers_label = gtk::Label::new(Some(&recording.get_performers()));
|
||||
performers_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
performers_label.set_opacity(0.5);
|
||||
performers_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
vbox.set_border_width(6);
|
||||
vbox.add(&work_label);
|
||||
vbox.add(&performers_label);
|
||||
|
||||
vbox.upcast()
|
||||
});
|
||||
|
||||
recording_list.set_filter(
|
||||
clone!(@strong search_entry => move |recording: &RecordingDescription| {
|
||||
let search = search_entry.get_text().to_string().to_lowercase();
|
||||
let text = recording.work.get_title() + &recording.get_performers();
|
||||
search.is_empty() || text.contains(&search)
|
||||
}),
|
||||
);
|
||||
|
||||
work_frame.add(&work_list.widget);
|
||||
recording_frame.add(&recording_list.widget);
|
||||
|
||||
let result = Rc::new(Self {
|
||||
backend,
|
||||
widget,
|
||||
stack,
|
||||
work_list,
|
||||
recording_list,
|
||||
navigator: RefCell::new(None),
|
||||
});
|
||||
|
||||
search_entry.connect_search_changed(clone!(@strong result => move |_| {
|
||||
result.work_list.invalidate_filter();
|
||||
result.recording_list.invalidate_filter();
|
||||
}));
|
||||
|
||||
back_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.clone().pop();
|
||||
}
|
||||
}));
|
||||
|
||||
result
|
||||
.work_list
|
||||
.set_selected(clone!(@strong result => move |work| {
|
||||
result.recording_list.clear_selection();
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.push(WorkScreen::new(result.backend.clone(), work.clone()));
|
||||
}
|
||||
}));
|
||||
|
||||
result
|
||||
.recording_list
|
||||
.set_selected(clone!(@strong result => move |recording| {
|
||||
result.work_list.clear_selection();
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.push(RecordingScreen::new(result.backend.clone(), recording.clone()));
|
||||
}
|
||||
}));
|
||||
|
||||
let context = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
context.spawn_local(async move {
|
||||
let works = clone
|
||||
.backend
|
||||
.get_work_descriptions(person.id)
|
||||
.await
|
||||
.unwrap();
|
||||
let recordings = clone
|
||||
.backend
|
||||
.get_recordings_for_person(person.id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if works.is_empty() && recordings.is_empty() {
|
||||
clone.stack.set_visible_child_name("nothing");
|
||||
} else {
|
||||
if works.is_empty() {
|
||||
work_box.hide();
|
||||
} else {
|
||||
clone.work_list.show_items(works);
|
||||
}
|
||||
|
||||
if recordings.is_empty() {
|
||||
recording_box.hide();
|
||||
} else {
|
||||
clone.recording_list.show_items(recordings);
|
||||
}
|
||||
|
||||
clone.stack.set_visible_child_name("content");
|
||||
}
|
||||
});
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl NavigatorScreen for PersonScreen {
|
||||
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);
|
||||
}
|
||||
}
|
||||
334
musicus/src/screens/player_screen.rs
Normal file
334
musicus/src/screens/player_screen.rs
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
use crate::player::*;
|
||||
use crate::widgets::*;
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
|
||||
struct PlaylistElement {
|
||||
pub item: usize,
|
||||
pub track: usize,
|
||||
pub title: String,
|
||||
pub subtitle: Option<String>,
|
||||
pub playable: bool,
|
||||
}
|
||||
|
||||
pub struct PlayerScreen {
|
||||
pub widget: gtk::Box,
|
||||
title_label: gtk::Label,
|
||||
subtitle_label: gtk::Label,
|
||||
previous_button: gtk::Button,
|
||||
play_button: gtk::Button,
|
||||
next_button: gtk::Button,
|
||||
position_label: gtk::Label,
|
||||
position: gtk::Adjustment,
|
||||
duration_label: gtk::Label,
|
||||
play_image: gtk::Image,
|
||||
pause_image: gtk::Image,
|
||||
list: Rc<List<PlaylistElement>>,
|
||||
player: Rc<RefCell<Option<Rc<Player>>>>,
|
||||
seeking: Rc<Cell<bool>>,
|
||||
current_item: Rc<Cell<usize>>,
|
||||
current_track: Rc<Cell<usize>>,
|
||||
back_cb: Rc<RefCell<Option<Box<dyn Fn() -> ()>>>>,
|
||||
}
|
||||
|
||||
impl PlayerScreen {
|
||||
pub fn new() -> Self {
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/player_screen.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
get_widget!(builder, gtk::Button, back_button);
|
||||
get_widget!(builder, gtk::Label, title_label);
|
||||
get_widget!(builder, gtk::Label, subtitle_label);
|
||||
get_widget!(builder, gtk::Button, previous_button);
|
||||
get_widget!(builder, gtk::Button, play_button);
|
||||
get_widget!(builder, gtk::Button, next_button);
|
||||
get_widget!(builder, gtk::Button, stop_button);
|
||||
get_widget!(builder, gtk::Label, position_label);
|
||||
get_widget!(builder, gtk::Scale, position_scale);
|
||||
get_widget!(builder, gtk::Adjustment, position);
|
||||
get_widget!(builder, gtk::Label, duration_label);
|
||||
get_widget!(builder, gtk::Image, play_image);
|
||||
get_widget!(builder, gtk::Image, pause_image);
|
||||
get_widget!(builder, gtk::Frame, frame);
|
||||
|
||||
let back_cb = Rc::new(RefCell::new(None::<Box<dyn Fn() -> ()>>));
|
||||
|
||||
back_button.connect_clicked(clone!(@strong back_cb => move |_| {
|
||||
if let Some(cb) = &*back_cb.borrow() {
|
||||
cb();
|
||||
}
|
||||
}));
|
||||
|
||||
let player = Rc::new(RefCell::new(None::<Rc<Player>>));
|
||||
let seeking = Rc::new(Cell::new(false));
|
||||
|
||||
previous_button.connect_clicked(clone!(@strong player => move |_| {
|
||||
if let Some(player) = &*player.borrow() {
|
||||
player.previous().unwrap();
|
||||
}
|
||||
}));
|
||||
|
||||
play_button.connect_clicked(clone!(@strong player => move |_| {
|
||||
if let Some(player) = &*player.borrow() {
|
||||
player.play_pause();
|
||||
}
|
||||
}));
|
||||
|
||||
next_button.connect_clicked(clone!(@strong player => move |_| {
|
||||
if let Some(player) = &*player.borrow() {
|
||||
player.next().unwrap();
|
||||
}
|
||||
}));
|
||||
|
||||
stop_button.connect_clicked(clone!(@strong player, @strong back_cb => move |_| {
|
||||
if let Some(player) = &*player.borrow() {
|
||||
if let Some(cb) = &*back_cb.borrow() {
|
||||
cb();
|
||||
}
|
||||
|
||||
player.clear();
|
||||
}
|
||||
}));
|
||||
|
||||
position_scale.connect_button_press_event(clone!(@strong seeking => move |_, _| {
|
||||
seeking.replace(true);
|
||||
Inhibit(false)
|
||||
}));
|
||||
|
||||
position_scale.connect_button_release_event(
|
||||
clone!(@strong seeking, @strong position, @strong player => move |_, _| {
|
||||
if let Some(player) = &*player.borrow() {
|
||||
player.seek(position.get_value() as u64);
|
||||
}
|
||||
|
||||
seeking.replace(false);
|
||||
Inhibit(false)
|
||||
}),
|
||||
);
|
||||
|
||||
position_scale.connect_value_changed(
|
||||
clone!(@strong seeking, @strong position, @strong position_label => move |_| {
|
||||
if seeking.get() {
|
||||
let ms = position.get_value() as u64;
|
||||
let min = ms / 60000;
|
||||
let sec = (ms % 60000) / 1000;
|
||||
position_label.set_text(&format!("{}:{:02}", min, sec));
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let current_item = Rc::new(Cell::<usize>::new(0));
|
||||
let current_track = Rc::new(Cell::<usize>::new(0));
|
||||
let list = List::new("");
|
||||
|
||||
list.set_make_widget(clone!(
|
||||
@strong current_item,
|
||||
@strong current_track
|
||||
=> move |element: &PlaylistElement| {
|
||||
let title_label = gtk::Label::new(Some(&element.title));
|
||||
title_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
title_label.set_halign(gtk::Align::Start);
|
||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
vbox.add(&title_label);
|
||||
if let Some(subtitle) = &element.subtitle {
|
||||
let subtitle_label = gtk::Label::new(Some(&subtitle));
|
||||
subtitle_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
subtitle_label.set_halign(gtk::Align::Start);
|
||||
subtitle_label.set_opacity(0.5);
|
||||
vbox.add(&subtitle_label);
|
||||
}
|
||||
|
||||
let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 6);
|
||||
hbox.set_border_width(6);
|
||||
|
||||
if element.playable {
|
||||
let image = gtk::Image::new();
|
||||
|
||||
if element.item == current_item.get() && element.track == current_track.get() {
|
||||
image.set_from_icon_name(
|
||||
Some("media-playback-start-symbolic"),
|
||||
gtk::IconSize::Button,
|
||||
);
|
||||
}
|
||||
|
||||
hbox.add(&image);
|
||||
} else if element.item > 0 {
|
||||
hbox.set_margin_top(18);
|
||||
}
|
||||
hbox.add(&vbox);
|
||||
hbox.upcast()
|
||||
}
|
||||
));
|
||||
|
||||
list.set_selected(clone!(@strong player => move |element| {
|
||||
if let Some(player) = &*player.borrow() {
|
||||
player.set_track(element.item, element.track).unwrap();
|
||||
}
|
||||
}));
|
||||
|
||||
frame.add(&list.widget);
|
||||
|
||||
Self {
|
||||
widget,
|
||||
title_label,
|
||||
subtitle_label,
|
||||
previous_button,
|
||||
play_button,
|
||||
next_button,
|
||||
position_label,
|
||||
position,
|
||||
duration_label,
|
||||
play_image,
|
||||
pause_image,
|
||||
list,
|
||||
player,
|
||||
seeking,
|
||||
current_item,
|
||||
current_track,
|
||||
back_cb,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_player(&self, player: Option<Rc<Player>>) {
|
||||
self.player.replace(player.clone());
|
||||
|
||||
if let Some(player) = player {
|
||||
let playlist = Rc::new(RefCell::new(Vec::<PlaylistItem>::new()));
|
||||
|
||||
player.add_playlist_cb(clone!(
|
||||
@strong player,
|
||||
@strong self.previous_button as previous_button,
|
||||
@strong self.next_button as next_button,
|
||||
@strong self.list as list,
|
||||
@strong playlist
|
||||
=> move |new_playlist| {
|
||||
playlist.replace(new_playlist);
|
||||
previous_button.set_sensitive(player.has_previous());
|
||||
next_button.set_sensitive(player.has_next());
|
||||
|
||||
let mut elements = Vec::new();
|
||||
for (item_index, item) in playlist.borrow().iter().enumerate() {
|
||||
elements.push(PlaylistElement {
|
||||
item: item_index,
|
||||
track: 0,
|
||||
title: item.recording.work.get_title(),
|
||||
subtitle: Some(item.recording.get_performers()),
|
||||
playable: false,
|
||||
});
|
||||
|
||||
for (track_index, track) in item.tracks.iter().enumerate() {
|
||||
let mut parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
parts.push(item.recording.work.parts[*part].title.clone());
|
||||
}
|
||||
|
||||
let title = if parts.is_empty() {
|
||||
gettext("Unknown")
|
||||
} else {
|
||||
parts.join(", ")
|
||||
};
|
||||
|
||||
elements.push(PlaylistElement {
|
||||
item: item_index,
|
||||
track: track_index,
|
||||
title: title,
|
||||
subtitle: None,
|
||||
playable: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
list.show_items(elements);
|
||||
}
|
||||
));
|
||||
|
||||
player.add_track_cb(clone!(
|
||||
@strong player,
|
||||
@strong playlist,
|
||||
@strong self.previous_button as previous_button,
|
||||
@strong self.next_button as next_button,
|
||||
@strong self.title_label as title_label,
|
||||
@strong self.subtitle_label as subtitle_label,
|
||||
@strong self.position_label as position_label,
|
||||
@strong self.current_item as self_item,
|
||||
@strong self.current_track as self_track,
|
||||
@strong self.list as list
|
||||
=> move |current_item, current_track| {
|
||||
previous_button.set_sensitive(player.has_previous());
|
||||
next_button.set_sensitive(player.has_next());
|
||||
|
||||
let item = &playlist.borrow()[current_item];
|
||||
let track = &item.tracks[current_track];
|
||||
|
||||
let mut parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
parts.push(item.recording.work.parts[*part].title.clone());
|
||||
}
|
||||
|
||||
let mut title = item.recording.work.get_title();
|
||||
if !parts.is_empty() {
|
||||
title = format!("{}: {}", title, parts.join(", "));
|
||||
}
|
||||
|
||||
title_label.set_text(&title);
|
||||
subtitle_label.set_text(&item.recording.get_performers());
|
||||
position_label.set_text("0:00");
|
||||
|
||||
self_item.replace(current_item);
|
||||
self_track.replace(current_track);
|
||||
list.update();
|
||||
}
|
||||
));
|
||||
|
||||
player.add_duration_cb(clone!(
|
||||
@strong self.duration_label as duration_label,
|
||||
@strong self.position as position
|
||||
=> move |ms| {
|
||||
let min = ms / 60000;
|
||||
let sec = (ms % 60000) / 1000;
|
||||
duration_label.set_text(&format!("{}:{:02}", min, sec));
|
||||
position.set_upper(ms as f64);
|
||||
}
|
||||
));
|
||||
|
||||
player.add_playing_cb(clone!(
|
||||
@strong self.play_button as play_button,
|
||||
@strong self.play_image as play_image,
|
||||
@strong self.pause_image as pause_image
|
||||
=> move |playing| {
|
||||
if let Some(child) = play_button.get_child() {
|
||||
play_button.remove( &child);
|
||||
}
|
||||
|
||||
play_button.add(if playing {
|
||||
&pause_image
|
||||
} else {
|
||||
&play_image
|
||||
});
|
||||
}
|
||||
));
|
||||
|
||||
player.add_position_cb(clone!(
|
||||
@strong self.position_label as position_label,
|
||||
@strong self.position as position,
|
||||
@strong self.seeking as seeking
|
||||
=> move |ms| {
|
||||
if !seeking.get() {
|
||||
let min = ms / 60000;
|
||||
let sec = (ms % 60000) / 1000;
|
||||
position_label.set_text(&format!("{}:{:02}", min, sec));
|
||||
position.set_value(ms as f64);
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_back_cb<F: Fn() -> () + 'static>(&self, cb: F) {
|
||||
self.back_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
}
|
||||
156
musicus/src/screens/recording_screen.rs
Normal file
156
musicus/src/screens/recording_screen.rs
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
use crate::backend::*;
|
||||
use crate::database::*;
|
||||
use crate::player::*;
|
||||
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;
|
||||
|
||||
pub struct RecordingScreen {
|
||||
backend: Rc<Backend>,
|
||||
widget: gtk::Box,
|
||||
stack: gtk::Stack,
|
||||
tracks: RefCell<Vec<TrackDescription>>,
|
||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||
}
|
||||
|
||||
impl RecordingScreen {
|
||||
pub fn new(backend: Rc<Backend>, recording: RecordingDescription) -> Rc<Self> {
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_screen.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
get_widget!(builder, libhandy::HeaderBar, header);
|
||||
get_widget!(builder, gtk::Button, back_button);
|
||||
get_widget!(builder, gtk::MenuButton, menu_button);
|
||||
get_widget!(builder, gtk::Stack, stack);
|
||||
get_widget!(builder, gtk::Frame, frame);
|
||||
get_widget!(builder, gtk::Button, add_to_playlist_button);
|
||||
|
||||
header.set_title(Some(&recording.work.get_title()));
|
||||
header.set_subtitle(Some(&recording.get_performers()));
|
||||
|
||||
let edit_menu_item = gio::MenuItem::new(Some(&gettext("Edit recording")), None);
|
||||
edit_menu_item.set_action_and_target_value(
|
||||
Some("win.edit-recording"),
|
||||
Some(&glib::Variant::from(recording.id)),
|
||||
);
|
||||
|
||||
let delete_menu_item = gio::MenuItem::new(Some(&gettext("Delete recording")), None);
|
||||
delete_menu_item.set_action_and_target_value(
|
||||
Some("win.delete-recording"),
|
||||
Some(&glib::Variant::from(recording.id)),
|
||||
);
|
||||
|
||||
let edit_tracks_menu_item = gio::MenuItem::new(Some(&gettext("Edit tracks")), None);
|
||||
edit_tracks_menu_item.set_action_and_target_value(
|
||||
Some("win.edit-tracks"),
|
||||
Some(&glib::Variant::from(recording.id)),
|
||||
);
|
||||
|
||||
let delete_tracks_menu_item = gio::MenuItem::new(Some(&gettext("Delete tracks")), None);
|
||||
delete_tracks_menu_item.set_action_and_target_value(
|
||||
Some("win.delete-tracks"),
|
||||
Some(&glib::Variant::from(recording.id)),
|
||||
);
|
||||
|
||||
let menu = gio::Menu::new();
|
||||
menu.append_item(&edit_menu_item);
|
||||
menu.append_item(&delete_menu_item);
|
||||
menu.append_item(&edit_tracks_menu_item);
|
||||
menu.append_item(&delete_tracks_menu_item);
|
||||
|
||||
menu_button.set_menu_model(Some(&menu));
|
||||
|
||||
let recording = Rc::new(recording);
|
||||
let list = List::new(&gettext("No tracks found."));
|
||||
|
||||
list.set_make_widget(
|
||||
clone!(@strong recording => move |track: &TrackDescription| {
|
||||
let mut title_parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
title_parts.push(recording.work.parts[*part].title.clone());
|
||||
}
|
||||
|
||||
let title = if title_parts.is_empty() {
|
||||
gettext("Unknown")
|
||||
} else {
|
||||
title_parts.join(", ")
|
||||
};
|
||||
|
||||
let title_label = gtk::Label::new(Some(&title));
|
||||
title_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
title_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let file_name_label = gtk::Label::new(Some(&track.file_name));
|
||||
file_name_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
file_name_label.set_opacity(0.5);
|
||||
file_name_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
vbox.set_border_width(6);
|
||||
vbox.add(&title_label);
|
||||
vbox.add(&file_name_label);
|
||||
|
||||
vbox.upcast()
|
||||
}),
|
||||
);
|
||||
|
||||
frame.add(&list.widget);
|
||||
|
||||
let result = Rc::new(Self {
|
||||
backend,
|
||||
widget,
|
||||
stack,
|
||||
tracks: RefCell::new(Vec::new()),
|
||||
navigator: RefCell::new(None),
|
||||
});
|
||||
|
||||
back_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.clone().pop();
|
||||
}
|
||||
}));
|
||||
|
||||
add_to_playlist_button.connect_clicked(
|
||||
clone!(@strong result, @strong recording => move |_| {
|
||||
if let Some(player) = result.backend.get_player() {
|
||||
player.add_item(PlaylistItem {
|
||||
recording: (*recording).clone(),
|
||||
tracks: result.tracks.borrow().clone(),
|
||||
}).unwrap();
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let context = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
let id = recording.id;
|
||||
context.spawn_local(async move {
|
||||
let tracks = clone.backend.get_tracks(id).await.unwrap();
|
||||
list.show_items(tracks.clone());
|
||||
clone.stack.set_visible_child_name("content");
|
||||
clone.tracks.replace(tracks);
|
||||
});
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl NavigatorScreen for RecordingScreen {
|
||||
fn attach_navigator(&self, navigator: Rc<Navigator>) {
|
||||
self.navigator.replace(Some(navigator));
|
||||
}
|
||||
|
||||
fn get_widget(&self) -> gtk::Widget {
|
||||
self.widget.clone().upcast()
|
||||
}
|
||||
|
||||
fn detach_navigator(&self) {
|
||||
self.navigator.replace(None);
|
||||
}
|
||||
}
|
||||
144
musicus/src/screens/work_screen.rs
Normal file
144
musicus/src/screens/work_screen.rs
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
use super::*;
|
||||
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;
|
||||
|
||||
pub struct WorkScreen {
|
||||
backend: Rc<Backend>,
|
||||
widget: gtk::Box,
|
||||
stack: gtk::Stack,
|
||||
recording_list: Rc<List<RecordingDescription>>,
|
||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||
}
|
||||
|
||||
impl WorkScreen {
|
||||
pub fn new(backend: Rc<Backend>, work: WorkDescription) -> Rc<Self> {
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_screen.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
get_widget!(builder, libhandy::HeaderBar, header);
|
||||
get_widget!(builder, gtk::Button, back_button);
|
||||
get_widget!(builder, gtk::MenuButton, menu_button);
|
||||
get_widget!(builder, gtk::SearchEntry, search_entry);
|
||||
get_widget!(builder, gtk::Stack, stack);
|
||||
get_widget!(builder, gtk::Frame, recording_frame);
|
||||
|
||||
header.set_title(Some(&work.title));
|
||||
header.set_subtitle(Some(&work.composer.name_fl()));
|
||||
|
||||
let edit_menu_item = gio::MenuItem::new(Some(&gettext("Edit work")), None);
|
||||
edit_menu_item.set_action_and_target_value(
|
||||
Some("win.edit-work"),
|
||||
Some(&glib::Variant::from(work.id)),
|
||||
);
|
||||
|
||||
let delete_menu_item = gio::MenuItem::new(Some(&gettext("Delete work")), None);
|
||||
delete_menu_item.set_action_and_target_value(
|
||||
Some("win.delete-work"),
|
||||
Some(&glib::Variant::from(work.id)),
|
||||
);
|
||||
|
||||
let menu = gio::Menu::new();
|
||||
menu.append_item(&edit_menu_item);
|
||||
menu.append_item(&delete_menu_item);
|
||||
|
||||
menu_button.set_menu_model(Some(&menu));
|
||||
|
||||
let recording_list = List::new(&gettext("No recordings found."));
|
||||
|
||||
recording_list.set_make_widget(|recording: &RecordingDescription| {
|
||||
let work_label = gtk::Label::new(Some(&recording.work.get_title()));
|
||||
|
||||
work_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
work_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let performers_label = gtk::Label::new(Some(&recording.get_performers()));
|
||||
performers_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
performers_label.set_opacity(0.5);
|
||||
performers_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
vbox.set_border_width(6);
|
||||
vbox.add(&work_label);
|
||||
vbox.add(&performers_label);
|
||||
|
||||
vbox.upcast()
|
||||
});
|
||||
|
||||
recording_list.set_filter(clone!(@strong search_entry => move |recording: &RecordingDescription| {
|
||||
let search = search_entry.get_text().to_string().to_lowercase();
|
||||
let text = recording.work.get_title().to_lowercase() + &recording.get_performers().to_lowercase();
|
||||
search.is_empty() || text.contains(&search)
|
||||
}),);
|
||||
|
||||
recording_frame.add(&recording_list.widget);
|
||||
|
||||
let result = Rc::new(Self {
|
||||
backend,
|
||||
widget,
|
||||
stack,
|
||||
recording_list,
|
||||
navigator: RefCell::new(None),
|
||||
});
|
||||
|
||||
search_entry.connect_search_changed(clone!(@strong result => move |_| {
|
||||
result.recording_list.invalidate_filter();
|
||||
}));
|
||||
|
||||
back_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.clone().pop();
|
||||
}
|
||||
}));
|
||||
|
||||
result
|
||||
.recording_list
|
||||
.set_selected(clone!(@strong result => move |recording| {
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.push(RecordingScreen::new(result.backend.clone(), recording.clone()));
|
||||
}
|
||||
}));
|
||||
|
||||
let context = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
context.spawn_local(async move {
|
||||
let recordings = clone
|
||||
.backend
|
||||
.get_recordings_for_work(work.id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if recordings.is_empty() {
|
||||
clone.stack.set_visible_child_name("nothing");
|
||||
} else {
|
||||
clone.recording_list.show_items(recordings);
|
||||
clone.stack.set_visible_child_name("content");
|
||||
}
|
||||
});
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl NavigatorScreen for WorkScreen {
|
||||
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);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue