aboutsummaryrefslogtreecommitdiff
path: root/src/subtitle_selection_dialog.rs
diff options
context:
space:
mode:
authorMalte Voos <git@mal.tc>2025-10-07 20:57:48 +0200
committerMalte Voos <git@mal.tc>2025-10-07 20:57:48 +0200
commitee29a3b1291e9cedd8b54c31fa9f273e39f51970 (patch)
treee41665482ef2668e0313adc9701f03384152b208 /src/subtitle_selection_dialog.rs
parent8aa48d67b0908b62d012b589df9b35f2f8551968 (diff)
downloadlleap-ee29a3b1291e9cedd8b54c31fa9f273e39f51970.tar.gz
lleap-ee29a3b1291e9cedd8b54c31fa9f273e39f51970.zip
revamp subtitle selection
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);
+}