PIKApp/libpika/pikapatternselectbutton.c

426 lines
13 KiB
C

/* 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
* <https://www.gnu.org/licenses/>.
*/
/* Similar to pikabrushselectbutton.c.
* FUTURE: inherit or share common code.
*/
#include "config.h"
#include <gegl.h>
#include <gtk/gtk.h>
#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);
}