diff options
Diffstat (limited to 'src/subtitle_selection_dialog.rs')
-rw-r--r-- | src/subtitle_selection_dialog.rs | 257 |
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); +} |