diff --git a/musicus/res/ui/medium_preview.ui b/musicus/res/ui/medium_preview.ui index 5f25d3f..adf95e4 100644 --- a/musicus/res/ui/medium_preview.ui +++ b/musicus/res/ui/medium_preview.ui @@ -53,6 +53,11 @@ + + + document-edit-symbolic + + @@ -61,7 +66,7 @@ - + 6 6 6 @@ -76,6 +81,11 @@ + + + vertical + + diff --git a/musicus/src/import/import_screen.rs b/musicus/src/import/import_screen.rs index e77841d..4b998a5 100644 --- a/musicus/src/import/import_screen.rs +++ b/musicus/src/import/import_screen.rs @@ -153,7 +153,7 @@ impl Screen, ()> for ImportScreen { add_button.connect_clicked(clone!(@weak this => move |_| { spawn!(@clone this, async move { - if let Some(medium) = push!(this.handle, MediumEditor, Arc::clone(&this.session)).await { + if let Some(medium) = push!(this.handle, MediumEditor, (Arc::clone(&this.session), None)).await { this.select_medium(medium); } }); diff --git a/musicus/src/import/medium_editor.rs b/musicus/src/import/medium_editor.rs index ce37254..46483f6 100644 --- a/musicus/src/import/medium_editor.rs +++ b/musicus/src/import/medium_editor.rs @@ -1,4 +1,4 @@ -use super::track_set_editor::{TrackSetData, TrackSetEditor}; +use super::track_set_editor::{TrackData, TrackSetData, TrackSetEditor}; use crate::navigator::{NavigationHandle, Screen}; use crate::widgets::{List, Widget}; use anyhow::Result; @@ -26,9 +26,9 @@ pub struct MediumEditor { track_sets: RefCell>, } -impl Screen, Medium> for MediumEditor { +impl Screen<(Arc, Option), Medium> for MediumEditor { /// Create a new medium editor. - fn new(session: Arc, handle: NavigationHandle) -> Rc { + fn new((session, medium): (Arc, Option), handle: NavigationHandle) -> Rc { // Create UI let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/medium_editor.ui"); @@ -126,6 +126,37 @@ impl Screen, Medium> for MediumEditor { this.handle.pop(None); })); + // Initialize, if necessary. + + if let Some(medium) = medium { + this.name_entry.set_text(&medium.name); + + let mut track_sets: Vec = Vec::new(); + + for track in medium.tracks { + let track_data = TrackData { + track_source: track.source_index, + work_parts: track.work_parts, + }; + + if let Some(track_set) = track_sets.last_mut() { + if track.recording.id == track_set.recording.id { + track_set.tracks.push(track_data); + continue; + } + } + + track_sets.push(TrackSetData { + recording: track.recording, + tracks: vec![track_data], + }); + } + + let length = track_sets.len(); + this.track_sets.replace(track_sets); + this.track_set_list.update(length); + } + this } } diff --git a/musicus/src/import/medium_preview.rs b/musicus/src/import/medium_preview.rs index 829ab69..d945c7d 100644 --- a/musicus/src/import/medium_preview.rs +++ b/musicus/src/import/medium_preview.rs @@ -1,12 +1,14 @@ +use super::medium_editor::MediumEditor; use crate::navigator::{NavigationHandle, Screen}; use crate::widgets::Widget; -use anyhow::Result; +use anyhow::{anyhow, Result}; use gettextrs::gettext; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; use musicus_backend::db::Medium; use musicus_backend::import::{ImportSession, State}; +use std::cell::RefCell; use std::path::PathBuf; use std::rc::Rc; use std::sync::Arc; @@ -15,10 +17,12 @@ use std::sync::Arc; pub struct MediumPreview { handle: NavigationHandle<()>, session: Arc, - medium: Medium, + medium: RefCell>, widget: gtk::Box, import_button: gtk::Button, done_stack: gtk::Stack, + name_label: gtk::Label, + medium_box: gtk::Box, } impl Screen<(Arc, Medium), ()> for MediumPreview { @@ -30,6 +34,7 @@ impl Screen<(Arc, Medium), ()> for MediumPreview { get_widget!(builder, gtk::Box, widget); get_widget!(builder, gtk::Button, back_button); + get_widget!(builder, gtk::Button, edit_button); get_widget!(builder, gtk::Button, import_button); get_widget!(builder, gtk::Stack, done_stack); get_widget!(builder, gtk::Box, medium_box); @@ -38,10 +43,12 @@ impl Screen<(Arc, Medium), ()> for MediumPreview { let this = Rc::new(Self { handle, session, - medium, + medium: RefCell::new(None), widget, import_button, done_stack, + name_label, + medium_box, }); // Connect signals and callbacks @@ -50,6 +57,15 @@ impl Screen<(Arc, Medium), ()> for MediumPreview { this.handle.pop(None); })); + edit_button.connect_clicked(clone!(@weak this => move |_| { + spawn!(@clone this, async move { + let old_medium = this.medium.borrow().clone().unwrap(); + if let Some(medium) = push!(this.handle, MediumEditor, (this.session.clone(), Some(old_medium))).await { + this.set_medium(medium); + } + }); + })); + this.import_button.connect_clicked(clone!(@weak this => move |_| { spawn!(@clone this, async move { this.import().await.unwrap(); @@ -57,16 +73,50 @@ impl Screen<(Arc, Medium), ()> for MediumPreview { }); })); - // Populate the widget + this.set_medium(medium); - name_label.set_text(&this.medium.name); + this.handle_state(&this.session.state()); + spawn!(@clone this, async move { + loop { + let state = this.session.state_change().await; + this.handle_state(&state); + + match state { + State::Ready | State::Error => break, + _ => (), + } + } + }); + + this + } +} + +impl MediumPreview { + /// Set a new medium and update the view accordingly. + fn set_medium(&self, medium: Medium) { + self.name_label.set_text(&medium.name); + + if let Some(widget) = self.medium_box.get_first_child() { + let mut child = widget; + + loop { + let next_child = child.get_next_sibling(); + self.medium_box.remove(&child); + + match next_child { + Some(widget) => child = widget, + None => break, + } + } + } let mut last_recording_id = ""; let mut last_list = None::; - let import_tracks = this.session.tracks(); + let import_tracks = self.session.tracks(); - for track in &this.medium.tracks { + for track in &medium.tracks { if track.recording.id != last_recording_id { last_recording_id = &track.recording.id; @@ -88,9 +138,7 @@ impl Screen<(Arc, Medium), ()> for MediumPreview { .build(); frame.set_child(Some(list)); - medium_box.append(&frame); - - last_list = None; + self.medium_box.append(&frame); } last_list = Some(list); @@ -125,27 +173,12 @@ impl Screen<(Arc, Medium), ()> for MediumPreview { .build(); frame.set_child(Some(list)); - medium_box.append(&frame); + self.medium_box.append(&frame); } - this.handle_state(&this.session.state()); - spawn!(@clone this, async move { - loop { - let state = this.session.state_change().await; - this.handle_state(&state); - - match state { - State::Ready | State::Error => break, - _ => (), - } - } - }); - - this + self.medium.replace(Some(medium)); } -} -impl MediumPreview { /// Handle a state change of the import process. fn handle_state(&self, state: &State) { match state { @@ -161,10 +194,13 @@ impl MediumPreview { /// Copy the tracks to the music library and add the medium to the database. async fn import(&self) -> Result<()> { + let medium = self.medium.borrow(); + let medium = medium.as_ref().ok_or(anyhow!("No medium set!"))?; + // Create a new directory in the music library path for the imported medium. let music_library_path = self.handle.backend.get_music_library_path().unwrap(); - let directory = PathBuf::from(&self.medium.id); + let directory = PathBuf::from(&medium.id); std::fs::create_dir(&music_library_path.join(&directory))?; // Copy the tracks to the music library. @@ -172,7 +208,7 @@ impl MediumPreview { let mut tracks = Vec::new(); let import_tracks = self.session.tracks(); - for track in &self.medium.tracks { + for track in &medium.tracks { let mut track = track.clone(); // Set the track path to the new audio file location. @@ -190,9 +226,9 @@ impl MediumPreview { // Add the modified medium to the database. let medium = Medium { - id: self.medium.id.clone(), - name: self.medium.name.clone(), - discid: self.medium.discid.clone(), + id: medium.id.clone(), + name: medium.name.clone(), + discid: medium.discid.clone(), tracks, };