gtk4/dialog.rs
1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{
4 cell::{Cell, RefCell},
5 future::Future,
6 pin::Pin,
7 ptr,
8 rc::Rc,
9};
10
11use glib::translate::*;
12
13use crate::{ffi, prelude::*, Dialog, DialogFlags, ResponseType, Widget, Window};
14
15impl Dialog {
16 /// Creates a new [`Dialog`][crate::Dialog] with the given title and transient parent.
17 ///
18 /// The @flags argument can be used to make the dialog modal, have it
19 /// destroyed along with its transient parent, or make it use a headerbar.
20 ///
21 /// Button text/response ID pairs should be listed in pairs, with a [`None`]
22 /// pointer ending the list. Button text can be arbitrary text. A response
23 /// ID can be any positive number, or one of the values in the
24 /// [`ResponseType`][crate::ResponseType] enumeration. If the user clicks one of these
25 /// buttons, [`Dialog`][crate::Dialog] will emit the [`response`][struct@crate::Dialog#response] signal
26 /// with the corresponding response ID.
27 ///
28 /// If a [`Dialog`][crate::Dialog] receives a delete event, it will emit ::response with a
29 /// response ID of [`ResponseType::DeleteEvent`][crate::ResponseType::DeleteEvent].
30 ///
31 /// However, destroying a dialog does not emit the ::response signal;
32 /// so be careful relying on ::response when using the
33 /// [`DialogFlags::DESTROY_WITH_PARENT`][crate::DialogFlags::DESTROY_WITH_PARENT] flag.
34 ///
35 /// Here’s a simple example:
36 /// **⚠️ The following code is in c ⚠️**
37 ///
38 /// ```c
39 /// GtkWindow *main_app_window; // Window the dialog should show up on
40 /// GtkWidget *dialog;
41 /// GtkDialogFlags flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT;
42 /// dialog = gtk_dialog_new_with_buttons ("My dialog",
43 /// main_app_window,
44 /// flags,
45 /// _("_OK"),
46 /// GTK_RESPONSE_ACCEPT,
47 /// _("_Cancel"),
48 /// GTK_RESPONSE_REJECT,
49 /// NULL);
50 /// ```
51 ///
52 /// # Deprecated since 4.10
53 ///
54 /// Use [`Window`][crate::Window] instead
55 /// ## `title`
56 /// Title of the dialog
57 /// ## `parent`
58 /// Transient parent of the dialog
59 /// ## `flags`
60 /// from [`DialogFlags`][crate::DialogFlags]
61 /// ## `first_button_text`
62 /// text to go in first button
63 ///
64 /// # Returns
65 ///
66 /// a new [`Dialog`][crate::Dialog]
67 #[doc(alias = "gtk_dialog_new_with_buttons")]
68 #[doc(alias = "new_with_buttons")]
69 #[cfg_attr(feature = "v4_10", deprecated = "Since 4.10")]
70 #[allow(deprecated)]
71 pub fn with_buttons<T: IsA<Window>>(
72 title: impl IntoOptionalGStr,
73 parent: Option<&T>,
74 flags: DialogFlags,
75 buttons: &[(&str, ResponseType)],
76 ) -> Self {
77 assert_initialized_main_thread!();
78 let ret: Self = unsafe {
79 title.run_with_gstr(|title| {
80 Widget::from_glib_none(ffi::gtk_dialog_new_with_buttons(
81 title.to_glib_none().0,
82 parent.map(|p| p.as_ref()).to_glib_none().0,
83 flags.into_glib(),
84 ptr::null_mut(),
85 ))
86 .unsafe_cast()
87 })
88 };
89
90 ret.add_buttons(buttons);
91 ret
92 }
93}
94
95// rustdoc-stripper-ignore-next
96/// Trait containing manually implemented methods of [`Dialog`](crate::Dialog).
97#[cfg_attr(feature = "v4_10", deprecated = "Since 4.10")]
98#[allow(deprecated)]
99pub trait DialogExtManual: IsA<Dialog> + 'static {
100 /// Adds multiple buttons.
101 ///
102 /// This is the same as calling [`DialogExt::add_button()`][crate::prelude::DialogExt::add_button()]
103 /// repeatedly. The variable argument list should be [`None`]-terminated
104 /// as with [`Dialog::with_buttons()`][crate::Dialog::with_buttons()]. Each button must have both
105 /// text and response ID.
106 ///
107 /// # Deprecated since 4.10
108 ///
109 /// Use [`Window`][crate::Window] instead
110 /// ## `first_button_text`
111 /// button text
112 #[doc(alias = "gtk_dialog_add_buttons")]
113 fn add_buttons(&self, buttons: &[(&str, ResponseType)]) {
114 for &(text, id) in buttons {
115 Self::add_button(self, text, id);
116 }
117 }
118
119 /// Gets the response id of a widget in the action area
120 /// of a dialog.
121 ///
122 /// # Deprecated since 4.10
123 ///
124 /// Use [`Window`][crate::Window] instead
125 /// ## `widget`
126 /// a widget in the action area of @self
127 ///
128 /// # Returns
129 ///
130 /// the response id of @widget, or [`ResponseType::None`][crate::ResponseType::None]
131 /// if @widget doesn’t have a response id set.
132 #[doc(alias = "gtk_dialog_get_response_for_widget")]
133 #[doc(alias = "get_response_for_widget")]
134 fn response_for_widget<P: IsA<Widget>>(&self, widget: &P) -> ResponseType {
135 unsafe {
136 from_glib(ffi::gtk_dialog_get_response_for_widget(
137 AsRef::<Dialog>::as_ref(self).to_glib_none().0,
138 widget.as_ref().to_glib_none().0,
139 ))
140 }
141 }
142
143 // rustdoc-stripper-ignore-next
144 /// Shows the dialog and returns a `Future` that resolves to the
145 /// `ResponseType` on response.
146 ///
147 /// ```no_run
148 /// use gtk4::prelude::*;
149 ///
150 /// # async fn run() {
151 /// let dialog = gtk4::MessageDialog::builder()
152 /// .buttons(gtk4::ButtonsType::OkCancel)
153 /// .text("What is your answer?")
154 /// .build();
155 ///
156 /// let answer = dialog.run_future().await;
157 /// dialog.close();
158 /// println!("Answer: {:?}", answer);
159 /// # }
160 /// ```
161 fn run_future<'a>(&'a self) -> Pin<Box<dyn Future<Output = ResponseType> + 'a>> {
162 Box::pin(async move {
163 let (sender, receiver) = futures_channel::oneshot::channel();
164
165 let sender = Cell::new(Some(sender));
166
167 let response_handler = self.connect_response(move |_, response_type| {
168 if let Some(m) = sender.replace(None) {
169 let _result = m.send(response_type);
170 }
171 });
172
173 self.as_ref().present();
174
175 if let Ok(response) = receiver.await {
176 self.disconnect(response_handler);
177 response
178 } else {
179 ResponseType::None
180 }
181 })
182 }
183
184 // rustdoc-stripper-ignore-next
185 /// Shows the dialog and calls the callback when a response has been
186 /// received.
187 ///
188 /// **Important**: this function isn't blocking.
189 ///
190 /// ```no_run
191 /// use gtk4::prelude::*;
192 ///
193 /// let dialog = gtk4::MessageDialog::builder()
194 /// .buttons(gtk4::ButtonsType::OkCancel)
195 /// .text("What is your answer?")
196 /// .build();
197 ///
198 /// dialog.run_async(|obj, answer| {
199 /// obj.close();
200 /// println!("Answer: {:?}", answer);
201 /// });
202 /// ```
203 fn run_async<F: FnOnce(&Self, ResponseType) + 'static>(&self, f: F) {
204 let response_handler = Rc::new(RefCell::new(None));
205 let response_handler_clone = response_handler.clone();
206 let f = RefCell::new(Some(f));
207 *response_handler.borrow_mut() = Some(self.connect_response(move |s, response_type| {
208 if let Some(handler) = response_handler_clone.borrow_mut().take() {
209 s.disconnect(handler);
210 }
211 (*f.borrow_mut()).take().expect("cannot get callback")(s, response_type);
212 }));
213 self.as_ref().present();
214 }
215}
216
217impl<O: IsA<Dialog>> DialogExtManual for O {}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222 use crate as gtk4;
223
224 #[test]
225 async fn dialog_future() {
226 let dialog = Dialog::new();
227 glib::idle_add_local_once(glib::clone!(
228 #[strong]
229 dialog,
230 move || {
231 dialog.response(ResponseType::Ok);
232 }
233 ));
234 let response = dialog.run_future().await;
235 assert_eq!(response, ResponseType::Ok);
236 }
237}