/* LIBPIKA - The PIKA Library * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball * * pikabrushchooser.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 * . */ #include "config.h" #include #include #include "libpikawidgets/pikawidgets.h" #include "pika.h" #include "pikauitypes.h" #include "pikabrushchooser.h" #include "pikauimarshal.h" #include "libpika-intl.h" /** * SECTION: pikabrushchooser * @title: PikaBrushChooser * @short_description: A button which pops up a brush selection dialog. * * A button which pops up a brush selection dialog. * * Note that this widget draws itself using `GEGL` code. You **must** call * [func@Gegl.init] first to be able to use this chooser. **/ #define CELL_SIZE 40 struct _PikaBrushChooser { PikaResourceChooser parent_instance; GtkWidget *preview; GtkWidget *popup; PikaBrush *brush; gint width; gint height; GeglBuffer *buffer; GeglBuffer *mask; }; static void pika_brush_chooser_finalize (GObject *object); static gboolean pika_brush_select_on_preview_events (GtkWidget *widget, GdkEvent *event, PikaBrushChooser *button); static void pika_brush_select_preview_draw (PikaBrushChooser *chooser, PikaPreviewArea *area); static void pika_brush_chooser_draw (PikaBrushChooser *self); static void pika_brush_chooser_get_brush_bitmap (PikaBrushChooser *self, gint width, gint height); /* Popup methods. */ static void pika_brush_chooser_open_popup (PikaBrushChooser *button, gint x, gint y); static void pika_brush_chooser_close_popup (PikaBrushChooser *button); static const GtkTargetEntry drag_target = { "application/x-pika-brush-name", 0, 0 }; G_DEFINE_FINAL_TYPE (PikaBrushChooser, pika_brush_chooser, PIKA_TYPE_RESOURCE_CHOOSER) static void pika_brush_chooser_class_init (PikaBrushChooserClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaResourceChooserClass *res_class = PIKA_RESOURCE_CHOOSER_CLASS (klass); res_class->draw_interior = (void (*)(PikaResourceChooser *)) pika_brush_chooser_draw; res_class->resource_type = PIKA_TYPE_BRUSH; object_class->finalize = pika_brush_chooser_finalize; } static void pika_brush_chooser_init (PikaBrushChooser *chooser) { GtkWidget *widget; gint scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (chooser)); chooser->brush = NULL; chooser->buffer = NULL; chooser->mask = NULL; widget = gtk_aspect_frame_new (NULL, 0.5, 0.5, 1.0, FALSE); gtk_frame_set_shadow_type (GTK_FRAME (widget), GTK_SHADOW_IN); gtk_box_pack_start (GTK_BOX (chooser), widget, FALSE, FALSE, 0); gtk_widget_show (widget); chooser->preview = pika_preview_area_new (); gtk_widget_add_events (chooser->preview, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK); gtk_widget_set_size_request (chooser->preview, scale_factor * CELL_SIZE, scale_factor * CELL_SIZE); gtk_container_add (GTK_CONTAINER (widget), chooser->preview); gtk_widget_show (chooser->preview); g_signal_connect_swapped (chooser->preview, "size-allocate", G_CALLBACK (pika_brush_chooser_draw), chooser); g_signal_connect (chooser->preview, "event", G_CALLBACK (pika_brush_select_on_preview_events), chooser); widget = gtk_button_new_with_mnemonic (_("_Browse...")); gtk_box_pack_start (GTK_BOX (chooser), widget, FALSE, FALSE, 0); pika_resource_chooser_set_drag_target (PIKA_RESOURCE_CHOOSER (chooser), chooser->preview, &drag_target); pika_resource_chooser_set_clickable (PIKA_RESOURCE_CHOOSER (chooser), widget); gtk_widget_show (widget); } static void pika_brush_chooser_finalize (GObject *object) { PikaBrushChooser *chooser = PIKA_BRUSH_CHOOSER (object); g_clear_object (&chooser->buffer); g_clear_object (&chooser->mask); G_OBJECT_CLASS (pika_brush_chooser_parent_class)->finalize (object); } /** * pika_brush_chooser_new: * @title: (nullable): Title of the dialog to use or %NULL to use the default title. * @label: (nullable): Button label or %NULL for no label. * @resource: (nullable): Initial brush. * * Creates a new #GtkWidget that lets a user choose a brush. * You can put this widget in a plug-in dialog. * * When brush is NULL, initial choice is from context. * * Returns: A #GtkWidget that you can use in your UI. * * Since: 2.4 */ GtkWidget * pika_brush_chooser_new (const gchar *title, const gchar *label, PikaResource *resource) { GtkWidget *self; if (resource == NULL) resource = PIKA_RESOURCE (pika_context_get_brush ()); if (title) self = g_object_new (PIKA_TYPE_BRUSH_CHOOSER, "title", title, "label", label, "resource", resource, NULL); else self = g_object_new (PIKA_TYPE_BRUSH_CHOOSER, "label", label, "resource", resource, NULL); return self; } /* private functions */ static void pika_brush_chooser_draw (PikaBrushChooser *chooser) { GtkAllocation allocation; gtk_widget_get_allocation (chooser->preview, &allocation); pika_brush_chooser_get_brush_bitmap (chooser, allocation.width, allocation.height); pika_brush_select_preview_draw (chooser, PIKA_PREVIEW_AREA (chooser->preview)); } static void pika_brush_chooser_get_brush_bitmap (PikaBrushChooser *chooser, gint width, gint height) { PikaBrush *brush; g_object_get (chooser, "resource", &brush, NULL); if (chooser->brush == brush && chooser->width == width && chooser->height == height) { /* Let's assume brush contents is not changing in a single run. */ g_object_unref (brush); return; } g_clear_object (&chooser->buffer); g_clear_object (&chooser->mask); chooser->width = chooser->height = 0; chooser->brush = brush; chooser->buffer = pika_brush_get_buffer (brush, width, height, NULL); if (chooser->buffer) { GeglColor *white_color = gegl_color_new ("rgb(1.0,1.0,1.0)"); GeglNode *graph; GeglNode *white; GeglNode *source; GeglNode *op; chooser->width = gegl_buffer_get_width (chooser->buffer); chooser->height = gegl_buffer_get_height (chooser->buffer); graph = gegl_node_new (); white = gegl_node_new_child (graph, "operation", "gegl:rectangle", "x", 0.0, "y", 0.0, "width", (gdouble) gegl_buffer_get_width (chooser->buffer), "height", (gdouble) gegl_buffer_get_height (chooser->buffer), "color", white_color, NULL); source = gegl_node_new_child (graph, "operation", "gegl:buffer-source", "buffer", chooser->buffer, NULL); op = gegl_node_new_child (graph, "operation", "svg:src-over", NULL); gegl_node_link_many (white, op, NULL); gegl_node_connect (source, "output", op, "aux"); gegl_node_blit_buffer (op, chooser->buffer, NULL, 0, GEGL_ABYSS_NONE); g_object_unref (graph); g_object_unref (white_color); } else { GeglBufferIterator *iter; chooser->mask = pika_brush_get_mask (brush, width, height, NULL); chooser->width = gegl_buffer_get_width (chooser->mask); chooser->height = gegl_buffer_get_height (chooser->mask); /* More common to display mask brushes as black on white. */ iter = gegl_buffer_iterator_new (chooser->mask, NULL, 0, babl_format ("Y' u8"), GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE, 1); while (gegl_buffer_iterator_next (iter)) { guint8 *data = iter->items[0].data; for (gint i = 0; i < iter->length; i++) { *data = 255 - *data; data++; } } } g_object_unref (brush); } /* On mouse events in self's preview, popup a zoom view of entire brush */ static gboolean pika_brush_select_on_preview_events (GtkWidget *widget, GdkEvent *event, PikaBrushChooser *self) { GdkEventButton *bevent; switch (event->type) { case GDK_BUTTON_PRESS: bevent = (GdkEventButton *) event; if (bevent->button == 1) { gtk_grab_add (widget); pika_brush_chooser_open_popup (self, bevent->x, bevent->y); } break; case GDK_BUTTON_RELEASE: bevent = (GdkEventButton *) event; if (bevent->button == 1) { gtk_grab_remove (widget); pika_brush_chooser_close_popup (self); } break; default: break; } return FALSE; } /* Draw a PikaPreviewArea with a given bitmap. */ static void pika_brush_select_preview_draw (PikaBrushChooser *chooser, PikaPreviewArea *preview) { PikaPreviewArea *area = PIKA_PREVIEW_AREA (preview); GeglBuffer *src_buffer; const Babl *format; guchar *src; PikaImageType type; gint rowstride; GtkAllocation allocation; gint x = 0; gint y = 0; gtk_widget_get_allocation (GTK_WIDGET (preview), &allocation); /* Fill with white. */ if (chooser->width < allocation.width || chooser->height < allocation.height) { pika_preview_area_fill (area, 0, 0, allocation.width, allocation.height, 0xFF, 0xFF, 0xFF); x = ((allocation.width - chooser->width) / 2); y = ((allocation.height - chooser->height) / 2); } /* Draw the brush. */ if (chooser->buffer) { src_buffer = chooser->buffer; format = gegl_buffer_get_format (src_buffer); rowstride = chooser->width * babl_format_get_bytes_per_pixel (format); if (babl_format_has_alpha (format)) type = PIKA_RGBA_IMAGE; else type = PIKA_RGB_IMAGE; } else { src_buffer = chooser->mask; format = babl_format ("Y' u8"); rowstride = chooser->width; type = PIKA_GRAY_IMAGE; } src = g_try_malloc (sizeof (guchar) * rowstride * chooser->height); gegl_buffer_get (src_buffer, NULL, 1.0, format, src, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); pika_preview_area_draw (area, x, y, chooser->width, chooser->height, type, src, rowstride); g_free (src); } /* popup methods. */ static void pika_brush_chooser_open_popup (PikaBrushChooser *chooser, gint x, gint y) { GtkWidget *frame; GtkWidget *preview; GdkMonitor *monitor; GdkRectangle workarea; gint x_org; gint y_org; if (chooser->popup) pika_brush_chooser_close_popup (chooser); if (chooser->width <= CELL_SIZE && chooser->height <= CELL_SIZE) return; chooser->popup = gtk_window_new (GTK_WINDOW_POPUP); gtk_window_set_type_hint (GTK_WINDOW (chooser->popup), GDK_WINDOW_TYPE_HINT_DND); gtk_window_set_screen (GTK_WINDOW (chooser->popup), gtk_widget_get_screen (GTK_WIDGET (chooser))); frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); gtk_container_add (GTK_CONTAINER (chooser->popup), frame); gtk_widget_show (frame); preview = pika_preview_area_new (); gtk_widget_set_size_request (preview, chooser->width, chooser->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 (chooser->preview), &x_org, &y_org); monitor = pika_widget_get_monitor (GTK_WIDGET (chooser)); gdk_monitor_get_workarea (monitor, &workarea); x = x_org + x - (chooser->width / 2); y = y_org + y - (chooser->height / 2); x = CLAMP (x, workarea.x, workarea.x + workarea.width - chooser->width); y = CLAMP (y, workarea.y, workarea.y + workarea.height - chooser->height); gtk_window_move (GTK_WINDOW (chooser->popup), x, y); gtk_widget_show (chooser->popup); /* Draw popup now. Usual events do not cause a draw. */ pika_brush_select_preview_draw (chooser, PIKA_PREVIEW_AREA (preview)); gdk_window_set_transient_for (gtk_widget_get_window (chooser->popup), gtk_widget_get_window (gtk_widget_get_toplevel (GTK_WIDGET (chooser)))); } static void pika_brush_chooser_close_popup (PikaBrushChooser *self) { g_clear_pointer (&self->popup, gtk_widget_destroy); }