use gtk::prelude::{Cast, FileChooserExt, FileExt, ListModelExt, NativeDialogExt};
use relm4::{gtk, ComponentParts, ComponentSender, SimpleComponent};
use std::{fmt::Debug, marker::PhantomData, path::PathBuf};
pub type OpenDialog = OpenDialogInner<SingleSelection>;
pub type OpenDialogMulti = OpenDialogInner<MultiSelection>;
pub trait Select: Debug {
type Selection: Debug;
const SELECT_MULTIPLE: bool;
fn select(dialog: >k::FileChooserNative) -> Self::Selection;
}
#[derive(Debug)]
pub struct SingleSelection;
impl Select for SingleSelection {
type Selection = PathBuf;
const SELECT_MULTIPLE: bool = false;
fn select(dialog: >k::FileChooserNative) -> Self::Selection {
dialog
.file()
.expect("No file selected")
.path()
.expect("No path")
}
}
#[derive(Debug)]
pub struct MultiSelection;
impl Select for MultiSelection {
type Selection = Vec<PathBuf>;
const SELECT_MULTIPLE: bool = true;
fn select(dialog: >k::FileChooserNative) -> Self::Selection {
let list_model = dialog.files();
(0..list_model.n_items())
.filter_map(|index| list_model.item(index))
.filter_map(|obj| obj.downcast::<gtk::gio::File>().ok())
.filter_map(|file| file.path())
.collect()
}
}
#[derive(Clone, Debug)]
pub struct OpenDialogSettings {
pub folder_mode: bool,
pub cancel_label: String,
pub accept_label: String,
pub create_folders: bool,
pub is_modal: bool,
pub filters: Vec<gtk::FileFilter>,
}
impl Default for OpenDialogSettings {
fn default() -> Self {
OpenDialogSettings {
folder_mode: false,
accept_label: String::from("Open"),
cancel_label: String::from("Cancel"),
create_folders: true,
is_modal: true,
filters: Vec::new(),
}
}
}
#[derive(Debug)]
pub struct OpenDialogInner<S: Select> {
visible: bool,
_phantom: PhantomData<S>,
}
#[derive(Debug, Clone)]
pub enum OpenDialogMsg {
Open,
#[doc(hidden)]
Hide,
}
#[derive(Debug, Clone)]
pub enum OpenDialogResponse<S: Select> {
Accept(S::Selection),
Cancel,
}
#[relm4::component(pub)]
impl<S: Select + 'static> SimpleComponent for OpenDialogInner<S> {
type Init = OpenDialogSettings;
type Input = OpenDialogMsg;
type Output = OpenDialogResponse<S>;
view! {
gtk::FileChooserNative {
set_action: if settings.folder_mode {
gtk::FileChooserAction::SelectFolder
} else {
gtk::FileChooserAction::Open
},
set_select_multiple: S::SELECT_MULTIPLE,
set_create_folders: settings.create_folders,
set_modal: settings.is_modal,
set_accept_label: Some(&settings.accept_label),
set_cancel_label: Some(&settings.cancel_label),
#[iterate]
add_filter: &settings.filters,
#[watch]
set_visible: model.visible,
connect_response[sender] => move |dialog, res_ty| {
match res_ty {
gtk::ResponseType::Accept => {
let selection = S::select(dialog);
sender.output(OpenDialogResponse::Accept(selection)).unwrap();
}
_ => sender.output(OpenDialogResponse::Cancel).unwrap(),
}
sender.input(OpenDialogMsg::Hide);
}
}
}
fn init(
settings: Self::Init,
root: Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
let model = OpenDialogInner {
visible: false,
_phantom: PhantomData,
};
let widgets = view_output!();
ComponentParts { model, widgets }
}
fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {
match message {
OpenDialogMsg::Open => {
self.visible = true;
}
OpenDialogMsg::Hide => {
self.visible = false;
}
}
}
}