430 lines
13 KiB
C
430 lines
13 KiB
C
/* 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
|
|
* <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <gegl.h>
|
|
#include <gtk/gtk.h>
|
|
|
|
#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);
|
|
}
|