file_dialogs/
file_dialogs.rs1#![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 = >k::HeaderBar {
46 pack_start = >k::Button {
47 set_label: "Open",
48 connect_clicked => Input::OpenRequest,
49 },
50 pack_end = >k::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 = >k::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}