1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// Take a look at the license at the top of the repository in the LICENSE file.

use crate::prelude::*;
use crate::Dialog;
use crate::DialogFlags;
use crate::ResponseType;
use crate::Widget;
use crate::Window;
use glib::translate::*;
use std::cell::Cell;
use std::future::Future;
use std::pin::Pin;
use std::ptr;

impl Dialog {
    /// Creates a new [`Dialog`][crate::Dialog] with title `title` (or [`None`] for the default
    /// title; see [`GtkWindowExt::set_title()`][crate::prelude::GtkWindowExt::set_title()]) and transient parent `parent` (or
    /// [`None`] for none; see [`GtkWindowExt::set_transient_for()`][crate::prelude::GtkWindowExt::set_transient_for()]). The `flags`
    /// argument can be used to make the dialog modal ([`DialogFlags::MODAL`][crate::DialogFlags::MODAL])
    /// and/or to have it destroyed along with its transient parent
    /// ([`DialogFlags::DESTROY_WITH_PARENT`][crate::DialogFlags::DESTROY_WITH_PARENT]). After `flags`, button
    /// text/response ID pairs should be listed, with a [`None`] pointer ending
    /// the list. Button text can be arbitrary text. A response ID can be
    /// any positive number, or one of the values in the [`ResponseType`][crate::ResponseType]
    /// enumeration. If the user clicks one of these dialog buttons,
    /// [`Dialog`][crate::Dialog] will emit the `signal::Dialog::response` signal with the corresponding
    /// response ID. If a [`Dialog`][crate::Dialog] receives the `signal::Widget::delete-event` signal,
    /// it will emit ::response with a response ID of [`ResponseType::DeleteEvent`][crate::ResponseType::DeleteEvent].
    /// However, destroying a dialog does not emit the ::response signal;
    /// so be careful relying on ::response when using the
    /// [`DialogFlags::DESTROY_WITH_PARENT`][crate::DialogFlags::DESTROY_WITH_PARENT] flag. Buttons are from left to right,
    /// so the first button in the list will be the leftmost button in the dialog.
    ///
    /// Here’s a simple example:
    ///
    ///
    /// **⚠️ The following code is in C ⚠️**
    ///
    /// ```C
    ///  GtkWidget *main_app_window; // Window the dialog should show up on
    ///  GtkWidget *dialog;
    ///  GtkDialogFlags flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT;
    ///  dialog = gtk_dialog_new_with_buttons ("My dialog",
    ///                                        main_app_window,
    ///                                        flags,
    ///                                        _("_OK"),
    ///                                        GTK_RESPONSE_ACCEPT,
    ///                                        _("_Cancel"),
    ///                                        GTK_RESPONSE_REJECT,
    ///                                        NULL);
    /// ```
    /// ## `title`
    /// Title of the dialog, or [`None`]
    /// ## `parent`
    /// Transient parent of the dialog, or [`None`]
    /// ## `flags`
    /// from [`DialogFlags`][crate::DialogFlags]
    /// ## `first_button_text`
    /// text to go in first button, or [`None`]
    ///
    /// # Returns
    ///
    /// a new [`Dialog`][crate::Dialog]
    #[doc(alias = "gtk_dialog_new_with_buttons")]
    pub fn with_buttons<T: IsA<Window>>(
        title: Option<&str>,
        parent: Option<&T>,
        flags: DialogFlags,
        buttons: &[(&str, ResponseType)],
    ) -> Dialog {
        assert_initialized_main_thread!();
        let ret: Dialog = unsafe {
            Widget::from_glib_none(ffi::gtk_dialog_new_with_buttons(
                title.to_glib_none().0,
                parent.map(|p| p.as_ref()).to_glib_none().0,
                flags.into_glib(),
                ptr::null_mut(),
            ))
            .unsafe_cast()
        };

        ret.add_buttons(buttons);
        ret
    }
}

pub trait DialogExtManual: 'static {
    #[doc(alias = "gtk_dialog_add_buttons")]
    fn add_buttons(&self, buttons: &[(&str, ResponseType)]);

    // rustdoc-stripper-ignore-next
    /// Shows the dialog and returns a `Future` that resolves to the
    /// `ResponseType` on response.
    ///
    /// ```no_run
    /// use gtk::prelude::*;
    ///
    /// # async fn run() {
    /// let dialog = gtk::MessageDialogBuilder::new()
    ///    .buttons(gtk::ButtonsType::OkCancel)
    ///    .text("What is your answer?")
    ///    .build();
    ///
    /// let answer = dialog.run_future().await;
    /// dialog.close();
    /// println!("Answer: {:?}", answer);
    /// # }
    /// ```
    fn run_future<'a>(&'a self) -> Pin<Box<dyn Future<Output = ResponseType> + 'a>>;
}

impl<O: IsA<Dialog> + IsA<Widget>> DialogExtManual for O {
    fn add_buttons(&self, buttons: &[(&str, ResponseType)]) {
        for &(text, id) in buttons {
            //FIXME: self.add_button don't work on 1.8
            O::add_button(self, text, id);
        }
    }

    fn run_future<'a>(&'a self) -> Pin<Box<dyn Future<Output = ResponseType> + 'a>> {
        Box::pin(async move {
            let (sender, receiver) = futures_channel::oneshot::channel();

            let sender = Cell::new(Some(sender));

            let response_handler = self.connect_response(move |_, response_type| {
                if let Some(m) = sender.replace(None) {
                    let _result = m.send(response_type);
                }
            });

            self.show();

            if let Ok(response) = receiver.await {
                if response != ResponseType::DeleteEvent {
                    self.disconnect(response_handler);
                }
                response
            } else {
                ResponseType::None
            }
        })
    }
}