summaryrefslogtreecommitdiffstats
path: root/src/widget.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/widget.c')
-rw-r--r--src/widget.c503
1 files changed, 503 insertions, 0 deletions
diff --git a/src/widget.c b/src/widget.c
new file mode 100644
index 0000000..b08715b
--- /dev/null
+++ b/src/widget.c
@@ -0,0 +1,503 @@
+/* This file is part of Ganv.
+ * Copyright 2007-2016 David Robillard <http://drobilla.net>
+ *
+ * Ganv is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or any later version.
+ *
+ * Ganv 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 General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with Ganv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Based on GnomeCanvasWidget, by Federico Mena <federico@nuclecu.unam.mx>
+ * Copyright 1997-2000 Free Software Foundation
+ */
+
+#include <math.h>
+
+#include <gtk/gtksignal.h>
+
+#include "ganv/canvas.h"
+#include "ganv/widget.h"
+
+#include "./gettext.h"
+#include "./ganv-private.h"
+
+G_DEFINE_TYPE_WITH_CODE(GanvWidget, ganv_widget, GANV_TYPE_ITEM,
+ G_ADD_PRIVATE(GanvWidget))
+
+static GanvItemClass* parent_class;
+
+enum {
+ PROP_0,
+ PROP_WIDGET,
+ PROP_X,
+ PROP_Y,
+ PROP_WIDTH,
+ PROP_HEIGHT,
+ PROP_ANCHOR,
+ PROP_SIZE_PIXELS
+};
+
+static void
+ganv_widget_init(GanvWidget* witem)
+{
+ GanvWidgetPrivate* impl = ganv_widget_get_instance_private(witem);
+
+ witem->impl = impl;
+ witem->impl->x = 0.0;
+ witem->impl->y = 0.0;
+ witem->impl->width = 0.0;
+ witem->impl->height = 0.0;
+ witem->impl->anchor = GTK_ANCHOR_NW;
+ witem->impl->size_pixels = FALSE;
+}
+
+static void
+ganv_widget_destroy(GtkObject* object)
+{
+ g_return_if_fail(object != NULL);
+ g_return_if_fail(GANV_IS_WIDGET(object));
+
+ GanvWidget* witem = GANV_WIDGET(object);
+
+ if (witem->impl->widget && !witem->impl->in_destroy) {
+ g_signal_handler_disconnect(witem->impl->widget, witem->impl->destroy_id);
+ gtk_widget_destroy(witem->impl->widget);
+ witem->impl->widget = NULL;
+ }
+
+ if (GTK_OBJECT_CLASS(parent_class)->destroy) {
+ (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
+ }
+}
+
+static void
+recalc_bounds(GanvWidget* witem)
+{
+ GanvItem* item = GANV_ITEM(witem);
+
+ /* Get world coordinates */
+
+ double wx = witem->impl->x;
+ double wy = witem->impl->y;
+ ganv_item_i2w(item, &wx, &wy);
+
+ /* Get canvas pixel coordinates */
+
+ ganv_canvas_w2c(item->impl->canvas, wx, wy, &witem->impl->cx, &witem->impl->cy);
+
+ /* Anchor widget item */
+
+ switch (witem->impl->anchor) {
+ case GTK_ANCHOR_N:
+ case GTK_ANCHOR_CENTER:
+ case GTK_ANCHOR_S:
+ witem->impl->cx -= witem->impl->cwidth / 2;
+ break;
+
+ case GTK_ANCHOR_NE:
+ case GTK_ANCHOR_E:
+ case GTK_ANCHOR_SE:
+ witem->impl->cx -= witem->impl->cwidth;
+ break;
+
+ default:
+ break;
+ }
+
+ switch (witem->impl->anchor) {
+ case GTK_ANCHOR_W:
+ case GTK_ANCHOR_CENTER:
+ case GTK_ANCHOR_E:
+ witem->impl->cy -= witem->impl->cheight / 2;
+ break;
+
+ case GTK_ANCHOR_SW:
+ case GTK_ANCHOR_S:
+ case GTK_ANCHOR_SE:
+ witem->impl->cy -= witem->impl->cheight;
+ break;
+
+ default:
+ break;
+ }
+
+ /* Bounds */
+
+ item->impl->x1 = witem->impl->cx;
+ item->impl->y1 = witem->impl->cy;
+ item->impl->x2 = witem->impl->cx + witem->impl->cwidth;
+ item->impl->y2 = witem->impl->cy + witem->impl->cheight;
+
+ int zoom_xofs, zoom_yofs;
+ ganv_canvas_get_zoom_offsets(item->impl->canvas, &zoom_xofs, &zoom_yofs);
+ if (witem->impl->widget) {
+ gtk_layout_move(GTK_LAYOUT(item->impl->canvas), witem->impl->widget,
+ witem->impl->cx + zoom_xofs,
+ witem->impl->cy + zoom_yofs);
+ }
+}
+
+static void
+do_destroy(GtkObject* object, gpointer data)
+{
+ GanvWidget* witem = GANV_WIDGET(data);
+
+ witem->impl->in_destroy = TRUE;
+ gtk_object_destroy(GTK_OBJECT(data));
+}
+
+static void
+ganv_widget_set_property(GObject* object,
+ guint param_id,
+ const GValue* value,
+ GParamSpec* pspec)
+{
+ GanvItem* item = GANV_ITEM(object);
+ GanvWidget* witem = GANV_WIDGET(object);
+ int update = FALSE;
+ int calc_bounds = FALSE;
+ GObject* obj;
+
+ switch (param_id) {
+ case PROP_WIDGET:
+ if (witem->impl->widget) {
+ g_signal_handler_disconnect(witem->impl->widget, witem->impl->destroy_id);
+ gtk_container_remove(GTK_CONTAINER(item->impl->canvas), witem->impl->widget);
+ }
+
+ obj = (GObject*)g_value_get_object(value);
+ if (obj) {
+ witem->impl->widget = GTK_WIDGET(obj);
+ witem->impl->destroy_id = g_signal_connect(obj, "destroy",
+ G_CALLBACK(do_destroy),
+ witem);
+ int zoom_xofs, zoom_yofs;
+ ganv_canvas_get_zoom_offsets(item->impl->canvas, &zoom_xofs, &zoom_yofs);
+
+ gtk_layout_put(GTK_LAYOUT(item->impl->canvas), witem->impl->widget,
+ witem->impl->cx + zoom_xofs,
+ witem->impl->cy + zoom_yofs);
+ }
+
+ update = TRUE;
+ break;
+
+ case PROP_X:
+ if (witem->impl->x != g_value_get_double(value)) {
+ witem->impl->x = g_value_get_double(value);
+ calc_bounds = TRUE;
+ }
+ break;
+
+ case PROP_Y:
+ if (witem->impl->y != g_value_get_double(value)) {
+ witem->impl->y = g_value_get_double(value);
+ calc_bounds = TRUE;
+ }
+ break;
+
+ case PROP_WIDTH:
+ if (witem->impl->width != fabs(g_value_get_double(value))) {
+ witem->impl->width = fabs(g_value_get_double(value));
+ update = TRUE;
+ }
+ break;
+
+ case PROP_HEIGHT:
+ if (witem->impl->height != fabs(g_value_get_double(value))) {
+ witem->impl->height = fabs(g_value_get_double(value));
+ update = TRUE;
+ }
+ break;
+
+ case PROP_ANCHOR:
+ if (witem->impl->anchor != (GtkAnchorType)g_value_get_enum(value)) {
+ witem->impl->anchor = (GtkAnchorType)g_value_get_enum(value);
+ update = TRUE;
+ }
+ break;
+
+ case PROP_SIZE_PIXELS:
+ if (witem->impl->size_pixels != g_value_get_boolean(value)) {
+ witem->impl->size_pixels = g_value_get_boolean(value);
+ update = TRUE;
+ }
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
+ break;
+ }
+
+ if (update) {
+ (*GANV_ITEM_GET_CLASS(item)->update)(item, 0);
+ }
+
+ if (calc_bounds) {
+ recalc_bounds(witem);
+ }
+}
+
+static void
+ganv_widget_get_property(GObject* object,
+ guint param_id,
+ GValue* value,
+ GParamSpec* pspec)
+{
+ g_return_if_fail(object != NULL);
+ g_return_if_fail(GANV_IS_WIDGET(object));
+
+ GanvWidget* witem = GANV_WIDGET(object);
+
+ switch (param_id) {
+ case PROP_WIDGET:
+ g_value_set_object(value, (GObject*)witem->impl->widget);
+ break;
+
+ case PROP_X:
+ g_value_set_double(value, witem->impl->x);
+ break;
+
+ case PROP_Y:
+ g_value_set_double(value, witem->impl->y);
+ break;
+
+ case PROP_WIDTH:
+ g_value_set_double(value, witem->impl->width);
+ break;
+
+ case PROP_HEIGHT:
+ g_value_set_double(value, witem->impl->height);
+ break;
+
+ case PROP_ANCHOR:
+ g_value_set_enum(value, witem->impl->anchor);
+ break;
+
+ case PROP_SIZE_PIXELS:
+ g_value_set_boolean(value, witem->impl->size_pixels);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+ganv_widget_update(GanvItem* item, int flags)
+{
+ GanvWidget* witem = GANV_WIDGET(item);
+
+ if (parent_class->update) {
+ (*parent_class->update)(item, flags);
+ }
+
+ if (witem->impl->widget) {
+ const double pixels_per_unit = ganv_canvas_get_zoom(item->impl->canvas);
+ if (witem->impl->size_pixels) {
+ witem->impl->cwidth = (int)(witem->impl->width + 0.5);
+ witem->impl->cheight = (int)(witem->impl->height + 0.5);
+ } else {
+ witem->impl->cwidth = (int)(witem->impl->width * pixels_per_unit + 0.5);
+ witem->impl->cheight = (int)(witem->impl->height * pixels_per_unit + 0.5);
+ }
+
+ gtk_widget_set_size_request(witem->impl->widget, witem->impl->cwidth, witem->impl->cheight);
+ } else {
+ witem->impl->cwidth = 0.0;
+ witem->impl->cheight = 0.0;
+ }
+
+ recalc_bounds(witem);
+}
+
+static void
+ganv_widget_draw(GanvItem* item,
+ cairo_t* cr, double cx, double cy, double cw, double ch)
+{
+ GanvWidget* witem = GANV_WIDGET(item);
+
+ if (witem->impl->widget) {
+ gtk_widget_queue_draw(witem->impl->widget);
+ }
+}
+
+static double
+ganv_widget_point(GanvItem* item, double x, double y, GanvItem** actual_item)
+{
+ GanvWidget* witem = GANV_WIDGET(item);
+
+ *actual_item = item;
+
+ double x1, y1;
+ ganv_canvas_c2w(item->impl->canvas, witem->impl->cx, witem->impl->cy, &x1, &y1);
+
+ const double pixels_per_unit = ganv_canvas_get_zoom(item->impl->canvas);
+
+ double x2 = x1 + (witem->impl->cwidth - 1) / pixels_per_unit;
+ double y2 = y1 + (witem->impl->cheight - 1) / pixels_per_unit;
+
+ /* Is point inside widget bounds? */
+
+ if ((x >= x1) && (y >= y1) && (x <= x2) && (y <= y2)) {
+ return 0.0;
+ }
+
+ /* Point is outside widget bounds */
+
+ double dx;
+ if (x < x1) {
+ dx = x1 - x;
+ } else if (x > x2) {
+ dx = x - x2;
+ } else {
+ dx = 0.0;
+ }
+
+ double dy;
+ if (y < y1) {
+ dy = y1 - y;
+ } else if (y > y2) {
+ dy = y - y2;
+ } else {
+ dy = 0.0;
+ }
+
+ return sqrt(dx * dx + dy * dy);
+}
+
+static void
+ganv_widget_bounds(GanvItem* item, double* x1, double* y1, double* x2, double* y2)
+{
+ GanvWidget* witem = GANV_WIDGET(item);
+
+ *x1 = witem->impl->x;
+ *y1 = witem->impl->y;
+
+ switch (witem->impl->anchor) {
+ case GTK_ANCHOR_NW:
+ case GTK_ANCHOR_W:
+ case GTK_ANCHOR_SW:
+ break;
+
+ case GTK_ANCHOR_N:
+ case GTK_ANCHOR_CENTER:
+ case GTK_ANCHOR_S:
+ *x1 -= witem->impl->width / 2.0;
+ break;
+
+ case GTK_ANCHOR_NE:
+ case GTK_ANCHOR_E:
+ case GTK_ANCHOR_SE:
+ *x1 -= witem->impl->width;
+ break;
+
+ default:
+ break;
+ }
+
+ switch (witem->impl->anchor) {
+ case GTK_ANCHOR_NW:
+ case GTK_ANCHOR_N:
+ case GTK_ANCHOR_NE:
+ break;
+
+ case GTK_ANCHOR_W:
+ case GTK_ANCHOR_CENTER:
+ case GTK_ANCHOR_E:
+ *y1 -= witem->impl->height / 2.0;
+ break;
+
+ case GTK_ANCHOR_SW:
+ case GTK_ANCHOR_S:
+ case GTK_ANCHOR_SE:
+ *y1 -= witem->impl->height;
+ break;
+
+ default:
+ break;
+ }
+
+ *x2 = *x1 + witem->impl->width;
+ *y2 = *y1 + witem->impl->height;
+}
+
+static void
+ganv_widget_class_init(GanvWidgetClass* klass)
+{
+ GObjectClass* gobject_class = (GObjectClass*)klass;
+ GtkObjectClass* object_class = (GtkObjectClass*)klass;
+ GanvItemClass* item_class = (GanvItemClass*)klass;
+
+ parent_class = (GanvItemClass*)g_type_class_peek_parent(klass);
+
+ gobject_class->set_property = ganv_widget_set_property;
+ gobject_class->get_property = ganv_widget_get_property;
+
+ g_object_class_install_property(
+ gobject_class, PROP_WIDGET, g_param_spec_object(
+ "widget", _("Widget"),
+ _("The widget to embed in this item."),
+ GTK_TYPE_WIDGET,
+ (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE)));
+
+ g_object_class_install_property(
+ gobject_class, PROP_X, g_param_spec_double(
+ "x", _("x"),
+ _("The x coordinate of the anchor"),
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ gobject_class, PROP_Y, g_param_spec_double(
+ "y", _("y"),
+ _("The x coordinate of the anchor"),
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ gobject_class, PROP_WIDTH, g_param_spec_double(
+ "width", _("Width"),
+ _("The width of the widget."),
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ gobject_class, PROP_HEIGHT, g_param_spec_double(
+ "height", _("Height"),
+ _("The height of the widget."),
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ gobject_class, PROP_ANCHOR, g_param_spec_enum(
+ "anchor", _("Anchor"),
+ _("The anchor point of the widget."),
+ GTK_TYPE_ANCHOR_TYPE,
+ GTK_ANCHOR_NW,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ gobject_class, PROP_SIZE_PIXELS, g_param_spec_boolean(
+ "size-pixels", ("Size is in pixels"),
+ _("Specifies whether the widget size is specified in pixels or"
+ " canvas units. If it is in pixels, then the widget will not"
+ " be scaled when the canvas zoom factor changes. Otherwise,"
+ " it will be scaled."),
+ FALSE,
+ G_PARAM_READWRITE));
+
+ object_class->destroy = ganv_widget_destroy;
+
+ item_class->update = ganv_widget_update;
+ item_class->point = ganv_widget_point;
+ item_class->bounds = ganv_widget_bounds;
+ item_class->draw = ganv_widget_draw;
+}