/* LIBPIKA - The PIKA Library * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball * * pikapatternselectbutton.c * Copyright (C) 1998 Andy Thomas * * This library 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 3 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 Lesser General Public * License along with this library. If not, see * . */ /* Similar to pikabrushselectbutton.c. * FUTURE: inherit or share common code. */ #include "config.h" #include #include #include "libpikawidgets/pikawidgets.h" #include "pika.h" #include "pikauitypes.h" #include "pikapatternselectbutton.h" #include "pikauimarshal.h" #include "libpika-intl.h" /** * SECTION: pikapatternselectbutton * @title: PikaPatternSelectButton * @short_description: A button which pops up a pattern selection dialog. * * A button which pops up a pattern selection dialog. **/ #define CELL_SIZE 20 /* An image. pixelels is allocated and must be freed. */ typedef struct _PreviewImage { gint width; gint height; gint bpp; guchar *pixelels; } _PreviewImage; struct _PikaPatternSelectButton { PikaResourceSelectButton parent_instance; GtkWidget *preview; GtkWidget *popup; }; /* local */ static void pika_pattern_select_button_draw_interior (PikaResourceSelectButton *self); static void pika_pattern_select_on_preview_resize (PikaPatternSelectButton *button); static gboolean pika_pattern_select_on_preview_events (GtkWidget *widget, GdkEvent *event, PikaPatternSelectButton *button); /* local drawing methods. */ static void pika_pattern_select_preview_draw (PikaPreviewArea *area, gint x, gint y, _PreviewImage image, gint rowstride); static void pika_pattern_select_preview_fill_draw (GtkWidget *preview, _PreviewImage image); static void pika_pattern_select_button_draw (PikaPatternSelectButton *self); static _PreviewImage pika_pattern_select_button_get_pattern_image (PikaPatternSelectButton *self); /* Popup methods. */ static void pika_pattern_select_button_open_popup (PikaPatternSelectButton *button, gint x, gint y); static void pika_pattern_select_button_close_popup (PikaPatternSelectButton *button); /* A GtkTargetEntry has a string and two ints. * This is one, but we treat it as an array. */ static const GtkTargetEntry drag_target = { "application/x-pika-pattern-name", 0, 0 }; G_DEFINE_FINAL_TYPE (PikaPatternSelectButton, pika_pattern_select_button, PIKA_TYPE_RESOURCE_SELECT_BUTTON) static void pika_pattern_select_button_class_init (PikaPatternSelectButtonClass *klass) { PikaResourceSelectButtonClass *superclass = PIKA_RESOURCE_SELECT_BUTTON_CLASS (klass); superclass->draw_interior = pika_pattern_select_button_draw_interior; superclass->resource_type = PIKA_TYPE_PATTERN; } static void pika_pattern_select_button_init (PikaPatternSelectButton *self) { GtkWidget *frame; GtkWidget *button; frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); gtk_box_pack_start (GTK_BOX (self), frame, FALSE, FALSE, 0); self->preview = pika_preview_area_new (); gtk_widget_add_events (self->preview, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK); gtk_widget_set_size_request (self->preview, CELL_SIZE, CELL_SIZE); gtk_container_add (GTK_CONTAINER (frame), self->preview); g_signal_connect_swapped (self->preview, "size-allocate", G_CALLBACK (pika_pattern_select_on_preview_resize), self); g_signal_connect (self->preview, "event", G_CALLBACK (pika_pattern_select_on_preview_events), self); button = gtk_button_new_with_mnemonic (_("_Browse...")); gtk_box_pack_start (GTK_BOX (self), button, FALSE, FALSE, 0); gtk_widget_show_all (GTK_WIDGET (self)); pika_resource_select_button_set_drag_target (PIKA_RESOURCE_SELECT_BUTTON (self), self->preview, &drag_target); pika_resource_select_button_set_clickable (PIKA_RESOURCE_SELECT_BUTTON (self), button); } static void pika_pattern_select_button_draw_interior (PikaResourceSelectButton *self) { pika_pattern_select_button_draw (PIKA_PATTERN_SELECT_BUTTON (self)); } /** * pika_pattern_select_button_new: * @title: (nullable): Title of the dialog to use or %NULL to use the default title. * @resource: (nullable): Initial pattern. * * Creates a new #GtkWidget that lets a user choose a pattern. * You can put this widget in a table in a plug-in dialog. * * When pattern is NULL, initial choice is from context. * * Returns: A #GtkWidget that you can use in your UI. * * Since: 2.4 */ GtkWidget * pika_pattern_select_button_new (const gchar *title, PikaResource *resource) { GtkWidget *self; if (resource == NULL) resource = PIKA_RESOURCE (pika_context_get_pattern ()); g_return_val_if_fail (PIKA_IS_PATTERN (resource), NULL); if (title) self = g_object_new (PIKA_TYPE_PATTERN_SELECT_BUTTON, "title", title, "resource", resource, NULL); else self = g_object_new (PIKA_TYPE_PATTERN_SELECT_BUTTON, "resource", resource, NULL); pika_pattern_select_button_draw_interior (PIKA_RESOURCE_SELECT_BUTTON (self)); return self; } /* Draw self. * * This knows that we only draw the preview. Gtk draws the browse button. */ static void pika_pattern_select_button_draw (PikaPatternSelectButton *self) { _PreviewImage image; image = pika_pattern_select_button_get_pattern_image (self); pika_pattern_select_preview_fill_draw (self->preview, image); g_free (image.pixelels); } /* Return the image of self's pattern. * Caller must free pixelels. */ static _PreviewImage pika_pattern_select_button_get_pattern_image (PikaPatternSelectButton *self) { PikaPattern *pattern; gsize pixelels_size; GBytes *color_bytes; _PreviewImage result; g_object_get (self, "resource", &pattern, NULL); pika_pattern_get_pixels (pattern, &result.width, &result.height, &result.bpp, &color_bytes); result.pixelels = g_bytes_unref_to_data (color_bytes, &pixelels_size); return result; } /* On resize event, redraw. */ static void pika_pattern_select_on_preview_resize (PikaPatternSelectButton *self) { pika_pattern_select_button_draw (self); } /* On mouse events in self's preview, popup a zoom view of entire pattern */ static gboolean pika_pattern_select_on_preview_events (GtkWidget *widget, GdkEvent *event, PikaPatternSelectButton *self) { GdkEventButton *bevent; switch (event->type) { case GDK_BUTTON_PRESS: bevent = (GdkEventButton *) event; if (bevent->button == 1) { gtk_grab_add (widget); pika_pattern_select_button_open_popup (self, bevent->x, bevent->y); } break; case GDK_BUTTON_RELEASE: bevent = (GdkEventButton *) event; if (bevent->button == 1) { gtk_grab_remove (widget); pika_pattern_select_button_close_popup (self); } break; default: break; } return FALSE; } /* Draw a PikaPreviewArea with a given image. */ static void pika_pattern_select_preview_draw (PikaPreviewArea *area, gint x, gint y, _PreviewImage image, gint rowstride) { PikaImageType type; switch (image.bpp) { case 1: type = PIKA_GRAY_IMAGE; break; case 2: type = PIKA_GRAYA_IMAGE; break; case 3: type = PIKA_RGB_IMAGE; break; case 4: type = PIKA_RGBA_IMAGE; break; default: g_warning ("Invalid bpp"); return; } pika_preview_area_draw (area, x, y, image.width, image.height, type, image.pixelels, rowstride); } /* Fill a PikaPreviewArea with a image then draw. */ static void pika_pattern_select_preview_fill_draw (GtkWidget *preview, _PreviewImage image) { PikaPreviewArea *area = PIKA_PREVIEW_AREA (preview); GtkAllocation allocation; gint x, y; gint width, height; _PreviewImage drawn_image; gtk_widget_get_allocation (preview, &allocation); width = MIN (image.width, allocation.width); height = MIN (image.height, allocation.height); x = ((allocation.width - width) / 2); y = ((allocation.height - height) / 2); if (x || y) pika_preview_area_fill (area, 0, 0, allocation.width, allocation.height, 0xFF, 0xFF, 0xFF); /* Draw same data to new bounds. * drawn_image.pixelels points to same array. */ drawn_image.width = width; drawn_image.height = height; drawn_image.pixelels = image.pixelels; drawn_image.bpp = image.bpp; pika_pattern_select_preview_draw (area, x, y, drawn_image, image.width * image.bpp ); /* row stride */ /* Caller will free image.pixelels */ } /* popup methods. */ static void pika_pattern_select_button_open_popup (PikaPatternSelectButton *self, gint x, gint y) { GtkWidget *frame; GtkWidget *preview; GdkMonitor *monitor; GdkRectangle workarea; gint x_org; gint y_org; _PreviewImage image; if (self->popup) pika_pattern_select_button_close_popup (self); image = pika_pattern_select_button_get_pattern_image (self); if (image.width <= CELL_SIZE && image.height <= CELL_SIZE) return; self->popup = gtk_window_new (GTK_WINDOW_POPUP); gtk_window_set_type_hint (GTK_WINDOW (self->popup), GDK_WINDOW_TYPE_HINT_DND); gtk_window_set_screen (GTK_WINDOW (self->popup), gtk_widget_get_screen (GTK_WIDGET (self))); frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); gtk_container_add (GTK_CONTAINER (self->popup), frame); gtk_widget_show (frame); preview = pika_preview_area_new (); gtk_widget_set_size_request (preview, image.width, image.height); gtk_container_add (GTK_CONTAINER (frame), preview); gtk_widget_show (preview); /* decide where to put the popup: near the preview i.e. at mousedown coords */ gdk_window_get_origin (gtk_widget_get_window (self->preview), &x_org, &y_org); monitor = pika_widget_get_monitor (GTK_WIDGET (self)); gdk_monitor_get_workarea (monitor, &workarea); x = x_org + x - (image.width / 2); y = y_org + y - (image.height / 2); x = CLAMP (x, workarea.x, workarea.x + workarea.width - image.width); y = CLAMP (y, workarea.y, workarea.y + workarea.height - image.height); gtk_window_move (GTK_WINDOW (self->popup), x, y); gtk_widget_show (self->popup); /* Draw popup now. Usual events do not cause a draw. */ pika_pattern_select_preview_draw (PIKA_PREVIEW_AREA (preview), 0, 0, image, image.width * image.bpp); g_free (image.pixelels); } static void pika_pattern_select_button_close_popup (PikaPatternSelectButton *self) { g_clear_pointer (&self->popup, gtk_widget_destroy); }