[libadwaita/wip/exalm/message-dialog] Add AdwMessageDialog
- From: Alexander Mikhaylenko <alexm src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libadwaita/wip/exalm/message-dialog] Add AdwMessageDialog
- Date: Sat, 25 Jun 2022 02:54:22 +0000 (UTC)
commit acef637d36f0009eb80da51d97aeca7a27977511
Author: Alexander Mikhaylenko <alexm gnome org>
Date: Fri Jun 24 19:59:41 2022 +0400
Add AdwMessageDialog
demo/adw-demo-window.c | 2 +
demo/adw-demo-window.ui | 8 +
demo/adwaita-demo.gresources.xml | 2 +
.../scalable/actions/widget-dialog-symbolic.svg | 2 +
demo/meson.build | 1 +
demo/pages/dialogs/adw-demo-page-dialogs.c | 90 ++
demo/pages/dialogs/adw-demo-page-dialogs.h | 11 +
demo/pages/dialogs/adw-demo-page-dialogs.ui | 24 +
src/adw-gtkbuilder-utils-private.h | 50 +
src/adw-gtkbuilder-utils.c | 138 ++
src/adw-message-dialog.c | 1680 ++++++++++++++++++++
src/adw-message-dialog.h | 138 ++
src/adw-message-dialog.ui | 121 ++
src/adwaita.gresources.xml | 1 +
src/adwaita.h | 1 +
src/meson.build | 3 +
src/stylesheet/widgets/_message-dialog.scss | 65 +-
17 files changed, 2316 insertions(+), 21 deletions(-)
---
diff --git a/demo/adw-demo-window.c b/demo/adw-demo-window.c
index befb473e..b15faf9c 100644
--- a/demo/adw-demo-window.c
+++ b/demo/adw-demo-window.c
@@ -7,6 +7,7 @@
#include "pages/buttons/adw-demo-page-buttons.h"
#include "pages/carousel/adw-demo-page-carousel.h"
#include "pages/clamp/adw-demo-page-clamp.h"
+#include "pages/dialogs/adw-demo-page-dialogs.h"
#include "pages/flap/adw-demo-page-flap.h"
#include "pages/leaflet/adw-demo-page-leaflet.h"
#include "pages/lists/adw-demo-page-lists.h"
@@ -120,6 +121,7 @@ adw_demo_window_init (AdwDemoWindow *self)
g_type_ensure (ADW_TYPE_DEMO_PAGE_BUTTONS);
g_type_ensure (ADW_TYPE_DEMO_PAGE_CAROUSEL);
g_type_ensure (ADW_TYPE_DEMO_PAGE_CLAMP);
+ g_type_ensure (ADW_TYPE_DEMO_PAGE_DIALOGS);
g_type_ensure (ADW_TYPE_DEMO_PAGE_FLAP);
g_type_ensure (ADW_TYPE_DEMO_PAGE_LEAFLET);
g_type_ensure (ADW_TYPE_DEMO_PAGE_LISTS);
diff --git a/demo/adw-demo-window.ui b/demo/adw-demo-window.ui
index 4ce61d67..2353d47f 100644
--- a/demo/adw-demo-window.ui
+++ b/demo/adw-demo-window.ui
@@ -218,6 +218,14 @@
</property>
</object>
</child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="title" translatable="yes">Dialogs</property>
+ <property name="child">
+ <object class="AdwDemoPageDialogs"/>
+ </property>
+ </object>
+ </child>
</object>
</child>
</object>
diff --git a/demo/adwaita-demo.gresources.xml b/demo/adwaita-demo.gresources.xml
index 80f528b9..b27a6b98 100644
--- a/demo/adwaita-demo.gresources.xml
+++ b/demo/adwaita-demo.gresources.xml
@@ -27,6 +27,7 @@
<file preprocess="xml-stripblanks">icons/scalable/actions/view-sidebar-end-symbolic-rtl.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/widget-carousel-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/widget-clamp-symbolic.svg</file>
+ <file preprocess="xml-stripblanks">icons/scalable/actions/widget-dialog-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/widget-flap-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/widget-leaflet-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/widget-list-symbolic.svg</file>
@@ -47,6 +48,7 @@
<file preprocess="xml-stripblanks">pages/buttons/adw-demo-page-buttons.ui</file>
<file preprocess="xml-stripblanks">pages/carousel/adw-demo-page-carousel.ui</file>
<file preprocess="xml-stripblanks">pages/clamp/adw-demo-page-clamp.ui</file>
+ <file preprocess="xml-stripblanks">pages/dialogs/adw-demo-page-dialogs.ui</file>
<file preprocess="xml-stripblanks">pages/flap/adw-demo-page-flap.ui</file>
<file preprocess="xml-stripblanks">pages/flap/adw-flap-demo-window.ui</file>
<file preprocess="xml-stripblanks">pages/leaflet/adw-demo-page-leaflet.ui</file>
diff --git a/demo/icons/scalable/actions/widget-dialog-symbolic.svg
b/demo/icons/scalable/actions/widget-dialog-symbolic.svg
new file mode 100644
index 00000000..3d31e026
--- /dev/null
+++ b/demo/icons/scalable/actions/widget-dialog-symbolic.svg
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g
fill="#222222"><path d="m 2 1.007812 c -0.550781 0 -1 0.449219 -1 1 v 1.984376 h 2 v -0.984376 h 9 v 0.992188
h 2 v -1.992188 c 0 -0.550781 -0.449219 -1 -1 -1 z m 10 12 v 0.992188 h -9 v -0.984375 h -2 v 1.984375 c 0
0.550781 0.449219 1 1 1 h 11 c 0.550781 0 1 -0.449219 1 -1 v -1.992188 z m 0 0" fill-opacity="0.34902"/><path
d="m 4 4 c -0.550781 0 -1 0.449219 -1 1 v 7.007812 c 0 0.550782 0.449219 1 1 1 h 7 c 0.550781 0 1 -0.449218 1
-1 v -7.007812 c 0 -0.550781 -0.449219 -1 -1 -1 z m 0 2 h 7 v 3.992188 h -7 z m 0 4.992188 h 3 v 1.007812 h
-3 z m 4 0 h 3 v 1.007812 h -3 z m 0 0"/></g></svg>
diff --git a/demo/meson.build b/demo/meson.build
index 6af941fe..13280a64 100644
--- a/demo/meson.build
+++ b/demo/meson.build
@@ -17,6 +17,7 @@ adwaita_demo_sources = [
'pages/buttons/adw-demo-page-buttons.c',
'pages/carousel/adw-demo-page-carousel.c',
'pages/clamp/adw-demo-page-clamp.c',
+ 'pages/dialogs/adw-demo-page-dialogs.c',
'pages/flap/adw-demo-page-flap.c',
'pages/flap/adw-flap-demo-window.c',
'pages/leaflet/adw-demo-page-leaflet.c',
diff --git a/demo/pages/dialogs/adw-demo-page-dialogs.c b/demo/pages/dialogs/adw-demo-page-dialogs.c
new file mode 100644
index 00000000..2db73053
--- /dev/null
+++ b/demo/pages/dialogs/adw-demo-page-dialogs.c
@@ -0,0 +1,90 @@
+#include "adw-demo-page-dialogs.h"
+
+#include <glib/gi18n.h>
+
+struct _AdwDemoPageDialogs
+{
+ AdwBin parent_instance;
+};
+
+G_DEFINE_TYPE (AdwDemoPageDialogs, adw_demo_page_dialogs, ADW_TYPE_BIN)
+
+static void
+message_response_cb (AdwMessageDialog *dialog,
+ const char *response)
+{
+ g_print ("Response: %s\n", response);
+}
+
+static void
+message_save_cb (AdwMessageDialog *dialog)
+{
+ GtkWidget *entry = adw_message_dialog_get_extra_child (dialog);
+
+ g_print ("Entry contents: %s\n", gtk_editable_get_text (GTK_EDITABLE (entry)));
+}
+
+static void
+entry_changed_cb (GtkEditable *entry,
+ AdwMessageDialog *dialog)
+{
+ const char *text = gtk_editable_get_text (entry);
+
+ adw_message_dialog_set_response_enabled (dialog, "save", text && *text);
+
+ if (text && *text)
+ gtk_widget_remove_css_class (GTK_WIDGET (entry), "error");
+ else
+ gtk_widget_add_css_class (GTK_WIDGET (entry), "error");
+}
+
+static void
+demo_message_dialog_cb (AdwDemoPageDialogs *self)
+{
+ GtkWindow *parent = GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (self)));
+ GtkWidget *entry = gtk_entry_new ();
+ GtkWidget *dialog;
+
+ gtk_entry_set_placeholder_text (GTK_ENTRY (entry), "Extra child");
+ gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
+
+ dialog = adw_message_dialog_new (parent, _("Save Changes?"), NULL);
+ adw_message_dialog_format_secondary_text (ADW_MESSAGE_DIALOG (dialog),
+ _("Open document ā%sā contains unsaved changes. Changes which
are not saved will be permanently lost."),
+ "Something");
+ adw_message_dialog_set_extra_child (ADW_MESSAGE_DIALOG (dialog), entry);
+
+ adw_message_dialog_add_responses (ADW_MESSAGE_DIALOG (dialog),
+ "cancel", _("_Cancel"),
+ "discard", _("_Discard All"),
+ "save", _("_Save"),
+ NULL);
+
+ adw_message_dialog_set_response_enabled (ADW_MESSAGE_DIALOG (dialog), "save", FALSE);
+ adw_message_dialog_set_response_destructive (ADW_MESSAGE_DIALOG (dialog), "discard", TRUE);
+
+ adw_message_dialog_set_default_response (ADW_MESSAGE_DIALOG (dialog), "save");
+
+ g_signal_connect (entry, "changed", G_CALLBACK (entry_changed_cb), dialog);
+
+ g_signal_connect (dialog, "response", G_CALLBACK (message_response_cb), self);
+ g_signal_connect (dialog, "response::save", G_CALLBACK (message_save_cb), self);
+
+ gtk_window_present (GTK_WINDOW (dialog));
+}
+
+static void
+adw_demo_page_dialogs_class_init (AdwDemoPageDialogsClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/Adwaita1/Demo/ui/pages/dialogs/adw-demo-page-dialogs.ui");
+
+ gtk_widget_class_install_action (widget_class, "demo.message-dialog", NULL, (GtkWidgetActionActivateFunc)
demo_message_dialog_cb);
+}
+
+static void
+adw_demo_page_dialogs_init (AdwDemoPageDialogs *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
diff --git a/demo/pages/dialogs/adw-demo-page-dialogs.h b/demo/pages/dialogs/adw-demo-page-dialogs.h
new file mode 100644
index 00000000..73190a8e
--- /dev/null
+++ b/demo/pages/dialogs/adw-demo-page-dialogs.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <adwaita.h>
+
+G_BEGIN_DECLS
+
+#define ADW_TYPE_DEMO_PAGE_DIALOGS (adw_demo_page_dialogs_get_type())
+
+G_DECLARE_FINAL_TYPE (AdwDemoPageDialogs, adw_demo_page_dialogs, ADW, DEMO_PAGE_DIALOGS, AdwBin)
+
+G_END_DECLS
diff --git a/demo/pages/dialogs/adw-demo-page-dialogs.ui b/demo/pages/dialogs/adw-demo-page-dialogs.ui
new file mode 100644
index 00000000..eaccadcb
--- /dev/null
+++ b/demo/pages/dialogs/adw-demo-page-dialogs.ui
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <requires lib="libadwaita" version="1.0"/>
+ <template class="AdwDemoPageDialogs" parent="AdwBin">
+ <property name="child">
+ <object class="AdwStatusPage">
+ <property name="icon-name">widget-dialog-symbolic</property>
+ <property name="title" translatable="yes">Dialogs</property>
+ <property name="description" translatable="yes">Adaptive dialog widgets.</property>
+ <property name="child">
+ <object class="GtkButton">
+ <property name="label" translatable="yes">Message Dialog</property>
+ <property name="halign">center</property>
+ <property name="action-name">demo.message-dialog</property>
+ <style>
+ <class name="pill"/>
+ </style>
+ </object>
+ </property>
+ </object>
+ </property>
+ </template>
+</interface>
diff --git a/src/adw-gtkbuilder-utils-private.h b/src/adw-gtkbuilder-utils-private.h
new file mode 100644
index 00000000..b3a99fb3
--- /dev/null
+++ b/src/adw-gtkbuilder-utils-private.h
@@ -0,0 +1,50 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1998-2002 James Henstridge <james daa com au>
+ * Copyright (C) 2006-2007 Async Open Source,
+ * Johan Dahlin <jdahlin async com br>,
+ * Henrique Romano <henrique async com br>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#if !defined(_ADWAITA_INSIDE) && !defined(ADWAITA_COMPILATION)
+#error "Only <adwaita.h> can be included directly."
+#endif
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+gboolean _gtk_builder_check_parent (GtkBuilder *builder,
+ GtkBuildableParseContext *context,
+ const gchar *parent_name,
+ GError **error);
+
+void _gtk_builder_prefix_error (GtkBuilder *builder,
+ GtkBuildableParseContext *context,
+ GError **error);
+
+void _gtk_builder_error_unhandled_tag (GtkBuilder *builder,
+ GtkBuildableParseContext *context,
+ const char *object,
+ const char *element_name,
+ GError **error);
+
+const char *_gtk_builder_parser_translate (const char *domain,
+ const char *context,
+ const char *text);
+
+G_END_DECLS
diff --git a/src/adw-gtkbuilder-utils.c b/src/adw-gtkbuilder-utils.c
new file mode 100644
index 00000000..32974743
--- /dev/null
+++ b/src/adw-gtkbuilder-utils.c
@@ -0,0 +1,138 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1998-2002 James Henstridge <james daa com au>
+ * Copyright (C) 2006-2007 Async Open Source,
+ * Johan Dahlin <jdahlin async com br>,
+ * Henrique Romano <henrique async com br>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Copied and modified from gtkbuilder.c. */
+
+#include "adw-gtkbuilder-utils-private.h"
+
+/*< private >
+ * @builder: a `GtkBuilder`
+ * @context: the `GtkBuildableParseContext`
+ * @parent_name: the name of the expected parent element
+ * @error: return location for an error
+ *
+ * Checks that the parent element of the currently handled
+ * start tag is @parent_name and set @error if it isn't.
+ *
+ * This is intended to be called in start_element vfuncs to
+ * ensure that element nesting is as intended.
+ *
+ * Returns: %TRUE if @parent_name is the parent element
+ */
+gboolean
+_gtk_builder_check_parent (GtkBuilder *builder,
+ GtkBuildableParseContext *context,
+ const gchar *parent_name,
+ GError **error)
+{
+ GPtrArray *stack;
+ int line, col;
+ const char *parent;
+ const char *element;
+
+ stack = gtk_buildable_parse_context_get_element_stack (context);
+
+ element = g_ptr_array_index (stack, stack->len - 1);
+ parent = stack->len > 1 ? g_ptr_array_index (stack, stack->len - 2) : "";
+
+ if (g_str_equal (parent_name, parent) ||
+ (g_str_equal (parent_name, "object") && g_str_equal (parent, "template")))
+ return TRUE;
+
+ gtk_buildable_parse_context_get_position (context, &line, &col);
+ g_set_error (error,
+ GTK_BUILDER_ERROR,
+ GTK_BUILDER_ERROR_INVALID_TAG,
+ "%d:%d Can't use <%s> here",
+ line, col, element);
+
+ return FALSE;
+}
+
+/*< private >
+ * _gtk_builder_prefix_error:
+ * @builder: a `GtkBuilder`
+ * @context: the `GtkBuildableParseContext`
+ * @error: an error
+ *
+ * Calls g_prefix_error() to prepend a filename:line:column marker
+ * to the given error. The filename is taken from @builder, and
+ * the line and column are obtained by calling
+ * g_markup_parse_context_get_position().
+ *
+ * This is intended to be called on errors returned by
+ * g_markup_collect_attributes() in a start_element vfunc.
+ */
+void
+_gtk_builder_prefix_error (GtkBuilder *builder,
+ GtkBuildableParseContext *context,
+ GError **error)
+{
+ int line, col;
+
+ gtk_buildable_parse_context_get_position (context, &line, &col);
+ g_prefix_error (error, ":%d:%d ", line, col);
+}
+
+/*< private >
+ * _gtk_builder_error_unhandled_tag:
+ * @builder: a `GtkBuilder`
+ * @context: the `GtkBuildableParseContext`
+ * @object: name of the object that is being handled
+ * @element_name: name of the element whose start tag is being handled
+ * @error: return location for the error
+ *
+ * Sets @error to a suitable error indicating that an @element_name
+ * tag is not expected in the custom markup for @object.
+ *
+ * This is intended to be called in a start_element vfunc.
+ */
+void
+_gtk_builder_error_unhandled_tag (GtkBuilder *builder,
+ GtkBuildableParseContext *context,
+ const char *object,
+ const char *element_name,
+ GError **error)
+{
+ int line, col;
+
+ gtk_buildable_parse_context_get_position (context, &line, &col);
+ g_set_error (error,
+ GTK_BUILDER_ERROR,
+ GTK_BUILDER_ERROR_UNHANDLED_TAG,
+ "%d:%d Unsupported tag for %s: <%s>",
+ line, col,
+ object, element_name);
+}
+
+const char *
+_gtk_builder_parser_translate (const char *domain,
+ const char *context,
+ const char *text)
+{
+ const char *s;
+
+ if (context)
+ s = g_dpgettext2 (domain, context, text);
+ else
+ s = g_dgettext (domain, text);
+
+ return s;
+}
diff --git a/src/adw-message-dialog.c b/src/adw-message-dialog.c
new file mode 100644
index 00000000..df32ec41
--- /dev/null
+++ b/src/adw-message-dialog.c
@@ -0,0 +1,1680 @@
+/*
+ * Copyright (C) 2022 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * Author: Alexander Mikhaylenko <alexander mikhaylenko puri sm>
+ */
+
+#include "config.h"
+#include "adw-message-dialog.h"
+
+#include "adw-gtkbuilder-utils-private.h"
+#include "adw-squeezer.h"
+
+/**
+ * AdwMessageDialog:
+ *
+ * TODO
+ *
+ * ```xml
+ * <object class="AdwMessageDialog">
+ * <property name="text" translatable="yes">Save Changes?</property>
+ * <property name="secondary-text" translatable="yes">Open documents contain unsaved changes.</property>
+ * <property name="default-response">save</property>
+ * <responses>
+ * <response id="cancel" translatable="yes">_Cancel</response>
+ * <response id="discard" translatable="yes" destructive="true">_Discard</response>
+ * <response id="save" translatable="yes">_Save</response>
+ * </responses>
+ * </object>
+ * ```
+ *
+ * ## Accessibility
+ *
+ * `AdwMessageDialog` uses the `GTK_ACCESSIBLE_ROLE_DIALOG` role.
+ *
+ * Since: 1.2
+ */
+
+#define DIALOG_MARGIN 30
+
+typedef struct {
+ AdwMessageDialog *dialog;
+ GQuark id;
+ char *label;
+ gboolean destructive;
+ gboolean enabled;
+
+ GtkWidget *wide_button;
+ GtkWidget *narrow_button;
+} ResponseInfo;
+
+typedef struct
+{
+ GtkWidget *label;
+ GtkWidget *secondary_label;
+ GtkBox *message_area;
+ GtkBox *wide_action_area;
+ GtkBox *narrow_action_area;
+ AdwSqueezer *action_box;
+
+ char *text;
+ gboolean use_markup;
+ char *secondary_text;
+ gboolean secondary_use_markup;
+ GtkWidget *child;
+
+ GList *responses;
+ GQuark default_response;
+ GQuark close_response;
+
+ gboolean block_close_response;
+
+ GtkWindow *parent_window;
+ int parent_width;
+ int parent_height;
+} AdwMessageDialogPrivate;
+
+static void adw_message_dialog_buildable_init (GtkBuildableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (AdwMessageDialog, adw_message_dialog, GTK_TYPE_WINDOW,
+ G_ADD_PRIVATE (AdwMessageDialog)
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, adw_message_dialog_buildable_init))
+
+static GtkBuildableIface *parent_buildable_iface;
+
+enum {
+ PROP_0,
+ PROP_TEXT,
+ PROP_USE_MARKUP,
+ PROP_SECONDARY_TEXT,
+ PROP_SECONDARY_USE_MARKUP,
+ PROP_EXTRA_CHILD,
+ PROP_DEFAULT_RESPONSE,
+ PROP_CLOSE_RESPONSE,
+ LAST_PROP,
+};
+
+static GParamSpec *props[LAST_PROP];
+
+enum {
+ SIGNAL_RESPONSE,
+ SIGNAL_LAST_SIGNAL,
+};
+static guint signals[SIGNAL_LAST_SIGNAL];
+
+static void
+response_info_free (ResponseInfo *info)
+{
+ g_free (info->label);
+ g_free (info);
+}
+
+static ResponseInfo *
+find_response_by_quark (AdwMessageDialog *self,
+ GQuark id)
+{
+ AdwMessageDialogPrivate *priv = adw_message_dialog_get_instance_private (self);
+ GList *l;
+
+ for (l = priv->responses; l; l = l->next) {
+ ResponseInfo *info = l->data;
+
+ if (info->id == id)
+ return info;
+ }
+
+ return NULL;
+}
+
+static inline ResponseInfo *
+find_response (AdwMessageDialog *self,
+ const char *id)
+{
+ return find_response_by_quark (self, g_quark_try_string (id));
+}
+
+static void
+parent_size_cb (AdwMessageDialog *self)
+{
+ AdwMessageDialogPrivate *priv = adw_message_dialog_get_instance_private (self);
+ int w = gtk_widget_get_allocated_width (GTK_WIDGET (priv->parent_window));
+ int h = gtk_widget_get_allocated_height (GTK_WIDGET (priv->parent_window));
+
+ if (w == priv->parent_width && h == priv->parent_height)
+ return;
+
+ priv->parent_width = w;
+ priv->parent_height = h;
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+static void
+parent_realize_cb (AdwMessageDialog *self)
+{
+ AdwMessageDialogPrivate *priv = adw_message_dialog_get_instance_private (self);
+ GdkSurface *surface;
+
+ g_assert (GTK_IS_NATIVE (priv->parent_window));
+
+ surface = gtk_native_get_surface (GTK_NATIVE (priv->parent_window));
+
+ g_signal_connect_swapped (surface, "compute-size",
+ G_CALLBACK (parent_size_cb), self);
+ g_signal_connect_swapped (surface, "notify::width",
+ G_CALLBACK (parent_size_cb), self);
+ g_signal_connect_swapped (surface, "notify::height",
+ G_CALLBACK (parent_size_cb), self);
+
+ parent_size_cb (self);
+}
+
+static void
+parent_unrealize_cb (AdwMessageDialog *self)
+{
+ AdwMessageDialogPrivate *priv = adw_message_dialog_get_instance_private (self);
+ GdkSurface *surface;
+
+ g_assert (GTK_IS_NATIVE (priv->parent_window));
+
+ surface = gtk_native_get_surface (GTK_NATIVE (priv->parent_window));
+
+ g_signal_handlers_disconnect_by_func (surface,
+ G_CALLBACK (parent_size_cb),
+ self);
+
+ priv->parent_width = -1;
+ priv->parent_height = -1;
+}
+
+static void
+set_parent (AdwMessageDialog *self,
+ GtkWindow *parent)
+{
+ AdwMessageDialogPrivate *priv = adw_message_dialog_get_instance_private (self);
+
+ if (priv->parent_window == parent)
+ return;
+
+ if (priv->parent_window) {
+ g_signal_handlers_disconnect_by_func (priv->parent_window,
+ G_CALLBACK (parent_realize_cb),
+ self);
+ g_signal_handlers_disconnect_by_func (priv->parent_window,
+ G_CALLBACK (parent_unrealize_cb),
+ self);
+
+ if (gtk_widget_get_realized (GTK_WIDGET (priv->parent_window)))
+ parent_unrealize_cb (self);
+ }
+
+ priv->parent_window = parent;
+
+ if (priv->parent_window) {
+ if (gtk_widget_get_realized (GTK_WIDGET (priv->parent_window)))
+ parent_realize_cb (self);
+
+ g_signal_connect_swapped (priv->parent_window, "realize",
+ G_CALLBACK (parent_realize_cb), self);
+ g_signal_connect_swapped (priv->parent_window, "unrealize",
+ G_CALLBACK (parent_unrealize_cb), self);
+ }
+}
+
+static void
+parent_changed_cb (AdwMessageDialog *self)
+{
+ GtkWindow *transient_for = gtk_window_get_transient_for (GTK_WINDOW (self));
+
+ set_parent (self, transient_for);
+}
+
+static void
+button_clicked_cb (ResponseInfo *info)
+{
+ AdwMessageDialog *self = info->dialog;
+
+ AdwMessageDialogPrivate *priv = adw_message_dialog_get_instance_private (self);
+
+ g_object_ref (self);
+ priv->block_close_response = TRUE;
+
+ gtk_window_close (GTK_WINDOW (self));
+ g_signal_emit (self, signals[SIGNAL_RESPONSE], info->id, g_quark_to_string (info->id));
+
+ priv->block_close_response = FALSE;
+ g_object_unref (self);
+}
+
+static GtkWidget *
+create_response_button (AdwMessageDialog *self,
+ ResponseInfo *info)
+{
+ GtkWidget *button = gtk_button_new_with_mnemonic (info->label);
+
+ if (info->destructive)
+ gtk_widget_add_css_class (button, "destructive-action");
+
+ gtk_widget_set_sensitive (button, info->enabled);
+
+ g_signal_connect_swapped (button, "clicked", G_CALLBACK (button_clicked_cb), info);
+
+ return button;
+}
+
+static void
+update_default_response (AdwMessageDialog *self)
+{
+ AdwMessageDialogPrivate *priv = adw_message_dialog_get_instance_private (self);
+ ResponseInfo *info;
+
+ if (!priv->default_response)
+ return;
+
+ info = find_response_by_quark (self, priv->default_response);
+
+ if (!info)
+ return;
+
+ if (adw_squeezer_get_visible_child (priv->action_box) == GTK_WIDGET (priv->narrow_action_area))
+ gtk_window_set_default_widget (GTK_WINDOW (self), info->narrow_button);
+ else
+ gtk_window_set_default_widget (GTK_WINDOW (self), info->wide_button);
+}
+
+static gboolean
+adw_message_dialog_close_request (GtkWindow *window)
+{
+ AdwMessageDialog *self = ADW_MESSAGE_DIALOG (window);
+ AdwMessageDialogPrivate *priv = adw_message_dialog_get_instance_private (self);
+
+ if (!priv->block_close_response)
+ g_signal_emit (self, signals[SIGNAL_RESPONSE],
+ priv->close_response,
+ g_quark_to_string (priv->close_response));
+
+ return GTK_WINDOW_CLASS (adw_message_dialog_parent_class)->close_request (window);
+}
+
+static void
+adw_message_dialog_map (GtkWidget *widget)
+{
+ AdwMessageDialog *self = ADW_MESSAGE_DIALOG (widget);
+
+ GTK_WIDGET_CLASS (adw_message_dialog_parent_class)->map (widget);
+
+ update_default_response (self);
+}
+
+static void
+adw_message_dialog_measure (GtkWidget *widget,
+ GtkOrientation orientation,
+ int for_size,
+ int *min,
+ int *nat,
+ int *min_baseline,
+ int *nat_baseline)
+{
+ AdwMessageDialog *self = ADW_MESSAGE_DIALOG (widget);
+ AdwMessageDialogPrivate *priv = adw_message_dialog_get_instance_private (self);
+ int parent_size, max_size, base_min, base_nat;
+
+ if (min_baseline)
+ *min_baseline = -1;
+ if (nat_baseline)
+ *nat_baseline = -1;
+
+ GTK_WIDGET_CLASS (adw_message_dialog_parent_class)->measure (widget,
+ orientation,
+ for_size,
+ &base_min,
+ &base_nat,
+ NULL, NULL);
+
+ if (min)
+ *min = base_min;
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ parent_size = priv->parent_width;
+ else
+ parent_size = priv->parent_height;
+
+ if (parent_size < 0) {
+ if (nat)
+ *nat = base_nat;
+
+ return;
+ }
+
+ max_size = parent_size - DIALOG_MARGIN * 2;
+ max_size = MAX (base_min, max_size);
+
+ if (nat)
+ *nat = CLAMP (base_nat, base_min, max_size);
+}
+
+static void
+adw_message_dialog_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ AdwMessageDialog *self = ADW_MESSAGE_DIALOG (object);
+
+ switch (prop_id) {
+ case PROP_TEXT:
+ g_value_set_string (value, adw_message_dialog_get_text (self));
+ break;
+ case PROP_USE_MARKUP:
+ g_value_set_boolean (value, adw_message_dialog_get_use_markup (self));
+ break;
+ case PROP_SECONDARY_TEXT:
+ g_value_set_string (value, adw_message_dialog_get_secondary_text (self));
+ break;
+ case PROP_SECONDARY_USE_MARKUP:
+ g_value_set_boolean (value, adw_message_dialog_get_secondary_use_markup (self));
+ break;
+ case PROP_EXTRA_CHILD:
+ g_value_set_object (value, adw_message_dialog_get_extra_child (self));
+ break;
+ case PROP_DEFAULT_RESPONSE:
+ g_value_set_string (value, adw_message_dialog_get_default_response (self));
+ break;
+ case PROP_CLOSE_RESPONSE:
+ g_value_set_string (value, adw_message_dialog_get_close_response (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+adw_message_dialog_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ AdwMessageDialog *self = ADW_MESSAGE_DIALOG (object);
+
+ switch (prop_id) {
+ case PROP_TEXT:
+ adw_message_dialog_set_text (self, g_value_get_string (value));
+ break;
+ case PROP_USE_MARKUP:
+ adw_message_dialog_set_use_markup (self, g_value_get_boolean (value));
+ break;
+ case PROP_SECONDARY_TEXT:
+ adw_message_dialog_set_secondary_text (self, g_value_get_string (value));
+ break;
+ case PROP_SECONDARY_USE_MARKUP:
+ adw_message_dialog_set_secondary_use_markup (self, g_value_get_boolean (value));
+ break;
+ case PROP_EXTRA_CHILD:
+ adw_message_dialog_set_extra_child (self, g_value_get_object (value));
+ break;
+ case PROP_DEFAULT_RESPONSE:
+ adw_message_dialog_set_default_response (self, g_value_get_string (value));
+ break;
+ case PROP_CLOSE_RESPONSE:
+ adw_message_dialog_set_close_response (self, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+adw_message_dialog_dispose (GObject *object)
+{
+ AdwMessageDialog *self = ADW_MESSAGE_DIALOG (object);
+ AdwMessageDialogPrivate *priv = adw_message_dialog_get_instance_private (self);
+
+ set_parent (self, NULL);
+
+ if (priv->child) {
+ gtk_box_remove (priv->message_area, priv->child);
+ priv->child = NULL;
+ }
+
+ g_list_free_full (priv->responses, (GDestroyNotify) response_info_free);
+
+ G_OBJECT_CLASS (adw_message_dialog_parent_class)->dispose (object);
+}
+
+static void
+adw_message_dialog_finalize (GObject *object)
+{
+ AdwMessageDialog *self = ADW_MESSAGE_DIALOG (object);
+ AdwMessageDialogPrivate *priv = adw_message_dialog_get_instance_private (self);
+
+ g_clear_pointer (&priv->text, g_free);
+ g_clear_pointer (&priv->secondary_text, g_free);
+
+ G_OBJECT_CLASS (adw_message_dialog_parent_class)->finalize (object);
+}
+
+static void
+adw_message_dialog_class_init (AdwMessageDialogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkWindowClass *window_class = GTK_WINDOW_CLASS (klass);
+
+ object_class->get_property = adw_message_dialog_get_property;
+ object_class->set_property = adw_message_dialog_set_property;
+ object_class->dispose = adw_message_dialog_dispose;
+ object_class->finalize = adw_message_dialog_finalize;
+
+ widget_class->map = adw_message_dialog_map;
+ widget_class->measure = adw_message_dialog_measure;
+
+ window_class->close_request = adw_message_dialog_close_request;
+
+ /**
+ * AdwMessageDialog:text: (attributes org.gtk.Property.get=adw_message_dialog_get_text
org.gtk.Property.set=adw_message_dialog_set_text)
+ *
+ * TODO
+ *
+ * Since: 1.2
+ */
+ props[PROP_TEXT] =
+ g_param_spec_string ("text", NULL, NULL,
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * AdwMessageDialog:use-markup: (attributes org.gtk.Property.get=adw_message_dialog_get_use_markup
org.gtk.Property.set=adw_message_dialog_set_use_markup)
+ *
+ * TODO
+ *
+ * Since: 1.2
+ */
+ props[PROP_USE_MARKUP] =
+ g_param_spec_boolean ("use-markup", NULL, NULL,
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * AdwMessageDialog:secondary-text: (attributes org.gtk.Property.get=adw_message_dialog_get_secondary_text
org.gtk.Property.set=adw_message_dialog_set_secondary_text)
+ *
+ * TODO
+ *
+ * Since: 1.2
+ */
+ props[PROP_SECONDARY_TEXT] =
+ g_param_spec_string ("secondary-text", NULL, NULL,
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * AdwMessageDialog:secondary-use-markup: (attributes
org.gtk.Property.get=adw_message_dialog_get_secondary_use_markup
org.gtk.Property.set=adw_message_dialog_set_secondary_use_markup)
+ *
+ * TODO
+ *
+ * Since: 1.2
+ */
+ props[PROP_SECONDARY_USE_MARKUP] =
+ g_param_spec_boolean ("secondary-use-markup", NULL, NULL,
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * AdwMessageDialog:extra-child: (attributes org.gtk.Property.get=adw_message_dialog_get_extra_child
org.gtk.Property.set=adw_message_dialog_set_extra_child)
+ *
+ * TODO
+ *
+ * Since: 1.2
+ */
+ props[PROP_EXTRA_CHILD] =
+ g_param_spec_object ("extra-child", NULL, NULL,
+ GTK_TYPE_WIDGET,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * AdwMessageDialog:default-response: (attributes
org.gtk.Property.get=adw_message_dialog_get_default_response
org.gtk.Property.set=adw_message_dialog_set_default_response)
+ *
+ * TODO
+ *
+ * Since: 1.2
+ */
+ props[PROP_DEFAULT_RESPONSE] =
+ g_param_spec_string ("default-response", NULL, NULL,
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * AdwMessageDialog:close-response: (attributes org.gtk.Property.get=adw_message_dialog_get_close_response
org.gtk.Property.set=adw_message_dialog_set_close_response)
+ *
+ * TODO
+ *
+ * Since: 1.2
+ */
+ props[PROP_CLOSE_RESPONSE] =
+ g_param_spec_string ("close-response", NULL, NULL,
+ "close",
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, LAST_PROP, props);
+
+ /**
+ * AdwMessageDialog::response:
+ * @self: a message dialog
+ * @response: TODO
+ *
+ * TODO
+ *
+ * Since: 1.2
+ */
+ signals[SIGNAL_RESPONSE] =
+ g_signal_new ("response",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ G_STRUCT_OFFSET (AdwMessageDialogClass, response),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_STRING);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/Adwaita/ui/adw-message-dialog.ui");
+
+ gtk_widget_class_bind_template_child_private (widget_class, AdwMessageDialog, label);
+ gtk_widget_class_bind_template_child_private (widget_class, AdwMessageDialog, secondary_label);
+ gtk_widget_class_bind_template_child_private (widget_class, AdwMessageDialog, message_area);
+ gtk_widget_class_bind_template_child_private (widget_class, AdwMessageDialog, action_box);
+ gtk_widget_class_bind_template_child_private (widget_class, AdwMessageDialog, wide_action_area);
+ gtk_widget_class_bind_template_child_private (widget_class, AdwMessageDialog, narrow_action_area);
+
+ gtk_widget_class_bind_template_callback (widget_class, update_default_response);
+
+ gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Escape, 0, "window.close", NULL);
+
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_DIALOG);
+}
+
+static void
+adw_message_dialog_init (AdwMessageDialog *self)
+{
+ AdwMessageDialogPrivate *priv = adw_message_dialog_get_instance_private (self);
+
+ gtk_window_set_resizable (GTK_WINDOW (self), FALSE);
+ gtk_window_set_modal (GTK_WINDOW (self), TRUE);
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (self), TRUE);
+
+ priv->close_response = g_quark_from_string ("close");
+
+ priv->parent_width = -1;
+ priv->parent_height = -1;
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ parent_changed_cb (self);
+ g_signal_connect (self, "notify::transient-for",
+ G_CALLBACK (parent_changed_cb), self);
+}
+
+/* Custom tag handling was copied and modified
+ * from gtk-size-group.c and gtk-scale.c */
+
+typedef struct {
+ GObject *object;
+ GtkBuilder *builder;
+ GSList *responses;
+} ResponseParserData;
+
+typedef struct {
+ char *id;
+
+ GString *label;
+ char *context;
+ gboolean translatable;
+
+ gboolean destructive;
+ gboolean enabled;
+
+ int line;
+ int col;
+} ResponseData;
+
+static void
+response_data_free (gpointer data)
+{
+ ResponseData *response = data;
+
+ g_free (response->id);
+ g_string_free (response->label, TRUE);
+ g_free (response->context);
+ g_free (response);
+}
+
+static void
+response_start_element (GtkBuildableParseContext *context,
+ const char *element_name,
+ const char **names,
+ const char **values,
+ gpointer user_data,
+ GError **error)
+{
+ ResponseParserData *data = user_data;
+
+ if (strcmp (element_name, "response") == 0) {
+ const char *id;
+ const char *msg_context = NULL;
+ gboolean translatable = FALSE;
+ gboolean destructive = FALSE;
+ gboolean enabled = TRUE;
+ ResponseData *response;
+
+ if (!_gtk_builder_check_parent (data->builder, context, "responses", error))
+ return;
+
+ if (!g_markup_collect_attributes (element_name, names, values, error,
+ G_MARKUP_COLLECT_STRING, "id", &id,
+ G_MARKUP_COLLECT_BOOLEAN | G_MARKUP_COLLECT_OPTIONAL, "destructive",
&destructive,
+ G_MARKUP_COLLECT_TRISTATE | G_MARKUP_COLLECT_OPTIONAL, "enabled",
&enabled,
+ G_MARKUP_COLLECT_BOOLEAN | G_MARKUP_COLLECT_OPTIONAL, "translatable",
&translatable,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "comments", NULL,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "context",
&msg_context,
+ G_MARKUP_COLLECT_INVALID)) {
+ _gtk_builder_prefix_error (data->builder, context, error);
+ return;
+ }
+
+ /* Normalize a tri-state value */
+ enabled = enabled != FALSE;
+
+ response = g_new (ResponseData, 1);
+ response->id = g_strdup (id);
+ response->context = g_strdup (msg_context);
+ response->translatable = translatable;
+ response->label = g_string_new ("");
+ response->destructive = destructive;
+ response->enabled = enabled;
+
+ gtk_buildable_parse_context_get_position (context, &response->line, &response->col);
+ data->responses = g_slist_prepend (data->responses, response);
+ } else if (strcmp (element_name, "responses") == 0) {
+ if (!_gtk_builder_check_parent (data->builder, context, "object", error))
+ return;
+
+ if (!g_markup_collect_attributes (element_name, names, values, error,
+ G_MARKUP_COLLECT_INVALID, NULL, NULL,
+ G_MARKUP_COLLECT_INVALID))
+ _gtk_builder_prefix_error (data->builder, context, error);
+ } else {
+ _gtk_builder_error_unhandled_tag (data->builder, context,
+ "AdwMessageDialog", element_name,
+ error);
+ }
+}
+
+static void
+response_text (GtkBuildableParseContext *context,
+ const char *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ ResponseParserData *data = user_data;
+
+ if (strcmp (gtk_buildable_parse_context_get_element (context), "response") == 0) {
+ ResponseData *response = data->responses->data;
+
+ g_string_append_len (response->label, text, text_len);
+ }
+}
+
+static const GtkBuildableParser response_parser = {
+ response_start_element,
+ NULL,
+ response_text,
+ NULL
+};
+
+static gboolean
+adw_message_dialog_buildable_custom_tag_start (GtkBuildable *buildable,
+ GtkBuilder *builder,
+ GObject *child,
+ const char *tagname,
+ GtkBuildableParser *parser,
+ gpointer *parser_data)
+{
+ ResponseParserData *data;
+
+ if (child)
+ return FALSE;
+
+ if (strcmp (tagname, "responses") == 0) {
+ data = g_new0 (ResponseParserData, 1);
+ data->responses = NULL;
+ data->object = G_OBJECT (buildable);
+ data->builder = builder;
+
+ *parser = response_parser;
+ *parser_data = data;
+
+ return TRUE;
+ }
+
+ return parent_buildable_iface->custom_tag_start (buildable, builder, child,
+ tagname, parser, parser_data);
+}
+
+static void
+adw_message_dialog_buildable_custom_finished (GtkBuildable *buildable,
+ GtkBuilder *builder,
+ GObject *child,
+ const char *tagname,
+ gpointer user_data)
+{
+ GSList *l;
+ ResponseParserData *data;
+
+ if (strcmp (tagname, "responses") != 0) {
+ parent_buildable_iface->custom_finished (buildable, builder, child,
+ tagname, user_data);
+ return;
+ }
+
+ data = (ResponseParserData*)user_data;
+ data->responses = g_slist_reverse (data->responses);
+
+ for (l = data->responses; l; l = l->next) {
+ ResponseData *response = l->data;
+ const char *label;
+
+ if (response->translatable && response->label->len)
+ label = _gtk_builder_parser_translate (gtk_builder_get_translation_domain (builder),
+ response->context,
+ response->label->str);
+ else
+ label = response->label->str;
+
+ adw_message_dialog_add_response (ADW_MESSAGE_DIALOG (data->object),
+ response->id, label);
+
+ if (response->destructive)
+ adw_message_dialog_set_response_destructive (ADW_MESSAGE_DIALOG (data->object),
+ response->id, TRUE);
+
+ if (!response->enabled)
+ adw_message_dialog_set_response_enabled (ADW_MESSAGE_DIALOG (data->object),
+ response->id, FALSE);
+ }
+
+ g_slist_free_full (data->responses, response_data_free);
+ g_free (data);
+}
+
+static void
+adw_message_dialog_buildable_add_child (GtkBuildable *buildable,
+ GtkBuilder *builder,
+ GObject *child,
+ const char *type)
+{
+ AdwMessageDialog *self = ADW_MESSAGE_DIALOG (buildable);
+
+ if (GTK_IS_WIDGET (child))
+ adw_message_dialog_set_extra_child (self, GTK_WIDGET (child));
+ else
+ parent_buildable_iface->add_child (buildable, builder, child, type);
+}
+
+static void
+adw_message_dialog_buildable_init (GtkBuildableIface *iface)
+{
+ parent_buildable_iface = g_type_interface_peek_parent (iface);
+
+ iface->add_child = adw_message_dialog_buildable_add_child;
+ iface->custom_tag_start = adw_message_dialog_buildable_custom_tag_start;
+ iface->custom_finished = adw_message_dialog_buildable_custom_finished;
+}
+
+/**
+ * adw_message_dialog_new:
+ * @parent: (nullable): transient parent
+ * @text: (nullable): TODO
+ * @secondary_text: (nullable): TODO
+ *
+ * Creates a new `AdwMessageDialog`.
+ *
+ * If @text or @secondary_text are `NULL`, they will not be set.
+ *
+ * TODO
+ *
+ * Returns: the newly created `AdwMessageDialog`
+ *
+ * Since: 1.2
+ */
+GtkWidget *
+adw_message_dialog_new (GtkWindow *parent,
+ const char *text,
+ const char *secondary_text)
+{
+ GtkWidget *dialog;
+
+ g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), NULL);
+
+ dialog = g_object_new (ADW_TYPE_MESSAGE_DIALOG,
+ "transient-for", parent,
+ NULL);
+
+ if (text)
+ adw_message_dialog_set_text (ADW_MESSAGE_DIALOG (dialog), text);
+
+ if (secondary_text)
+ adw_message_dialog_set_secondary_text (ADW_MESSAGE_DIALOG (dialog), secondary_text);
+
+ return dialog;
+}
+
+/**
+ * adw_message_dialog_get_text: (attributes org.gtk.Method.get_property=text)
+ * @self: a message dialog
+ *
+ * Gets the TODO of @self.
+ *
+ * Returns: (nullable): the TODO of @self.
+ *
+ * Since: 1.2
+ */
+const char *
+adw_message_dialog_get_text (AdwMessageDialog *self)
+{
+ AdwMessageDialogPrivate *priv;
+
+ g_return_val_if_fail (ADW_IS_MESSAGE_DIALOG (self), NULL);
+
+ priv = adw_message_dialog_get_instance_private (self);
+
+ return priv->text;
+}
+
+/**
+ * adw_message_dialog_set_text: (attributes org.gtk.Method.set_property=text)
+ * @self: a message dialog
+ * @text: (nullable): the new value to set
+ *
+ * Sets the TODO of @self.
+ *
+ * Since: 1.2
+ */
+void
+adw_message_dialog_set_text (AdwMessageDialog *self,
+ const char *text)
+{
+ AdwMessageDialogPrivate *priv;
+
+ g_return_if_fail (ADW_IS_MESSAGE_DIALOG (self));
+ g_return_if_fail (text != NULL);
+
+ priv = adw_message_dialog_get_instance_private (self);
+
+ if (text == priv->text)
+ return;
+
+ g_free (priv->text);
+ priv->text = g_strdup (text);
+
+ gtk_label_set_label (GTK_LABEL (priv->label), priv->text);
+ gtk_widget_set_visible (priv->label, priv->text && *priv->text);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TEXT]);
+}
+
+/**
+ * adw_message_dialog_get_use_markup: (attributes org.gtk.Method.get_property=use-markup)
+ * @self: a message dialog
+ *
+ * Gets the TODO of @self.
+ *
+ * Returns: the TODO of @self.
+ *
+ * Since: 1.2
+ */
+gboolean
+adw_message_dialog_get_use_markup (AdwMessageDialog *self)
+{
+ AdwMessageDialogPrivate *priv;
+
+ g_return_val_if_fail (ADW_IS_MESSAGE_DIALOG (self), FALSE);
+
+ priv = adw_message_dialog_get_instance_private (self);
+
+ return priv->use_markup;
+}
+
+/**
+ * adw_message_dialog_set_use_markup: (attributes org.gtk.Method.set_property=use-markup)
+ * @self: a message dialog
+ * @use_markup: TODO
+ *
+ * Sets the TODO of @self.
+ *
+ * Since: 1.2
+ */
+void
+adw_message_dialog_set_use_markup (AdwMessageDialog *self,
+ gboolean use_markup)
+{
+ AdwMessageDialogPrivate *priv;
+
+ g_return_if_fail (ADW_IS_MESSAGE_DIALOG (self));
+
+ priv = adw_message_dialog_get_instance_private (self);
+
+ use_markup = !!use_markup;
+
+ if (use_markup == priv->use_markup)
+ return;
+
+ priv->use_markup = use_markup;
+
+ gtk_label_set_use_markup (GTK_LABEL (priv->label), use_markup);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_USE_MARKUP]);
+}
+
+
+/**
+ * adw_message_dialog_format_text:
+ * @self: a message dialog
+ * @format: the formatted string for the text
+ * @...: the parameters to insert into @format
+ *
+ * Sets the text of @self.
+ *
+ * Since: 1.2
+ */
+void
+adw_message_dialog_format_text (AdwMessageDialog *self,
+ const char *format,
+ ...)
+{
+ va_list args;
+
+ g_return_if_fail (ADW_IS_MESSAGE_DIALOG (self));
+ g_return_if_fail (format != NULL);
+
+ g_object_freeze_notify (G_OBJECT (self));
+
+ adw_message_dialog_set_secondary_use_markup (self, FALSE);
+
+ if (format) {
+ char *text;
+
+ va_start (args, format);
+ text = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ adw_message_dialog_set_secondary_text (self, text);
+
+ g_free (text);
+ } else {
+ adw_message_dialog_set_secondary_text (self, NULL);
+ }
+
+ g_object_thaw_notify (G_OBJECT (self));
+}
+
+/**
+ * adw_message_dialog_format_markup:
+ * @self: a message dialog
+ * @format: the formatted string for the text with Pango markup
+ * @...: the parameters to insert into @format
+ *
+ * Sets the text of @self.
+ *
+ * The @format is assumed to contain Pango markup.
+ *
+ * Special XML characters in the printf() arguments passed to this function will
+ * automatically be escaped as necessary, see [func@GLib.markup_printf_escaped].
+ *
+ * Since: 1.2
+ */
+void
+adw_message_dialog_format_markup (AdwMessageDialog *self,
+ const char *format,
+ ...)
+{
+ va_list args;
+
+ g_return_if_fail (ADW_IS_MESSAGE_DIALOG (self));
+ g_return_if_fail (format != NULL);
+
+ g_object_freeze_notify (G_OBJECT (self));
+
+ adw_message_dialog_set_use_markup (self, TRUE);
+
+ if (format) {
+ char *text;
+
+ va_start (args, format);
+ text = g_markup_vprintf_escaped (format, args);
+ va_end (args);
+
+ adw_message_dialog_set_text (self, text);
+
+ g_free (text);
+ } else {
+ adw_message_dialog_set_text (self, "");
+ }
+
+ g_object_thaw_notify (G_OBJECT (self));
+}
+
+/**
+ * adw_message_dialog_get_secondary_text: (attributes org.gtk.Method.get_property=secondary-text)
+ * @self: a message dialog
+ *
+ * Gets the TODO of @self.
+ *
+ * Returns: the TODO of @self.
+ *
+ * Since: 1.2
+ */
+const char *
+adw_message_dialog_get_secondary_text (AdwMessageDialog *self)
+{
+ AdwMessageDialogPrivate *priv;
+
+ g_return_val_if_fail (ADW_IS_MESSAGE_DIALOG (self), NULL);
+
+ priv = adw_message_dialog_get_instance_private (self);
+
+ return priv->secondary_text;
+}
+
+/**
+ * adw_message_dialog_set_secondary_text: (attributes org.gtk.Method.set_property=secondary-text)
+ * @self: a message dialog
+ * @text: the new value to set
+ *
+ * Sets the TODO of @self.
+ *
+ * Since: 1.2
+ */
+void
+adw_message_dialog_set_secondary_text (AdwMessageDialog *self,
+ const char *text)
+{
+ AdwMessageDialogPrivate *priv;
+
+ g_return_if_fail (ADW_IS_MESSAGE_DIALOG (self));
+ g_return_if_fail (text != NULL);
+
+ priv = adw_message_dialog_get_instance_private (self);
+
+ if (text == priv->secondary_text)
+ return;
+
+ g_free (priv->secondary_text);
+ priv->secondary_text = g_strdup (text);
+
+ gtk_label_set_label (GTK_LABEL (priv->secondary_label), priv->secondary_text);
+
+ if (priv->secondary_text && *priv->secondary_text) {
+ gtk_widget_show (priv->secondary_label);
+ gtk_widget_add_css_class (priv->label, "title-2");
+ } else {
+ gtk_widget_hide (priv->secondary_label);
+ gtk_widget_remove_css_class (priv->label, "title-2");
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SECONDARY_TEXT]);
+}
+
+/**
+ * adw_message_dialog_get_secondary_use_markup: (attributes org.gtk.Method.get_property=secondary-use-markup)
+ * @self: a message dialog
+ *
+ * Gets the TODO of @self.
+ *
+ * Returns: the TODO of @self.
+ *
+ * Since: 1.2
+ */
+gboolean
+adw_message_dialog_get_secondary_use_markup (AdwMessageDialog *self)
+{
+ AdwMessageDialogPrivate *priv;
+
+ g_return_val_if_fail (ADW_IS_MESSAGE_DIALOG (self), FALSE);
+
+ priv = adw_message_dialog_get_instance_private (self);
+
+ return priv->secondary_use_markup;
+}
+
+/**
+ * adw_message_dialog_set_secondary_use_markup: (attributes org.gtk.Method.set_property=secondary-use-markup)
+ * @self: a message dialog
+ * @use_markup: the new value to set
+ *
+ * Sets the TODO of @self.
+ *
+ * Since: 1.2
+ */
+void
+adw_message_dialog_set_secondary_use_markup (AdwMessageDialog *self,
+ gboolean use_markup)
+{
+ AdwMessageDialogPrivate *priv;
+
+ g_return_if_fail (ADW_IS_MESSAGE_DIALOG (self));
+
+ priv = adw_message_dialog_get_instance_private (self);
+
+ use_markup = !!use_markup;
+
+ if (use_markup == priv->secondary_use_markup)
+ return;
+
+ priv->secondary_use_markup = use_markup;
+
+ gtk_label_set_use_markup (GTK_LABEL (priv->secondary_label), use_markup);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SECONDARY_USE_MARKUP]);
+}
+
+/**
+ * adw_message_dialog_format_secondary_text:
+ * @self: a message dialog
+ * @format: the formatted string for the secondary text
+ * @...: the parameters to insert into @format
+ *
+ * Sets the secondary text of @self.
+ *
+ * Since: 1.2
+ */
+void
+adw_message_dialog_format_secondary_text (AdwMessageDialog *self,
+ const char *format,
+ ...)
+{
+ va_list args;
+
+ g_return_if_fail (ADW_IS_MESSAGE_DIALOG (self));
+ g_return_if_fail (format != NULL);
+
+ g_object_freeze_notify (G_OBJECT (self));
+
+ adw_message_dialog_set_secondary_use_markup (self, FALSE);
+
+ if (format) {
+ char *text;
+
+ va_start (args, format);
+ text = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ adw_message_dialog_set_secondary_text (self, text);
+
+ g_free (text);
+ } else {
+ adw_message_dialog_set_secondary_text (self, "");
+ }
+
+ g_object_thaw_notify (G_OBJECT (self));
+}
+
+/**
+ * adw_message_dialog_format_secondary_markup:
+ * @self: a message dialog
+ * @format: the formatted string for the secondary text with Pango markup
+ * @...: the parameters to insert into @format
+ *
+ * Sets the secondary text of @self.
+ *
+ * The @format is assumed to contain Pango markup.
+ *
+ * Special XML characters in the printf() arguments passed to this function will
+ * automatically be escaped as necessary, see [func@GLib.markup_printf_escaped].
+ *
+ * Since: 1.2
+ */
+void
+adw_message_dialog_format_secondary_markup (AdwMessageDialog *self,
+ const char *format,
+ ...)
+{
+ va_list args;
+
+ g_return_if_fail (ADW_IS_MESSAGE_DIALOG (self));
+ g_return_if_fail (format != NULL);
+
+ g_object_freeze_notify (G_OBJECT (self));
+
+ adw_message_dialog_set_secondary_use_markup (self, TRUE);
+
+ if (format) {
+ char *text;
+
+ va_start (args, format);
+ text = g_markup_vprintf_escaped (format, args);
+ va_end (args);
+
+ adw_message_dialog_set_secondary_text (self, text);
+
+ g_free (text);
+ } else {
+ adw_message_dialog_set_secondary_text (self, NULL);
+ }
+
+ g_object_thaw_notify (G_OBJECT (self));
+}
+
+/**
+ * adw_message_dialog_get_extra_child: (attributes org.gtk.Method.get_property=extra-child)
+ * @self: a message dialog
+ *
+ * Gets the TODO of @self.
+ *
+ * Returns: (nullable) (transfer none): the TODO of @self.
+ *
+ * Since: 1.2
+ */
+GtkWidget *
+adw_message_dialog_get_extra_child (AdwMessageDialog *self)
+{
+ AdwMessageDialogPrivate *priv;
+
+ g_return_val_if_fail (ADW_IS_MESSAGE_DIALOG (self), NULL);
+
+ priv = adw_message_dialog_get_instance_private (self);
+
+ return priv->child;
+}
+
+/**
+ * adw_message_dialog_set_extra_child: (attributes org.gtk.Method.set_property=extra-child)
+ * @self: a message dialog
+ * @child: (nullable): the new value to set
+ *
+ * Sets the TODO of @self.
+ *
+ * Since: 1.2
+ */
+void
+adw_message_dialog_set_extra_child (AdwMessageDialog *self,
+ GtkWidget *child)
+{
+ AdwMessageDialogPrivate *priv;
+
+ g_return_if_fail (ADW_IS_MESSAGE_DIALOG (self));
+
+ priv = adw_message_dialog_get_instance_private (self);
+
+ if (child == priv->child)
+ return;
+
+ if (priv->child)
+ gtk_box_remove (priv->message_area, priv->child);
+
+ priv->child = child;
+
+ if (priv->child)
+ gtk_box_append (priv->message_area, priv->child);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_EXTRA_CHILD]);
+}
+
+/**
+ * adw_message_dialog_add_response:
+ * @self: a message dialog
+ * @id: TODO
+ * @label: TODO
+ *
+ * TODO
+ *
+ * Since: 1.2
+ */
+void
+adw_message_dialog_add_response (AdwMessageDialog *self,
+ const char *id,
+ const char *label)
+{
+ AdwMessageDialogPrivate *priv;
+ ResponseInfo *info;
+
+ g_return_if_fail (ADW_IS_MESSAGE_DIALOG (self));
+ g_return_if_fail (id != NULL);
+ g_return_if_fail (label != NULL);
+
+ priv = adw_message_dialog_get_instance_private (self);
+
+ if (find_response (self, id)) {
+ g_critical ("Trying to add a response with id '%s' to an "
+ "AdwMessageDialog, but such a response already exists", id);
+ return;
+ }
+
+ info = g_new0 (ResponseInfo, 1);
+
+ info->dialog = self;
+ info->id = g_quark_from_string (id);
+ info->label = g_strdup (label);
+ info->destructive = FALSE;
+ info->enabled = TRUE;
+
+ info->wide_button = create_response_button (self, info);
+ gtk_box_append (priv->wide_action_area, info->wide_button);
+
+ info->narrow_button = create_response_button (self, info);
+ gtk_box_append (priv->narrow_action_area, info->narrow_button);
+
+ priv->responses = g_list_append (priv->responses, info);
+
+ if (priv->default_response == info->id &&
+ gtk_widget_get_mapped (GTK_WIDGET (self)))
+ update_default_response (self);
+}
+
+/**
+ * adw_message_dialog_add_responses: (skip)
+ * @self: a message dialog
+ * @first_id: response id
+ * @...: label for first response, then more id-label pairs
+ *
+ * TODO
+ *
+ * Since: 1.2
+ */
+void
+adw_message_dialog_add_responses (AdwMessageDialog *self,
+ const char *first_id,
+ ...)
+{
+ va_list args;
+ const char *id, *label;
+
+ g_return_if_fail (ADW_IS_MESSAGE_DIALOG (self));
+
+ va_start (args, first_id);
+
+ if (!first_id)
+ return;
+
+ id = first_id;
+ label = va_arg (args, const char *);
+
+ while (id) {
+ adw_message_dialog_add_response (self, id, label);
+
+ id = va_arg (args, const char *);
+ if (!id)
+ break;
+
+ label = va_arg (args, const char *);
+ }
+
+ va_end (args);
+}
+
+
+const char *
+adw_message_dialog_get_response_label (AdwMessageDialog *self,
+ const char *id)
+{
+ ResponseInfo *info;
+
+ g_return_val_if_fail (ADW_IS_MESSAGE_DIALOG (self), NULL);
+ g_return_val_if_fail (id != NULL, NULL);
+
+ info = find_response (self, id);
+ if (!info) {
+ g_critical ("AdwMessageDialog does not have a response with ID '%s'", id);
+ return NULL;
+ }
+
+ return info->label;
+}
+
+void
+adw_message_dialog_set_response_label (AdwMessageDialog *self,
+ const char *id,
+ const char *label)
+{
+ ResponseInfo *info;
+
+ g_return_if_fail (ADW_IS_MESSAGE_DIALOG (self));
+ g_return_if_fail (id != NULL);
+ g_return_if_fail (label != NULL);
+
+ info = find_response (self, id);
+ if (!info) {
+ g_critical ("AdwMessageDialog does not have a response with ID '%s'", id);
+ return;
+ }
+
+ g_free (info->label);
+ info->label = g_strdup (label);
+
+ gtk_button_set_label (GTK_BUTTON (info->wide_button), label);
+ gtk_button_set_label (GTK_BUTTON (info->narrow_button), label);
+}
+
+gboolean
+adw_message_dialog_get_response_destructive (AdwMessageDialog *self,
+ const char *id)
+{
+ ResponseInfo *info;
+
+ g_return_val_if_fail (ADW_IS_MESSAGE_DIALOG (self), FALSE);
+ g_return_val_if_fail (id != NULL, FALSE);
+
+ info = find_response (self, id);
+ if (!info) {
+ g_critical ("AdwMessageDialog does not have a response with ID '%s'", id);
+ return FALSE;
+ }
+
+ return info->destructive;
+}
+
+void
+adw_message_dialog_set_response_destructive (AdwMessageDialog *self,
+ const char *id,
+ gboolean destructive)
+{
+ ResponseInfo *info;
+
+ g_return_if_fail (ADW_IS_MESSAGE_DIALOG (self));
+ g_return_if_fail (id != NULL);
+
+ info = find_response (self, id);
+ if (!info) {
+ g_critical ("AdwMessageDialog does not have a response with ID '%s'", id);
+ return;
+ }
+
+ destructive = !!destructive;
+
+ if (destructive == info->destructive)
+ return;
+
+ info->destructive = destructive;
+
+ if (destructive) {
+ gtk_widget_add_css_class (info->wide_button, "destructive-action");
+ gtk_widget_add_css_class (info->narrow_button, "destructive-action");
+ } else {
+ gtk_widget_remove_css_class (info->wide_button, "destructive-action");
+ gtk_widget_remove_css_class (info->narrow_button, "destructive-action");
+ }
+}
+
+/**
+ * adw_message_dialog_get_response_enabled:
+ * @self: a message dialog
+ * @id: a response ID
+ *
+ * TODO
+ *
+ * Returns: Whether the response is enabled
+ *
+ * Since: 1.2
+ */
+gboolean
+adw_message_dialog_get_response_enabled (AdwMessageDialog *self,
+ const char *id)
+{
+ ResponseInfo *info;
+
+ g_return_val_if_fail (ADW_IS_MESSAGE_DIALOG (self), FALSE);
+ g_return_val_if_fail (id != NULL, FALSE);
+
+ info = find_response (self, id);
+ if (!info) {
+ g_critical ("AdwMessageDialog does not have a response with ID '%s'", id);
+ return FALSE;
+ }
+
+ return info->enabled;
+}
+
+/**
+ * adw_message_dialog_set_response_enabled:
+ * @self: a message dialog
+ * @id: a response ID
+ * @enabled: whether to enable the response
+ *
+ * TODO
+ *
+ * Since: 1.2
+ */
+void
+adw_message_dialog_set_response_enabled (AdwMessageDialog *self,
+ const char *id,
+ gboolean enabled)
+{
+ ResponseInfo *info;
+
+ g_return_if_fail (ADW_IS_MESSAGE_DIALOG (self));
+ g_return_if_fail (id != NULL);
+
+ info = find_response (self, id);
+ if (!info) {
+ g_critical ("AdwMessageDialog does not have a response with ID '%s'", id);
+ return;
+ }
+
+ enabled = !!enabled;
+
+ if (enabled == info->enabled)
+ return;
+
+ info->enabled = enabled;
+
+ gtk_widget_set_sensitive (info->wide_button, info->enabled);
+ gtk_widget_set_sensitive (info->narrow_button, info->enabled);
+}
+
+/**
+ * adw_message_dialog_get_default_response: (attributes org.gtk.Method.get_property=default-response)
+ * @self: a message dialog
+ *
+ * Gets the TODO of @self.
+ *
+ * Returns: (nullable): the TODO of @self.
+ *
+ * Since: 1.2
+ */
+const char *
+adw_message_dialog_get_default_response (AdwMessageDialog *self)
+{
+ AdwMessageDialogPrivate *priv;
+
+ g_return_val_if_fail (ADW_IS_MESSAGE_DIALOG (self), NULL);
+
+ priv = adw_message_dialog_get_instance_private (self);
+
+ if (!priv->default_response)
+ return NULL;
+
+ return g_quark_to_string (priv->default_response);
+}
+
+/**
+ * adw_message_dialog_set_default_response: (attributes org.gtk.Method.set_property=default-response)
+ * @self: a message dialog
+ * @response: (nullable): the new value to set
+ *
+ * Sets the TODO of @self.
+ *
+ * Since: 1.2
+ */
+void
+adw_message_dialog_set_default_response (AdwMessageDialog *self,
+ const char *response)
+{
+ AdwMessageDialogPrivate *priv;
+ GQuark quark;
+
+ g_return_if_fail (ADW_IS_MESSAGE_DIALOG (self));
+
+ priv = adw_message_dialog_get_instance_private (self);
+ quark = g_quark_from_string (response);
+
+ if (quark == priv->default_response)
+ return;
+
+ priv->default_response = quark;
+
+ if (gtk_widget_get_mapped (GTK_WIDGET (self)))
+ update_default_response (self);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DEFAULT_RESPONSE]);
+}
+
+/**
+ * adw_message_dialog_get_close_response: (attributes org.gtk.Method.get_property=close-response)
+ * @self: a message dialog
+ *
+ * Gets the TODO of @self.
+ *
+ * Returns: the TODO of @self.
+ *
+ * Since: 1.2
+ */const char *
+adw_message_dialog_get_close_response (AdwMessageDialog *self)
+{
+ AdwMessageDialogPrivate *priv;
+
+ g_return_val_if_fail (ADW_IS_MESSAGE_DIALOG (self), NULL);
+
+ priv = adw_message_dialog_get_instance_private (self);
+
+ return g_quark_to_string (priv->close_response);
+}
+
+/**
+ * adw_message_dialog_set_close_response: (attributes org.gtk.Method.set_property=close-response)
+ * @self: a message dialog
+ * @response: the new value to set
+ *
+ * Sets the TODO of @self.
+ *
+ * Since: 1.2
+ */
+void
+adw_message_dialog_set_close_response (AdwMessageDialog *self,
+ const char *response)
+{
+ AdwMessageDialogPrivate *priv;
+ GQuark quark;
+
+ g_return_if_fail (ADW_IS_MESSAGE_DIALOG (self));
+ g_return_if_fail (response != NULL);
+
+ priv = adw_message_dialog_get_instance_private (self);
+ quark = g_quark_from_string (response);
+
+ if (quark == priv->close_response)
+ return;
+
+ priv->close_response = quark;
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CLOSE_RESPONSE]);
+}
diff --git a/src/adw-message-dialog.h b/src/adw-message-dialog.h
new file mode 100644
index 00000000..e708eae5
--- /dev/null
+++ b/src/adw-message-dialog.h
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2022 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * Author: Alexander Mikhaylenko <alexander mikhaylenko puri sm>
+ */
+
+#pragma once
+
+#if !defined(_ADWAITA_INSIDE) && !defined(ADWAITA_COMPILATION)
+#error "Only <adwaita.h> can be included directly."
+#endif
+
+#include "adw-version.h"
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define ADW_TYPE_MESSAGE_DIALOG (adw_message_dialog_get_type())
+
+ADW_AVAILABLE_IN_1_2
+G_DECLARE_DERIVABLE_TYPE (AdwMessageDialog, adw_message_dialog, ADW, MESSAGE_DIALOG, GtkWindow)
+
+struct _AdwMessageDialogClass
+{
+ GtkWindowClass parent_class;
+
+ void (* response) (AdwMessageDialog *dialog,
+ const char *response);
+
+ /*< private >*/
+ gpointer padding[4];
+};
+
+ADW_AVAILABLE_IN_1_2
+GtkWidget *adw_message_dialog_new (GtkWindow *parent,
+ const char *text,
+ const char *secondary_text);
+
+ADW_AVAILABLE_IN_1_2
+const char *adw_message_dialog_get_text (AdwMessageDialog *self);
+ADW_AVAILABLE_IN_1_2
+void adw_message_dialog_set_text (AdwMessageDialog *self,
+ const char *text);
+
+ADW_AVAILABLE_IN_1_2
+gboolean adw_message_dialog_get_use_markup (AdwMessageDialog *self);
+ADW_AVAILABLE_IN_1_2
+void adw_message_dialog_set_use_markup (AdwMessageDialog *self,
+ gboolean use_markup);
+
+ADW_AVAILABLE_IN_1_2
+void adw_message_dialog_format_text (AdwMessageDialog *self,
+ const char *format,
+ ...) G_GNUC_PRINTF (2, 3);
+
+ADW_AVAILABLE_IN_1_2
+void adw_message_dialog_format_markup (AdwMessageDialog *self,
+ const char *format,
+ ...) G_GNUC_PRINTF (2, 3);
+
+ADW_AVAILABLE_IN_1_2
+const char *adw_message_dialog_get_secondary_text (AdwMessageDialog *self);
+ADW_AVAILABLE_IN_1_2
+void adw_message_dialog_set_secondary_text (AdwMessageDialog *self,
+ const char *text);
+
+ADW_AVAILABLE_IN_1_2
+gboolean adw_message_dialog_get_secondary_use_markup (AdwMessageDialog *self);
+ADW_AVAILABLE_IN_1_2
+void adw_message_dialog_set_secondary_use_markup (AdwMessageDialog *self,
+ gboolean use_markup);
+
+ADW_AVAILABLE_IN_1_2
+void adw_message_dialog_format_secondary_text (AdwMessageDialog *self,
+ const char *format,
+ ...) G_GNUC_PRINTF (2, 3);
+
+ADW_AVAILABLE_IN_1_2
+void adw_message_dialog_format_secondary_markup (AdwMessageDialog *self,
+ const char *format,
+ ...) G_GNUC_PRINTF (2, 3);
+
+ADW_AVAILABLE_IN_1_2
+GtkWidget *adw_message_dialog_get_extra_child (AdwMessageDialog *self);
+ADW_AVAILABLE_IN_1_2
+void adw_message_dialog_set_extra_child (AdwMessageDialog *self,
+ GtkWidget *child);
+
+ADW_AVAILABLE_IN_1_2
+void adw_message_dialog_add_response (AdwMessageDialog *self,
+ const char *id,
+ const char *label);
+
+ADW_AVAILABLE_IN_1_2
+void adw_message_dialog_add_responses (AdwMessageDialog *self,
+ const char *first_id,
+ ...) G_GNUC_NULL_TERMINATED;
+
+ADW_AVAILABLE_IN_1_2
+const char *adw_message_dialog_get_response_label (AdwMessageDialog *self,
+ const char *id);
+ADW_AVAILABLE_IN_1_2
+void adw_message_dialog_set_response_label (AdwMessageDialog *self,
+ const char *id,
+ const char *label);
+
+ADW_AVAILABLE_IN_1_2
+gboolean adw_message_dialog_get_response_destructive (AdwMessageDialog *self,
+ const char *id);
+ADW_AVAILABLE_IN_1_2
+void adw_message_dialog_set_response_destructive (AdwMessageDialog *self,
+ const char *id,
+ gboolean destructive);
+
+ADW_AVAILABLE_IN_1_2
+gboolean adw_message_dialog_get_response_enabled (AdwMessageDialog *self,
+ const char *id);
+ADW_AVAILABLE_IN_1_2
+void adw_message_dialog_set_response_enabled (AdwMessageDialog *self,
+ const char *id,
+ gboolean enabled);
+
+ADW_AVAILABLE_IN_1_2
+const char *adw_message_dialog_get_default_response (AdwMessageDialog *self);
+ADW_AVAILABLE_IN_1_2
+void adw_message_dialog_set_default_response (AdwMessageDialog *self,
+ const char *response);
+
+ADW_AVAILABLE_IN_1_2
+const char *adw_message_dialog_get_close_response (AdwMessageDialog *self);
+ADW_AVAILABLE_IN_1_2
+void adw_message_dialog_set_close_response (AdwMessageDialog *self,
+ const char *response);
+
+G_END_DECLS
diff --git a/src/adw-message-dialog.ui b/src/adw-message-dialog.ui
new file mode 100644
index 00000000..8e2da233
--- /dev/null
+++ b/src/adw-message-dialog.ui
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="libadwaita">
+ <requires lib="gtk" version="4.0"/>
+ <template class="AdwMessageDialog" parent="GtkWindow">
+ <style>
+ <class name="dialog"/>
+ <class name="message"/>
+ </style>
+ <property name="titlebar">
+ <object class="GtkBox">
+ <property name="visible">False</property>
+ </object>
+ </property>
+ <property name="child">
+ <object class="GtkWindowHandle">
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox">
+ <property name="height-request">16</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">0</property>
+ <property name="margin-top">6</property>
+ <property name="margin-bottom">6</property>
+ <property name="halign">center</property>
+ <property name="hexpand">True</property>
+ <style>
+ <class name="title"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <style>
+ <class name="dialog-vbox"/>
+ </style>
+ <child>
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="orientation">vertical</property>
+ <property name="spacing">20</property>
+ <child>
+ <object class="GtkBox" id="box">
+ <property name="margin-start">30</property>
+ <property name="margin-end">30</property>
+ <property name="spacing">30</property>
+ <child>
+ <object class="GtkBox" id="message_area">
+ <property name="orientation">vertical</property>
+ <property name="spacing">10</property>
+ <property name="hexpand">1</property>
+ <child>
+ <object class="GtkLabel" id="label">
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="max-width-chars">60</property>
+ <property name="justify">center</property>
+ <property name="xalign">0.5</property>
+ <property name="visible">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="secondary_label">
+ <property name="margin-bottom">2</property>
+ <property name="vexpand">True</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="max-width-chars">60</property>
+ <property name="justify">center</property>
+ <property name="xalign">0.5</property>
+ <property name="visible">False</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwSqueezer" id="action_box">
+ <property name="homogeneous">False</property>
+ <signal name="notify::visible-child" handler="update_default_response" swapped="yes"/>
+ <style>
+ <class name="dialog-action-box"/>
+ </style>
+ <child>
+ <object class="GtkBox" id="wide_action_area">
+ <property name="hexpand">1</property>
+ <property name="orientation">horizontal</property>
+ <property name="homogeneous">True</property>
+ <property name="valign">end</property>
+ <style>
+ <class name="dialog-action-area"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="narrow_action_area">
+ <property name="hexpand">1</property>
+ <property name="orientation">vertical</property>
+ <property name="homogeneous">True</property>
+ <style>
+ <class name="dialog-action-area"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </property>
+ </template>
+</interface>
diff --git a/src/adwaita.gresources.xml b/src/adwaita.gresources.xml
index 7f4a4236..da724d86 100644
--- a/src/adwaita.gresources.xml
+++ b/src/adwaita.gresources.xml
@@ -14,6 +14,7 @@
<file preprocess="xml-stripblanks">adw-entry-row.ui</file>
<file preprocess="xml-stripblanks">adw-expander-row.ui</file>
<file preprocess="xml-stripblanks">adw-inspector-page.ui</file>
+ <file preprocess="xml-stripblanks">adw-message-dialog.ui</file>
<file preprocess="xml-stripblanks">adw-preferences-group.ui</file>
<file preprocess="xml-stripblanks">adw-preferences-page.ui</file>
<file preprocess="xml-stripblanks">adw-preferences-window.ui</file>
diff --git a/src/adwaita.h b/src/adwaita.h
index aa1ca1da..a2fda744 100644
--- a/src/adwaita.h
+++ b/src/adwaita.h
@@ -47,6 +47,7 @@ G_BEGIN_DECLS
#include "adw-header-bar.h"
#include "adw-leaflet.h"
#include "adw-main.h"
+#include "adw-message-dialog.h"
#include "adw-navigation-direction.h"
#include "adw-password-entry-row.h"
#include "adw-preferences-group.h"
diff --git a/src/meson.build b/src/meson.build
index 0f6c5217..32483315 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -106,6 +106,7 @@ src_headers = [
'adw-header-bar.h',
'adw-leaflet.h',
'adw-main.h',
+ 'adw-message-dialog.h',
'adw-navigation-direction.h',
'adw-password-entry-row.h',
'adw-preferences-group.h',
@@ -168,6 +169,7 @@ src_sources = [
'adw-header-bar.c',
'adw-leaflet.c',
'adw-main.c',
+ 'adw-message-dialog.c',
'adw-navigation-direction.c',
'adw-password-entry-row.c',
'adw-preferences-group.c',
@@ -201,6 +203,7 @@ libadwaita_private_sources += files([
'adw-bidi.c',
'adw-fading-label.c',
'adw-gizmo.c',
+ 'adw-gtkbuilder-utils.c',
'adw-indicator-bin.c',
'adw-inspector-page.c',
'adw-settings.c',
diff --git a/src/stylesheet/widgets/_message-dialog.scss b/src/stylesheet/widgets/_message-dialog.scss
index 48be0c12..53c602fc 100644
--- a/src/stylesheet/widgets/_message-dialog.scss
+++ b/src/stylesheet/widgets/_message-dialog.scss
@@ -17,39 +17,62 @@ window.dialog.message {
}
}
+ .dialog-action-area {
+ border-top: 1px solid $border_color;
+ margin: 0;
+ border-spacing: 0;
+
+ > button {
+ @extend %button_basic_flat;
+ padding: 10px 14px; // labels are not vertically centered on message dialog, this is a workaround
+ border-radius: 0;
+ border: none;
+ background-clip: padding-box;
+ border-left: 1px solid $border_color;
+
+ &:first-child {
+ border-left: none;
+ }
+
+ &.suggested-action {
+ color: $accent_color;
+ }
+
+ &.destructive-action {
+ color: $destructive_color;
+ }
+ }
+
+ &.vertical {
+ border-top: none;
+
+ > button {
+ border-top: 1px solid $border_color;
+ border-left: none;
+ }
+ }
+ }
+
&.csd {
// bigger radius for better antialiasing
border-bottom-left-radius: $window_radius+1;
border-bottom-right-radius: $window_radius+1;
.dialog-action-area {
- border-top: 1px solid $border_color;
- margin: 0;
- border-spacing: 0;
-
- > button {
- @extend %button_basic_flat;
- padding: 10px 14px; // labels are not vertically centered on message dialog, this is a workaround
- border-radius: 0;
- border: none;
- background-clip: padding-box;
- border-left: 1px solid $border_color;
-
- &:first-child {
+ &.horizontal {
+ > button:first-child {
border-bottom-left-radius: $window_radius+1;
- border-left: none;
}
- &:last-child {
+ > button:last-child {
border-bottom-right-radius: $window_radius+1;
}
+ }
- &.suggested-action {
- color: $accent_color;
- }
-
- &.destructive-action {
- color: $destructive_color;
+ &.vertical {
+ > button:last-child {
+ border-bottom-left-radius: $window_radius+1;
+ border-bottom-right-radius: $window_radius+1;
}
}
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]