summary refs log tree commit diff
path: root/src/subtitle_selection_dialog.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/subtitle_selection_dialog.rs')
-rw-r--r--src/subtitle_selection_dialog.rs257
1 files changed, 257 insertions, 0 deletions
diff --git a/src/subtitle_selection_dialog.rs b/src/subtitle_selection_dialog.rs
new file mode 100644
index 0000000..0c7f1cd
--- /dev/null
+++ b/src/subtitle_selection_dialog.rs
@@ -0,0 +1,257 @@
+use adw::prelude::*;
+use gtk::{gio, glib};
+use relm4::prelude::*;
+
+use crate::subtitle_extractor::{StreamIndex, TRACKS};
+use crate::util::Tracker;
+
+// Custom GObject wrapper for subtitle track information
+glib::wrapper! {
+    pub struct SubtitleTrackInfo(ObjectSubclass<imp::SubtitleTrackInfo>);
+}
+
+impl SubtitleTrackInfo {
+    pub fn new(
+        stream_index: StreamIndex,
+        language: Option<&'static str>,
+        title: Option<String>,
+    ) -> Self {
+        glib::Object::builder()
+            .property("stream-index", stream_index as i64)
+            .property("language", language.unwrap_or_default())
+            .property("title", title.unwrap_or_default())
+            .build()
+    }
+
+    pub fn get_stream_index(&self) -> StreamIndex {
+        let index: i64 = self.property("stream-index");
+        index as usize
+    }
+}
+
+mod imp {
+    use gtk::{glib, prelude::*, subclass::prelude::*};
+    use std::cell::RefCell;
+
+    #[derive(Default, glib::Properties)]
+    #[properties(wrapper_type = super::SubtitleTrackInfo)]
+    pub struct SubtitleTrackInfo {
+        #[property(get, set)]
+        stream_index: RefCell<i64>,
+        #[property(get, set)]
+        language: RefCell<String>,
+        #[property(get, set)]
+        title: RefCell<String>,
+    }
+
+    #[glib::object_subclass]
+    impl ObjectSubclass for SubtitleTrackInfo {
+        const NAME: &'static str = "SubtitleTrackInfo";
+        type Type = super::SubtitleTrackInfo;
+    }
+
+    #[glib::derived_properties]
+    impl ObjectImpl for SubtitleTrackInfo {}
+}
+
+pub struct SubtitleSelectionDialog {
+    parent_window: adw::ApplicationWindow,
+    dialog: adw::PreferencesDialog,
+    track_list_model: Tracker<gio::ListStore>,
+    primary_track_ix: Option<StreamIndex>,
+    secondary_track_ix: Option<StreamIndex>,
+}
+
+#[derive(Debug)]
+pub enum SubtitleSelectionDialogMsg {
+    Show,
+    PrimaryTrackChanged(Option<StreamIndex>),
+    SecondaryTrackChanged(Option<StreamIndex>),
+}
+
+#[derive(Debug)]
+pub enum SubtitleSelectionDialogOutput {
+    PrimaryTrackSelected(Option<StreamIndex>),
+    SecondaryTrackSelected(Option<StreamIndex>),
+}
+
+#[relm4::component(pub)]
+impl SimpleComponent for SubtitleSelectionDialog {
+    type Init = adw::ApplicationWindow;
+    type Input = SubtitleSelectionDialogMsg;
+    type Output = SubtitleSelectionDialogOutput;
+
+    view! {
+        #[root]
+        adw::PreferencesDialog {
+            set_title: "Subtitle Settings",
+            add: &page,
+        },
+
+        #[name(page)]
+        adw::PreferencesPage {
+            adw::PreferencesGroup {
+                #[name(primary_combo)]
+                adw::ComboRow {
+                    set_title: "Primary Subtitle Track",
+                    set_subtitle: "Main subtitle track for learning",
+                    set_factory: Some(&track_factory),
+                    #[track(model.track_list_model.is_dirty())]
+                    set_model: Some(model.track_list_model.get()),
+                    #[track(model.track_list_model.is_dirty())]
+                    set_selected: model.primary_track_ix.map_or(gtk::INVALID_LIST_POSITION, |ix| get_list_ix_from_stream_ix(model.track_list_model.get(), ix)),
+                    connect_selected_notify[sender] => move |combo| {
+                        let stream_index = get_stream_ix_from_combo(combo);
+                        sender.input(SubtitleSelectionDialogMsg::PrimaryTrackChanged(stream_index));
+                    },
+                },
+
+                #[name(secondary_combo)]
+                adw::ComboRow {
+                    set_title: "Secondary Subtitle Track",
+                    set_subtitle: "Optional second track for comparison",
+                    set_factory: Some(&track_factory),
+                    #[track(model.track_list_model.is_dirty())]
+                    set_model: Some(model.track_list_model.get()),
+                    #[track(model.track_list_model.is_dirty())]
+                    set_selected: model.secondary_track_ix.map_or(gtk::INVALID_LIST_POSITION, |ix| get_list_ix_from_stream_ix(model.track_list_model.get(), ix)),
+                    connect_selected_notify[sender] => move |combo| {
+                        let stream_index = get_stream_ix_from_combo(combo);
+                        sender.input(SubtitleSelectionDialogMsg::SecondaryTrackChanged(stream_index));
+                    },
+                },
+            }
+        },
+
+        #[name(track_factory)]
+        gtk::SignalListItemFactory {
+            connect_setup => move |_, list_item| {
+                let list_item = list_item.downcast_ref::<gtk::ListItem>().unwrap();
+                let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
+
+                let language_label = gtk::Label::new(None);
+                language_label.set_halign(gtk::Align::Start);
+                language_label.set_ellipsize(gtk::pango::EllipsizeMode::End);
+
+                let title_label = gtk::Label::new(None);
+                title_label.set_halign(gtk::Align::Start);
+                title_label.set_ellipsize(gtk::pango::EllipsizeMode::End);
+                title_label.add_css_class("subtitle");
+
+                vbox.append(&language_label);
+                vbox.append(&title_label);
+                list_item.set_child(Some(&vbox));
+            },
+            connect_bind => move |_, list_item| {
+                let list_item = list_item.downcast_ref::<gtk::ListItem>().unwrap();
+                let item = list_item.item().unwrap();
+                let track_info = item.downcast_ref::<SubtitleTrackInfo>().unwrap();
+                let vbox = list_item.child().unwrap().downcast::<gtk::Box>().unwrap();
+                let language_label = vbox.first_child().unwrap().downcast::<gtk::Label>().unwrap();
+                let title_label = vbox.last_child().unwrap().downcast::<gtk::Label>().unwrap();
+
+                let language = track_info.language();
+                let title = track_info.title();
+
+                let language_text = if !language.is_empty() {
+                    &language
+                } else {
+                    "Unknown Language"
+                };
+
+                language_label.set_text(&language_text);
+                title_label.set_text(&title);
+                title_label.set_visible(!title.is_empty());
+            },
+        },
+    }
+
+    fn init(
+        parent_window: Self::Init,
+        root: Self::Root,
+        sender: ComponentSender<Self>,
+    ) -> ComponentParts<Self> {
+        let track_list_model = gio::ListStore::new::<SubtitleTrackInfo>();
+
+        let model = Self {
+            parent_window,
+            dialog: root.clone(),
+            track_list_model: Tracker::new(track_list_model),
+            primary_track_ix: None,
+            secondary_track_ix: None,
+        };
+
+        let widgets = view_output!();
+
+        ComponentParts { model, widgets }
+    }
+
+    fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
+        self.track_list_model.reset();
+
+        match msg {
+            SubtitleSelectionDialogMsg::Show => {
+                self.update_combo_models();
+                self.dialog.present(Some(&self.parent_window));
+            }
+            SubtitleSelectionDialogMsg::PrimaryTrackChanged(stream_index) => {
+                self.primary_track_ix = stream_index;
+                sender
+                    .output(SubtitleSelectionDialogOutput::PrimaryTrackSelected(
+                        stream_index,
+                    ))
+                    .unwrap();
+            }
+            SubtitleSelectionDialogMsg::SecondaryTrackChanged(stream_index) => {
+                self.secondary_track_ix = stream_index;
+                sender
+                    .output(SubtitleSelectionDialogOutput::SecondaryTrackSelected(
+                        stream_index,
+                    ))
+                    .unwrap();
+            }
+        }
+    }
+}
+
+impl SubtitleSelectionDialog {
+    fn update_combo_models(&mut self) {
+        let tracks = TRACKS.read();
+
+        // Clear previous entries
+        self.track_list_model.get_mut().remove_all();
+
+        // Add all available tracks
+        for (&stream_index, track) in tracks.iter() {
+            let track_info = SubtitleTrackInfo::new(
+                stream_index,
+                track.language.map(|lang| lang.to_name()),
+                track.title.clone(),
+            );
+            self.track_list_model.get_mut().append(&track_info);
+        }
+    }
+}
+
+fn get_stream_ix_from_combo(combo: &adw::ComboRow) -> Option<StreamIndex> {
+    let ix = combo
+        .selected_item()?
+        .downcast_ref::<SubtitleTrackInfo>()
+        .unwrap()
+        .get_stream_index();
+
+    Some(ix)
+}
+
+fn get_list_ix_from_stream_ix(list_model: &gio::ListStore, stream_ix: StreamIndex) -> u32 {
+    for i in 0..list_model.n_items() {
+        if let Some(item) = list_model.item(i) {
+            if let Some(track_info) = item.downcast_ref::<SubtitleTrackInfo>() {
+                if track_info.get_stream_index() == stream_ix {
+                    return i;
+                }
+            }
+        }
+    }
+    panic!("Stream index {} not found in list model", stream_ix);
+}