file_dialogs/
file_dialogs.rs

1#![allow(deprecated)]
2
3use gtk::prelude::*;
4use relm4::{
5    Component, ComponentController, ComponentParts, ComponentSender, Controller, RelmApp,
6    RelmWidgetExt, SimpleComponent, gtk,
7};
8use relm4_components::{open_dialog::*, save_dialog::*};
9
10use std::path::PathBuf;
11
12struct App {
13    open_dialog: Controller<OpenDialog>,
14    save_dialog: Controller<SaveDialog>,
15    buffer: gtk::TextBuffer,
16    file_name: Option<String>,
17    message: Option<String>,
18}
19
20#[derive(Debug)]
21enum Input {
22    OpenRequest,
23    OpenResponse(PathBuf),
24    SaveRequest,
25    SaveResponse(PathBuf),
26    ShowMessage(String),
27    ResetMessage,
28    Ignore,
29}
30
31#[relm4::component]
32impl SimpleComponent for App {
33    type Init = ();
34    type Input = Input;
35    type Output = ();
36
37    view! {
38        root = gtk::ApplicationWindow {
39            set_default_size: (600, 400),
40
41            #[watch]
42            set_title: Some(model.file_name.as_deref().unwrap_or_default()),
43
44            #[wrap(Some)]
45            set_titlebar = &gtk::HeaderBar {
46                pack_start = &gtk::Button {
47                    set_label: "Open",
48                    connect_clicked => Input::OpenRequest,
49                },
50                pack_end = &gtk::Button {
51                    set_label: "Save As",
52                    connect_clicked => Input::SaveRequest,
53
54                    #[watch]
55                    set_sensitive: model.file_name.is_some(),
56                }
57            },
58
59            gtk::Box {
60                set_orientation: gtk::Orientation::Vertical,
61                set_margin_all: 5,
62
63                gtk::ScrolledWindow {
64                    set_min_content_height: 380,
65
66                    #[wrap(Some)]
67                    set_child = &gtk::TextView {
68                        set_buffer: Some(&model.buffer),
69
70                        #[watch]
71                        set_visible: model.file_name.is_some(),
72                    },
73                },
74            }
75        }
76    }
77
78    fn post_view() {
79        if let Some(text) = &model.message {
80            let dialog = gtk::MessageDialog::builder()
81                .text(text)
82                .use_markup(true)
83                .transient_for(&widgets.root)
84                .modal(true)
85                .buttons(gtk::ButtonsType::Ok)
86                .build();
87            dialog.connect_response(|dialog, _| dialog.destroy());
88            dialog.set_visible(true);
89            sender.input(Input::ResetMessage);
90        }
91    }
92
93    fn init(
94        _: Self::Init,
95        root: Self::Root,
96        sender: ComponentSender<Self>,
97    ) -> ComponentParts<Self> {
98        let open_dialog = OpenDialog::builder()
99            .transient_for_native(&root)
100            .launch(OpenDialogSettings::default())
101            .forward(sender.input_sender(), |response| match response {
102                OpenDialogResponse::Accept(path) => Input::OpenResponse(path),
103                OpenDialogResponse::Cancel => Input::Ignore,
104            });
105
106        let save_dialog = SaveDialog::builder()
107            .transient_for_native(&root)
108            .launch(SaveDialogSettings::default())
109            .forward(sender.input_sender(), |response| match response {
110                SaveDialogResponse::Accept(path) => Input::SaveResponse(path),
111                SaveDialogResponse::Cancel => Input::Ignore,
112            });
113
114        let model = App {
115            open_dialog,
116            save_dialog,
117            buffer: gtk::TextBuffer::new(None),
118            file_name: None,
119            message: None,
120        };
121
122        let widgets = view_output!();
123
124        sender.input(Input::ShowMessage(String::from(
125            "A simple text editor showing the usage of\n<b>OpenFileDialog</b> and <b>SaveFileDialog</b> components.\n\nStart by clicking <b>Open</b> on the header bar.",
126        )));
127
128        ComponentParts { model, widgets }
129    }
130
131    fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
132        match message {
133            Input::OpenRequest => self.open_dialog.emit(OpenDialogMsg::Open),
134            Input::OpenResponse(path) => match std::fs::read_to_string(&path) {
135                Ok(contents) => {
136                    self.buffer.set_text(&contents);
137                    self.file_name = Some(
138                        path.file_name()
139                            .expect("The path has no file name")
140                            .to_str()
141                            .expect("Cannot convert file name to string")
142                            .to_string(),
143                    );
144                }
145                Err(e) => sender.input(Input::ShowMessage(e.to_string())),
146            },
147            Input::SaveRequest => self
148                .save_dialog
149                .emit(SaveDialogMsg::SaveAs(self.file_name.clone().unwrap())),
150            Input::SaveResponse(path) => match std::fs::write(
151                &path,
152                self.buffer
153                    .text(&self.buffer.start_iter(), &self.buffer.end_iter(), false),
154            ) {
155                Ok(_) => {
156                    sender.input(Input::ShowMessage(format!(
157                        "File saved successfully at {path:?}"
158                    )));
159                    self.buffer.set_text("");
160                    self.file_name = None;
161                }
162                Err(e) => sender.input(Input::ShowMessage(e.to_string())),
163            },
164            Input::ShowMessage(message) => {
165                self.message = Some(message);
166            }
167            Input::ResetMessage => {
168                self.message = None;
169            }
170            Input::Ignore => {}
171        }
172    }
173}
174
175fn main() {
176    let app = RelmApp::new("relm4.example.file_dialogs");
177    app.run::<App>(());
178}