From 68881434bf9a9addca7614d28c3a9aed663deb2c Mon Sep 17 00:00:00 2001 From: James Westman Date: Mon, 20 Jan 2020 19:11:36 -0600 Subject: [PATCH] composer: Use ReflowBox widget for toolbar This new widget replaces the size-allocate hack introduced a few commits ago. --- src/client/components/components-reflow-box.c | 495 +++++++++++ src/client/composer/composer-widget.vala | 20 +- src/client/meson.build | 1 + ui/composer-widget.ui | 767 +++++++++--------- 4 files changed, 874 insertions(+), 409 deletions(-) create mode 100644 src/client/components/components-reflow-box.c diff --git a/src/client/components/components-reflow-box.c b/src/client/components/components-reflow-box.c new file mode 100644 index 00000000..f9d9fbe4 --- /dev/null +++ b/src/client/components/components-reflow-box.c @@ -0,0 +1,495 @@ +/* + * Copyright (C) 2020 Alexander Mikhaylenko + * Copyright (C) 2020 James Westman + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" +#include +#include + + +#define COMPONENTS_TYPE_REFLOW_BOX (components_reflow_box_get_type()) + +G_DECLARE_FINAL_TYPE (ComponentsReflowBox, components_reflow_box, COMPONENTS, REFLOW_BOX, GtkContainer) + + +ComponentsReflowBox *components_reflow_box_new (void); + +guint components_reflow_box_get_spacing (ComponentsReflowBox *self); +void components_reflow_box_set_spacing (ComponentsReflowBox *self, + guint spacing); + +guint components_reflow_box_get_row_spacing (ComponentsReflowBox *self); +void components_reflow_box_set_row_spacing (ComponentsReflowBox *self, + guint row_spacing); + + +struct _ComponentsReflowBox +{ + GtkContainer parent_instance; + + GList *children; + + guint spacing; + guint row_spacing; +}; + +G_DEFINE_TYPE (ComponentsReflowBox, components_reflow_box, GTK_TYPE_CONTAINER); + +enum { + PROP_0, + PROP_SPACING, + PROP_ROW_SPACING, + LAST_PROP, +}; + +static GParamSpec *props[LAST_PROP]; + + +static void +components_reflow_box_init (ComponentsReflowBox *self) +{ + gtk_widget_set_has_window (GTK_WIDGET (self), FALSE); +} + +static void +components_reflow_box_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + ComponentsReflowBox *self = COMPONENTS_REFLOW_BOX (object); + + switch (prop_id) { + case PROP_SPACING: + g_value_set_uint (value, components_reflow_box_get_spacing (self)); + break; + + case PROP_ROW_SPACING: + g_value_set_uint (value, components_reflow_box_get_row_spacing (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +components_reflow_box_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + ComponentsReflowBox *self = COMPONENTS_REFLOW_BOX (object); + + switch (prop_id) { + case PROP_SPACING: + components_reflow_box_set_spacing (self, g_value_get_uint (value)); + break; + + case PROP_ROW_SPACING: + components_reflow_box_set_row_spacing (self, g_value_get_uint (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +/** + * components_reflow_box_set_spacing: + * @self: a #ComponentsReflowBox + * @spacing: the spacing + * + * Sets the spacing for @self. + * + * Since: 0.0.14 + */ +void +components_reflow_box_set_spacing (ComponentsReflowBox *self, + guint spacing) +{ + if (self->spacing == spacing) + return; + + self->spacing = spacing; + gtk_widget_queue_resize (GTK_WIDGET (self)); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SPACING]); +} + +/** + * components_reflow_box_get_spacing: + * @self: a #ComponentsReflowBox + * + * Gets the spacing for @self. + * + * Returns: the spacing for @self. + * + * Since: 0.0.14 + */ +guint +components_reflow_box_get_spacing (ComponentsReflowBox *self) +{ + return self->spacing; +} + +/** + * components_reflow_box_set_row_spacing: + * @self: a #ComponentsReflowBox + * @row_spacing: the row spacing + * + * Sets the row spacing for @self. + * + * Since: 0.0.14 + */ +void +components_reflow_box_set_row_spacing (ComponentsReflowBox *self, + guint row_spacing) +{ + if (self->row_spacing == row_spacing) + return; + + self->row_spacing = row_spacing; + gtk_widget_queue_resize (GTK_WIDGET (self)); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ROW_SPACING]); +} + +/** + * components_reflow_box_get_row_spacing: + * @self: a #ComponentsReflowBox + * + * Gets the row spacing for @self. + * + * Returns: the row spacing for @self. + * + * Since: 0.0.14 + */ +guint +components_reflow_box_get_row_spacing (ComponentsReflowBox *self) +{ + return self->row_spacing; +} + + +static void +allocate_row (ComponentsReflowBox *self, + GtkAllocation *allocation, + gint y, + GList *row_start, + GList *next_row, + gint row_height, + gint extra_space, + gint n_expand_children) +{ + gboolean rtl; + gint x = 0; + gint expand_per_child = 0; + + if (row_start == NULL) + return; + + rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL; + if (rtl) + x = allocation->width; + + if (n_expand_children > 0) { + expand_per_child = extra_space / n_expand_children; + } else { + GtkAlign align; + align = gtk_widget_get_halign (GTK_WIDGET (self)); + if (align == GTK_ALIGN_CENTER) { + if (rtl) + x -= (extra_space / 2); + else + x += (extra_space / 2); + } else if (align == GTK_ALIGN_END) { + if (rtl) + x -= extra_space; + else + x += extra_space; + } + } + + for (GList *l = row_start; l != NULL && l != next_row; l = l->next) { + GtkWidget *child = GTK_WIDGET (l->data); + int w, min_w; + GtkAllocation child_alloc; + + if (!gtk_widget_get_visible (child)) + continue; + + gtk_widget_get_preferred_width (child, &min_w, &w); + w = CLAMP (w, min_w, allocation->width); + + if (gtk_widget_get_hexpand (child)) { + w += expand_per_child; + } + + if (rtl) + x -= w; + + child_alloc.x = x + allocation->x; + + if (rtl) + x -= self->spacing; + else + x += w + self->spacing; + + child_alloc.y = y + allocation->y; + child_alloc.width = w; + child_alloc.height = row_height; + + gtk_widget_size_allocate (child, &child_alloc); + } +} + +static gint +calculate_sizes (ComponentsReflowBox *self, + GtkAllocation *allocation, + gboolean dry_run) +{ + gint x = 0; + gint y = 0; + gint row_height = 0; + + GList *row_start = self->children; + gint n_expand_children = 0; + + for (GList *l = self->children; l != NULL; l = l->next) { + GtkWidget *child = GTK_WIDGET (l->data); + int w, h, min_w; + + if (!gtk_widget_get_visible (child)) + continue; + + gtk_widget_get_preferred_width (child, &min_w, &w); + gtk_widget_get_preferred_height (child, NULL, &h); + + w = CLAMP (w, min_w, allocation->width); + + if (x + w > allocation->width) { + /* no more space on this row, create a new one */ + + /* first, do the allocations for the previous row, if needed */ + if (!dry_run) { + allocate_row (self, allocation, y, row_start, l, row_height, + allocation->width + self->spacing - x, n_expand_children); + } + + /* now reset everything for the next row */ + x = 0; + y += row_height + self->row_spacing; + row_height = 0; + n_expand_children = 0; + row_start = l; + } + + if (gtk_widget_get_hexpand (child)) + n_expand_children ++; + + row_height = MAX (row_height, h); + + x += w + self->spacing; + } + + if (!dry_run) { + /* allocate the last row */ + allocate_row (self, allocation, y, row_start, NULL, row_height, + allocation->width + self->spacing - x, n_expand_children); + } + + return y + row_height; +} + +static void +components_reflow_box_size_allocate(GtkWidget *widget, + GtkAllocation *allocation) +{ + ComponentsReflowBox *self = COMPONENTS_REFLOW_BOX (widget); + + calculate_sizes(self, allocation, FALSE); + GTK_WIDGET_CLASS (components_reflow_box_parent_class)->size_allocate (widget, allocation); +} + +static GtkSizeRequestMode +components_reflow_box_get_request_mode(GtkWidget *widget) +{ + return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; +} + +static void +components_reflow_box_get_preferred_width(GtkWidget *widget, + gint *minimum_width, + gint *natural_width) +{ + ComponentsReflowBox *self = COMPONENTS_REFLOW_BOX (widget); + + gint min = 0; + gint nat = 0; + + for (GList *l = self->children; l != NULL; l = l->next) { + GtkWidget *child = GTK_WIDGET (l->data); + int child_min, child_nat; + + if (!gtk_widget_get_visible (child)) + continue; + + gtk_widget_get_preferred_width (child, &child_min, &child_nat); + + min = MAX (min, child_min); + nat += child_nat + self->spacing; + } + + /* remove the extra spacing, avoid off-by-one error */ + if (self->children != NULL) + nat -= self->spacing; + + if (minimum_width) + *minimum_width = min; + if (natural_width) + *natural_width = nat; +} + +static void +components_reflow_box_get_preferred_width_for_height (GtkWidget *widget, + gint height, + gint *minimum_width, + gint *natural_width) +{ + components_reflow_box_get_preferred_width (widget, minimum_width, natural_width); +} + +static void +components_reflow_box_get_preferred_height_for_width (GtkWidget *widget, + gint width, + gint *minimum_height, + gint *natural_height) +{ + ComponentsReflowBox *self = COMPONENTS_REFLOW_BOX (widget); + + GtkAllocation allocation; + gint h; + + allocation.width = width; + h = calculate_sizes (self, &allocation, TRUE); + + if (minimum_height) + *minimum_height = h; + if (natural_height) + *natural_height = h; +} + + +static void +components_reflow_box_add (GtkContainer *container, + GtkWidget *widget) +{ + ComponentsReflowBox *self = COMPONENTS_REFLOW_BOX (container); + + self->children = g_list_append (self->children, widget); + gtk_widget_set_parent (widget, GTK_WIDGET (self)); +} + +static void +components_reflow_box_remove (GtkContainer *container, + GtkWidget *widget) +{ + ComponentsReflowBox *self = COMPONENTS_REFLOW_BOX (container); + + gtk_widget_unparent (widget); + self->children = g_list_remove (self->children, widget); +} + +static void +components_reflow_box_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) +{ + ComponentsReflowBox *self = COMPONENTS_REFLOW_BOX (container); + + // while loop instead of for loop in case the callback removes children + GList *l = self->children; + while (l != NULL) { + GtkWidget *child = GTK_WIDGET (l->data); + l = l->next; + callback (child, callback_data); + } +} + +static void +components_reflow_box_class_init (ComponentsReflowBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); + + object_class->set_property = components_reflow_box_set_property; + object_class->get_property = components_reflow_box_get_property; + + widget_class->size_allocate = components_reflow_box_size_allocate; + widget_class->get_request_mode = components_reflow_box_get_request_mode; + widget_class->get_preferred_width = components_reflow_box_get_preferred_width; + widget_class->get_preferred_width_for_height = components_reflow_box_get_preferred_width_for_height; + widget_class->get_preferred_height_for_width = components_reflow_box_get_preferred_height_for_width; + + container_class->add = components_reflow_box_add; + container_class->remove = components_reflow_box_remove; + container_class->forall = components_reflow_box_forall; + + /** + * ComponentsReflowBox:spacing: + * + * The spacing between children + * + * Since: 0.0.14 + */ + props[PROP_SPACING] = + g_param_spec_uint ("spacing", + _("Spacing"), + _("Spacing between children"), + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * ComponentsReflowBox:row-spacing: + * + * The spacing between rows of children + * + * Since: 0.0.14 + */ + props[PROP_ROW_SPACING] = + g_param_spec_uint ("row-spacing", + _("Row spacing"), + _("Spacing between rows of children"), + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, LAST_PROP, props); +} + +/** + * components_reflow_box_new: + * + * Create a new #ComponentsReflowBox widget. + * + * Returns: The newly created #ComponentsReflowBox widget + * + * Since: 0.0.14 + */ +ComponentsReflowBox * +components_reflow_box_new (void) +{ + return g_object_new (COMPONENTS_TYPE_REFLOW_BOX, NULL); +} + + + diff --git a/src/client/composer/composer-widget.vala b/src/client/composer/composer-widget.vala index 6c490cf5..8c82f47c 100644 --- a/src/client/composer/composer-widget.vala +++ b/src/client/composer/composer-widget.vala @@ -11,6 +11,8 @@ private errordomain AttachmentError { DUPLICATE } +[CCode (cname = "components_reflow_box_get_type")] +private extern Type components_reflow_box_get_type(); /** * A widget for editing an email message. @@ -378,9 +380,6 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { [GtkChild] private Gtk.Box conversation_attach_buttons; [GtkChild] private Gtk.Revealer formatting; - [GtkChild] private Gtk.Box toolbar_box; - [GtkChild] private Gtk.Box top_buttons; - [GtkChild] private Gtk.Box bottom_buttons; [GtkChild] private Gtk.MenuButton font_button; [GtkChild] private Gtk.Stack font_button_stack; [GtkChild] private Gtk.MenuButton font_size_button; @@ -486,6 +485,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { public Widget(Application.Client application, Geary.Account initial_account, ComposeType compose_type) { + components_reflow_box_get_type(); base_ref(); this.application = application; this.account = initial_account; @@ -2895,18 +2895,4 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { this.background_work_pulse.reset(); this.show_background_work_timeout.reset(); } - - [GtkCallback] - private void on_toolbar_size_allocate(Gtk.Widget widget, Gtk.Allocation rect) { - int top_width = this.top_buttons.get_allocated_width(); - int bottom_width = this.bottom_buttons.get_allocated_width(); - // add 6 for spacing - int width = top_width + bottom_width + 6; - - if (rect.width <= width) { - this.toolbar_box.orientation = VERTICAL; - } else { - this.toolbar_box.orientation = HORIZONTAL; - } - } } diff --git a/src/client/meson.build b/src/client/meson.build index ed99bacf..bb61d9f5 100644 --- a/src/client/meson.build +++ b/src/client/meson.build @@ -37,6 +37,7 @@ geary_client_vala_sources = files( 'components/components-inspector-system-view.vala', 'components/components-placeholder-pane.vala', 'components/components-preferences-window.vala', + 'components/components-reflow-box.c', 'components/components-search-bar.vala', 'components/components-validator.vala', 'components/components-web-view.vala', diff --git a/ui/composer-widget.ui b/ui/composer-widget.ui index e48412ed..fb32a325 100644 --- a/ui/composer-widget.ui +++ b/ui/composer-widget.ui @@ -566,253 +566,306 @@ True - + True False - horizontal 6 + 6 True - - + True False - 6 - start - - True - False - - - True - True - False - False - Bold text - edt.bold - True - - - True - False - 16 - format-text-bold-symbolic - - - - - False - True - 0 - - - - - True - True - False - False - Italic text - edt.italic - True - - - True - False - 16 - format-text-italic-symbolic - - - - - False - True - 1 - - - - - True - True - False - False - Underline text - edt.underline - True - - - True - False - 16 - format-text-underline-symbolic - - - - - False - True - 2 - - - - - True - True - False - False - Strikethrough text - edt.strikethrough - True - - - True - False - 16 - format-text-strikethrough-symbolic - - - - - False - True - 3 - - - - - - - - True - False - - - True - True - False - False - Insert bulleted list - edt.ulist - True - - - True - False - 16 - format-unordered-list-symbolic - - - - - False - True - 0 - - - - - True - True - False - False - Insert numbered list - edt.olist - True - - - True - False - 16 - format-ordered-list-symbolic - - - - - False - True - 1 - - - - - - - - True - False - - - True - True - False - False - Indent or quote text - edt.indent - True - - - True - False - 16 - format-indent-more-symbolic - - - - - False - True - 0 - - - - - True - True - False - False - Un-indent or unquote text - edt.outdent - True - - - True - False - 16 - format-indent-less-symbolic - - - - - False - True - 1 - - - - - - - + True True False False - Remove text formatting - edt.remove-format + Bold text + edt.bold True - + True False 16 - format-text-remove-symbolic + format-text-bold-symbolic + + + + + False + True + 0 + + + + + True + True + False + False + Italic text + edt.italic + True + + + True + False + 16 + format-text-italic-symbolic + + + + + False + True + 1 + + + + + True + True + False + False + Underline text + edt.underline + True + + + True + False + 16 + format-text-underline-symbolic + + + + + False + True + 2 + + + + + True + True + False + False + Strikethrough text + edt.strikethrough + True + + + True + False + 16 + format-text-strikethrough-symbolic + + + + + False + True + 3 + + + + + + + + True + False + + + True + True + False + False + Insert bulleted list + edt.ulist + True + + + True + False + 16 + format-unordered-list-symbolic + + + + + False + True + 0 + + + + + True + True + False + False + Insert numbered list + edt.olist + True + + + True + False + 16 + format-ordered-list-symbolic + + + + + False + True + 1 + + + + + + + + True + False + + + True + True + False + False + Indent or quote text + edt.indent + True + + + True + False + 16 + format-indent-more-symbolic + + + + + False + True + 0 + + + + + True + True + False + False + Un-indent or unquote text + edt.outdent + True + + + True + False + 16 + format-indent-less-symbolic + + + + + False + True + 1 + + + + + + + + True + True + False + False + Remove text formatting + edt.remove-format + True + + + True + False + 16 + format-text-remove-symbolic + + + + + + + True + True + font_menu + Change font type + up + + + True + False + horizontal + + + True + False + + + True + False + Sans Serif + start + + + sans + + + + + True + False + Serif + start + + + serif + + + + + True + False + Fixed Width + start + + + monospace + + + + + + + True + False + pan-down @@ -820,176 +873,106 @@ - + True - False - 6 - start + True + edt.color + Change font color - - True - True - font_menu - Change font type - up - - - True - False - horizontal - - - True - False - - - True - False - Sans Serif - start - - - sans - - - - - True - False - Serif - start - - - serif - - - - - True - False - Fixed Width - start - - - monospace - - - - - - - True - False - pan-down - - - - - - - - - True - True - edt.color - Change font color - - - True - False - - - - - - - True - True - font_size_menu - Change font size - up - - - True - False - horizontal - - - True - False - font-size-symbolic - - - - - True - False - pan-down - - - - - - - - + True False - - - True - True - False - False - Insert or update text link - edt.insert-link - True - - - True - False - 16 - insert-link-symbolic - - - - - False - True - 0 - - - - - True - True - False - False - Insert an image - edt.insert-image - True - - - True - False - 16 - insert-image-symbolic - - - - - False - True - 1 - - - + + + True + True + font_size_menu + Change font size + up + + + True + False + horizontal + + + True + False + font-size-symbolic + + + + + True + False + pan-down + + + + + + + + + True + False + + + True + True + False + False + Insert or update text link + edt.insert-link + True + + + True + False + 16 + insert-link-symbolic + + + + + False + True + 0 + + + + + True + True + False + False + Insert an image + edt.insert-image + True + + + True + False + 16 + insert-image-symbolic + + + + + False + True + 1 + + + + +