diff --git a/Cargo.lock b/Cargo.lock index f532a1c..f7b2665 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "atomic_refcell" @@ -40,9 +40,9 @@ checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bitflags" @@ -52,9 +52,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] name = "block" @@ -70,33 +70,35 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "cairo-rs" -version = "0.20.0" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "797fd5a634dcb0ad0d7d583df794deb0a236d88e759cd34b7da20198c6c9d145" +checksum = "ae50b5510d86cf96ac2370e66d8dc960882f3df179d6a5a1e52bd94a1416c0f7" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cairo-sys-rs", - "glib 0.20.0", + "glib 0.20.7", "libc", - "thiserror", ] [[package]] name = "cairo-sys-rs" -version = "0.20.0" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428290f914b9b86089f60f5d8a9f6e440508e1bcff23b25afd51502b0a2da88f" +checksum = "f18b6bb8e43c7eb0f2aac7976afe0c61b6f5fc2ab7bc4c139537ea56c92290df" dependencies = [ - "glib-sys 0.20.0", + "glib-sys 0.20.7", "libc", - "system-deps 7.0.1", + "system-deps 7.0.3", ] [[package]] name = "cc" -version = "1.1.5" +version = "1.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324c74f2155653c90b04f25b2a47a8a631360cb908f92a772695f430c7e31052" +checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b" +dependencies = [ + "shlex", +] [[package]] name = "cfg-expr" @@ -108,6 +110,16 @@ dependencies = [ "target-lexicon", ] +[[package]] +name = "cfg-expr" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d4ba6e40bd1184518716a6e1a781bf9160e286d219ccdb8ab2612e74cfe4789" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -116,9 +128,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -130,9 +142,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "darling" @@ -155,7 +167,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.71", + "syn 2.0.96", ] [[package]] @@ -166,7 +178,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.71", + "syn 2.0.96", ] [[package]] @@ -190,9 +202,9 @@ dependencies = [ [[package]] name = "diesel" -version = "2.2.1" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62d6dcd069e7b5fe49a302411f759d4cf1cf2c27fe798ef46fb8baefc053dd2b" +checksum = "ccf1bedf64cdb9643204a36dd15b19a6ce8e7aa7f7b105868e9f1fad5ffa7d12" dependencies = [ "chrono", "diesel_derives", @@ -202,15 +214,15 @@ dependencies = [ [[package]] name = "diesel_derives" -version = "2.2.1" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59de76a222c2b8059f789cbe07afbfd8deb8c31dd0bc2a21f85e256c1def8259" +checksum = "e7f2c3de51e2ba6bf2a648285696137aaf0f5f487bcbea93972fe8a364e131a4" dependencies = [ "diesel_table_macro_syntax", "dsl_auto_type", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.96", ] [[package]] @@ -230,21 +242,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" dependencies = [ - "syn 2.0.71", + "syn 2.0.96", ] [[package]] name = "dsl_auto_type" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0892a17df262a24294c382f0d5997571006e7a4348b4327557c4ff1cd4a8bccc" +checksum = "c5d9abe6314103864cc2d8901b7ae224e0ab1a103a0a416661b4097b0779b607" dependencies = [ "darling", "either", "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.96", ] [[package]] @@ -283,24 +295,24 @@ checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -309,32 +321,32 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.96", ] [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-macro", @@ -346,59 +358,59 @@ dependencies = [ [[package]] name = "gdk-pixbuf" -version = "0.20.0" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28bb53ecb56857c683c9ec859908e076dd3969c7d67598bd8b1ce095d211304a" +checksum = "b6efc7705f7863d37b12ad6974cbb310d35d054f5108cdc1e69037742f573c4c" dependencies = [ "gdk-pixbuf-sys", "gio", - "glib 0.20.0", + "glib 0.20.7", "libc", ] [[package]] name = "gdk-pixbuf-sys" -version = "0.20.0" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f6681a0c1330d1d3968bec1529f7172d62819ef0bdbb0d18022320654158b03" +checksum = "67f2587c9202bf997476bbba6aaed4f78a11538a2567df002a5f57f5331d0b5c" dependencies = [ "gio-sys", - "glib-sys 0.20.0", - "gobject-sys 0.20.0", + "glib-sys 0.20.7", + "gobject-sys 0.20.7", "libc", - "system-deps 7.0.1", + "system-deps 7.0.3", ] [[package]] name = "gdk4" -version = "0.9.0" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b7d7237c1487ed4b300aac7744efcbf1319e12d60d7afcd6f505414bd5b5dea" +checksum = "d0196720118f880f71fe7da971eff58cc43a89c9cf73f46076b7cb1e60889b15" dependencies = [ "cairo-rs", "gdk-pixbuf", "gdk4-sys", "gio", - "glib 0.20.0", + "glib 0.20.7", "libc", "pango", ] [[package]] name = "gdk4-sys" -version = "0.9.0" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a67576c8ec012156d7f680e201a807b4432a77babb3157e0555e990ab6bcd878" +checksum = "60b0e1340bd15e7a78810cf39fed9e5d85f0a8f80b1d999d384ca17dcc452b60" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", "gio-sys", - "glib-sys 0.20.0", - "gobject-sys 0.20.0", + "glib-sys 0.20.7", + "gobject-sys 0.20.7", "libc", "pango-sys", "pkg-config", - "system-deps 7.0.1", + "system-deps 7.0.3", ] [[package]] @@ -414,9 +426,9 @@ dependencies = [ [[package]] name = "gettext-rs" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e49ea8a8fad198aaa1f9655a2524b64b70eb06b2f3ff37da407566c93054f364" +checksum = "a44e92f7dc08430aca7ed55de161253a22276dfd69c5526e5c5e95d1f7cf338a" dependencies = [ "gettext-sys", "locale_config", @@ -424,9 +436,9 @@ dependencies = [ [[package]] name = "gettext-sys" -version = "0.21.3" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c63ce2e00f56a206778276704bbe38564c8695249fdc8f354b4ef71c57c3839d" +checksum = "bb45773f5b8945f12aecd04558f545964f943dacda1b1155b3d738f5469ef661" dependencies = [ "cc", "temp-dir", @@ -434,32 +446,31 @@ dependencies = [ [[package]] name = "gio" -version = "0.20.0" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "398e3da68749fdc32783cbf7521ec3f65c9cf946db8c7774f8460af49e52c6e2" +checksum = "a517657589a174be9f60c667f1fec8b7ac82ed5db4ebf56cf073a3b5955d8e2e" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-util", "gio-sys", - "glib 0.20.0", + "glib 0.20.7", "libc", "pin-project-lite", "smallvec", - "thiserror", ] [[package]] name = "gio-sys" -version = "0.20.0" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4feb96b31c32730ea3e1e89aecd2e4e37ecb1c473ad8f685e3430a159419f63" +checksum = "8446d9b475730ebef81802c1738d972db42fde1c5a36a627ebc4d665fc87db04" dependencies = [ - "glib-sys 0.20.0", - "gobject-sys 0.20.0", + "glib-sys 0.20.7", + "gobject-sys 0.20.7", "libc", - "system-deps 7.0.1", + "system-deps 7.0.3", "windows-sys", ] @@ -480,29 +491,28 @@ dependencies = [ "libc", "once_cell", "smallvec", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "glib" -version = "0.20.0" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee90a615ce05be7a32932cfb8adf2c4bbb4700e80d37713c981fb24c0c56238" +checksum = "f969edf089188d821a30cde713b6f9eb08b20c63fc2e584aba2892a7984a8cc0" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "futures-channel", "futures-core", "futures-executor", "futures-task", "futures-util", "gio-sys", - "glib-macros 0.20.0", - "glib-sys 0.20.0", - "gobject-sys 0.20.0", + "glib-macros 0.20.7", + "glib-sys 0.20.7", + "gobject-sys 0.20.7", "libc", "memchr", "smallvec", - "thiserror", ] [[package]] @@ -522,15 +532,15 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.20.0" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4da558d8177c0c8c54368818b508a4244e1286fce2858cef4e547023f0cfa5ef" +checksum = "715601f8f02e71baef9c1f94a657a9a77c192aea6097cf9ae7e5e177cd8cde68" dependencies = [ "heck 0.5.0", - "proc-macro-crate 3.1.0", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.96", ] [[package]] @@ -545,12 +555,12 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.20.0" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4958c26e5a01c9af00dea669a97369eccbec29a8e6d125c24ea2d85ee7467b60" +checksum = "b360ff0f90d71de99095f79c526a5888c9c92fc9ee1b19da06c6f5e75f0c2a53" dependencies = [ "libc", - "system-deps 7.0.1", + "system-deps 7.0.3", ] [[package]] @@ -566,47 +576,47 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.20.0" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6908864f5ffff15b56df7e90346863904f49b949337ed0456b9287af61903b8" +checksum = "67a56235e971a63bfd75abb13ef70064e1346388723422a68580d8a6fbac6423" dependencies = [ - "glib-sys 0.20.0", + "glib-sys 0.20.7", "libc", - "system-deps 7.0.1", + "system-deps 7.0.3", ] [[package]] name = "graphene-rs" -version = "0.20.0" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630e940ad5824f90221d6579043a9cd1f8bec86b4a17faaf7827d58eb16e8c1f" +checksum = "f39d3bcd2e24fd9c2874a56f277b72c03e728de9bdc95a8d4ef4c962f10ced98" dependencies = [ - "glib 0.20.0", + "glib 0.20.7", "graphene-sys", "libc", ] [[package]] name = "graphene-sys" -version = "0.20.0" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8fade7b754982f47ebbed241fd2680816fdd4598321784da10b9e1168836a" +checksum = "11a68d39515bf340e879b72cecd4a25c1332557757ada6e8aba8654b4b81d23a" dependencies = [ - "glib-sys 0.20.0", + "glib-sys 0.20.7", "libc", "pkg-config", - "system-deps 7.0.1", + "system-deps 7.0.3", ] [[package]] name = "gsk4" -version = "0.9.0" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3cf2091e1af185b347b3450817d93dea6fe435df7abd4c2cd7fb5bcb4cfda8" +checksum = "32b9188db0a6219e708b6b6e7225718e459def664023dbddb8395ca1486d8102" dependencies = [ "cairo-rs", "gdk4", - "glib 0.20.0", + "glib 0.20.7", "graphene-rs", "gsk4-sys", "libc", @@ -615,31 +625,31 @@ dependencies = [ [[package]] name = "gsk4-sys" -version = "0.9.0" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aa69614a26d8760c186c3690f1b0fbb917572ca23ef83137445770ceddf8cde" +checksum = "bca10fc65d68528a548efa3d8747934adcbe7058b73695c9a7f43a25352fce14" dependencies = [ "cairo-sys-rs", "gdk4-sys", - "glib-sys 0.20.0", - "gobject-sys 0.20.0", + "glib-sys 0.20.7", + "gobject-sys 0.20.7", "graphene-sys", "libc", "pango-sys", - "system-deps 7.0.1", + "system-deps 7.0.3", ] [[package]] name = "gstreamer" -version = "0.23.0" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e95b1d1153239a621ec143501fdcca6c1ad3efb87d268597285f85c4136f73" +checksum = "700cb1b2e86dda424f85eb728102a111602317e40b4dd71cf1c0dc04e0cc5d95" dependencies = [ "cfg-if", "futures-channel", "futures-core", "futures-util", - "glib 0.20.0", + "glib 0.20.7", "gstreamer-sys", "itertools", "libc", @@ -651,18 +661,18 @@ dependencies = [ "paste", "pin-project-lite", "smallvec", - "thiserror", + "thiserror 2.0.11", ] [[package]] name = "gstreamer-base" -version = "0.23.0" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3faa0b3cf361bf538dcb25b5cb7f6e73bd872ed031d85f3997b42d3fbd66409" +checksum = "d152db7983f98d5950cf64e53805286548063475fb61a5e5450fba4cec05899b" dependencies = [ "atomic_refcell", "cfg-if", - "glib 0.20.0", + "glib 0.20.7", "gstreamer", "gstreamer-base-sys", "libc", @@ -670,24 +680,24 @@ dependencies = [ [[package]] name = "gstreamer-base-sys" -version = "0.23.0" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a6643ef963c636b8022adc93aa19eac6f356bd174a187c499339fc5d64c1e05" +checksum = "d47cc2d15f2a3d5eb129e5dacbbeec9600432b706805c15dff57b6aa11b2791c" dependencies = [ - "glib-sys 0.20.0", - "gobject-sys 0.20.0", + "glib-sys 0.20.7", + "gobject-sys 0.20.7", "gstreamer-sys", "libc", - "system-deps 7.0.1", + "system-deps 7.0.3", ] [[package]] name = "gstreamer-play" -version = "0.23.0" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc9fac3926d08f882d5a7b98fc7780b2ea812cbebc0c56c56c7186bde34fcdd" +checksum = "2d7a815750a28ac838bfd745d6da07cfd142bb2fa471397cd9992c8b6f235665" dependencies = [ - "glib 0.20.0", + "glib 0.20.7", "gstreamer", "gstreamer-play-sys", "gstreamer-video", @@ -696,66 +706,66 @@ dependencies = [ [[package]] name = "gstreamer-play-sys" -version = "0.23.0" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ffb862566a025cb5477089bbcdb15cc871e317ad451169835a2c06efaa7097" +checksum = "a1f8ef790b8a697c759a9bbbaa7b0c061f529c4581e0cc72839ae753af533591" dependencies = [ - "glib-sys 0.20.0", - "gobject-sys 0.20.0", + "glib-sys 0.20.7", + "gobject-sys 0.20.7", "gstreamer-sys", "gstreamer-video-sys", "libc", - "system-deps 7.0.1", + "system-deps 7.0.3", ] [[package]] name = "gstreamer-sys" -version = "0.23.0" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c9005b55dd2b1784645963c1ec409f9d420a56f6348d0ae69c2eaff584bcc3" +checksum = "16cf1ae0a869aa7066ce3c685b76053b4b4f48f364a5b18c4b1f36ef57469719" dependencies = [ - "glib-sys 0.20.0", - "gobject-sys 0.20.0", + "glib-sys 0.20.7", + "gobject-sys 0.20.7", "libc", - "system-deps 7.0.1", + "system-deps 7.0.3", ] [[package]] name = "gstreamer-video" -version = "0.23.0" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57332bca1ae7825a53fe57d993b63389f132d335aed691ac76f0ffe4304548e3" +checksum = "8fa41e40319e923236e96f0b691711d1504746ab9c89607d77d22aa84777f33f" dependencies = [ "cfg-if", "futures-channel", - "glib 0.20.0", + "glib 0.20.7", "gstreamer", "gstreamer-base", "gstreamer-video-sys", "libc", "once_cell", - "thiserror", + "thiserror 2.0.11", ] [[package]] name = "gstreamer-video-sys" -version = "0.23.0" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f5c334d143384e8dc714af948c2e5d7d12cb588fdcfb56f3bf37c24daf350ef" +checksum = "31dc0f49c117f4867b0f98c712aa55ebf25580151d794be8f9179ec2d877fd14" dependencies = [ - "glib-sys 0.20.0", - "gobject-sys 0.20.0", + "glib-sys 0.20.7", + "gobject-sys 0.20.7", "gstreamer-base-sys", "gstreamer-sys", "libc", - "system-deps 7.0.1", + "system-deps 7.0.3", ] [[package]] name = "gtk4" -version = "0.9.0" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaffc6c743c9160514cc9b67eace364e5dc5798369fa809cdb04e035c21c5c5d" +checksum = "b697ff938136625f6acf75f01951220f47a45adcf0060ee55b4671cf734dac44" dependencies = [ "cairo-rs", "field-offset", @@ -763,7 +773,7 @@ dependencies = [ "gdk-pixbuf", "gdk4", "gio", - "glib 0.20.0", + "glib 0.20.7", "graphene-rs", "gsk4", "gtk4-macros", @@ -774,40 +784,40 @@ dependencies = [ [[package]] name = "gtk4-macros" -version = "0.9.0" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "188211f546ce5801f6d0245c37b6249143a2cb4fa040e54829ca1e76796e9f09" +checksum = "0ed1786c4703dd196baf7e103525ce0cf579b3a63a0570fe653b7ee6bac33999" dependencies = [ - "proc-macro-crate 3.1.0", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.96", ] [[package]] name = "gtk4-sys" -version = "0.9.0" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1114a207af8ada02cf4658a76692f4190f06f093380d5be07e3ca8b43aa7c666" +checksum = "3af4b680cee5d2f786a2f91f1c77e95ecf2254522f0ca4edf3a2dce6cb35cecf" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", "gdk4-sys", "gio-sys", - "glib-sys 0.20.0", - "gobject-sys 0.20.0", + "glib-sys 0.20.7", + "gobject-sys 0.20.7", "graphene-sys", "gsk4-sys", "libc", "pango-sys", - "system-deps 7.0.1", + "system-deps 7.0.3", ] [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heck" @@ -823,9 +833,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -852,9 +862,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "2.2.6" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown", @@ -871,16 +881,17 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -892,13 +903,13 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libadwaita" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ff9c222b5c783729de45185f07b2fec2d43a7f9c63961e777d3667e20443878" +checksum = "8611ee9fb85e7606c362b513afcaf5b59853f79e4d98caaaf581d99465014247" dependencies = [ "gdk4", "gio", - "glib 0.20.0", + "glib 0.20.7", "gtk4", "libadwaita-sys", "libc", @@ -907,25 +918,25 @@ dependencies = [ [[package]] name = "libadwaita-sys" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c44d8bdbad31d6639e1f20cc9c1424f1a8e02d751fc28d44659bf743fb9eca6" +checksum = "b099a223560118d4d4fa04b6d23f3ea5b7171fe1d83dfb7e6b45b54cdfc83af9" dependencies = [ "gdk4-sys", "gio-sys", - "glib-sys 0.20.0", - "gobject-sys 0.20.0", + "glib-sys 0.20.7", + "gobject-sys 0.20.7", "gtk4-sys", "libc", "pango-sys", - "system-deps 7.0.1", + "system-deps 7.0.3", ] [[package]] name = "libc" -version = "0.2.155" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libdbus-sys" @@ -938,9 +949,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.28.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "pkg-config", "vcpkg", @@ -961,9 +972,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "malloc_buf" @@ -1124,9 +1135,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "option-operations" @@ -1145,26 +1156,26 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pango" -version = "0.20.0" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54768854025df6903061d0084fd9702a253ddfd60db7d9b751d43b76689a7f0a" +checksum = "9e89bd74250a03a05cec047b43465469102af803be2bf5e5a1088f8b8455e087" dependencies = [ "gio", - "glib 0.20.0", + "glib 0.20.7", "libc", "pango-sys", ] [[package]] name = "pango-sys" -version = "0.20.0" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b07cc57d10cee4ec661f718a6902cee18c2f4cfae08e87e5a390525946913390" +checksum = "71787e0019b499a5eda889279e4adb455a4f3fdd6870cd5ab7f4a5aa25df6699" dependencies = [ - "glib-sys 0.20.0", - "gobject-sys 0.20.0", + "glib-sys 0.20.7", + "gobject-sys 0.20.7", "libc", - "system-deps 7.0.1", + "system-deps 7.0.3", ] [[package]] @@ -1175,9 +1186,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -1187,9 +1198,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "powerfmt" @@ -1209,11 +1220,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.21.1", + "toml_edit 0.22.22", ] [[package]] @@ -1242,27 +1253,27 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] [[package]] name = "regex" -version = "1.10.5" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -1272,9 +1283,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -1283,19 +1294,25 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + [[package]] name = "ryu" version = "1.0.18" @@ -1304,46 +1321,47 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "semver" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" [[package]] name = "serde" -version = "1.0.204" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.204" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.96", ] [[package]] name = "serde_json" -version = "1.0.120" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] [[package]] name = "serde_spanned" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -1357,6 +1375,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "slab" version = "0.4.9" @@ -1391,9 +1415,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.71" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -1406,7 +1430,7 @@ version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ - "cfg-expr", + "cfg-expr 0.15.8", "heck 0.5.0", "pkg-config", "toml", @@ -1415,11 +1439,11 @@ dependencies = [ [[package]] name = "system-deps" -version = "7.0.1" +version = "7.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c81f13d9a334a6c242465140bd262fae382b752ff2011c4f7419919a9c97922" +checksum = "66d23aaf9f331227789a99e8de4c91bf46703add012bdfd45fdecdfb2975a005" dependencies = [ - "cfg-expr", + "cfg-expr 0.17.2", "heck 0.5.0", "pkg-config", "toml", @@ -1428,34 +1452,54 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.15" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4873307b7c257eddcb50c9bedf158eb669578359fb28428bef438fec8e6ba7c2" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "temp-dir" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f227968ec00f0e5322f9b8173c7a0cbcff6181a0a5b28e9892491c286277231" +checksum = "bc1ee6eef34f12f765cb94725905c6312b6610ab2b0940889cfe58dae7bc3c72" [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl 2.0.11", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.96", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", ] [[package]] @@ -1470,9 +1514,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -1491,9 +1535,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -1501,21 +1545,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.15" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.16", + "toml_edit 0.22.22", ] [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] @@ -1533,33 +1577,22 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.21.1" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.22.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.13", + "winnow 0.6.24", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -1578,9 +1611,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "nu-ansi-term", "sharded-slab", @@ -1592,15 +1625,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "uuid" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4" dependencies = [ "getrandom", ] @@ -1625,9 +1658,9 @@ checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" @@ -1637,34 +1670,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", + "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.96", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1672,22 +1706,25 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.71", + "syn 2.0.96", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "winapi" @@ -1722,9 +1759,9 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] @@ -1804,9 +1841,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.13" +version = "0.6.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" dependencies = [ "memchr", ] diff --git a/data/ui/ensemble_editor.blp b/data/ui/ensemble_editor.blp new file mode 100644 index 0000000..947f59e --- /dev/null +++ b/data/ui/ensemble_editor.blp @@ -0,0 +1,42 @@ +using Gtk 4.0; +using Adw 1; + +template $MusicusEnsembleEditor: Adw.NavigationPage { + title: _("Ensemble"); + + Adw.ToolbarView { + [top] + Adw.HeaderBar header_bar {} + + Adw.Clamp { + Gtk.Box { + orientation: vertical; + + Gtk.Label { + label: _("Name"); + xalign: 0; + margin-top: 24; + + styles [ + "heading" + ] + } + + $MusicusTranslationEditor name_editor { + margin-top: 12; + } + + Gtk.Button save_button { + margin-top: 24; + label: _("Create ensemble"); + clicked => $save() swapped; + + styles [ + "card", + "save" + ] + } + } + } + } +} diff --git a/data/ui/ensemble_selector_popover.blp b/data/ui/ensemble_selector_popover.blp new file mode 100644 index 0000000..f4c91e0 --- /dev/null +++ b/data/ui/ensemble_selector_popover.blp @@ -0,0 +1,35 @@ +using Gtk 4.0; +using Adw 1; + +template $MusicusEnsembleSelectorPopover: Gtk.Popover { + styles [ + "selector" + ] + + Adw.ToolbarView { + [top] + Gtk.SearchEntry search_entry { + placeholder-text: _("Search ensembles…"); + margin-start: 8; + margin-end: 8; + margin-top: 8; + margin-bottom: 6; + search-changed => $search_changed() swapped; + activate => $activate() swapped; + stop-search => $stop_search() swapped; + } + + Gtk.ScrolledWindow scrolled_window { + height-request: 200; + + Gtk.ListBox list_box { + styles [ + "selector-list" + ] + + selection-mode: none; + activate-on-single-click: true; + } + } + } +} diff --git a/data/ui/instrument_editor.blp b/data/ui/instrument_editor.blp new file mode 100644 index 0000000..93c36f8 --- /dev/null +++ b/data/ui/instrument_editor.blp @@ -0,0 +1,42 @@ +using Gtk 4.0; +using Adw 1; + +template $MusicusInstrumentEditor: Adw.NavigationPage { + title: _("Instrument"); + + Adw.ToolbarView { + [top] + Adw.HeaderBar header_bar {} + + Adw.Clamp { + Gtk.Box { + orientation: vertical; + + Gtk.Label { + label: _("Name"); + xalign: 0; + margin-top: 24; + + styles [ + "heading" + ] + } + + $MusicusTranslationEditor name_editor { + margin-top: 12; + } + + Gtk.Button save_button { + margin-top: 24; + label: _("Create instrument"); + clicked => $save() swapped; + + styles [ + "card", + "save" + ] + } + } + } + } +} diff --git a/data/ui/library_manager.blp b/data/ui/library_manager.blp index 2672a02..f43c697 100644 --- a/data/ui/library_manager.blp +++ b/data/ui/library_manager.blp @@ -8,5 +8,50 @@ template $MusicusLibraryManager : Adw.NavigationPage { Adw.ToolbarView { [top] Adw.HeaderBar {} + + Gtk.Box { + orientation: vertical; + spacing: 12; + + Gtk.Button { + label: _("Add person"); + clicked => $add_person() swapped; + } + + Gtk.Button { + label: _("Add role"); + clicked => $add_role() swapped; + } + + Gtk.Button { + label: _("Add instrument"); + clicked => $add_instrument() swapped; + } + + Gtk.Button { + label: _("Add work"); + clicked => $add_work() swapped; + } + + Gtk.Button { + label: _("Add ensemble"); + clicked => $add_ensemble() swapped; + } + + Gtk.Button { + label: _("Add recording"); + clicked => $add_recording() swapped; + } + + Gtk.Button { + label: _("Add album"); + clicked => $add_album() swapped; + } + + Gtk.Button { + label: _("Add medium"); + clicked => $add_medium() swapped; + } + } } } \ No newline at end of file diff --git a/data/ui/performer_role_selector_popover.blp b/data/ui/performer_role_selector_popover.blp new file mode 100644 index 0000000..ca2cc69 --- /dev/null +++ b/data/ui/performer_role_selector_popover.blp @@ -0,0 +1,94 @@ +using Gtk 4.0; +using Adw 1; + +template $MusicusPerformerRoleSelectorPopover: Gtk.Popover { + styles [ + "selector" + ] + + Gtk.Stack stack { + transition-type: slide_left_right; + + Adw.ToolbarView role_view { + [top] + Gtk.SearchEntry role_search_entry { + placeholder-text: _("Search roles…"); + margin-start: 8; + margin-end: 8; + margin-top: 8; + margin-bottom: 6; + search-changed => $role_search_changed() swapped; + activate => $role_activate() swapped; + stop-search => $stop_search() swapped; + } + + Gtk.ScrolledWindow role_scrolled_window { + height-request: 200; + + Gtk.ListBox role_list { + styles [ + "selector-list" + ] + + selection-mode: none; + activate-on-single-click: true; + } + } + } + + Adw.ToolbarView instrument_view { + [top] + Gtk.Box { + margin-start: 8; + margin-end: 8; + margin-top: 8; + margin-bottom: 6; + orientation: vertical; + + Gtk.CenterBox { + [start] + Gtk.Button { + styles [ + "flat" + ] + + icon-name: "go-previous-symbolic"; + clicked => $back_button_clicked() swapped; + } + + [center] + Gtk.Label { + styles [ + "heading" + ] + + label: _("Performer"); + ellipsize: end; + margin-start: 6; + } + } + + Gtk.SearchEntry instrument_search_entry { + placeholder-text: _("Search instruments…"); + margin-top: 6; + search-changed => $instrument_search_changed() swapped; + activate => $instrument_activate() swapped; + stop-search => $stop_search() swapped; + } + } + + Gtk.ScrolledWindow instrument_scrolled_window { + height-request: 200; + + Gtk.ListBox instrument_list { + styles [ + "selector-list" + ] + + selection-mode: none; + activate-on-single-click: true; + } + } + } + } +} diff --git a/data/ui/recording_editor.blp b/data/ui/recording_editor.blp new file mode 100644 index 0000000..ce943f4 --- /dev/null +++ b/data/ui/recording_editor.blp @@ -0,0 +1,141 @@ +using Gtk 4.0; +using Adw 1; + +template $MusicusRecordingEditor: Adw.NavigationPage { + title: _("Recording"); + + Adw.ToolbarView { + [top] + Adw.HeaderBar header_bar {} + + Gtk.ScrolledWindow { + Adw.Clamp { + Gtk.Box { + orientation: vertical; + margin-bottom: 24; + margin-start: 12; + margin-end: 12; + + Gtk.Label { + label: _("Recording"); + xalign: 0; + margin-top: 24; + + styles [ + "heading" + ] + } + + Gtk.ListBox { + selection-mode: none; + margin-top: 12; + + styles [ + "boxed-list" + ] + + Adw.ActionRow work_row { + title: _("Select work"); + activatable: true; + activated => $select_work() swapped; + + [prefix] + Gtk.Box select_work_box { + Gtk.Image { + icon-name: "document-edit-symbolic"; + } + } + } + + Adw.SpinRow year_row { + title: _("Year"); + + adjustment: Gtk.Adjustment { + lower: 0; + upper: 3000; + value: 2000; + step-increment: 1; + page-increment: 10; + }; + } + } + + Gtk.Label { + label: _("Performers"); + xalign: 0; + margin-top: 24; + + styles [ + "heading" + ] + } + + Gtk.ListBox performer_list { + selection-mode: none; + margin-top: 12; + + styles [ + "boxed-list" + ] + + Adw.ActionRow { + title: _("Add performer"); + activatable: true; + activated => $select_person() swapped; + + [prefix] + Gtk.Box select_person_box { + Gtk.Image { + icon-name: "list-add-symbolic"; + } + } + } + } + + Gtk.Label { + label: _("Ensembles"); + xalign: 0; + margin-top: 24; + + styles [ + "heading" + ] + } + + Gtk.ListBox ensemble_list { + selection-mode: none; + margin-top: 12; + + styles [ + "boxed-list" + ] + + Adw.ActionRow { + title: _("Add ensemble"); + activatable: true; + activated => $select_ensemble() swapped; + + [prefix] + Gtk.Box select_ensemble_box { + Gtk.Image { + icon-name: "list-add-symbolic"; + } + } + } + } + + Gtk.Button save_button { + margin-top: 24; + label: _("Create recording"); + clicked => $save() swapped; + + styles [ + "card", + "save" + ] + } + } + } + } + } +} diff --git a/data/ui/recording_editor_ensemble_row.blp b/data/ui/recording_editor_ensemble_row.blp new file mode 100644 index 0000000..ff9e49d --- /dev/null +++ b/data/ui/recording_editor_ensemble_row.blp @@ -0,0 +1,33 @@ +using Gtk 4.0; +using Adw 1; + +template $MusicusRecordingEditorEnsembleRow: Adw.ActionRow { + Gtk.Button { + icon-name: "user-trash-symbolic"; + valign: center; + clicked => $remove() swapped; + + styles [ + "flat" + ] + } + + Gtk.Button { + valign: center; + clicked => $open_role_popover() swapped; + + styles [ + "flat" + ] + + Gtk.Box role_box { + spacing: 6; + + Gtk.Label role_label {} + + Gtk.Image { + icon-name: "pan-down-symbolic"; + } + } + } +} diff --git a/data/ui/recording_editor_performer_row.blp b/data/ui/recording_editor_performer_row.blp new file mode 100644 index 0000000..559d16b --- /dev/null +++ b/data/ui/recording_editor_performer_row.blp @@ -0,0 +1,33 @@ +using Gtk 4.0; +using Adw 1; + +template $MusicusRecordingEditorPerformerRow: Adw.ActionRow { + Gtk.Button { + icon-name: "user-trash-symbolic"; + valign: center; + clicked => $remove() swapped; + + styles [ + "flat" + ] + } + + Gtk.Button { + valign: center; + clicked => $open_role_popover() swapped; + + styles [ + "flat" + ] + + Gtk.Box role_box { + spacing: 6; + + Gtk.Label role_label {} + + Gtk.Image { + icon-name: "pan-down-symbolic"; + } + } + } +} diff --git a/data/ui/recording_tile.blp b/data/ui/recording_tile.blp index 00e3a76..6e76183 100644 --- a/data/ui/recording_tile.blp +++ b/data/ui/recording_tile.blp @@ -1,8 +1,11 @@ using Gtk 4.0; -using Adw 1; -template $MusicusRecordingTile : Gtk.FlowBoxChild { - styles ["card", "activatable", "tile"] +template $MusicusRecordingTile: Gtk.FlowBoxChild { + styles [ + "card", + "activatable", + "tile" + ] Gtk.Box { spacing: 12; @@ -16,31 +19,52 @@ template $MusicusRecordingTile : Gtk.FlowBoxChild { Gtk.Box { orientation: vertical; hexpand: true; - + Gtk.Label work_label { - styles ["work"] + styles [ + "work" + ] + halign: start; wrap: true; } - + Gtk.Label composer_label { - styles ["composer"] + styles [ + "composer" + ] + halign: start; wrap: true; } Gtk.Label performances_label { - styles ["performances", "dim-label"] + styles [ + "performances", + "dim-label" + ] + halign: start; wrap: true; } } - Gtk.Button { - styles ["flat"] + Gtk.MenuButton { + styles [ + "flat" + ] + valign: start; margin-top: 12; icon-name: "view-more-symbolic"; + + popover: Gtk.PopoverMenu { + menu-model: edit_menu; + }; } } -} \ No newline at end of file +} + +menu edit_menu { + item (_("Edit recording"), "recording.edit") +} diff --git a/data/ui/work_editor.blp b/data/ui/work_editor.blp index 1e6c463..34d6110 100644 --- a/data/ui/work_editor.blp +++ b/data/ui/work_editor.blp @@ -124,6 +124,17 @@ template $MusicusWorkEditor: Adw.NavigationPage { } } } + + Gtk.Button save_button { + margin-top: 24; + label: _("Create work"); + clicked => $save() swapped; + + styles [ + "card", + "save" + ] + } } } } diff --git a/data/ui/work_selector_popover.blp b/data/ui/work_selector_popover.blp new file mode 100644 index 0000000..445fa7f --- /dev/null +++ b/data/ui/work_selector_popover.blp @@ -0,0 +1,93 @@ +using Gtk 4.0; +using Adw 1; + +template $MusicusWorkSelectorPopover: Gtk.Popover { + styles [ + "selector" + ] + + Gtk.Stack stack { + transition-type: slide_left_right; + + Adw.ToolbarView composer_view { + [top] + Gtk.SearchEntry composer_search_entry { + placeholder-text: _("Search composers…"); + margin-start: 8; + margin-end: 8; + margin-top: 8; + margin-bottom: 6; + search-changed => $composer_search_changed() swapped; + activate => $composer_activate() swapped; + stop-search => $stop_search() swapped; + } + + Gtk.ScrolledWindow composer_scrolled_window { + height-request: 200; + + Gtk.ListBox composer_list { + styles [ + "selector-list" + ] + + selection-mode: none; + activate-on-single-click: true; + } + } + } + + Adw.ToolbarView work_view { + [top] + Gtk.Box { + margin-start: 8; + margin-end: 8; + margin-top: 8; + margin-bottom: 6; + orientation: vertical; + + Gtk.CenterBox { + [start] + Gtk.Button { + styles [ + "flat" + ] + + icon-name: "go-previous-symbolic"; + clicked => $back_button_clicked() swapped; + } + + [center] + Gtk.Label composer_label { + styles [ + "heading" + ] + + ellipsize: end; + margin-start: 6; + } + } + + Gtk.SearchEntry work_search_entry { + placeholder-text: _("Search works…"); + margin-top: 6; + search-changed => $work_search_changed() swapped; + activate => $work_activate() swapped; + stop-search => $stop_search() swapped; + } + } + + Gtk.ScrolledWindow work_scrolled_window { + height-request: 200; + + Gtk.ListBox work_list { + styles [ + "selector-list" + ] + + selection-mode: none; + activate-on-single-click: true; + } + } + } + } +} diff --git a/src/db/models.rs b/src/db/models.rs index 5c7680f..54b0a78 100644 --- a/src/db/models.rs +++ b/src/db/models.rs @@ -1,17 +1,19 @@ //! This module contains higher-level models combining information from //! multiple database tables. -use std::{fmt::Display, path::Path}; +use std::fmt::Display; use anyhow::Result; use diesel::prelude::*; +use gtk::glib::{self, Boxed}; use super::{schema::*, tables, TranslatedString}; // Re-exports for tables that don't need additional information. pub use tables::{Album, Instrument, Person, Role}; -#[derive(Clone, Debug)] +#[derive(Boxed, Clone, Debug)] +#[boxed_type(name = "MusicusWork")] pub struct Work { pub work_id: String, pub name: TranslatedString, @@ -36,21 +38,22 @@ pub struct Composer { pub role: Role, } -#[derive(Clone, Debug)] +#[derive(Boxed, Clone, Debug)] +#[boxed_type(name = "MusicusEnsemble")] pub struct Ensemble { pub ensemble_id: String, pub name: TranslatedString, pub persons: Vec<(Person, Instrument)>, } -#[derive(Clone, Debug)] +#[derive(Boxed, Clone, Debug)] +#[boxed_type(name = "MusicusRecording")] pub struct Recording { pub recording_id: String, pub work: Work, pub year: Option, pub persons: Vec, - pub ensembles: Vec, - pub tracks: Vec, + pub ensembles: Vec, } #[derive(Clone, Debug)] @@ -60,6 +63,12 @@ pub struct Performer { pub instrument: Option, } +#[derive(Clone, Debug)] +pub struct EnsemblePerformer { + pub ensemble: Ensemble, + pub role: Role, +} + #[derive(Clone, Debug)] pub struct Track { pub track_id: String, @@ -229,11 +238,7 @@ impl Display for Ensemble { } impl Recording { - pub fn from_table( - data: tables::Recording, - library_path: &str, - connection: &mut SqliteConnection, - ) -> Result { + pub fn from_table(data: tables::Recording, connection: &mut SqliteConnection) -> Result { let work = Work::from_table( works::table .filter(works::work_id.eq(&data.work_id)) @@ -249,24 +254,15 @@ impl Recording { .map(|r| Performer::from_table(r, connection)) .collect::>>()?; - let ensembles: Vec = ensembles::table + let ensembles = ensembles::table .inner_join(recording_ensembles::table) .order(recording_ensembles::sequence_number) .filter(recording_ensembles::recording_id.eq(&data.recording_id)) - .select(tables::Ensemble::as_select()) - .load::(connection)? + .select(tables::RecordingEnsemble::as_select()) + .load::(connection)? .into_iter() - .map(|e| Ensemble::from_table(e, connection)) - .collect::>>()?; - - let tracks: Vec = tracks::table - .order(tracks::recording_index) - .filter(tracks::recording_id.eq(&data.recording_id)) - .select(tables::Track::as_select()) - .load::(connection)? - .into_iter() - .map(|t| Track::from_table(t, library_path, connection)) - .collect::>>()?; + .map(|e| EnsemblePerformer::from_table(e, connection)) + .collect::>>()?; Ok(Self { recording_id: data.recording_id, @@ -274,7 +270,6 @@ impl Recording { year: data.year, persons, ensembles, - tracks, }) } @@ -289,7 +284,7 @@ impl Recording { &mut self .ensembles .iter() - .map(|e| e.name.get().to_string()) + .map(ToString::to_string) .collect::>(), ); @@ -344,12 +339,33 @@ impl Display for Performer { } } -impl Track { +impl EnsemblePerformer { pub fn from_table( - data: tables::Track, - library_path: &str, + data: tables::RecordingEnsemble, connection: &mut SqliteConnection, ) -> Result { + let ensemble_data = ensembles::table + .filter(ensembles::ensemble_id.eq(&data.ensemble_id)) + .first::(connection)?; + + let ensemble = Ensemble::from_table(ensemble_data, connection)?; + + let role: Role = roles::table + .filter(roles::role_id.eq(&data.role_id)) + .first(connection)?; + + Ok(Self { ensemble, role }) + } +} + +impl Display for EnsemblePerformer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.ensemble.name.get().fmt(f) + } +} + +impl Track { + pub fn from_table(data: tables::Track, connection: &mut SqliteConnection) -> Result { let works: Vec = works::table .inner_join(track_works::table) .order(track_works::sequence_number) @@ -362,11 +378,7 @@ impl Track { Ok(Self { track_id: data.track_id, - path: Path::new(library_path) - .join(&data.path) - .to_str() - .unwrap() - .to_string(), + path: data.path, works, }) } @@ -377,4 +389,4 @@ impl PartialEq for Album { fn eq(&self, other: &Self) -> bool { self.album_id == other.album_id } -} \ No newline at end of file +} diff --git a/src/db/tables.rs b/src/db/tables.rs index 5421685..a9d6477 100644 --- a/src/db/tables.rs +++ b/src/db/tables.rs @@ -32,7 +32,7 @@ pub struct Role { } #[derive(Boxed, Insertable, Queryable, Selectable, Clone, Debug)] -#[boxed_type(name = "MusicusInstrument")] +#[boxed_type(name = "MusicusInstrument", nullable)] #[diesel(check_for_backend(Sqlite))] pub struct Instrument { pub instrument_id: String, @@ -183,4 +183,4 @@ pub struct AlbumMedium { pub album_id: String, pub medium_id: String, pub sequence_number: i32, -} \ No newline at end of file +} diff --git a/src/editor/ensemble_editor.rs b/src/editor/ensemble_editor.rs new file mode 100644 index 0000000..44332bc --- /dev/null +++ b/src/editor/ensemble_editor.rs @@ -0,0 +1,112 @@ +use std::cell::OnceCell; + +use adw::{prelude::*, subclass::prelude::*}; +use gettextrs::gettext; +use gtk::glib::{self, subclass::Signal}; +use once_cell::sync::Lazy; + +use crate::{ + db::models::Ensemble, editor::translation_editor::MusicusTranslationEditor, + library::MusicusLibrary, +}; + +mod imp { + + use super::*; + + #[derive(Debug, Default, gtk::CompositeTemplate)] + #[template(file = "data/ui/ensemble_editor.blp")] + pub struct MusicusEnsembleEditor { + pub navigation: OnceCell, + pub library: OnceCell, + pub ensemble_id: OnceCell, + + #[template_child] + pub name_editor: TemplateChild, + #[template_child] + pub save_button: TemplateChild, + } + + #[glib::object_subclass] + impl ObjectSubclass for MusicusEnsembleEditor { + const NAME: &'static str = "MusicusEnsembleEditor"; + type Type = super::MusicusEnsembleEditor; + type ParentType = adw::NavigationPage; + + fn class_init(klass: &mut Self::Class) { + MusicusTranslationEditor::static_type(); + klass.bind_template(); + klass.bind_template_instance_callbacks(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } + } + + impl ObjectImpl for MusicusEnsembleEditor { + fn signals() -> &'static [Signal] { + static SIGNALS: Lazy> = Lazy::new(|| { + vec![Signal::builder("created") + .param_types([Ensemble::static_type()]) + .build()] + }); + + SIGNALS.as_ref() + } + } + + impl WidgetImpl for MusicusEnsembleEditor {} + impl NavigationPageImpl for MusicusEnsembleEditor {} +} + +glib::wrapper! { + pub struct MusicusEnsembleEditor(ObjectSubclass) + @extends gtk::Widget, adw::NavigationPage; +} + +#[gtk::template_callbacks] +impl MusicusEnsembleEditor { + pub fn new( + navigation: &adw::NavigationView, + library: &MusicusLibrary, + ensemble: Option<&Ensemble>, + ) -> Self { + let obj: Self = glib::Object::new(); + + obj.imp().navigation.set(navigation.to_owned()).unwrap(); + obj.imp().library.set(library.to_owned()).unwrap(); + + if let Some(ensemble) = ensemble { + obj.imp().save_button.set_label(&gettext("Save changes")); + obj.imp().ensemble_id.set(ensemble.ensemble_id.clone()).unwrap(); + obj.imp().name_editor.set_translation(&ensemble.name); + } + + obj + } + + pub fn connect_created(&self, f: F) -> glib::SignalHandlerId { + self.connect_local("created", true, move |values| { + let obj = values[0].get::().unwrap(); + let ensemble = values[1].get::().unwrap(); + f(&obj, ensemble); + None + }) + } + + #[template_callback] + fn save(&self, _: >k::Button) { + let library = self.imp().library.get().unwrap(); + let name = self.imp().name_editor.translation(); + + if let Some(ensemble_id) = self.imp().ensemble_id.get() { + library.update_ensemble(ensemble_id, name).unwrap(); + } else { + let ensemble = library.create_ensemble(name).unwrap(); + self.emit_by_name::<()>("created", &[&ensemble]); + } + + self.imp().navigation.get().unwrap().pop(); + } +} diff --git a/src/editor/ensemble_selector_popover.rs b/src/editor/ensemble_selector_popover.rs new file mode 100644 index 0000000..bf3a90e --- /dev/null +++ b/src/editor/ensemble_selector_popover.rs @@ -0,0 +1,204 @@ +use crate::{db::models::Ensemble, library::MusicusLibrary}; + +use gettextrs::gettext; +use gtk::{ + glib::{self, subclass::Signal, Properties}, + prelude::*, + subclass::prelude::*, +}; +use once_cell::sync::Lazy; + +use std::cell::{OnceCell, RefCell}; + +use super::activatable_row::MusicusActivatableRow; + +mod imp { + use super::*; + + #[derive(Debug, Default, gtk::CompositeTemplate, Properties)] + #[properties(wrapper_type = super::MusicusEnsembleSelectorPopover)] + #[template(file = "data/ui/ensemble_selector_popover.blp")] + pub struct MusicusEnsembleSelectorPopover { + #[property(get, construct_only)] + pub library: OnceCell, + + pub ensembles: RefCell>, + + #[template_child] + pub search_entry: TemplateChild, + #[template_child] + pub scrolled_window: TemplateChild, + #[template_child] + pub list_box: TemplateChild, + } + + #[glib::object_subclass] + impl ObjectSubclass for MusicusEnsembleSelectorPopover { + const NAME: &'static str = "MusicusEnsembleSelectorPopover"; + type Type = super::MusicusEnsembleSelectorPopover; + type ParentType = gtk::Popover; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + klass.bind_template_instance_callbacks(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } + } + + #[glib::derived_properties] + impl ObjectImpl for MusicusEnsembleSelectorPopover { + fn constructed(&self) { + self.parent_constructed(); + + self.obj() + .connect_visible_notify(|obj: &super::MusicusEnsembleSelectorPopover| { + if obj.is_visible() { + obj.imp().search_entry.set_text(""); + obj.imp().search_entry.grab_focus(); + obj.imp().scrolled_window.vadjustment().set_value(0.0); + } + }); + + self.obj().search(""); + } + + fn signals() -> &'static [Signal] { + static SIGNALS: Lazy> = Lazy::new(|| { + vec![ + Signal::builder("ensemble-selected") + .param_types([Ensemble::static_type()]) + .build(), + Signal::builder("create").build(), + ] + }); + + SIGNALS.as_ref() + } + } + + impl WidgetImpl for MusicusEnsembleSelectorPopover { + // TODO: Fix focus. + fn focus(&self, direction_type: gtk::DirectionType) -> bool { + if direction_type == gtk::DirectionType::Down { + self.list_box.child_focus(direction_type) + } else { + self.parent_focus(direction_type) + } + } + } + + impl PopoverImpl for MusicusEnsembleSelectorPopover {} +} + +glib::wrapper! { + pub struct MusicusEnsembleSelectorPopover(ObjectSubclass) + @extends gtk::Widget, gtk::Popover; +} + +#[gtk::template_callbacks] +impl MusicusEnsembleSelectorPopover { + pub fn new(library: &MusicusLibrary) -> Self { + glib::Object::builder().property("library", library).build() + } + + pub fn connect_ensemble_selected( + &self, + f: F, + ) -> glib::SignalHandlerId { + self.connect_local("ensemble-selected", true, move |values| { + let obj = values[0].get::().unwrap(); + let ensemble = values[1].get::().unwrap(); + f(&obj, ensemble); + None + }) + } + + pub fn connect_create(&self, f: F) -> glib::SignalHandlerId { + self.connect_local("create", true, move |values| { + let obj = values[0].get::().unwrap(); + f(&obj); + None + }) + } + + #[template_callback] + fn search_changed(&self, entry: >k::SearchEntry) { + self.search(&entry.text()); + } + + #[template_callback] + fn activate(&self, _: >k::SearchEntry) { + if let Some(ensemble) = self.imp().ensembles.borrow().first() { + self.select(ensemble.clone()); + } else { + self.create(); + } + } + + #[template_callback] + fn stop_search(&self, _: >k::SearchEntry) { + self.popdown(); + } + + fn search(&self, search: &str) { + let imp = self.imp(); + + let ensembles = imp + .library + .get() + .unwrap() + .search_ensembles(search) + .unwrap(); + + imp.list_box.remove_all(); + + for ensemble in &ensembles { + let row = MusicusActivatableRow::new( + >k::Label::builder() + .label(ensemble.to_string()) + .halign(gtk::Align::Start) + .build(), + ); + + let ensemble = ensemble.clone(); + let obj = self.clone(); + row.connect_activated(move |_: &MusicusActivatableRow| { + obj.select(ensemble.clone()); + }); + + imp.list_box.append(&row); + } + + let create_box = gtk::Box::builder().spacing(12).build(); + create_box.append(>k::Image::builder().icon_name("list-add-symbolic").build()); + create_box.append( + >k::Label::builder() + .label(gettext("Create new ensemble")) + .halign(gtk::Align::Start) + .build(), + ); + + let create_row = MusicusActivatableRow::new(&create_box); + let obj = self.clone(); + create_row.connect_activated(move |_: &MusicusActivatableRow| { + obj.create(); + }); + + imp.list_box.append(&create_row); + + imp.ensembles.replace(ensembles); + } + + fn select(&self, ensemble: Ensemble) { + self.emit_by_name::<()>("ensemble-selected", &[&ensemble]); + self.popdown(); + } + + fn create(&self) { + self.emit_by_name::<()>("create", &[]); + self.popdown(); + } +} diff --git a/src/editor/instrument_editor.rs b/src/editor/instrument_editor.rs new file mode 100644 index 0000000..59e6265 --- /dev/null +++ b/src/editor/instrument_editor.rs @@ -0,0 +1,112 @@ +use std::cell::OnceCell; + +use adw::{prelude::*, subclass::prelude::*}; +use gettextrs::gettext; +use gtk::glib::{self, subclass::Signal}; +use once_cell::sync::Lazy; + +use crate::{ + db::models::Instrument, editor::translation_editor::MusicusTranslationEditor, + library::MusicusLibrary, +}; + +mod imp { + + use super::*; + + #[derive(Debug, Default, gtk::CompositeTemplate)] + #[template(file = "data/ui/instrument_editor.blp")] + pub struct MusicusInstrumentEditor { + pub navigation: OnceCell, + pub library: OnceCell, + pub instrument_id: OnceCell, + + #[template_child] + pub name_editor: TemplateChild, + #[template_child] + pub save_button: TemplateChild, + } + + #[glib::object_subclass] + impl ObjectSubclass for MusicusInstrumentEditor { + const NAME: &'static str = "MusicusInstrumentEditor"; + type Type = super::MusicusInstrumentEditor; + type ParentType = adw::NavigationPage; + + fn class_init(klass: &mut Self::Class) { + MusicusTranslationEditor::static_type(); + klass.bind_template(); + klass.bind_template_instance_callbacks(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } + } + + impl ObjectImpl for MusicusInstrumentEditor { + fn signals() -> &'static [Signal] { + static SIGNALS: Lazy> = Lazy::new(|| { + vec![Signal::builder("created") + .param_types([Instrument::static_type()]) + .build()] + }); + + SIGNALS.as_ref() + } + } + + impl WidgetImpl for MusicusInstrumentEditor {} + impl NavigationPageImpl for MusicusInstrumentEditor {} +} + +glib::wrapper! { + pub struct MusicusInstrumentEditor(ObjectSubclass) + @extends gtk::Widget, adw::NavigationPage; +} + +#[gtk::template_callbacks] +impl MusicusInstrumentEditor { + pub fn new( + navigation: &adw::NavigationView, + library: &MusicusLibrary, + instrument: Option<&Instrument>, + ) -> Self { + let obj: Self = glib::Object::new(); + + obj.imp().navigation.set(navigation.to_owned()).unwrap(); + obj.imp().library.set(library.to_owned()).unwrap(); + + if let Some(instrument) = instrument { + obj.imp().save_button.set_label(&gettext("Save changes")); + obj.imp().instrument_id.set(instrument.instrument_id.clone()).unwrap(); + obj.imp().name_editor.set_translation(&instrument.name); + } + + obj + } + + pub fn connect_created(&self, f: F) -> glib::SignalHandlerId { + self.connect_local("created", true, move |values| { + let obj = values[0].get::().unwrap(); + let instrument = values[1].get::().unwrap(); + f(&obj, instrument); + None + }) + } + + #[template_callback] + fn save(&self, _: >k::Button) { + let library = self.imp().library.get().unwrap(); + let name = self.imp().name_editor.translation(); + + if let Some(instrument_id) = self.imp().instrument_id.get() { + library.update_instrument(instrument_id, name).unwrap(); + } else { + let instrument = library.create_instrument(name).unwrap(); + self.emit_by_name::<()>("created", &[&instrument]); + } + + self.imp().navigation.get().unwrap().pop(); + } +} diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 124637b..8eb1c32 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -1,10 +1,18 @@ pub mod activatable_row; +pub mod ensemble_editor; +pub mod ensemble_selector_popover; +pub mod instrument_editor; pub mod instrument_selector_popover; +pub mod performer_role_selector_popover; pub mod person_editor; pub mod person_selector_popover; +pub mod recording_editor; +pub mod recording_editor_ensemble_row; +pub mod recording_editor_performer_row; pub mod role_editor; pub mod role_selector_popover; pub mod translation_editor; pub mod translation_entry; pub mod work_editor; -pub mod work_editor_composer_row; \ No newline at end of file +pub mod work_editor_composer_row; +pub mod work_selector_popover; diff --git a/src/editor/performer_role_selector_popover.rs b/src/editor/performer_role_selector_popover.rs new file mode 100644 index 0000000..c4752a0 --- /dev/null +++ b/src/editor/performer_role_selector_popover.rs @@ -0,0 +1,328 @@ +use crate::{ + db::models::{Instrument, Role}, + library::MusicusLibrary, +}; + +use gettextrs::gettext; +use gtk::{ + glib::{self, subclass::Signal, Properties}, + pango, + prelude::*, + subclass::prelude::*, +}; +use once_cell::sync::Lazy; + +use std::cell::{OnceCell, RefCell}; + +use super::activatable_row::MusicusActivatableRow; + +mod imp { + use super::*; + + #[derive(Debug, Default, gtk::CompositeTemplate, Properties)] + #[properties(wrapper_type = super::MusicusPerformerRoleSelectorPopover)] + #[template(file = "data/ui/performer_role_selector_popover.blp")] + pub struct MusicusPerformerRoleSelectorPopover { + #[property(get, construct_only)] + pub library: OnceCell, + + pub roles: RefCell>, + pub instruments: RefCell>, + + #[template_child] + pub stack: TemplateChild, + #[template_child] + pub role_view: TemplateChild, + #[template_child] + pub role_search_entry: TemplateChild, + #[template_child] + pub role_scrolled_window: TemplateChild, + #[template_child] + pub role_list: TemplateChild, + #[template_child] + pub instrument_view: TemplateChild, + #[template_child] + pub instrument_search_entry: TemplateChild, + #[template_child] + pub instrument_scrolled_window: TemplateChild, + #[template_child] + pub instrument_list: TemplateChild, + } + + #[glib::object_subclass] + impl ObjectSubclass for MusicusPerformerRoleSelectorPopover { + const NAME: &'static str = "MusicusPerformerRoleSelectorPopover"; + type Type = super::MusicusPerformerRoleSelectorPopover; + type ParentType = gtk::Popover; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + klass.bind_template_instance_callbacks(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } + } + + #[glib::derived_properties] + impl ObjectImpl for MusicusPerformerRoleSelectorPopover { + fn constructed(&self) { + self.parent_constructed(); + + self.obj().connect_visible_notify( + |obj: &super::MusicusPerformerRoleSelectorPopover| { + if obj.is_visible() { + obj.imp().stack.set_visible_child(&*obj.imp().role_view); + obj.imp().role_search_entry.set_text(""); + obj.imp().role_search_entry.grab_focus(); + obj.imp().role_scrolled_window.vadjustment().set_value(0.0); + } + }, + ); + + self.obj().search_roles(""); + } + + fn signals() -> &'static [Signal] { + static SIGNALS: Lazy> = Lazy::new(|| { + vec![ + Signal::builder("selected") + .param_types([Role::static_type(), Instrument::static_type()]) + .build(), + Signal::builder("create-role").build(), + Signal::builder("create-instrument").build(), + ] + }); + + SIGNALS.as_ref() + } + } + + impl WidgetImpl for MusicusPerformerRoleSelectorPopover { + // TODO: Fix focus. + fn focus(&self, direction_type: gtk::DirectionType) -> bool { + if direction_type == gtk::DirectionType::Down { + if self.stack.visible_child() == Some(self.role_list.get().upcast()) { + self.role_list.child_focus(direction_type) + } else { + self.instrument_list.child_focus(direction_type) + } + } else { + self.parent_focus(direction_type) + } + } + } + + impl PopoverImpl for MusicusPerformerRoleSelectorPopover {} +} + +glib::wrapper! { + pub struct MusicusPerformerRoleSelectorPopover(ObjectSubclass) + @extends gtk::Widget, gtk::Popover; +} + +#[gtk::template_callbacks] +impl MusicusPerformerRoleSelectorPopover { + pub fn new(library: &MusicusLibrary) -> Self { + glib::Object::builder().property("library", library).build() + } + + pub fn connect_selected) + 'static>( + &self, + f: F, + ) -> glib::SignalHandlerId { + self.connect_local("selected", true, move |values| { + let obj = values[0].get::().unwrap(); + let role = values[1].get::().unwrap(); + let instrument = values[2].get::>().unwrap(); + f(&obj, role, instrument); + None + }) + } + + pub fn connect_create_role(&self, f: F) -> glib::SignalHandlerId { + self.connect_local("create-role", true, move |values| { + let obj = values[0].get::().unwrap(); + f(&obj); + None + }) + } + + pub fn connect_create_instrument(&self, f: F) -> glib::SignalHandlerId { + self.connect_local("create-instrument", true, move |values| { + let obj = values[0].get::().unwrap(); + f(&obj); + None + }) + } + + #[template_callback] + fn role_search_changed(&self, entry: >k::SearchEntry) { + self.search_roles(&entry.text()); + } + + #[template_callback] + fn role_activate(&self, _: >k::SearchEntry) { + if let Some(role) = self.imp().roles.borrow().first() { + self.select_role(role.to_owned()); + } else { + self.create_role(); + } + } + + #[template_callback] + fn back_button_clicked(&self, _: >k::Button) { + self.imp().stack.set_visible_child(&*self.imp().role_view); + self.imp().role_search_entry.grab_focus(); + } + + #[template_callback] + fn instrument_search_changed(&self, entry: >k::SearchEntry) { + self.search_instruments(&entry.text()); + } + + #[template_callback] + fn instrument_activate(&self, _: >k::SearchEntry) { + if let Some(instrument) = self.imp().instruments.borrow().first() { + self.select_instrument(instrument.clone()); + } else { + self.create_instrument(); + } + } + + #[template_callback] + fn stop_search(&self, _: >k::SearchEntry) { + self.popdown(); + } + + fn search_roles(&self, search: &str) { + let imp = self.imp(); + + let roles = imp.library.get().unwrap().search_roles(search).unwrap(); + + imp.role_list.remove_all(); + + for role in &roles { + let row = MusicusActivatableRow::new( + >k::Label::builder() + .label(role.to_string()) + .halign(gtk::Align::Start) + .ellipsize(pango::EllipsizeMode::Middle) + .build(), + ); + + let role = role.clone(); + let obj = self.clone(); + row.connect_activated(move |_: &MusicusActivatableRow| { + obj.select_role(role.clone()); + }); + + imp.role_list.append(&row); + } + + let create_box = gtk::Box::builder().spacing(12).build(); + create_box.append(>k::Image::builder().icon_name("list-add-symbolic").build()); + create_box.append( + >k::Label::builder() + .label(gettext("Create new role")) + .halign(gtk::Align::Start) + .build(), + ); + + let create_row = MusicusActivatableRow::new(&create_box); + let obj = self.clone(); + create_row.connect_activated(move |_: &MusicusActivatableRow| { + obj.create_role(); + }); + + imp.role_list.append(&create_row); + + imp.roles.replace(roles); + } + + fn search_instruments(&self, search: &str) { + let imp = self.imp(); + + let instruments = imp + .library + .get() + .unwrap() + .search_instruments(search) + .unwrap(); + + imp.instrument_list.remove_all(); + + for instrument in &instruments { + let row = MusicusActivatableRow::new( + >k::Label::builder() + .label(instrument.name.get()) + .halign(gtk::Align::Start) + .ellipsize(pango::EllipsizeMode::Middle) + .build(), + ); + + let instrument = instrument.clone(); + let obj = self.clone(); + row.connect_activated(move |_: &MusicusActivatableRow| { + obj.select_instrument(instrument.clone()); + }); + + imp.instrument_list.append(&row); + } + + let create_box = gtk::Box::builder().spacing(12).build(); + create_box.append(>k::Image::builder().icon_name("list-add-symbolic").build()); + create_box.append( + >k::Label::builder() + .label(gettext("Create new instrument")) + .halign(gtk::Align::Start) + .build(), + ); + + let create_row = MusicusActivatableRow::new(&create_box); + let obj = self.clone(); + create_row.connect_activated(move |_: &MusicusActivatableRow| { + obj.create_instrument(); + }); + + imp.instrument_list.append(&create_row); + + imp.instruments.replace(instruments); + } + + fn select_role(&self, role: Role) { + if role == self.library().performer_default_role().unwrap() { + self.imp().instrument_search_entry.set_text(""); + self.imp().instrument_search_entry.grab_focus(); + self.imp() + .instrument_scrolled_window + .vadjustment() + .set_value(0.0); + self.imp() + .stack + .set_visible_child(&*self.imp().instrument_view); + + self.search_instruments(""); + } else { + self.emit_by_name::<()>("selected", &[&role, &None::]); + self.popdown(); + } + } + + fn select_instrument(&self, instrument: Instrument) { + let role = self.library().performer_default_role().unwrap(); + self.emit_by_name::<()>("selected", &[&role, &instrument]); + self.popdown(); + } + + fn create_role(&self) { + self.emit_by_name::<()>("create-role", &[]); + self.popdown(); + } + + fn create_instrument(&self) { + self.emit_by_name::<()>("create-instrument", &[]); + self.popdown(); + } +} diff --git a/src/editor/recording_editor.rs b/src/editor/recording_editor.rs new file mode 100644 index 0000000..75488b6 --- /dev/null +++ b/src/editor/recording_editor.rs @@ -0,0 +1,317 @@ +use std::cell::{OnceCell, RefCell}; + +use adw::{prelude::*, subclass::prelude::*}; +use gettextrs::gettext; +use gtk::glib::{self, clone, subclass::Signal, Properties}; +use once_cell::sync::Lazy; + +use crate::{ + db::models::{Ensemble, EnsemblePerformer, Performer, Person, Recording, Work}, + editor::{ + ensemble_editor::MusicusEnsembleEditor, + ensemble_selector_popover::MusicusEnsembleSelectorPopover, + person_editor::MusicusPersonEditor, person_selector_popover::MusicusPersonSelectorPopover, + recording_editor_ensemble_row::MusicusRecordingEditorEnsembleRow, + recording_editor_performer_row::MusicusRecordingEditorPerformerRow, + work_selector_popover::MusicusWorkSelectorPopover, + }, + library::MusicusLibrary, +}; + +mod imp { + use crate::editor::work_editor::MusicusWorkEditor; + + use super::*; + + #[derive(Debug, Default, gtk::CompositeTemplate, Properties)] + #[properties(wrapper_type = super::MusicusRecordingEditor)] + #[template(file = "data/ui/recording_editor.blp")] + pub struct MusicusRecordingEditor { + #[property(get, construct_only)] + pub navigation: OnceCell, + + #[property(get, construct_only)] + pub library: OnceCell, + + pub recording_id: OnceCell, + + pub work: RefCell>, + pub performer_rows: RefCell>, + pub ensemble_rows: RefCell>, + + pub work_selector_popover: OnceCell, + pub persons_popover: OnceCell, + pub ensembles_popover: OnceCell, + + #[template_child] + pub work_row: TemplateChild, + #[template_child] + pub select_work_box: TemplateChild, + #[template_child] + pub year_row: TemplateChild, + #[template_child] + pub performer_list: TemplateChild, + #[template_child] + pub select_person_box: TemplateChild, + #[template_child] + pub ensemble_list: TemplateChild, + #[template_child] + pub select_ensemble_box: TemplateChild, + #[template_child] + pub save_button: TemplateChild, + } + + #[glib::object_subclass] + impl ObjectSubclass for MusicusRecordingEditor { + const NAME: &'static str = "MusicusRecordingEditor"; + type Type = super::MusicusRecordingEditor; + type ParentType = adw::NavigationPage; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + klass.bind_template_instance_callbacks(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } + } + + #[glib::derived_properties] + impl ObjectImpl for MusicusRecordingEditor { + fn signals() -> &'static [Signal] { + static SIGNALS: Lazy> = Lazy::new(|| { + vec![Signal::builder("created") + .param_types([Recording::static_type()]) + .build()] + }); + + SIGNALS.as_ref() + } + + fn constructed(&self) { + self.parent_constructed(); + + let work_selector_popover = + MusicusWorkSelectorPopover::new(self.library.get().unwrap()); + + let obj = self.obj().clone(); + work_selector_popover.connect_selected(move |_, work| { + obj.set_work(work); + }); + + let obj = self.obj().clone(); + work_selector_popover.connect_create(move |_| { + let editor = MusicusWorkEditor::new(&obj.navigation(), &obj.library(), None); + + editor.connect_created(clone!( + #[weak] + obj, + move |_, work| { + obj.set_work(work); + } + )); + + obj.navigation().push(&editor); + }); + + self.select_work_box.append(&work_selector_popover); + self.work_selector_popover + .set(work_selector_popover) + .unwrap(); + + let persons_popover = MusicusPersonSelectorPopover::new(self.library.get().unwrap()); + + let obj = self.obj().clone(); + persons_popover.connect_person_selected(move |_, person| { + obj.add_performer(person); + }); + + let obj = self.obj().clone(); + persons_popover.connect_create(move |_| { + let editor = MusicusPersonEditor::new(&obj.navigation(), &obj.library(), None); + + editor.connect_created(clone!( + #[weak] + obj, + move |_, person| { + obj.add_performer(person); + } + )); + + obj.navigation().push(&editor); + }); + + self.select_person_box.append(&persons_popover); + self.persons_popover.set(persons_popover).unwrap(); + + let ensembles_popover = + MusicusEnsembleSelectorPopover::new(self.library.get().unwrap()); + + let obj = self.obj().clone(); + ensembles_popover.connect_ensemble_selected(move |_, ensemble| { + obj.add_ensemble(ensemble); + }); + + let obj = self.obj().clone(); + ensembles_popover.connect_create(move |_| { + let editor = MusicusEnsembleEditor::new(&obj.navigation(), &obj.library(), None); + + editor.connect_created(clone!( + #[weak] + obj, + move |_, ensemble| { + obj.add_ensemble(ensemble); + } + )); + + obj.navigation().push(&editor); + }); + + self.select_ensemble_box.append(&ensembles_popover); + self.ensembles_popover.set(ensembles_popover).unwrap(); + } + } + + impl WidgetImpl for MusicusRecordingEditor {} + impl NavigationPageImpl for MusicusRecordingEditor {} +} + +glib::wrapper! { + pub struct MusicusRecordingEditor(ObjectSubclass) + @extends gtk::Widget, adw::NavigationPage; +} + +#[gtk::template_callbacks] +impl MusicusRecordingEditor { + pub fn new( + navigation: &adw::NavigationView, + library: &MusicusLibrary, + recording: Option<&Recording>, + ) -> Self { + let obj: Self = glib::Object::builder() + .property("navigation", navigation) + .property("library", library) + .build(); + + if let Some(recording) = recording { + obj.imp().save_button.set_label(&gettext("Save changes")); + obj.imp() + .recording_id + .set(recording.recording_id.clone()) + .unwrap(); + // TODO: Initialize data. + } + + obj + } + + #[template_callback] + fn select_work(&self, _: &adw::ActionRow) { + self.imp().work_selector_popover.get().unwrap().popup(); + } + + #[template_callback] + fn select_person(&self, _: &adw::ActionRow) { + self.imp().persons_popover.get().unwrap().popup(); + } + + #[template_callback] + fn select_ensemble(&self, _: &adw::ActionRow) { + self.imp().ensembles_popover.get().unwrap().popup(); + } + + fn set_work(&self, work: Work) { + self.imp().work_row.set_title(&work.name.get()); + self.imp().work_row.set_subtitle(&work.composers_string()); + self.imp().work.replace(Some(work)); + } + + fn add_performer(&self, person: Person) { + let role = self.library().performer_default_role().unwrap(); + let performer = Performer { + person, + role, + instrument: None, + }; + + let row = + MusicusRecordingEditorPerformerRow::new(&self.navigation(), &self.library(), performer); + + row.connect_remove(clone!( + #[weak(rename_to = this)] + self, + move |row| { + this.imp().performer_list.remove(row); + this.imp().performer_rows.borrow_mut().retain(|c| c != row); + } + )); + + self.imp() + .performer_list + .insert(&row, self.imp().performer_rows.borrow().len() as i32); + + self.imp().performer_rows.borrow_mut().push(row); + } + + fn add_ensemble(&self, ensemble: Ensemble) { + let role = self.library().performer_default_role().unwrap(); + let performer = EnsemblePerformer { ensemble, role }; + + let row = + MusicusRecordingEditorEnsembleRow::new(&self.navigation(), &self.library(), performer); + + row.connect_remove(clone!( + #[weak(rename_to = this)] + self, + move |row| { + this.imp().ensemble_list.remove(row); + this.imp().ensemble_rows.borrow_mut().retain(|c| c != row); + } + )); + + self.imp() + .ensemble_list + .insert(&row, self.imp().ensemble_rows.borrow().len() as i32); + + self.imp().ensemble_rows.borrow_mut().push(row); + } + + #[template_callback] + fn save(&self, _: >k::Button) { + let library = self.imp().library.get().unwrap(); + + // TODO: No work selected? + let work = self.imp().work.borrow().as_ref().unwrap().clone(); + let year = self.imp().year_row.value() as i32; + + let performers = self + .imp() + .performer_rows + .borrow() + .iter() + .map(|p| p.performer()) + .collect::>(); + + let ensembles = self + .imp() + .ensemble_rows + .borrow() + .iter() + .map(|e| e.ensemble()) + .collect::>(); + + if let Some(recording_id) = self.imp().recording_id.get() { + library + .update_recording(recording_id, work, Some(year), performers, ensembles) + .unwrap(); + } else { + let recording = library + .create_recording(work, Some(year), performers, ensembles) + .unwrap(); + self.emit_by_name::<()>("created", &[&recording]); + } + + self.imp().navigation.get().unwrap().pop(); + } +} diff --git a/src/editor/recording_editor_ensemble_row.rs b/src/editor/recording_editor_ensemble_row.rs new file mode 100644 index 0000000..ea480fa --- /dev/null +++ b/src/editor/recording_editor_ensemble_row.rs @@ -0,0 +1,149 @@ +use std::cell::{OnceCell, RefCell}; + +use adw::{prelude::*, subclass::prelude::*}; +use gtk::glib::{self, clone, subclass::Signal, Properties}; +use once_cell::sync::Lazy; + +use crate::{ + db::models::EnsemblePerformer, + editor::{role_editor::MusicusRoleEditor, role_selector_popover::MusicusRoleSelectorPopover}, + library::MusicusLibrary, +}; + +mod imp { + use super::*; + + #[derive(Properties, Debug, Default, gtk::CompositeTemplate)] + #[properties(wrapper_type = super::MusicusRecordingEditorEnsembleRow)] + #[template(file = "data/ui/recording_editor_ensemble_row.blp")] + pub struct MusicusRecordingEditorEnsembleRow { + #[property(get, construct_only)] + pub navigation: OnceCell, + + #[property(get, construct_only)] + pub library: OnceCell, + + pub ensemble: RefCell>, + pub role_popover: OnceCell, + + #[template_child] + pub role_label: TemplateChild, + #[template_child] + pub role_box: TemplateChild, + } + + #[glib::object_subclass] + impl ObjectSubclass for MusicusRecordingEditorEnsembleRow { + const NAME: &'static str = "MusicusRecordingEditorEnsembleRow"; + type Type = super::MusicusRecordingEditorEnsembleRow; + type ParentType = adw::ActionRow; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + klass.bind_template_instance_callbacks(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } + } + + #[glib::derived_properties] + impl ObjectImpl for MusicusRecordingEditorEnsembleRow { + fn signals() -> &'static [Signal] { + static SIGNALS: Lazy> = + Lazy::new(|| vec![Signal::builder("remove").build()]); + + SIGNALS.as_ref() + } + + fn constructed(&self) { + self.parent_constructed(); + + let role_popover = MusicusRoleSelectorPopover::new(self.library.get().unwrap()); + + let obj = self.obj().to_owned(); + role_popover.connect_role_selected(move |_, role| { + if let Some(ensemble) = &mut *obj.imp().ensemble.borrow_mut() { + obj.imp().role_label.set_label(&role.to_string()); + ensemble.role = role; + } + }); + + let obj = self.obj().to_owned(); + role_popover.connect_create(move |_| { + let editor = MusicusRoleEditor::new(&obj.navigation(), &obj.library(), None); + + editor.connect_created(clone!( + #[weak] + obj, + move |_, role| { + if let Some(ensemble) = &mut *obj.imp().ensemble.borrow_mut() { + obj.imp().role_label.set_label(&role.to_string()); + ensemble.role = role; + }; + } + )); + + obj.navigation().push(&editor); + }); + + self.role_box.append(&role_popover); + self.role_popover.set(role_popover).unwrap(); + } + } + + impl WidgetImpl for MusicusRecordingEditorEnsembleRow {} + impl ListBoxRowImpl for MusicusRecordingEditorEnsembleRow {} + impl PreferencesRowImpl for MusicusRecordingEditorEnsembleRow {} + impl ActionRowImpl for MusicusRecordingEditorEnsembleRow {} +} + +glib::wrapper! { + pub struct MusicusRecordingEditorEnsembleRow(ObjectSubclass) + @extends gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow, adw::ActionRow; +} + +#[gtk::template_callbacks] +impl MusicusRecordingEditorEnsembleRow { + pub fn new( + navigation: &adw::NavigationView, + library: &MusicusLibrary, + ensemble: EnsemblePerformer, + ) -> Self { + let obj: Self = glib::Object::builder() + .property("navigation", navigation) + .property("library", library) + .build(); + obj.set_ensemble(ensemble); + obj + } + + pub fn connect_remove(&self, f: F) -> glib::SignalHandlerId { + self.connect_local("remove", true, move |values| { + let obj = values[0].get::().unwrap(); + f(&obj); + None + }) + } + + pub fn ensemble(&self) -> EnsemblePerformer { + self.imp().ensemble.borrow().to_owned().unwrap() + } + + fn set_ensemble(&self, ensemble: EnsemblePerformer) { + self.set_title(&ensemble.ensemble.to_string()); + self.imp().role_label.set_label(&ensemble.role.to_string()); + self.imp().ensemble.replace(Some(ensemble)); + } + + #[template_callback] + fn open_role_popover(&self, _: >k::Button) { + self.imp().role_popover.get().unwrap().popup(); + } + + #[template_callback] + fn remove(&self, _: >k::Button) { + self.emit_by_name::<()>("remove", &[]); + } +} diff --git a/src/editor/recording_editor_performer_row.rs b/src/editor/recording_editor_performer_row.rs new file mode 100644 index 0000000..76817e6 --- /dev/null +++ b/src/editor/recording_editor_performer_row.rs @@ -0,0 +1,188 @@ +use std::cell::{OnceCell, RefCell}; + +use adw::{prelude::*, subclass::prelude::*}; +use gtk::glib::{self, clone, subclass::Signal, Properties}; +use once_cell::sync::Lazy; + +use crate::{ + db::models::Performer, + editor::{ + performer_role_selector_popover::MusicusPerformerRoleSelectorPopover, + role_editor::MusicusRoleEditor, + }, + library::MusicusLibrary, +}; + +mod imp { + use crate::editor::instrument_editor::MusicusInstrumentEditor; + + use super::*; + + #[derive(Properties, Debug, Default, gtk::CompositeTemplate)] + #[properties(wrapper_type = super::MusicusRecordingEditorPerformerRow)] + #[template(file = "data/ui/recording_editor_performer_row.blp")] + pub struct MusicusRecordingEditorPerformerRow { + #[property(get, construct_only)] + pub navigation: OnceCell, + + #[property(get, construct_only)] + pub library: OnceCell, + + pub performer: RefCell>, + pub role_popover: OnceCell, + + #[template_child] + pub role_label: TemplateChild, + #[template_child] + pub role_box: TemplateChild, + } + + #[glib::object_subclass] + impl ObjectSubclass for MusicusRecordingEditorPerformerRow { + const NAME: &'static str = "MusicusRecordingEditorPerformerRow"; + type Type = super::MusicusRecordingEditorPerformerRow; + type ParentType = adw::ActionRow; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + klass.bind_template_instance_callbacks(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } + } + + #[glib::derived_properties] + impl ObjectImpl for MusicusRecordingEditorPerformerRow { + fn signals() -> &'static [Signal] { + static SIGNALS: Lazy> = + Lazy::new(|| vec![Signal::builder("remove").build()]); + + SIGNALS.as_ref() + } + + fn constructed(&self) { + self.parent_constructed(); + + let role_popover = + MusicusPerformerRoleSelectorPopover::new(self.library.get().unwrap()); + + let obj = self.obj().to_owned(); + role_popover.connect_selected(move |_, role, instrument| { + if let Some(performer) = &mut *obj.imp().performer.borrow_mut() { + let label = match &instrument { + Some(instrument) => instrument.to_string(), + None => role.to_string(), + }; + + obj.imp().role_label.set_label(&label); + + performer.role = role; + performer.instrument = instrument; + } + }); + + let obj = self.obj().to_owned(); + role_popover.connect_create_role(move |_| { + let editor = MusicusRoleEditor::new(&obj.navigation(), &obj.library(), None); + + editor.connect_created(clone!( + #[weak] + obj, + move |_, role| { + if let Some(performer) = &mut *obj.imp().performer.borrow_mut() { + obj.imp().role_label.set_label(&role.to_string()); + performer.role = role; + performer.instrument = None; + }; + } + )); + + obj.navigation().push(&editor); + }); + + let obj = self.obj().to_owned(); + role_popover.connect_create_instrument(move |_| { + let editor = MusicusInstrumentEditor::new(&obj.navigation(), &obj.library(), None); + + editor.connect_created(clone!( + #[weak] + obj, + move |_, instrument| { + if let Some(performer) = &mut *obj.imp().performer.borrow_mut() { + obj.imp().role_label.set_label(&instrument.to_string()); + performer.role = obj.library().performer_default_role().unwrap(); + performer.instrument = Some(instrument); + }; + } + )); + + obj.navigation().push(&editor); + }); + + self.role_box.append(&role_popover); + self.role_popover.set(role_popover).unwrap(); + } + } + + impl WidgetImpl for MusicusRecordingEditorPerformerRow {} + impl ListBoxRowImpl for MusicusRecordingEditorPerformerRow {} + impl PreferencesRowImpl for MusicusRecordingEditorPerformerRow {} + impl ActionRowImpl for MusicusRecordingEditorPerformerRow {} +} + +glib::wrapper! { + pub struct MusicusRecordingEditorPerformerRow(ObjectSubclass) + @extends gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow, adw::ActionRow; +} + +#[gtk::template_callbacks] +impl MusicusRecordingEditorPerformerRow { + pub fn new( + navigation: &adw::NavigationView, + library: &MusicusLibrary, + performer: Performer, + ) -> Self { + let obj: Self = glib::Object::builder() + .property("navigation", navigation) + .property("library", library) + .build(); + obj.set_performer(performer); + obj + } + + pub fn connect_remove(&self, f: F) -> glib::SignalHandlerId { + self.connect_local("remove", true, move |values| { + let obj = values[0].get::().unwrap(); + f(&obj); + None + }) + } + + pub fn performer(&self) -> Performer { + self.imp().performer.borrow().to_owned().unwrap() + } + + fn set_performer(&self, performer: Performer) { + self.set_title(&performer.person.to_string()); + + let label = match &performer.instrument { + Some(instrument) => instrument.to_string(), + None => performer.role.to_string(), + }; + + self.imp().role_label.set_label(&label.to_string()); + self.imp().performer.replace(Some(performer)); + } + + #[template_callback] + fn open_role_popover(&self, _: >k::Button) { + self.imp().role_popover.get().unwrap().popup(); + } + + #[template_callback] + fn remove(&self, _: >k::Button) { + self.emit_by_name::<()>("remove", &[]); + } +} diff --git a/src/editor/work_editor.rs b/src/editor/work_editor.rs index adc29fe..915dcd3 100644 --- a/src/editor/work_editor.rs +++ b/src/editor/work_editor.rs @@ -1,9 +1,20 @@ +use std::cell::{OnceCell, RefCell}; + +use adw::{prelude::*, subclass::prelude::*}; +use gettextrs::gettext; +use gtk::glib::{ + clone, Properties, + {self, subclass::Signal}, +}; +use once_cell::sync::Lazy; + use crate::{ db::{ self, models::{Composer, Instrument, Person, Work, WorkPart}, }, editor::{ + instrument_editor::MusicusInstrumentEditor, instrument_selector_popover::MusicusInstrumentSelectorPopover, person_editor::MusicusPersonEditor, person_selector_popover::MusicusPersonSelectorPopover, translation_editor::MusicusTranslationEditor, @@ -12,12 +23,6 @@ use crate::{ library::MusicusLibrary, }; -use adw::{prelude::*, subclass::prelude::*}; -use gettextrs::gettext; -use gtk::glib::{self, clone, Properties}; - -use std::cell::{OnceCell, RefCell}; - mod imp { use super::*; @@ -31,10 +36,13 @@ mod imp { #[property(get, construct_only)] pub library: OnceCell, + pub work_id: OnceCell, + // Holding a reference to each composer row is the simplest way to enumerate all // results when finishing the process of editing the work. The composer rows // handle all state related to the composer. pub composer_rows: RefCell>, + // TODO: These need to be PartRows! pub parts: RefCell>, pub instruments: RefCell>, @@ -53,6 +61,8 @@ mod imp { pub instrument_list: TemplateChild, #[template_child] pub select_instrument_box: TemplateChild, + #[template_child] + pub save_button: TemplateChild, } #[glib::object_subclass] @@ -74,6 +84,16 @@ mod imp { #[glib::derived_properties] impl ObjectImpl for MusicusWorkEditor { + fn signals() -> &'static [Signal] { + static SIGNALS: Lazy> = Lazy::new(|| { + vec![Signal::builder("created") + .param_types([Work::static_type()]) + .build()] + }); + + SIGNALS.as_ref() + } + fn constructed(&self) { self.parent_constructed(); @@ -106,43 +126,24 @@ mod imp { MusicusInstrumentSelectorPopover::new(self.library.get().unwrap()); let obj = self.obj().clone(); - instruments_popover.connect_instrument_selected( - move |_: &MusicusInstrumentSelectorPopover, instrument: Instrument| { - let row = adw::ActionRow::builder() - .title(instrument.to_string()) - .build(); + instruments_popover.connect_instrument_selected(move |_, instrument| { + obj.add_instrument_row(instrument); + }); - let remove_button = gtk::Button::builder() - .icon_name("user-trash-symbolic") - .valign(gtk::Align::Center) - .css_classes(["flat"]) - .build(); + let obj = self.obj().clone(); + instruments_popover.connect_create(move |_| { + let editor = MusicusInstrumentEditor::new(&obj.navigation(), &obj.library(), None); - remove_button.connect_clicked(clone!( - #[weak] - obj, - #[weak] - row, - #[strong] - instrument, - move |_| { - obj.imp().instrument_list.remove(&row); - obj.imp() - .instruments - .borrow_mut() - .retain(|i| *i != instrument); - } - )); + editor.connect_created(clone!( + #[weak] + obj, + move |_, instrument| { + obj.add_instrument_row(instrument); + } + )); - row.add_suffix(&remove_button); - - obj.imp() - .instrument_list - .insert(&row, obj.imp().instruments.borrow().len() as i32); - - obj.imp().instruments.borrow_mut().push(instrument); - }, - ); + obj.navigation().push(&editor); + }); self.select_instrument_box.append(&instruments_popover); self.instruments_popover.set(instruments_popover).unwrap(); @@ -170,13 +171,24 @@ impl MusicusWorkEditor { .property("library", library) .build(); - if let Some(_work) = work { + if let Some(work) = work { + obj.imp().save_button.set_label(&gettext("Save changes")); + obj.imp().work_id.set(work.work_id.clone()).unwrap(); // TODO: Initialize work data. } obj } + pub fn connect_created(&self, f: F) -> glib::SignalHandlerId { + self.connect_local("created", true, move |values| { + let obj = values[0].get::().unwrap(); + let work = values[1].get::().unwrap(); + f(&obj, work); + None + }) + } + #[template_callback] fn add_person(&self, _: &adw::ActionRow) { self.imp().persons_popover.get().unwrap().popup(); @@ -246,4 +258,69 @@ impl MusicusWorkEditor { self.imp().composer_rows.borrow_mut().push(row); } + + fn add_instrument_row(&self, instrument: Instrument) { + let row = adw::ActionRow::builder() + .title(instrument.to_string()) + .build(); + + let remove_button = gtk::Button::builder() + .icon_name("user-trash-symbolic") + .valign(gtk::Align::Center) + .css_classes(["flat"]) + .build(); + + remove_button.connect_clicked(clone!( + #[weak(rename_to = this)] + self, + #[weak] + row, + #[strong] + instrument, + move |_| { + this.imp().instrument_list.remove(&row); + this.imp() + .instruments + .borrow_mut() + .retain(|i| *i != instrument); + } + )); + + row.add_suffix(&remove_button); + + self.imp() + .instrument_list + .insert(&row, self.imp().instruments.borrow().len() as i32); + + self.imp().instruments.borrow_mut().push(instrument); + } + + #[template_callback] + fn save(&self, _: >k::Button) { + let library = self.imp().library.get().unwrap(); + + let name = self.imp().name_editor.translation(); + let parts = self.imp().parts.borrow().clone(); + let composers = self + .imp() + .composer_rows + .borrow() + .iter() + .map(|c| c.composer()) + .collect::>(); + let instruments = self.imp().instruments.borrow().clone(); + + if let Some(work_id) = self.imp().work_id.get() { + library + .update_work(work_id, name, parts, composers, instruments) + .unwrap(); + } else { + let work = library + .create_work(name, parts, composers, instruments) + .unwrap(); + self.emit_by_name::<()>("created", &[&work]); + } + + self.imp().navigation.get().unwrap().pop(); + } } diff --git a/src/editor/work_editor_composer_row.rs b/src/editor/work_editor_composer_row.rs index 510f9cb..d635d51 100644 --- a/src/editor/work_editor_composer_row.rs +++ b/src/editor/work_editor_composer_row.rs @@ -5,7 +5,7 @@ use gtk::glib::{self, clone, subclass::Signal, Properties}; use once_cell::sync::Lazy; use crate::{ - db::models::{Composer, Role}, + db::models::Composer, editor::{role_editor::MusicusRoleEditor, role_selector_popover::MusicusRoleSelectorPopover}, library::MusicusLibrary, }; @@ -142,14 +142,6 @@ impl MusicusWorkEditorComposerRow { self.imp().role_popover.get().unwrap().popup(); } - #[template_callback] - fn role_selected(&self, role: Role) { - if let Some(composer) = &mut *self.imp().composer.borrow_mut() { - self.imp().role_label.set_label(&role.to_string()); - composer.role = role; - } - } - #[template_callback] fn remove(&self, _: >k::Button) { self.emit_by_name::<()>("remove", &[]); diff --git a/src/editor/work_selector_popover.rs b/src/editor/work_selector_popover.rs new file mode 100644 index 0000000..e9cad3d --- /dev/null +++ b/src/editor/work_selector_popover.rs @@ -0,0 +1,308 @@ +use crate::{ + db::models::{Person, Work}, + library::MusicusLibrary, +}; + +use gettextrs::gettext; +use gtk::{ + glib::{self, subclass::Signal, Properties}, + pango, + prelude::*, + subclass::prelude::*, +}; +use once_cell::sync::Lazy; + +use std::cell::{OnceCell, RefCell}; + +use super::activatable_row::MusicusActivatableRow; + +mod imp { + use super::*; + + #[derive(Debug, Default, gtk::CompositeTemplate, Properties)] + #[properties(wrapper_type = super::MusicusWorkSelectorPopover)] + #[template(file = "data/ui/work_selector_popover.blp")] + pub struct MusicusWorkSelectorPopover { + #[property(get, construct_only)] + pub library: OnceCell, + + pub composers: RefCell>, + pub composer: RefCell>, + pub works: RefCell>, + + #[template_child] + pub stack: TemplateChild, + #[template_child] + pub composer_view: TemplateChild, + #[template_child] + pub composer_search_entry: TemplateChild, + #[template_child] + pub composer_scrolled_window: TemplateChild, + #[template_child] + pub composer_list: TemplateChild, + #[template_child] + pub work_view: TemplateChild, + #[template_child] + pub composer_label: TemplateChild, + #[template_child] + pub work_search_entry: TemplateChild, + #[template_child] + pub work_scrolled_window: TemplateChild, + #[template_child] + pub work_list: TemplateChild, + } + + #[glib::object_subclass] + impl ObjectSubclass for MusicusWorkSelectorPopover { + const NAME: &'static str = "MusicusWorkSelectorPopover"; + type Type = super::MusicusWorkSelectorPopover; + type ParentType = gtk::Popover; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + klass.bind_template_instance_callbacks(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } + } + + #[glib::derived_properties] + impl ObjectImpl for MusicusWorkSelectorPopover { + fn constructed(&self) { + self.parent_constructed(); + + self.obj() + .connect_visible_notify(|obj: &super::MusicusWorkSelectorPopover| { + if obj.is_visible() { + obj.imp().stack.set_visible_child(&*obj.imp().composer_view); + obj.imp().composer_search_entry.set_text(""); + obj.imp().composer_search_entry.grab_focus(); + obj.imp() + .composer_scrolled_window + .vadjustment() + .set_value(0.0); + } + }); + + self.obj().search_composers(""); + } + + fn signals() -> &'static [Signal] { + static SIGNALS: Lazy> = Lazy::new(|| { + vec![ + Signal::builder("selected") + .param_types([Work::static_type()]) + .build(), + Signal::builder("create").build(), + ] + }); + + SIGNALS.as_ref() + } + } + + impl WidgetImpl for MusicusWorkSelectorPopover { + // TODO: Fix focus. + fn focus(&self, direction_type: gtk::DirectionType) -> bool { + if direction_type == gtk::DirectionType::Down { + if self.stack.visible_child() == Some(self.composer_list.get().upcast()) { + self.composer_list.child_focus(direction_type) + } else { + self.work_list.child_focus(direction_type) + } + } else { + self.parent_focus(direction_type) + } + } + } + + impl PopoverImpl for MusicusWorkSelectorPopover {} +} + +glib::wrapper! { + pub struct MusicusWorkSelectorPopover(ObjectSubclass) + @extends gtk::Widget, gtk::Popover; +} + +#[gtk::template_callbacks] +impl MusicusWorkSelectorPopover { + pub fn new(library: &MusicusLibrary) -> Self { + glib::Object::builder().property("library", library).build() + } + + pub fn connect_selected(&self, f: F) -> glib::SignalHandlerId { + self.connect_local("selected", true, move |values| { + let obj = values[0].get::().unwrap(); + let work = values[1].get::().unwrap(); + f(&obj, work); + None + }) + } + + pub fn connect_create(&self, f: F) -> glib::SignalHandlerId { + self.connect_local("create", true, move |values| { + let obj = values[0].get::().unwrap(); + f(&obj); + None + }) + } + + #[template_callback] + fn composer_search_changed(&self, entry: >k::SearchEntry) { + self.search_composers(&entry.text()); + } + + #[template_callback] + fn composer_activate(&self, _: >k::SearchEntry) { + if let Some(composer) = self.imp().composers.borrow().first() { + self.select_composer(composer.to_owned()); + } else { + self.create(); + } + } + + #[template_callback] + fn back_button_clicked(&self, _: >k::Button) { + self.imp() + .stack + .set_visible_child(&*self.imp().composer_view); + self.imp().composer_search_entry.grab_focus(); + } + + #[template_callback] + fn work_search_changed(&self, entry: >k::SearchEntry) { + self.search_works(&entry.text()); + } + + #[template_callback] + fn work_activate(&self, _: >k::SearchEntry) { + if let Some(work) = self.imp().works.borrow().first() { + self.select(work.clone()); + } else { + self.create(); + } + } + + #[template_callback] + fn stop_search(&self, _: >k::SearchEntry) { + self.popdown(); + } + + fn search_composers(&self, search: &str) { + let imp = self.imp(); + + let persons = imp.library.get().unwrap().search_persons(search).unwrap(); + + imp.composer_list.remove_all(); + + for person in &persons { + let row = MusicusActivatableRow::new( + >k::Label::builder() + .label(person.to_string()) + .halign(gtk::Align::Start) + .ellipsize(pango::EllipsizeMode::Middle) + .build(), + ); + + let person = person.clone(); + let obj = self.clone(); + row.connect_activated(move |_: &MusicusActivatableRow| { + obj.select_composer(person.clone()); + }); + + imp.composer_list.append(&row); + } + + let create_box = gtk::Box::builder().spacing(12).build(); + create_box.append(>k::Image::builder().icon_name("list-add-symbolic").build()); + create_box.append( + >k::Label::builder() + .label(gettext("Create new work")) + .halign(gtk::Align::Start) + .build(), + ); + + let create_row = MusicusActivatableRow::new(&create_box); + let obj = self.clone(); + create_row.connect_activated(move |_: &MusicusActivatableRow| { + obj.create(); + }); + + imp.composer_list.append(&create_row); + + imp.composers.replace(persons); + } + + fn search_works(&self, search: &str) { + let imp = self.imp(); + + let works = imp + .library + .get() + .unwrap() + .search_works(imp.composer.borrow().as_ref().unwrap(), search) + .unwrap(); + + imp.work_list.remove_all(); + + for work in &works { + let row = MusicusActivatableRow::new( + >k::Label::builder() + .label(work.name.get()) + .halign(gtk::Align::Start) + .ellipsize(pango::EllipsizeMode::Middle) + .build(), + ); + + let work = work.clone(); + let obj = self.clone(); + row.connect_activated(move |_: &MusicusActivatableRow| { + obj.select(work.clone()); + }); + + imp.work_list.append(&row); + } + + let create_box = gtk::Box::builder().spacing(12).build(); + create_box.append(>k::Image::builder().icon_name("list-add-symbolic").build()); + create_box.append( + >k::Label::builder() + .label(gettext("Create new work")) + .halign(gtk::Align::Start) + .build(), + ); + + let create_row = MusicusActivatableRow::new(&create_box); + let obj = self.clone(); + create_row.connect_activated(move |_: &MusicusActivatableRow| { + obj.create(); + }); + + imp.work_list.append(&create_row); + + imp.works.replace(works); + } + + fn select_composer(&self, person: Person) { + self.imp().composer_label.set_text(person.name.get()); + self.imp().work_search_entry.set_text(""); + self.imp().work_search_entry.grab_focus(); + self.imp().work_scrolled_window.vadjustment().set_value(0.0); + self.imp().stack.set_visible_child(&*self.imp().work_view); + + self.imp().composer.replace(Some(person.clone())); + self.search_works(""); + } + + fn select(&self, work: Work) { + self.emit_by_name::<()>("selected", &[&work]); + self.popdown(); + } + + fn create(&self) { + self.emit_by_name::<()>("create", &[]); + self.popdown(); + } +} diff --git a/src/library.rs b/src/library.rs index 44b4c7b..3340ab6 100644 --- a/src/library.rs +++ b/src/library.rs @@ -48,7 +48,7 @@ mod imp { let db_path = PathBuf::from(&self.folder.get().unwrap()).join("musicus.db"); let connection = db::connect(db_path.to_str().unwrap()).unwrap(); - self.connection.set(Some(connection)); + self.connection.replace(Some(connection)); } } } @@ -232,7 +232,7 @@ impl MusicusLibrary { .distinct() .load::(connection)? .into_iter() - .map(|r| Recording::from_table(r, &&self.folder(), connection)) + .map(|r| Recording::from_table(r, connection)) .collect::>>()?; let albums = albums::table @@ -293,7 +293,7 @@ impl MusicusLibrary { .distinct() .load::(connection)? .into_iter() - .map(|r| Recording::from_table(r, &self.folder(), connection)) + .map(|r| Recording::from_table(r, connection)) .collect::>>()?; let albums = albums::table @@ -336,7 +336,7 @@ impl MusicusLibrary { .distinct() .load::(connection)? .into_iter() - .map(|r| Recording::from_table(r, &self.folder(), connection)) + .map(|r| Recording::from_table(r, connection)) .collect::>>()?; LibraryResults { @@ -363,7 +363,7 @@ impl MusicusLibrary { .distinct() .load::(connection)? .into_iter() - .map(|r| Recording::from_table(r, &self.folder(), connection)) + .map(|r| Recording::from_table(r, connection)) .collect::>>()?; LibraryResults { @@ -378,7 +378,7 @@ impl MusicusLibrary { .filter(recordings::work_id.eq(&work.work_id)) .load::(connection)? .into_iter() - .map(|r| Recording::from_table(r, &self.folder(), connection)) + .map(|r| Recording::from_table(r, connection)) .collect::>>()?; LibraryResults { @@ -479,7 +479,23 @@ impl MusicusLibrary { .select(tables::Recording::as_select()) .first::(connection)?; - Recording::from_table(row, &self.folder(), connection) + Recording::from_table(row, connection) + } + + pub fn tracks_for_recording(&self, recording_id: &str) -> Result> { + let mut binding = self.imp().connection.borrow_mut(); + let connection = &mut *binding.as_mut().unwrap(); + + let tracks = tracks::table + .order(tracks::recording_index) + .filter(tracks::recording_id.eq(&recording_id)) + .select(tables::Track::as_select()) + .load::(connection)? + .into_iter() + .map(|t| Track::from_table(t, connection)) + .collect::>>()?; + + Ok(tracks) } pub fn track_played(&self, track_id: &str) -> Result<()> { @@ -572,6 +588,29 @@ impl MusicusLibrary { Ok(works) } + pub fn search_ensembles(&self, search: &str) -> Result> { + let search = format!("%{}%", search); + let mut binding = self.imp().connection.borrow_mut(); + let connection = &mut *binding.as_mut().unwrap(); + + let ensembles = ensembles::table + .order(ensembles::last_used_at.desc()) + .left_join(ensemble_persons::table.inner_join(persons::table)) + .filter( + ensembles::name + .like(&search) + .or(persons::name.like(&search)), + ) + .limit(20) + .select(ensembles::all_columns) + .load::(connection)? + .into_iter() + .map(|e| Ensemble::from_table(e, connection)) + .collect::>>()?; + + Ok(ensembles) + } + pub fn composer_default_role(&self) -> Result { let mut binding = self.imp().connection.borrow_mut(); let connection = &mut *binding.as_mut().unwrap(); @@ -581,6 +620,15 @@ impl MusicusLibrary { .first::(connection)?) } + pub fn performer_default_role(&self) -> Result { + let mut binding = self.imp().connection.borrow_mut(); + let connection = &mut *binding.as_mut().unwrap(); + + Ok(roles::table + .filter(roles::role_id.eq("28ff0aeb11c041a6916d93e9b4884eef")) + .first::(connection)?) + } + pub fn create_person(&self, name: TranslatedString) -> Result { let mut binding = self.imp().connection.borrow_mut(); let connection = &mut *binding.as_mut().unwrap(); @@ -621,6 +669,46 @@ impl MusicusLibrary { Ok(()) } + pub fn create_instrument(&self, name: TranslatedString) -> Result { + let mut binding = self.imp().connection.borrow_mut(); + let connection = &mut *binding.as_mut().unwrap(); + + let now = Local::now().naive_local(); + + let instrument = Instrument { + instrument_id: db::generate_id(), + name, + created_at: now, + edited_at: now, + last_used_at: now, + last_played_at: None, + }; + + diesel::insert_into(instruments::table) + .values(&instrument) + .execute(connection)?; + + Ok(instrument) + } + + pub fn update_instrument(&self, id: &str, name: TranslatedString) -> Result<()> { + let mut binding = self.imp().connection.borrow_mut(); + let connection = &mut *binding.as_mut().unwrap(); + + let now = Local::now().naive_local(); + + diesel::update(instruments::table) + .filter(instruments::instrument_id.eq(id)) + .set(( + instruments::name.eq(name), + instruments::edited_at.eq(now), + instruments::last_used_at.eq(now), + )) + .execute(connection)?; + + Ok(()) + } + pub fn create_role(&self, name: TranslatedString) -> Result { let mut binding = self.imp().connection.borrow_mut(); let connection = &mut *binding.as_mut().unwrap(); @@ -659,6 +747,221 @@ impl MusicusLibrary { Ok(()) } + + pub fn create_work( + &self, + name: TranslatedString, + parts: Vec, + persons: Vec, + instruments: Vec, + ) -> Result { + let mut binding = self.imp().connection.borrow_mut(); + let connection = &mut *binding.as_mut().unwrap(); + + let work_id = db::generate_id(); + let now = Local::now().naive_local(); + + let work_data = tables::Work { + work_id: work_id.clone(), + parent_work_id: None, + sequence_number: None, + name, + created_at: now, + edited_at: now, + last_used_at: now, + last_played_at: None, + }; + + diesel::insert_into(works::table) + .values(&work_data) + .execute(connection)?; + + for (index, part) in parts.into_iter().enumerate() { + let part_data = tables::Work { + work_id: part.work_id, + parent_work_id: Some(work_id.clone()), + sequence_number: Some(index as i32), + name: part.name, + created_at: now, + edited_at: now, + last_used_at: now, + last_played_at: None, + }; + + diesel::insert_into(works::table) + .values(&part_data) + .execute(connection)?; + } + + for (index, composer) in persons.into_iter().enumerate() { + let composer_data = tables::WorkPerson { + work_id: work_id.clone(), + person_id: composer.person.person_id, + role_id: composer.role.role_id, + sequence_number: index as i32, + }; + + diesel::insert_into(work_persons::table) + .values(composer_data) + .execute(connection)?; + } + + for (index, instrument) in instruments.into_iter().enumerate() { + let instrument_data = tables::WorkInstrument { + work_id: work_id.clone(), + instrument_id: instrument.instrument_id, + sequence_number: index as i32, + }; + + diesel::insert_into(work_instruments::table) + .values(instrument_data) + .execute(connection)?; + } + + let work = Work::from_table(work_data, connection)?; + + Ok(work) + } + + pub fn update_work( + &self, + id: &str, + name: TranslatedString, + parts: Vec, + persons: Vec, + instruments: Vec, + ) -> Result<()> { + let mut binding = self.imp().connection.borrow_mut(); + let connection = &mut *binding.as_mut().unwrap(); + + let now = Local::now().naive_local(); + + // TODO: Update work, check which work parts etc exist, update them, + // create new work parts, delete and readd composers and instruments. + todo!() + } + + pub fn create_ensemble(&self, name: TranslatedString) -> Result { + let mut binding = self.imp().connection.borrow_mut(); + let connection = &mut *binding.as_mut().unwrap(); + + let now = Local::now().naive_local(); + + let ensemble_data = tables::Ensemble { + ensemble_id: db::generate_id(), + name, + created_at: now, + edited_at: now, + last_used_at: now, + last_played_at: None, + }; + + // TODO: Add persons. + + diesel::insert_into(ensembles::table) + .values(&ensemble_data) + .execute(connection)?; + + let ensemble = Ensemble::from_table(ensemble_data, connection)?; + + Ok(ensemble) + } + + pub fn update_ensemble(&self, id: &str, name: TranslatedString) -> Result<()> { + let mut binding = self.imp().connection.borrow_mut(); + let connection = &mut *binding.as_mut().unwrap(); + + let now = Local::now().naive_local(); + + diesel::update(ensembles::table) + .filter(ensembles::ensemble_id.eq(id)) + .set(( + ensembles::name.eq(name), + ensembles::edited_at.eq(now), + ensembles::last_used_at.eq(now), + )) + .execute(connection)?; + + // TODO: Support updating persons. + + Ok(()) + } + + pub fn create_recording( + &self, + work: Work, + year: Option, + performers: Vec, + ensembles: Vec, + ) -> Result { + let mut binding = self.imp().connection.borrow_mut(); + let connection = &mut *binding.as_mut().unwrap(); + + let recording_id = db::generate_id(); + let now = Local::now().naive_local(); + + let recording_data = tables::Recording { + recording_id: recording_id.clone(), + work_id: work.work_id.clone(), + year, + created_at: now, + edited_at: now, + last_used_at: now, + last_played_at: None, + }; + + diesel::insert_into(recordings::table) + .values(&recording_data) + .execute(connection)?; + + for (index, performer) in performers.into_iter().enumerate() { + let recording_person_data = tables::RecordingPerson { + recording_id: recording_id.clone(), + person_id: performer.person.person_id, + role_id: performer.role.role_id, + instrument_id: performer.instrument.map(|i| i.instrument_id), + sequence_number: index as i32, + }; + + diesel::insert_into(recording_persons::table) + .values(&recording_person_data) + .execute(connection)?; + } + + for (index, ensemble) in ensembles.into_iter().enumerate() { + let recording_ensemble_data = tables::RecordingEnsemble { + recording_id: recording_id.clone(), + ensemble_id: ensemble.ensemble.ensemble_id, + role_id: ensemble.role.role_id, + sequence_number: index as i32, + }; + + diesel::insert_into(recording_ensembles::table) + .values(&recording_ensemble_data) + .execute(connection)?; + } + + let recording = Recording::from_table(recording_data, connection)?; + + Ok(recording) + } + + pub fn update_recording( + &self, + id: &str, + work: Work, + year: Option, + performers: Vec, + ensembles: Vec, + ) -> Result<()> { + let mut binding = self.imp().connection.borrow_mut(); + let connection = &mut *binding.as_mut().unwrap(); + + let now = Local::now().naive_local(); + + // TODO: Update recording. + todo!() + } } #[derive(Default, Debug)] diff --git a/src/library_manager.rs b/src/library_manager.rs index b5f0703..7b07b21 100644 --- a/src/library_manager.rs +++ b/src/library_manager.rs @@ -1,20 +1,23 @@ -use adw::{ - prelude::*, - subclass::{navigation_page::NavigationPageImpl, prelude::*}, -}; -use gtk::glib::{self, Properties}; +use adw::subclass::prelude::*; +use gtk::glib; use std::cell::OnceCell; -use crate::library::MusicusLibrary; +use crate::{ + editor::{ + ensemble_editor::MusicusEnsembleEditor, instrument_editor::MusicusInstrumentEditor, + person_editor::MusicusPersonEditor, recording_editor::MusicusRecordingEditor, + role_editor::MusicusRoleEditor, work_editor::MusicusWorkEditor, + }, + library::MusicusLibrary, +}; mod imp { use super::*; - #[derive(Properties, Debug, Default, gtk::CompositeTemplate)] - #[properties(wrapper_type = super::LibraryManager)] + #[derive(Debug, Default, gtk::CompositeTemplate)] #[template(file = "data/ui/library_manager.blp")] pub struct LibraryManager { - #[property(get, construct_only)] + pub navigation: OnceCell, pub library: OnceCell, } @@ -34,9 +37,7 @@ mod imp { } } - #[glib::derived_properties] impl ObjectImpl for LibraryManager {} - impl WidgetImpl for LibraryManager {} impl NavigationPageImpl for LibraryManager {} } @@ -48,7 +49,110 @@ glib::wrapper! { #[gtk::template_callbacks] impl LibraryManager { - pub fn new(library: &MusicusLibrary) -> Self { - glib::Object::builder().property("library", library).build() + pub fn new(navigation: &adw::NavigationView, library: &MusicusLibrary) -> Self { + let obj: Self = glib::Object::new(); + let imp = obj.imp(); + + imp.navigation.set(navigation.to_owned()).unwrap(); + imp.library.set(library.to_owned()).unwrap(); + + obj + } + + #[template_callback] + fn add_person(&self, _: >k::Button) { + self.imp() + .navigation + .get() + .unwrap() + .push(&MusicusPersonEditor::new( + &self.imp().navigation.get().unwrap(), + &self.imp().library.get().unwrap(), + None, + )); + } + + #[template_callback] + fn add_role(&self, _: >k::Button) { + self.imp() + .navigation + .get() + .unwrap() + .push(&MusicusRoleEditor::new( + &self.imp().navigation.get().unwrap(), + &self.imp().library.get().unwrap(), + None, + )); + } + + #[template_callback] + fn add_instrument(&self, _: >k::Button) { + self.imp() + .navigation + .get() + .unwrap() + .push(&MusicusInstrumentEditor::new( + &self.imp().navigation.get().unwrap(), + &self.imp().library.get().unwrap(), + None, + )); + } + + #[template_callback] + fn add_work(&self, _: >k::Button) { + self.imp() + .navigation + .get() + .unwrap() + .push(&MusicusWorkEditor::new( + &self.imp().navigation.get().unwrap(), + &self.imp().library.get().unwrap(), + None, + )); + } + + #[template_callback] + fn add_ensemble(&self, _: >k::Button) { + self.imp() + .navigation + .get() + .unwrap() + .push(&MusicusEnsembleEditor::new( + &self.imp().navigation.get().unwrap(), + &self.imp().library.get().unwrap(), + None, + )); + } + + #[template_callback] + fn add_recording(&self, _: >k::Button) { + self.imp() + .navigation + .get() + .unwrap() + .push(&MusicusRecordingEditor::new( + &self.imp().navigation.get().unwrap(), + &self.imp().library.get().unwrap(), + None, + )); + } + + #[template_callback] + fn add_medium(&self, _: >k::Button) { + todo!("Medium import"); + } + + #[template_callback] + fn add_album(&self, _: >k::Button) { + todo!("Album editor"); + // self.imp() + // .navigation + // .get() + // .unwrap() + // .push(&MusicusAlbumEditor::new( + // &self.imp().navigation.get().unwrap(), + // &self.imp().library.get().unwrap(), + // None, + // )); } } diff --git a/src/player.rs b/src/player.rs index 239b837..c6e3cfa 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,5 +1,6 @@ use std::{ cell::{Cell, OnceCell, RefCell}, + path::PathBuf, sync::Arc, }; @@ -236,7 +237,7 @@ impl MusicusPlayer { } pub fn play_recording(&self, recording: &Recording) { - let tracks = &recording.tracks; + let tracks = &self.library().unwrap().tracks_for_recording(&recording.recording_id).unwrap(); if tracks.is_empty() { log::warn!("Ignoring recording without tracks being added to the playlist."); @@ -254,7 +255,7 @@ impl MusicusPlayer { &recording.work.name.get(), Some(&performances), None, - &tracks[0].path, + &self.library_path_to_file_path(&tracks[0].path), &tracks[0].track_id, )); } else { @@ -282,7 +283,7 @@ impl MusicusPlayer { &recording.work.name.get(), Some(&performances), Some(&track_title(&first_track, 1)), - &first_track.path, + &self.library_path_to_file_path(&first_track.path), &first_track.track_id, )); @@ -294,7 +295,7 @@ impl MusicusPlayer { Some(&performances), // track number = track index + 1 (first track) + 1 (zero based) Some(&track_title(&track, index + 2)), - &track.path, + &self.library_path_to_file_path(&track.path), &track.track_id, )); } @@ -384,6 +385,14 @@ impl MusicusPlayer { self.play_recording(&recording); } } + + fn library_path_to_file_path(&self, path: &str) -> String { + PathBuf::from(self.library().unwrap().folder()) + .join(path) + .to_str() + .unwrap() + .to_owned() + } } impl Default for MusicusPlayer { diff --git a/src/recording_tile.rs b/src/recording_tile.rs index eebfe03..32dd290 100644 --- a/src/recording_tile.rs +++ b/src/recording_tile.rs @@ -1,7 +1,10 @@ -use gtk::{glib, subclass::prelude::*}; +use gtk::{gio, glib, prelude::*, subclass::prelude::*}; use std::cell::OnceCell; -use crate::db::models::Recording; +use crate::{ + db::models::Recording, editor::recording_editor::MusicusRecordingEditor, + library::MusicusLibrary, +}; mod imp { use super::*; @@ -16,6 +19,8 @@ mod imp { #[template_child] pub performances_label: TemplateChild, + pub navigation: OnceCell, + pub library: OnceCell, pub recording: OnceCell, } @@ -34,7 +39,31 @@ mod imp { } } - impl ObjectImpl for MusicusRecordingTile {} + impl ObjectImpl for MusicusRecordingTile { + fn constructed(&self) { + self.parent_constructed(); + + let obj = self.obj().to_owned(); + let edit_action = gio::ActionEntry::builder("edit") + .activate(move |_, _, _| { + obj.imp() + .navigation + .get() + .unwrap() + .push(&MusicusRecordingEditor::new( + obj.imp().navigation.get().unwrap(), + obj.imp().library.get().unwrap(), + Some(&obj.imp().recording.get().unwrap()), + )); + }) + .build(); + + let actions = gio::SimpleActionGroup::new(); + actions.add_action_entries([edit_action]); + self.obj().insert_action_group("recording", Some(&actions)); + } + } + impl WidgetImpl for MusicusRecordingTile {} impl FlowBoxChildImpl for MusicusRecordingTile {} } @@ -45,15 +74,23 @@ glib::wrapper! { } impl MusicusRecordingTile { - pub fn new(recording: &Recording) -> Self { + pub fn new( + navigation: &adw::NavigationView, + library: &MusicusLibrary, + recording: &Recording, + ) -> Self { let obj: Self = glib::Object::new(); let imp = obj.imp(); imp.work_label.set_label(&recording.work.name.get()); - imp.composer_label.set_label(&recording.work.composers_string()); - imp.performances_label.set_label(&recording.performers_string()); + imp.composer_label + .set_label(&recording.work.composers_string()); + imp.performances_label + .set_label(&recording.performers_string()); - imp.recording.set(recording.clone()).unwrap(); + imp.navigation.set(navigation.to_owned()).unwrap(); + imp.library.set(library.to_owned()).unwrap(); + imp.recording.set(recording.to_owned()).unwrap(); obj } diff --git a/src/window.rs b/src/window.rs index 6b1eaed..e16b6cf 100644 --- a/src/window.rs +++ b/src/window.rs @@ -175,6 +175,6 @@ impl MusicusWindow { let navigation = self.imp().navigation_view.get(); navigation .replace(&[MusicusHomePage::new(&navigation, &library, &self.imp().player).into()]); - navigation.add(&LibraryManager::new(&library)); + navigation.add(&LibraryManager::new(&navigation, &library)); } }