/* LIBPIKA - The PIKA Library * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball * * pikabrushselectbutton.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 "pikabrushselectbutton.h" #include "pikauimarshal.h" #include "libpika-intl.h" /** * SECTION: pikabrushselectbutton * @title: PikaBrushSelectButton * @short_description: A button which pops up a brush selection dialog. * * A button which pops up a brush selection dialog. **/ #define CELL_SIZE 20 /* A B&W image. mask_data is allocated and must be freed. */ typedef struct _PreviewBitmap { gint width; gint height; guchar *mask_data; } _PreviewBitmap; struct _PikaBrushSelectButton { PikaResourceSelectButton parent_instance; GtkWidget *preview; GtkWidget *popup; }; static void pika_brush_select_button_draw_interior (PikaResourceSelectButton *self); static void pika_brush_select_on_preview_resize (PikaBrushSelectButton *button); static gboolean pika_brush_select_on_preview_events (GtkWidget *widget, GdkEvent *event, PikaBrushSelectButton *button); static void pika_brush_select_preview_draw (PikaPreviewArea *area, gint x, gint y, _PreviewBitmap mask, gint rowstride); static void pika_brush_select_preview_fill_draw (GtkWidget *preview, _PreviewBitmap mask); static void pika_brush_select_button_draw (PikaBrushSelectButton *self); static _PreviewBitmap pika_brush_select_button_get_brush_bitmap (PikaBrushSelectButton *self); /* Popup methods. */ static void pika_brush_select_button_open_popup (PikaBrushSelectButton *button, gint x, gint y); static void pika_brush_select_button_close_popup (PikaBrushSelectButton *button); static const GtkTargetEntry drag_target = { "application/x-pika-brush-name", 0, 0 }; G_DEFINE_FINAL_TYPE (PikaBrushSelectButton, pika_brush_select_button, PIKA_TYPE_RESOURCE_SELECT_BUTTON) static void pika_brush_select_button_class_init (PikaBrushSelectButtonClass *klass) { PikaResourceSelectButtonClass *superclass = PIKA_RESOURCE_SELECT_BUTTON_CLASS (klass); superclass->draw_interior = pika_brush_select_button_draw_interior; superclass->resource_type = PIKA_TYPE_BRUSH; } static void pika_brush_select_button_init (PikaBrushSelectButton *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_brush_select_on_preview_resize), self); g_signal_connect (self->preview, "event", G_CALLBACK (pika_brush_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_brush_select_button_draw_interior (PikaResourceSelectButton *self) { pika_brush_select_button_draw (PIKA_BRUSH_SELECT_BUTTON (self)); } /** * pika_brush_select_button_new: * @title: (nullable): Title of the dialog to use or %NULL to use the default title. * @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_select_button_new (const gchar *title, PikaResource *resource) { GtkWidget *self; if (resource == NULL) resource = PIKA_RESOURCE (pika_context_get_brush ()); if (title) self = g_object_new (PIKA_TYPE_BRUSH_SELECT_BUTTON, "title", title, "resource", resource, NULL); else self = g_object_new (PIKA_TYPE_BRUSH_SELECT_BUTTON, "resource", resource, NULL); pika_brush_select_button_draw_interior (PIKA_RESOURCE_SELECT_BUTTON (self)); return self; } /* private functions */ /* Draw self. * * This knows that we only draw the preview. Gtk draws the browse button. */ static void pika_brush_select_button_draw (PikaBrushSelectButton *self) { _PreviewBitmap mask; mask = pika_brush_select_button_get_brush_bitmap (self); pika_brush_select_preview_fill_draw (self->preview, mask); g_free (mask.mask_data); } /* Return the mask portion of self's brush's data. * Caller must free mask_data. */ static _PreviewBitmap pika_brush_select_button_get_brush_bitmap (PikaBrushSelectButton *self) { PikaBrush *brush; gint mask_bpp; GBytes *mask_data; gsize mask_size; gint color_bpp; GBytes *color_data; _PreviewBitmap result; g_object_get (self, "resource", &brush, NULL); pika_brush_get_pixels (brush, &result.width, &result.height, &mask_bpp, &mask_data, &color_bpp, &color_data); result.mask_data = g_bytes_unref_to_data (mask_data, &mask_size); /* Discard any color data, bitmap is B&W i.e. i.e. depth one i.e. a mask */ g_bytes_unref (color_data); return result; } /* On resize event, redraw. */ static void pika_brush_select_on_preview_resize (PikaBrushSelectButton *self) { pika_brush_select_button_draw (self); } /* 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, PikaBrushSelectButton *self) { GdkEventButton *bevent; switch (event->type) { case GDK_BUTTON_PRESS: bevent = (GdkEventButton *) event; if (bevent->button == 1) { gtk_grab_add (widget); pika_brush_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_brush_select_button_close_popup (self); } break; default: break; } return FALSE; } /* Draw a PikaPreviewArea with a given bitmap. */ static void pika_brush_select_preview_draw (PikaPreviewArea *area, gint x, gint y, _PreviewBitmap mask, gint rowstride) { const guchar *src; guchar *dest; guchar *buf; gint i, j; buf = g_new (guchar, mask.width * mask.height); src = mask.mask_data; dest = buf; for (j = 0; j < mask.height; j++) { const guchar *s = src; for (i = 0; i < mask.width; i++, s++, dest++) *dest = 255 - *s; src += rowstride; } pika_preview_area_draw (area, x, y, mask.width, mask.height, PIKA_GRAY_IMAGE, buf, mask.width); g_free (buf); } /* Fill a PikaPreviewArea with a bitmap then draw. */ static void pika_brush_select_preview_fill_draw (GtkWidget *preview, _PreviewBitmap mask) { PikaPreviewArea *area = PIKA_PREVIEW_AREA (preview); GtkAllocation allocation; gint x, y; gint width, height; _PreviewBitmap drawn_mask; gtk_widget_get_allocation (preview, &allocation); width = MIN (mask.width, allocation.width); height = MIN (mask.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_mask.mask_data points to same array. */ drawn_mask.width = width; drawn_mask.height = height; drawn_mask.mask_data = mask.mask_data; pika_brush_select_preview_draw (area, x, y, drawn_mask, mask.width); /* row stride */ /* Caller will free mask.mask_data */ } /* popup methods. */ static void pika_brush_select_button_open_popup (PikaBrushSelectButton *self, gint x, gint y) { GtkWidget *frame; GtkWidget *preview; GdkMonitor *monitor; GdkRectangle workarea; gint x_org; gint y_org; _PreviewBitmap mask; if (self->popup) pika_brush_select_button_close_popup (self); mask = pika_brush_select_button_get_brush_bitmap (self); if (mask.width <= CELL_SIZE && mask.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, mask.width, mask.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 - (mask.width / 2); y = y_org + y - (mask.height / 2); x = CLAMP (x, workarea.x, workarea.x + workarea.width - mask.width); y = CLAMP (y, workarea.y, workarea.y + workarea.height - mask.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_brush_select_preview_draw (PIKA_PREVIEW_AREA (preview), 0, 0, mask, mask.width); g_free (mask.mask_data); } static void pika_brush_select_button_close_popup (PikaBrushSelectButton *self) { g_clear_pointer (&self->popup, gtk_widget_destroy); }