From 51ac461289b3d1282481ee191119a5deef9fde46 Mon Sep 17 00:00:00 2001 From: Ernestas Kulik Date: Fri, 22 Jun 2018 11:15:11 +0300 Subject: general: Use an in-tree copy of libgd code This will make it easier to do the GTK+ 4 port, as no external code will be built. --- .gitmodules | 4 - meson.build | 11 - src/libgd/gd-styled-text-renderer.c | 124 ++++ src/libgd/gd-styled-text-renderer.h | 79 +++ src/libgd/gd-tagged-entry.c | 1242 +++++++++++++++++++++++++++++++++++ src/libgd/gd-tagged-entry.h | 117 ++++ src/meson.build | 7 +- src/nautilus-list-view.c | 3 +- src/nautilus-query-editor.c | 2 +- src/nautilus-ui-utilities.c | 109 ++- subprojects/libgd | 1 - 11 files changed, 1675 insertions(+), 24 deletions(-) delete mode 100644 .gitmodules create mode 100644 src/libgd/gd-styled-text-renderer.c create mode 100644 src/libgd/gd-styled-text-renderer.h create mode 100644 src/libgd/gd-tagged-entry.c create mode 100644 src/libgd/gd-tagged-entry.h delete mode 160000 subprojects/libgd diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 16ee54864..000000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "subprojects/libgd"] - path = subprojects/libgd - url = https://gitlab.gnome.org/GNOME/libgd.git - branch = master diff --git a/meson.build b/meson.build index 70e41043b..6ce501466 100644 --- a/meson.build +++ b/meson.build @@ -114,17 +114,6 @@ pkgconfig = import('pkgconfig') ################ glib_ver = '>= 2.58.1' -libgd = subproject( - 'libgd', - default_options: [ - 'static=true', - 'with-gtk-hacks=true', - 'with-main-view=true', - 'with-tagged-entry=true' - ] -) -libgd_dep = libgd.get_variable('libgd_dep') - libm = cc.find_library('m') if get_option('extensions') diff --git a/src/libgd/gd-styled-text-renderer.c b/src/libgd/gd-styled-text-renderer.c new file mode 100644 index 000000000..50a315e21 --- /dev/null +++ b/src/libgd/gd-styled-text-renderer.c @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2011 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program 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 Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Cosimo Cecchi + * + */ + +#include "gd-styled-text-renderer.h" + +G_DEFINE_TYPE (GdStyledTextRenderer, gd_styled_text_renderer, GTK_TYPE_CELL_RENDERER_TEXT); + +struct _GdStyledTextRendererPrivate { + GList *style_classes; +}; + +static void +gd_styled_text_renderer_render (GtkCellRenderer *cell, + cairo_t *cr, + GtkWidget *widget, + const GdkRectangle *background_area, + const GdkRectangle *cell_area, + GtkCellRendererState flags) +{ + GdStyledTextRenderer *self = GD_STYLED_TEXT_RENDERER (cell); + GtkStyleContext *context; + const gchar *style_class; + GList *l; + + context = gtk_widget_get_style_context (widget); + gtk_style_context_save (context); + + for (l = self->priv->style_classes; l != NULL; l = l->next) + { + style_class = l->data; + gtk_style_context_add_class (context, style_class); + } + + GTK_CELL_RENDERER_CLASS (gd_styled_text_renderer_parent_class)->render + (cell, cr, widget, + background_area, cell_area, flags); + + gtk_style_context_restore (context); +} + +static void +gd_styled_text_renderer_finalize (GObject *obj) +{ + GdStyledTextRenderer *self = GD_STYLED_TEXT_RENDERER (obj); + + if (self->priv->style_classes != NULL) + { + g_list_free_full (self->priv->style_classes, g_free); + self->priv->style_classes = NULL; + } + + G_OBJECT_CLASS (gd_styled_text_renderer_parent_class)->finalize (obj); +} + +static void +gd_styled_text_renderer_class_init (GdStyledTextRendererClass *klass) +{ + GtkCellRendererClass *crclass = GTK_CELL_RENDERER_CLASS (klass); + GObjectClass *oclass = G_OBJECT_CLASS (klass); + + oclass->finalize = gd_styled_text_renderer_finalize; + crclass->render = gd_styled_text_renderer_render; + + g_type_class_add_private (klass, sizeof (GdStyledTextRendererPrivate)); +} + +static void +gd_styled_text_renderer_init (GdStyledTextRenderer *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GD_TYPE_STYLED_TEXT_RENDERER, + GdStyledTextRendererPrivate); +} + +GtkCellRenderer * +gd_styled_text_renderer_new (void) +{ + return g_object_new (GD_TYPE_STYLED_TEXT_RENDERER, + NULL); +} + +void +gd_styled_text_renderer_add_class (GdStyledTextRenderer *self, + const gchar *class) +{ + if (g_list_find_custom (self->priv->style_classes, class, (GCompareFunc) g_strcmp0)) + return; + + self->priv->style_classes = g_list_append (self->priv->style_classes, g_strdup (class)); +} + +void +gd_styled_text_renderer_remove_class (GdStyledTextRenderer *self, + const gchar *class) +{ + GList *class_element; + + class_element = g_list_find_custom (self->priv->style_classes, class, (GCompareFunc) g_strcmp0); + + if (class_element == NULL) + return; + + self->priv->style_classes = g_list_remove_link (self->priv->style_classes, + class_element); + g_free (class_element->data); + g_list_free_1 (class_element); +} diff --git a/src/libgd/gd-styled-text-renderer.h b/src/libgd/gd-styled-text-renderer.h new file mode 100644 index 000000000..fc1995b1c --- /dev/null +++ b/src/libgd/gd-styled-text-renderer.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2011 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program 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 Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Cosimo Cecchi + * + */ + +#ifndef _GD_STYLED_TEXT_RENDERER_H +#define _GD_STYLED_TEXT_RENDERER_H + +#include + +#include + +G_BEGIN_DECLS + +#define GD_TYPE_STYLED_TEXT_RENDERER gd_styled_text_renderer_get_type() + +#define GD_STYLED_TEXT_RENDERER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + GD_TYPE_STYLED_TEXT_RENDERER, GdStyledTextRenderer)) + +#define GD_STYLED_TEXT_RENDERER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + GD_TYPE_STYLED_TEXT_RENDERER, GdStyledTextRendererClass)) + +#define GD_IS_STYLED_TEXT_RENDERER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + GD_TYPE_STYLED_TEXT_RENDERER)) + +#define GD_IS_STYLED_TEXT_RENDERER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + GD_TYPE_STYLED_TEXT_RENDERER)) + +#define GD_STYLED_TEXT_RENDERER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + GD_TYPE_STYLED_TEXT_RENDERER, GdStyledTextRendererClass)) + +typedef struct _GdStyledTextRenderer GdStyledTextRenderer; +typedef struct _GdStyledTextRendererClass GdStyledTextRendererClass; +typedef struct _GdStyledTextRendererPrivate GdStyledTextRendererPrivate; + +struct _GdStyledTextRenderer +{ + GtkCellRendererText parent; + + GdStyledTextRendererPrivate *priv; +}; + +struct _GdStyledTextRendererClass +{ + GtkCellRendererTextClass parent_class; +}; + +GType gd_styled_text_renderer_get_type (void) G_GNUC_CONST; + +GtkCellRenderer *gd_styled_text_renderer_new (void); +void gd_styled_text_renderer_add_class (GdStyledTextRenderer *self, + const gchar *class); +void gd_styled_text_renderer_remove_class (GdStyledTextRenderer *self, + const gchar *class); + +G_END_DECLS + +#endif /* _GD_STYLED_TEXT_RENDERER_H */ diff --git a/src/libgd/gd-tagged-entry.c b/src/libgd/gd-tagged-entry.c new file mode 100644 index 000000000..380962c60 --- /dev/null +++ b/src/libgd/gd-tagged-entry.c @@ -0,0 +1,1242 @@ +/* + * Copyright (c) 2011 Red Hat, Inc. + * Copyright (c) 2013 Ignacio Casal Quinteiro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program 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 Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Cosimo Cecchi + * + */ + +#include "gd-tagged-entry.h" + +#include + +#define BUTTON_INTERNAL_SPACING 6 + +struct _GdTaggedEntryTagPrivate { + GdTaggedEntry *entry; + GdkWindow *window; + PangoLayout *layout; + + gchar *label; + gchar *style; + gboolean has_close_button; + + cairo_surface_t *close_surface; + GtkStateFlags last_button_state; +}; + +struct _GdTaggedEntryPrivate { + GList *tags; + + GdTaggedEntryTag *in_child; + gboolean in_child_button; + gboolean in_child_active; + gboolean in_child_button_active; + gboolean button_visible; +}; + +enum { + SIGNAL_TAG_CLICKED, + SIGNAL_TAG_BUTTON_CLICKED, + LAST_SIGNAL +}; + +enum { + PROP_0, + PROP_TAG_BUTTON_VISIBLE, + NUM_PROPERTIES +}; + +enum { + PROP_TAG_0, + PROP_TAG_LABEL, + PROP_TAG_HAS_CLOSE_BUTTON, + PROP_TAG_STYLE, + NUM_TAG_PROPERTIES +}; + +G_DEFINE_TYPE (GdTaggedEntry, gd_tagged_entry, GTK_TYPE_SEARCH_ENTRY) +G_DEFINE_TYPE (GdTaggedEntryTag, gd_tagged_entry_tag, G_TYPE_OBJECT) + +static guint signals[LAST_SIGNAL] = { 0, }; +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; +static GParamSpec *tag_properties[NUM_TAG_PROPERTIES] = { NULL, }; + +static void gd_tagged_entry_get_text_area_size (GtkEntry *entry, + gint *x, + gint *y, + gint *width, + gint *height); +static gint gd_tagged_entry_tag_get_width (GdTaggedEntryTag *tag, + GdTaggedEntry *entry); +static GtkStyleContext * gd_tagged_entry_tag_get_context (GdTaggedEntryTag *tag, + GdTaggedEntry *entry); + +static void +gd_tagged_entry_tag_get_margin (GdTaggedEntryTag *tag, + GdTaggedEntry *entry, + GtkBorder *margin) +{ + GtkStyleContext *context; + + context = gd_tagged_entry_tag_get_context (tag, entry); + gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL); + gtk_style_context_get_margin (context, + gtk_style_context_get_state (context), + margin); + gtk_style_context_restore (context); +} + +static void +gd_tagged_entry_tag_ensure_close_surface (GdTaggedEntryTag *tag, + GtkStyleContext *context) +{ + GtkIconInfo *info; + GdkPixbuf *pixbuf; + gint icon_size; + gint scale_factor; + + if (tag->priv->close_surface != NULL) + return; + + gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, + &icon_size, NULL); + scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (tag->priv->entry)); + + info = gtk_icon_theme_lookup_icon_for_scale (gtk_icon_theme_get_default (), + "window-close-symbolic", + icon_size, scale_factor, + GTK_ICON_LOOKUP_GENERIC_FALLBACK); + + /* FIXME: we need a fallback icon in case the icon is not found */ + pixbuf = gtk_icon_info_load_symbolic_for_context (info, context, NULL, NULL); + tag->priv->close_surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale_factor, tag->priv->window); + + g_object_unref (info); + g_object_unref (pixbuf); +} + +static gint +gd_tagged_entry_tag_panel_get_height (GdTaggedEntryTag *tag, + GdTaggedEntry *entry) +{ + GtkWidget *widget = GTK_WIDGET (entry); + gint height, req_height; + GtkRequisition requisition; + GtkAllocation allocation; + GtkBorder margin; + + gtk_widget_get_allocation (widget, &allocation); + gtk_widget_get_preferred_size (widget, &requisition, NULL); + gd_tagged_entry_tag_get_margin (tag, entry, &margin); + + /* the tag panel height is the whole entry height, minus the tag margins */ + req_height = requisition.height - gtk_widget_get_margin_top (widget) - gtk_widget_get_margin_bottom (widget); + height = MIN (req_height, allocation.height) - margin.top - margin.bottom; + + return height; +} + +static void +gd_tagged_entry_tag_panel_get_position (GdTaggedEntry *self, + gint *x_out, + gint *y_out) +{ + GtkWidget *widget = GTK_WIDGET (self); + gint text_x, text_y, text_width, text_height, req_height; + GtkAllocation allocation; + GtkRequisition requisition; + + gtk_widget_get_allocation (widget, &allocation); + gtk_widget_get_preferred_size (widget, &requisition, NULL); + req_height = requisition.height - gtk_widget_get_margin_top (widget) - gtk_widget_get_margin_bottom (widget); + + gd_tagged_entry_get_text_area_size (GTK_ENTRY (self), &text_x, &text_y, &text_width, &text_height); + + /* allocate the panel immediately after the text area */ + if (x_out) + *x_out = allocation.x + text_x + text_width; + if (y_out) + *y_out = allocation.y + (gint) floor ((allocation.height - req_height) / 2); +} + +static gint +gd_tagged_entry_tag_panel_get_width (GdTaggedEntry *self) +{ + GdTaggedEntryTag *tag; + gint width; + GList *l; + + width = 0; + + for (l = self->priv->tags; l != NULL; l = l->next) + { + tag = l->data; + width += gd_tagged_entry_tag_get_width (tag, self); + } + + return width; +} + +static void +gd_tagged_entry_tag_ensure_layout (GdTaggedEntryTag *tag, + GdTaggedEntry *entry) +{ + if (tag->priv->layout != NULL) + return; + + tag->priv->layout = pango_layout_new (gtk_widget_get_pango_context (GTK_WIDGET (entry))); + pango_layout_set_text (tag->priv->layout, tag->priv->label, -1); +} + +static GtkStateFlags +gd_tagged_entry_tag_get_state (GdTaggedEntryTag *tag, + GdTaggedEntry *entry) +{ + GtkStateFlags state = GTK_STATE_FLAG_NORMAL; + + if (entry->priv->in_child == tag) + state |= GTK_STATE_FLAG_PRELIGHT; + + if (entry->priv->in_child_active) + state |= GTK_STATE_FLAG_ACTIVE; + + return state; +} + +static GtkStateFlags +gd_tagged_entry_tag_get_button_state (GdTaggedEntryTag *tag, + GdTaggedEntry *entry) +{ + GtkStateFlags state = GTK_STATE_FLAG_NORMAL; + + if (entry->priv->in_child == tag) + { + if (entry->priv->in_child_button_active) + state |= GTK_STATE_FLAG_ACTIVE; + + else if (entry->priv->in_child_button) + state |= GTK_STATE_FLAG_PRELIGHT; + } + + return state; +} + +static GtkStyleContext * +gd_tagged_entry_tag_get_context (GdTaggedEntryTag *tag, + GdTaggedEntry *entry) +{ + GtkWidget *widget = GTK_WIDGET (entry); + GtkStyleContext *retval; + GList *l, *list; + + retval = gtk_widget_get_style_context (widget); + gtk_style_context_save (retval); + + list = gtk_style_context_list_classes (retval); + for (l = list; l; l = l->next) + gtk_style_context_remove_class (retval, l->data); + g_list_free (list); + gtk_style_context_add_class (retval, tag->priv->style); + + return retval; +} + +static gint +gd_tagged_entry_tag_get_width (GdTaggedEntryTag *tag, + GdTaggedEntry *entry) +{ + GtkBorder button_padding, button_border, button_margin; + GtkStyleContext *context; + GtkStateFlags state; + gint layout_width; + gint button_width; + gint scale_factor; + + gd_tagged_entry_tag_ensure_layout (tag, entry); + pango_layout_get_pixel_size (tag->priv->layout, &layout_width, NULL); + + context = gd_tagged_entry_tag_get_context (tag, entry); + state = gd_tagged_entry_tag_get_state (tag, entry); + + gtk_style_context_set_state (context, state); + gtk_style_context_get_padding (context, + gtk_style_context_get_state (context), + &button_padding); + gtk_style_context_get_border (context, + gtk_style_context_get_state (context), + &button_border); + gtk_style_context_get_margin (context, + gtk_style_context_get_state (context), + &button_margin); + + gd_tagged_entry_tag_ensure_close_surface (tag, context); + + gtk_style_context_restore (context); + + button_width = 0; + if (entry->priv->button_visible && tag->priv->has_close_button) + { + scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (entry)); + button_width = cairo_image_surface_get_width (tag->priv->close_surface) / scale_factor + + BUTTON_INTERNAL_SPACING; + } + + return layout_width + button_padding.left + button_padding.right + + button_border.left + button_border.right + + button_margin.left + button_margin.right + + button_width; +} + +static void +gd_tagged_entry_tag_get_size (GdTaggedEntryTag *tag, + GdTaggedEntry *entry, + gint *width_out, + gint *height_out) +{ + gint width, panel_height; + + width = gd_tagged_entry_tag_get_width (tag, entry); + panel_height = gd_tagged_entry_tag_panel_get_height (tag, entry); + + if (width_out) + *width_out = width; + if (height_out) + *height_out = panel_height; +} + +static void +gd_tagged_entry_tag_get_relative_allocations (GdTaggedEntryTag *tag, + GdTaggedEntry *entry, + GtkStyleContext *context, + GtkAllocation *background_allocation_out, + GtkAllocation *layout_allocation_out, + GtkAllocation *button_allocation_out) +{ + GtkAllocation background_allocation, layout_allocation, button_allocation; + gint width, height, x, y, pix_width, pix_height; + gint layout_width, layout_height; + gint scale_factor; + GtkBorder padding, border; + GtkStateFlags state; + + width = gdk_window_get_width (tag->priv->window); + height = gdk_window_get_height (tag->priv->window); + scale_factor = gdk_window_get_scale_factor (tag->priv->window); + + state = gd_tagged_entry_tag_get_state (tag, entry); + gtk_style_context_save (context); + gtk_style_context_set_state (context, state); + gtk_style_context_get_margin (context, + gtk_style_context_get_state (context), + &padding); + gtk_style_context_restore (context); + + width -= padding.left + padding.right; + height -= padding.top + padding.bottom; + x = padding.left; + y = padding.top; + + background_allocation.x = x; + background_allocation.y = y; + background_allocation.width = width; + background_allocation.height = height; + + layout_allocation = button_allocation = background_allocation; + + gtk_style_context_save (context); + gtk_style_context_set_state (context, state); + gtk_style_context_get_padding (context, + gtk_style_context_get_state (context), + &padding); + gtk_style_context_get_border (context, + gtk_style_context_get_state (context), + &border); + gtk_style_context_restore (context); + + gd_tagged_entry_tag_ensure_layout (tag, entry); + pango_layout_get_pixel_size (tag->priv->layout, &layout_width, &layout_height); + + layout_allocation.x += border.left + padding.left; + layout_allocation.y += (layout_allocation.height - layout_height) / 2; + + if (entry->priv->button_visible && tag->priv->has_close_button) + { + pix_width = cairo_image_surface_get_width (tag->priv->close_surface) / scale_factor; + pix_height = cairo_image_surface_get_height (tag->priv->close_surface) / scale_factor; + } + else + { + pix_width = 0; + pix_height = 0; + } + + button_allocation.x += width - pix_width - border.right - padding.right; + button_allocation.y += (height - pix_height) / 2; + button_allocation.width = pix_width; + button_allocation.height = pix_height; + + if (background_allocation_out) + *background_allocation_out = background_allocation; + if (layout_allocation_out) + *layout_allocation_out = layout_allocation; + if (button_allocation_out) + *button_allocation_out = button_allocation; +} + +static gboolean +gd_tagged_entry_tag_event_is_button (GdTaggedEntryTag *tag, + GdTaggedEntry *entry, + gdouble event_x, + gdouble event_y) +{ + GtkAllocation button_allocation; + GtkStyleContext *context; + + if (!entry->priv->button_visible || !tag->priv->has_close_button) + return FALSE; + + context = gd_tagged_entry_tag_get_context (tag, entry); + gd_tagged_entry_tag_get_relative_allocations (tag, entry, context, NULL, NULL, &button_allocation); + + gtk_style_context_restore (context); + + /* see if the event falls into the button allocation */ + if ((event_x >= button_allocation.x && + event_x <= button_allocation.x + button_allocation.width) && + (event_y >= button_allocation.y && + event_y <= button_allocation.y + button_allocation.height)) + return TRUE; + + return FALSE; +} + +gboolean +gd_tagged_entry_tag_get_area (GdTaggedEntryTag *tag, + cairo_rectangle_int_t *rect) +{ + GtkStyleContext *context; + GtkAllocation background_allocation; + int window_x, window_y; + GtkAllocation alloc; + + g_return_val_if_fail (GD_IS_TAGGED_ENTRY_TAG (tag), FALSE); + g_return_val_if_fail (rect != NULL, FALSE); + + gdk_window_get_position (tag->priv->window, &window_x, &window_y); + gtk_widget_get_allocation (GTK_WIDGET (tag->priv->entry), &alloc); + context = gd_tagged_entry_tag_get_context (tag, tag->priv->entry); + gd_tagged_entry_tag_get_relative_allocations (tag, tag->priv->entry, context, + &background_allocation, + NULL, NULL); + gtk_style_context_restore (context); + + rect->x = window_x - alloc.x + background_allocation.x; + rect->y = window_y - alloc.y + background_allocation.y; + rect->width = background_allocation.width; + rect->height = background_allocation.height; + + return TRUE; +} + +static void +gd_tagged_entry_tag_draw (GdTaggedEntryTag *tag, + cairo_t *cr, + GdTaggedEntry *entry) +{ + GtkStyleContext *context; + GtkStateFlags state; + GtkAllocation background_allocation, layout_allocation, button_allocation; + + context = gd_tagged_entry_tag_get_context (tag, entry); + gd_tagged_entry_tag_get_relative_allocations (tag, entry, context, + &background_allocation, + &layout_allocation, + &button_allocation); + + cairo_save (cr); + gtk_cairo_transform_to_window (cr, GTK_WIDGET (entry), tag->priv->window); + + gtk_style_context_save (context); + + state = gd_tagged_entry_tag_get_state (tag, entry); + gtk_style_context_set_state (context, state); + gtk_render_background (context, cr, + background_allocation.x, background_allocation.y, + background_allocation.width, background_allocation.height); + gtk_render_frame (context, cr, + background_allocation.x, background_allocation.y, + background_allocation.width, background_allocation.height); + + gtk_render_layout (context, cr, + layout_allocation.x, layout_allocation.y, + tag->priv->layout); + + gtk_style_context_restore (context); + + if (!entry->priv->button_visible || !tag->priv->has_close_button) + goto done; + + gtk_style_context_add_class (context, GTK_STYLE_CLASS_BUTTON); + state = gd_tagged_entry_tag_get_button_state (tag, entry); + gtk_style_context_set_state (context, state); + + /* if the state changed since last time we draw the pixbuf, + * clear and redraw it. + */ + if (state != tag->priv->last_button_state) + { + g_clear_pointer (&tag->priv->close_surface, cairo_surface_destroy); + gd_tagged_entry_tag_ensure_close_surface (tag, context); + + tag->priv->last_button_state = state; + } + + gtk_render_background (context, cr, + button_allocation.x, button_allocation.y, + button_allocation.width, button_allocation.height); + gtk_render_frame (context, cr, + button_allocation.x, button_allocation.y, + button_allocation.width, button_allocation.height); + + gtk_render_icon_surface (context, cr, + tag->priv->close_surface, + button_allocation.x, button_allocation.y); + +done: + gtk_style_context_restore (context); + + cairo_restore (cr); +} + +static void +gd_tagged_entry_tag_unrealize (GdTaggedEntryTag *tag) +{ + if (tag->priv->window == NULL) + return; + + gdk_window_set_user_data (tag->priv->window, NULL); + gdk_window_destroy (tag->priv->window); + tag->priv->window = NULL; +} + +static void +gd_tagged_entry_tag_realize (GdTaggedEntryTag *tag, + GdTaggedEntry *entry) +{ + GtkWidget *widget = GTK_WIDGET (entry); + GdkWindowAttr attributes; + gint attributes_mask; + gint tag_width, tag_height; + + if (tag->priv->window != NULL) + return; + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.wclass = GDK_INPUT_ONLY; + attributes.event_mask = gtk_widget_get_events (widget); + attributes.event_mask |= GDK_BUTTON_PRESS_MASK + | GDK_BUTTON_RELEASE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK + | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK; + + gd_tagged_entry_tag_get_size (tag, entry, &tag_width, &tag_height); + attributes.x = 0; + attributes.y = 0; + attributes.width = tag_width; + attributes.height = tag_height; + + attributes_mask = GDK_WA_X | GDK_WA_Y; + + tag->priv->window = gdk_window_new (gtk_widget_get_window (widget), + &attributes, attributes_mask); + gdk_window_set_user_data (tag->priv->window, widget); +} + +static gboolean +gd_tagged_entry_draw (GtkWidget *widget, + cairo_t *cr) +{ + GdTaggedEntry *self = GD_TAGGED_ENTRY (widget); + GdTaggedEntryTag *tag; + GList *l; + + GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->draw (widget, cr); + + for (l = self->priv->tags; l != NULL; l = l->next) + { + tag = l->data; + gd_tagged_entry_tag_draw (tag, cr, self); + } + + return FALSE; +} + +static void +gd_tagged_entry_map (GtkWidget *widget) +{ + GdTaggedEntry *self = GD_TAGGED_ENTRY (widget); + GdTaggedEntryTag *tag; + GList *l; + + if (gtk_widget_get_realized (widget) && !gtk_widget_get_mapped (widget)) + { + GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->map (widget); + + for (l = self->priv->tags; l != NULL; l = l->next) + { + tag = l->data; + gdk_window_show (tag->priv->window); + } + } +} + +static void +gd_tagged_entry_unmap (GtkWidget *widget) +{ + GdTaggedEntry *self = GD_TAGGED_ENTRY (widget); + GdTaggedEntryTag *tag; + GList *l; + + if (gtk_widget_get_mapped (widget)) + { + for (l = self->priv->tags; l != NULL; l = l->next) + { + tag = l->data; + gdk_window_hide (tag->priv->window); + } + + GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->unmap (widget); + } +} + +static void +gd_tagged_entry_realize (GtkWidget *widget) +{ + GdTaggedEntry *self = GD_TAGGED_ENTRY (widget); + GdTaggedEntryTag *tag; + GList *l; + + GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->realize (widget); + + for (l = self->priv->tags; l != NULL; l = l->next) + { + tag = l->data; + gd_tagged_entry_tag_realize (tag, self); + } +} + +static void +gd_tagged_entry_unrealize (GtkWidget *widget) +{ + GdTaggedEntry *self = GD_TAGGED_ENTRY (widget); + GdTaggedEntryTag *tag; + GList *l; + + GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->unrealize (widget); + + for (l = self->priv->tags; l != NULL; l = l->next) + { + tag = l->data; + gd_tagged_entry_tag_unrealize (tag); + } +} + +static void +gd_tagged_entry_get_text_area_size (GtkEntry *entry, + gint *x, + gint *y, + gint *width, + gint *height) +{ + GdTaggedEntry *self = GD_TAGGED_ENTRY (entry); + gint tag_panel_width; + + GTK_ENTRY_CLASS (gd_tagged_entry_parent_class)->get_text_area_size (entry, x, y, width, height); + + tag_panel_width = gd_tagged_entry_tag_panel_get_width (self); + + if (width) + *width -= tag_panel_width; +} + +static void +gd_tagged_entry_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GdTaggedEntry *self = GD_TAGGED_ENTRY (widget); + gint x, y, width, height; + GdTaggedEntryTag *tag; + GList *l; + + gtk_widget_set_allocation (widget, allocation); + GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->size_allocate (widget, allocation); + + if (gtk_widget_get_realized (widget)) + { + gd_tagged_entry_tag_panel_get_position (self, &x, &y); + + for (l = self->priv->tags; l != NULL; l = l->next) + { + GtkBorder margin; + + tag = l->data; + gd_tagged_entry_tag_get_size (tag, self, &width, &height); + gd_tagged_entry_tag_get_margin (tag, self, &margin); + gdk_window_move_resize (tag->priv->window, x, y + margin.top, width, height); + + x += width; + } + + gtk_widget_queue_draw (widget); + } +} + +static void +gd_tagged_entry_get_preferred_width (GtkWidget *widget, + gint *minimum, + gint *natural) +{ + GdTaggedEntry *self = GD_TAGGED_ENTRY (widget); + gint tag_panel_width; + + GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->get_preferred_width (widget, minimum, natural); + + tag_panel_width = gd_tagged_entry_tag_panel_get_width (self); + + if (minimum) + *minimum += tag_panel_width; + if (natural) + *natural += tag_panel_width; +} + +static void +gd_tagged_entry_finalize (GObject *obj) +{ + GdTaggedEntry *self = GD_TAGGED_ENTRY (obj); + + if (self->priv->tags != NULL) + { + g_list_free_full (self->priv->tags, g_object_unref); + self->priv->tags = NULL; + } + + G_OBJECT_CLASS (gd_tagged_entry_parent_class)->finalize (obj); +} + +static GdTaggedEntryTag * +gd_tagged_entry_find_tag_by_window (GdTaggedEntry *self, + GdkWindow *window) +{ + GdTaggedEntryTag *tag = NULL, *elem; + GList *l; + + for (l = self->priv->tags; l != NULL; l = l->next) + { + elem = l->data; + if (elem->priv->window == window) + { + tag = elem; + break; + } + } + + return tag; +} + +static gint +gd_tagged_entry_enter_notify (GtkWidget *widget, + GdkEventCrossing *event) +{ + GdTaggedEntry *self = GD_TAGGED_ENTRY (widget); + GdTaggedEntryTag *tag; + + tag = gd_tagged_entry_find_tag_by_window (self, event->window); + + if (tag != NULL) + { + self->priv->in_child = tag; + gtk_widget_queue_draw (widget); + } + + return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->enter_notify_event (widget, event); +} + +static gint +gd_tagged_entry_leave_notify (GtkWidget *widget, + GdkEventCrossing *event) +{ + GdTaggedEntry *self = GD_TAGGED_ENTRY (widget); + + if (self->priv->in_child != NULL) + { + self->priv->in_child = NULL; + gtk_widget_queue_draw (widget); + } + + return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->leave_notify_event (widget, event); +} + +static gint +gd_tagged_entry_motion_notify (GtkWidget *widget, + GdkEventMotion *event) +{ + GdTaggedEntry *self = GD_TAGGED_ENTRY (widget); + GdTaggedEntryTag *tag; + + tag = gd_tagged_entry_find_tag_by_window (self, event->window); + + if (tag != NULL) + { + gdk_event_request_motions (event); + + self->priv->in_child = tag; + self->priv->in_child_button = gd_tagged_entry_tag_event_is_button (tag, self, event->x, event->y); + gtk_widget_queue_draw (widget); + + return FALSE; + } + + return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->motion_notify_event (widget, event); +} + +static gboolean +gd_tagged_entry_button_release_event (GtkWidget *widget, + GdkEventButton *event) +{ + GdTaggedEntry *self = GD_TAGGED_ENTRY (widget); + GdTaggedEntryTag *tag; + + tag = gd_tagged_entry_find_tag_by_window (self, event->window); + + if (tag != NULL) + { + self->priv->in_child_active = FALSE; + + if (gd_tagged_entry_tag_event_is_button (tag, self, event->x, event->y)) + { + self->priv->in_child_button_active = FALSE; + g_signal_emit (self, signals[SIGNAL_TAG_BUTTON_CLICKED], 0, tag); + } + else + { + g_signal_emit (self, signals[SIGNAL_TAG_CLICKED], 0, tag); + } + + gtk_widget_queue_draw (widget); + + return TRUE; + } + + return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->button_release_event (widget, event); +} + +static gboolean +gd_tagged_entry_button_press_event (GtkWidget *widget, + GdkEventButton *event) +{ + GdTaggedEntry *self = GD_TAGGED_ENTRY (widget); + GdTaggedEntryTag *tag; + + tag = gd_tagged_entry_find_tag_by_window (self, event->window); + + if (tag != NULL) + { + if (gd_tagged_entry_tag_event_is_button (tag, self, event->x, event->y)) + self->priv->in_child_button_active = TRUE; + else + self->priv->in_child_active = TRUE; + + gtk_widget_queue_draw (widget); + + return TRUE; + } + + return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->button_press_event (widget, event); +} + +static void +gd_tagged_entry_init (GdTaggedEntry *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GD_TYPE_TAGGED_ENTRY, GdTaggedEntryPrivate); + self->priv->button_visible = TRUE; +} + +static void +gd_tagged_entry_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GdTaggedEntry *self = GD_TAGGED_ENTRY (object); + + switch (property_id) + { + case PROP_TAG_BUTTON_VISIBLE: + g_value_set_boolean (value, gd_tagged_entry_get_tag_button_visible (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +gd_tagged_entry_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GdTaggedEntry *self = GD_TAGGED_ENTRY (object); + + switch (property_id) + { + case PROP_TAG_BUTTON_VISIBLE: + gd_tagged_entry_set_tag_button_visible (self, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +gd_tagged_entry_class_init (GdTaggedEntryClass *klass) +{ + GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass); + GtkEntryClass *eclass = GTK_ENTRY_CLASS (klass); + GObjectClass *oclass = G_OBJECT_CLASS (klass); + + oclass->finalize = gd_tagged_entry_finalize; + oclass->set_property = gd_tagged_entry_set_property; + oclass->get_property = gd_tagged_entry_get_property; + + wclass->realize = gd_tagged_entry_realize; + wclass->unrealize = gd_tagged_entry_unrealize; + wclass->map = gd_tagged_entry_map; + wclass->unmap = gd_tagged_entry_unmap; + wclass->size_allocate = gd_tagged_entry_size_allocate; + wclass->get_preferred_width = gd_tagged_entry_get_preferred_width; + wclass->draw = gd_tagged_entry_draw; + wclass->enter_notify_event = gd_tagged_entry_enter_notify; + wclass->leave_notify_event = gd_tagged_entry_leave_notify; + wclass->motion_notify_event = gd_tagged_entry_motion_notify; + wclass->button_press_event = gd_tagged_entry_button_press_event; + wclass->button_release_event = gd_tagged_entry_button_release_event; + + eclass->get_text_area_size = gd_tagged_entry_get_text_area_size; + + signals[SIGNAL_TAG_CLICKED] = + g_signal_new ("tag-clicked", + GD_TYPE_TAGGED_ENTRY, + G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED, + 0, NULL, NULL, NULL, + G_TYPE_NONE, + 1, GD_TYPE_TAGGED_ENTRY_TAG); + signals[SIGNAL_TAG_BUTTON_CLICKED] = + g_signal_new ("tag-button-clicked", + GD_TYPE_TAGGED_ENTRY, + G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED, + 0, NULL, NULL, NULL, + G_TYPE_NONE, + 1, GD_TYPE_TAGGED_ENTRY_TAG); + + properties[PROP_TAG_BUTTON_VISIBLE] = + g_param_spec_boolean ("tag-close-visible", "Tag close icon visibility", + "Whether the close button should be shown in tags.", TRUE, + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); + + g_type_class_add_private (klass, sizeof (GdTaggedEntryPrivate)); + g_object_class_install_properties (oclass, NUM_PROPERTIES, properties); +} + +static void +gd_tagged_entry_tag_init (GdTaggedEntryTag *self) +{ + GdTaggedEntryTagPrivate *priv; + + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GD_TYPE_TAGGED_ENTRY_TAG, GdTaggedEntryTagPrivate); + priv = self->priv; + + priv->last_button_state = GTK_STATE_FLAG_NORMAL; +} + +static void +gd_tagged_entry_tag_finalize (GObject *obj) +{ + GdTaggedEntryTag *tag = GD_TAGGED_ENTRY_TAG (obj); + GdTaggedEntryTagPrivate *priv = tag->priv; + + if (priv->window != NULL) + gd_tagged_entry_tag_unrealize (tag); + + g_clear_object (&priv->layout); + g_clear_pointer (&priv->close_surface, cairo_surface_destroy); + g_free (priv->label); + g_free (priv->style); + + G_OBJECT_CLASS (gd_tagged_entry_tag_parent_class)->finalize (obj); +} + +static void +gd_tagged_entry_tag_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GdTaggedEntryTag *self = GD_TAGGED_ENTRY_TAG (object); + + switch (property_id) + { + case PROP_TAG_LABEL: + g_value_set_string (value, gd_tagged_entry_tag_get_label (self)); + break; + case PROP_TAG_HAS_CLOSE_BUTTON: + g_value_set_boolean (value, gd_tagged_entry_tag_get_has_close_button (self)); + break; + case PROP_TAG_STYLE: + g_value_set_string (value, gd_tagged_entry_tag_get_style (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +gd_tagged_entry_tag_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GdTaggedEntryTag *self = GD_TAGGED_ENTRY_TAG (object); + + switch (property_id) + { + case PROP_TAG_LABEL: + gd_tagged_entry_tag_set_label (self, g_value_get_string (value)); + break; + case PROP_TAG_HAS_CLOSE_BUTTON: + gd_tagged_entry_tag_set_has_close_button (self, g_value_get_boolean (value)); + break; + case PROP_TAG_STYLE: + gd_tagged_entry_tag_set_style (self, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +gd_tagged_entry_tag_class_init (GdTaggedEntryTagClass *klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + + oclass->finalize = gd_tagged_entry_tag_finalize; + oclass->set_property = gd_tagged_entry_tag_set_property; + oclass->get_property = gd_tagged_entry_tag_get_property; + + tag_properties[PROP_TAG_LABEL] = + g_param_spec_string ("label", "Label", + "Text to show on the tag.", NULL, + G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + tag_properties[PROP_TAG_HAS_CLOSE_BUTTON] = + g_param_spec_boolean ("has-close-button", "Tag has a close button", + "Whether the tag has a close button.", TRUE, + G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + tag_properties[PROP_TAG_STYLE] = + g_param_spec_string ("style", "Style", + "Style of the tag.", "entry-tag", + G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_type_class_add_private (klass, sizeof (GdTaggedEntryTagPrivate)); + g_object_class_install_properties (oclass, NUM_TAG_PROPERTIES, tag_properties); +} + +GdTaggedEntry * +gd_tagged_entry_new (void) +{ + return g_object_new (GD_TYPE_TAGGED_ENTRY, NULL); +} + +gboolean +gd_tagged_entry_insert_tag (GdTaggedEntry *self, + GdTaggedEntryTag *tag, + gint position) +{ + if (g_list_find (self->priv->tags, tag) != NULL) + return FALSE; + + tag->priv->entry = self; + + self->priv->tags = g_list_insert (self->priv->tags, g_object_ref (tag), position); + + if (gtk_widget_get_realized (GTK_WIDGET (self))) + gd_tagged_entry_tag_realize (tag, self); + + if (gtk_widget_get_mapped (GTK_WIDGET (self))) + gdk_window_show_unraised (tag->priv->window); + + gtk_widget_queue_resize (GTK_WIDGET (self)); + + return TRUE; +} + +gboolean +gd_tagged_entry_add_tag (GdTaggedEntry *self, + GdTaggedEntryTag *tag) +{ + return gd_tagged_entry_insert_tag (self, tag, -1); +} + +gboolean +gd_tagged_entry_remove_tag (GdTaggedEntry *self, + GdTaggedEntryTag *tag) +{ + if (!g_list_find (self->priv->tags, tag)) + return FALSE; + + gd_tagged_entry_tag_unrealize (tag); + + self->priv->tags = g_list_remove (self->priv->tags, tag); + g_object_unref (tag); + + gtk_widget_queue_resize (GTK_WIDGET (self)); + + return TRUE; +} + +GdTaggedEntryTag * +gd_tagged_entry_tag_new (const gchar *label) +{ + return g_object_new (GD_TYPE_TAGGED_ENTRY_TAG, "label", label, NULL); +} + +void +gd_tagged_entry_tag_set_label (GdTaggedEntryTag *tag, + const gchar *label) +{ + GdTaggedEntryTagPrivate *priv; + + g_return_if_fail (GD_IS_TAGGED_ENTRY_TAG (tag)); + + priv = tag->priv; + + if (g_strcmp0 (priv->label, label) != 0) + { + GtkWidget *entry; + + g_free (priv->label); + priv->label = g_strdup (label); + g_clear_object (&priv->layout); + + entry = GTK_WIDGET (tag->priv->entry); + if (entry) + gtk_widget_queue_resize (entry); + } +} + +const gchar * +gd_tagged_entry_tag_get_label (GdTaggedEntryTag *tag) +{ + g_return_val_if_fail (GD_IS_TAGGED_ENTRY_TAG (tag), NULL); + + return tag->priv->label; +} + +void +gd_tagged_entry_tag_set_has_close_button (GdTaggedEntryTag *tag, + gboolean has_close_button) +{ + GdTaggedEntryTagPrivate *priv; + + g_return_if_fail (GD_IS_TAGGED_ENTRY_TAG (tag)); + + priv = tag->priv; + + has_close_button = has_close_button != FALSE; + if (priv->has_close_button != has_close_button) + { + GtkWidget *entry; + + priv->has_close_button = has_close_button; + g_clear_object (&priv->layout); + + entry = GTK_WIDGET (priv->entry); + if (entry) + gtk_widget_queue_resize (entry); + } +} + +gboolean +gd_tagged_entry_tag_get_has_close_button (GdTaggedEntryTag *tag) +{ + g_return_val_if_fail (GD_IS_TAGGED_ENTRY_TAG (tag), FALSE); + + return tag->priv->has_close_button; +} + +void +gd_tagged_entry_tag_set_style (GdTaggedEntryTag *tag, + const gchar *style) +{ + GdTaggedEntryTagPrivate *priv; + + g_return_if_fail (GD_IS_TAGGED_ENTRY_TAG (tag)); + + priv = tag->priv; + + if (g_strcmp0 (priv->style, style) != 0) + { + GtkWidget *entry; + + g_free (priv->style); + priv->style = g_strdup (style); + g_clear_object (&priv->layout); + + entry = GTK_WIDGET (tag->priv->entry); + if (entry) + gtk_widget_queue_resize (entry); + } +} + +const gchar * +gd_tagged_entry_tag_get_style (GdTaggedEntryTag *tag) +{ + g_return_val_if_fail (GD_IS_TAGGED_ENTRY_TAG (tag), NULL); + + return tag->priv->style; +} + +void +gd_tagged_entry_set_tag_button_visible (GdTaggedEntry *self, + gboolean visible) +{ + g_return_if_fail (GD_IS_TAGGED_ENTRY (self)); + + if (self->priv->button_visible == visible) + return; + + self->priv->button_visible = visible; + gtk_widget_queue_resize (GTK_WIDGET (self)); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TAG_BUTTON_VISIBLE]); +} + +gboolean +gd_tagged_entry_get_tag_button_visible (GdTaggedEntry *self) +{ + g_return_val_if_fail (GD_IS_TAGGED_ENTRY (self), FALSE); + + return self->priv->button_visible; +} diff --git a/src/libgd/gd-tagged-entry.h b/src/libgd/gd-tagged-entry.h new file mode 100644 index 000000000..ba9f67314 --- /dev/null +++ b/src/libgd/gd-tagged-entry.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2011 Red Hat, Inc. + * Copyright (c) 2013 Ignacio Casal Quinteiro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program 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 Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Cosimo Cecchi + * + */ + +#ifndef __GD_TAGGED_ENTRY_H__ +#define __GD_TAGGED_ENTRY_H__ + +#include + +#include + +G_BEGIN_DECLS + +#define GD_TYPE_TAGGED_ENTRY gd_tagged_entry_get_type() +#define GD_TAGGED_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GD_TYPE_TAGGED_ENTRY, GdTaggedEntry)) +#define GD_TAGGED_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GD_TYPE_TAGGED_ENTRY, GdTaggedEntryClass)) +#define GD_IS_TAGGED_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GD_TYPE_TAGGED_ENTRY)) +#define GD_IS_TAGGED_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GD_TYPE_TAGGED_ENTRY)) +#define GD_TAGGED_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GD_TYPE_TAGGED_ENTRY, GdTaggedEntryClass)) + +typedef struct _GdTaggedEntry GdTaggedEntry; +typedef struct _GdTaggedEntryClass GdTaggedEntryClass; +typedef struct _GdTaggedEntryPrivate GdTaggedEntryPrivate; + +typedef struct _GdTaggedEntryTag GdTaggedEntryTag; +typedef struct _GdTaggedEntryTagClass GdTaggedEntryTagClass; +typedef struct _GdTaggedEntryTagPrivate GdTaggedEntryTagPrivate; + +struct _GdTaggedEntry +{ + GtkSearchEntry parent; + + GdTaggedEntryPrivate *priv; +}; + +struct _GdTaggedEntryClass +{ + GtkSearchEntryClass parent_class; +}; + +#define GD_TYPE_TAGGED_ENTRY_TAG gd_tagged_entry_tag_get_type() +#define GD_TAGGED_ENTRY_TAG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GD_TYPE_TAGGED_ENTRY_TAG, GdTaggedEntryTag)) +#define GD_TAGGED_ENTRY_TAG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GD_TYPE_TAGGED_ENTRY_TAG, GdTaggedEntryTagClass)) +#define GD_IS_TAGGED_ENTRY_TAG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GD_TYPE_TAGGED_ENTRY_TAG)) +#define GD_IS_TAGGED_ENTRY_TAG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GD_TYPE_TAGGED_ENTRY_TAG)) +#define GD_TAGGED_ENTRY_TAG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GD_TYPE_TAGGED_ENTRY_TAG, GdTaggedEntryTagClass)) + +struct _GdTaggedEntryTag +{ + GObject parent; + + GdTaggedEntryTagPrivate *priv; +}; + +struct _GdTaggedEntryTagClass +{ + GObjectClass parent_class; +}; + +GType gd_tagged_entry_get_type (void) G_GNUC_CONST; + +GdTaggedEntry *gd_tagged_entry_new (void); + +void gd_tagged_entry_set_tag_button_visible (GdTaggedEntry *self, + gboolean visible); +gboolean gd_tagged_entry_get_tag_button_visible (GdTaggedEntry *self); + +gboolean gd_tagged_entry_insert_tag (GdTaggedEntry *self, + GdTaggedEntryTag *tag, + gint position); + +gboolean gd_tagged_entry_add_tag (GdTaggedEntry *self, + GdTaggedEntryTag *tag); + +gboolean gd_tagged_entry_remove_tag (GdTaggedEntry *self, + GdTaggedEntryTag *tag); + +GType gd_tagged_entry_tag_get_type (void) G_GNUC_CONST; + +GdTaggedEntryTag *gd_tagged_entry_tag_new (const gchar *label); + +void gd_tagged_entry_tag_set_label (GdTaggedEntryTag *tag, + const gchar *label); +const gchar *gd_tagged_entry_tag_get_label (GdTaggedEntryTag *tag); + +void gd_tagged_entry_tag_set_has_close_button (GdTaggedEntryTag *tag, + gboolean has_close_button); +gboolean gd_tagged_entry_tag_get_has_close_button (GdTaggedEntryTag *tag); + +void gd_tagged_entry_tag_set_style (GdTaggedEntryTag *tag, + const gchar *style); +const gchar *gd_tagged_entry_tag_get_style (GdTaggedEntryTag *tag); + +gboolean gd_tagged_entry_tag_get_area (GdTaggedEntryTag *tag, + cairo_rectangle_int_t *rect); + +G_END_DECLS + +#endif /* __GD_TAGGED_ENTRY_H__ */ diff --git a/src/meson.build b/src/meson.build index 91c0a00c8..1acae56f8 100644 --- a/src/meson.build +++ b/src/meson.build @@ -61,6 +61,10 @@ libnautilus_sources = [ 'gtk/nautilusgtkplacesviewprivate.h', 'gtk/nautilusgtkplacesviewrow.c', 'gtk/nautilusgtkplacesviewrowprivate.h', + 'libgd/gd-styled-text-renderer.c', + 'libgd/gd-styled-text-renderer.h', + 'libgd/gd-tagged-entry.c', + 'libgd/gd-tagged-entry.h', 'nautilus-application.c', 'nautilus-application.h', 'nautilus-bookmark-list.c', @@ -268,7 +272,7 @@ libnautilus_sources = [ 'nautilus-enums.h', 'nautilus-types.h', 'nautilus-tracker-utilities.c', - 'nautilus-tracker-utilities.h' + 'nautilus-tracker-utilities.h', ] nautilus_deps = [ @@ -278,7 +282,6 @@ nautilus_deps = [ gmodule, gnome_autoar, gsettings_desktop_schemas, - libgd_dep, nautilus_extension, seccomp, selinux, diff --git a/src/nautilus-list-view.c b/src/nautilus-list-view.c index 9b7b7826b..07e72e297 100644 --- a/src/nautilus-list-view.c +++ b/src/nautilus-list-view.c @@ -30,9 +30,10 @@ #include #include #include -#include #include +#include "libgd/gd-styled-text-renderer.h" + #define DEBUG_FLAG NAUTILUS_DEBUG_LIST_VIEW #include "nautilus-debug.h" diff --git a/src/nautilus-query-editor.c b/src/nautilus-query-editor.c index 0f284b5b3..382a0eb10 100644 --- a/src/nautilus-query-editor.c +++ b/src/nautilus-query-editor.c @@ -26,9 +26,9 @@ #include #include #include -#include #include +#include "libgd/gd-tagged-entry.h" #include "nautilus-file.h" #include "nautilus-file-utilities.h" #include "nautilus-global-preferences.h" diff --git a/src/nautilus-ui-utilities.c b/src/nautilus-ui-utilities.c index 1c84ee113..812199cbb 100644 --- a/src/nautilus-ui-utilities.c +++ b/src/nautilus-ui-utilities.c @@ -28,7 +28,7 @@ #include #include -#include +#include #include #include @@ -174,6 +174,107 @@ nautilus_gmenu_add_item_in_submodel (GMenu *menu, #define NAUTILUS_THUMBNAIL_FRAME_RIGHT 3 #define NAUTILUS_THUMBNAIL_FRAME_BOTTOM 3 +static cairo_surface_t * +embed_surface_in_frame (cairo_surface_t *source_image, + const gchar *frame_image_url, + GtkBorder *slice_width, + GtkBorder *border_width) +{ + cairo_surface_t *surface; + cairo_t *cr; + gint source_width; + gint source_height; + g_autofree gchar *css_str = NULL; + g_autoptr (GtkCssProvider) provider = NULL; + g_autoptr (GtkStyleContext) context = NULL; + g_autoptr (GError) error = NULL; + g_autoptr (GtkWidgetPath) path = NULL; + gdouble scale_x; + gdouble scale_y; + + cairo_surface_get_device_scale (source_image, &scale_x, &scale_y); + + source_width = cairo_image_surface_get_width (source_image) / (gint) floor (scale_x); + source_height = cairo_image_surface_get_height (source_image) / (gint) floor (scale_y); + + css_str = g_strdup_printf (".embedded-image { border-image: url(\"%s\") %d %d %d %d / %dpx %dpx %dpx %dpx }", + frame_image_url, + slice_width->top, slice_width->right, slice_width->bottom, slice_width->left, + border_width->top, border_width->right, border_width->bottom, border_width->left); + provider = gtk_css_provider_new (); + gtk_css_provider_load_from_data (provider, css_str, -1, &error); + + if (error != NULL) + { + g_warning ("Unable to create the thumbnail frame image: %s", error->message); + + return g_object_ref (source_image); + } + + surface = cairo_surface_create_similar (source_image, + CAIRO_CONTENT_COLOR_ALPHA, + source_width, source_height); + cr = cairo_create (surface); + + context = gtk_style_context_new (); + path = gtk_widget_path_new (); + gtk_widget_path_append_type (path, GTK_TYPE_ICON_VIEW); + + gtk_style_context_set_path (context, path); + gtk_style_context_add_provider (context, + GTK_STYLE_PROVIDER (provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + cairo_save (cr); + cairo_rectangle (cr, + border_width->left, + border_width->top, + source_width - border_width->left - border_width->right, + source_height - border_width->top - border_width->bottom); + cairo_clip (cr); + gtk_render_icon_surface (context, cr, source_image, 0, 0); + cairo_restore (cr); + + gtk_style_context_save (context); + gtk_style_context_add_class (context, "embedded-image"); + + gtk_render_frame (context, cr, 0, 0, source_width, source_height); + + gtk_style_context_restore (context); + cairo_destroy (cr); + + return surface; +} + +static GdkPixbuf * +embed_image_in_frame (GdkPixbuf *source_image, + const gchar *frame_image_url, + GtkBorder *slice_width, + GtkBorder *border_width) +{ + cairo_surface_t *surface; + cairo_surface_t *embedded_surface; + GdkPixbuf *retval; + + surface = gdk_cairo_surface_create_from_pixbuf (source_image, 0, NULL); + + /* Force the device scale to 1.0, since pixbufs are always in unscaled + * dimensions. + */ + cairo_surface_set_device_scale (surface, 1.0, 1.0); + embedded_surface = embed_surface_in_frame (surface, frame_image_url, + slice_width, border_width); + retval = gdk_pixbuf_get_from_surface (embedded_surface, + 0, 0, + cairo_image_surface_get_width (embedded_surface), + cairo_image_surface_get_height (embedded_surface)); + + cairo_surface_destroy (embedded_surface); + cairo_surface_destroy (surface); + + return retval; +} + void nautilus_ui_frame_image (GdkPixbuf **pixbuf) { @@ -185,9 +286,9 @@ nautilus_ui_frame_image (GdkPixbuf **pixbuf) border.right = NAUTILUS_THUMBNAIL_FRAME_RIGHT; border.bottom = NAUTILUS_THUMBNAIL_FRAME_BOTTOM; - pixbuf_with_frame = gd_embed_image_in_frame (*pixbuf, - "resource:///org/gnome/nautilus/icons/thumbnail_frame.png", - &border, &border); + pixbuf_with_frame = embed_image_in_frame (*pixbuf, + "resource:///org/gnome/nautilus/icons/thumbnail_frame.png", + &border, &border); g_object_unref (*pixbuf); *pixbuf = pixbuf_with_frame; diff --git a/subprojects/libgd b/subprojects/libgd deleted file mode 160000 index 8a68efc5b..000000000 --- a/subprojects/libgd +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8a68efc5bc2b4c3f24ad4a41b1656f8557bb8b44 -- cgit v1.2.1