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
95mod sealed {
96 pub trait Sealed {}
97 impl<T: super::IsA<super::Dialog>> Sealed for T {}
98}
99
100// rustdoc-stripper-ignore-next
101/// Trait containing manually implemented methods of [`Dialog`](crate::Dialog).
102#[cfg_attr(feature = "v4_10", deprecated = "Since 4.10")]
103#[allow(deprecated)]
104pub trait DialogExtManual: sealed::Sealed + IsA<Dialog> + 'static {
105 /// Adds multiple buttons.
106 ///
107 /// This is the same as calling [`DialogExt::add_button()`][crate::prelude::DialogExt::add_button()]
108 /// repeatedly. The variable argument list should be [`None`]-terminated
109 /// as with [`Dialog::with_buttons()`][crate::Dialog::with_buttons()]. Each button must have both
110 /// text and response ID.
111 ///
112 /// # Deprecated since 4.10
113 ///
114 /// Use [`Window`][crate::Window] instead
115 /// ## `first_button_text`
116 /// button text
117 #[doc(alias = "gtk_dialog_add_buttons")]
118 fn add_buttons(&self, buttons: &[(&str, ResponseType)]) {
119 for &(text, id) in buttons {
120 Self::add_button(self, text, id);
121 }
122 }
123
124 /// Gets the response id of a widget in the action area
125 /// of a dialog.
126 ///
127 /// # Deprecated since 4.10
128 ///
129 /// Use [`Window`][crate::Window] instead
130 /// ## `widget`
131 /// a widget in the action area of @self
132 ///
133 /// # Returns
134 ///
135 /// the response id of @widget, or [`ResponseType::None`][crate::ResponseType::None]
136 /// if @widget doesn’t have a response id set.
137 #[doc(alias = "gtk_dialog_get_response_for_widget")]
138 #[doc(alias = "get_response_for_widget")]
139 fn response_for_widget<P: IsA<Widget>>(&self, widget: &P) -> ResponseType {
140 unsafe {
141 from_glib(ffi::gtk_dialog_get_response_for_widget(
142 AsRef::<Dialog>::as_ref(self).to_glib_none().0,
143 widget.as_ref().to_glib_none().0,
144 ))
145 }
146 }
147
148 // rustdoc-stripper-ignore-next
149 /// Shows the dialog and returns a `Future` that resolves to the
150 /// `ResponseType` on response.
151 ///
152 /// ```no_run
153 /// use gtk4::prelude::*;
154 ///
155 /// # async fn run() {
156 /// let dialog = gtk4::MessageDialog::builder()
157 /// .buttons(gtk4::ButtonsType::OkCancel)
158 /// .text("What is your answer?")
159 /// .build();
160 ///
161 /// let answer = dialog.run_future().await;
162 /// dialog.close();
163 /// println!("Answer: {:?}", answer);
164 /// # }
165 /// ```
166 fn run_future<'a>(&'a self) -> Pin<Box<dyn Future<Output = ResponseType> + 'a>> {
167 Box::pin(async move {
168 let (sender, receiver) = futures_channel::oneshot::channel();
169
170 let sender = Cell::new(Some(sender));
171
172 let response_handler = self.connect_response(move |_, response_type| {
173 if let Some(m) = sender.replace(None) {
174 let _result = m.send(response_type);
175 }
176 });
177
178 self.as_ref().present();
179
180 if let Ok(response) = receiver.await {
181 self.disconnect(response_handler);
182 response
183 } else {
184 ResponseType::None
185 }
186 })
187 }
188
189 // rustdoc-stripper-ignore-next
190 /// Shows the dialog and calls the callback when a response has been
191 /// received.
192 ///
193 /// **Important**: this function isn't blocking.
194 ///
195 /// ```no_run
196 /// use gtk4::prelude::*;
197 ///
198 /// let dialog = gtk4::MessageDialog::builder()
199 /// .buttons(gtk4::ButtonsType::OkCancel)
200 /// .text("What is your answer?")
201 /// .build();
202 ///
203 /// dialog.run_async(|obj, answer| {
204 /// obj.close();
205 /// println!("Answer: {:?}", answer);
206 /// });
207 /// ```
208 fn run_async<F: FnOnce(&Self, ResponseType) + 'static>(&self, f: F) {
209 let response_handler = Rc::new(RefCell::new(None));
210 let response_handler_clone = response_handler.clone();
211 let f = RefCell::new(Some(f));
212 *response_handler.borrow_mut() = Some(self.connect_response(move |s, response_type| {
213 if let Some(handler) = response_handler_clone.borrow_mut().take() {
214 s.disconnect(handler);
215 }
216 (*f.borrow_mut()).take().expect("cannot get callback")(s, response_type);
217 }));
218 self.as_ref().present();
219 }
220}
221
222impl<O: IsA<Dialog>> DialogExtManual for O {}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227 use crate as gtk4;
228
229 #[test]
230 async fn dialog_future() {
231 let dialog = Dialog::new();
232 glib::idle_add_local_once(glib::clone!(
233 #[strong]
234 dialog,
235 move || {
236 dialog.response(ResponseType::Ok);
237 }
238 ));
239 let response = dialog.run_future().await;
240 assert_eq!(response, ResponseType::Ok);
241 }
242}