Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/libccc/0.0.4/ccc/cc-view-widget.c
diff options
context:
space:
mode:
authorBruno Coudoin <bcoudoin@src.gnome.org>2007-05-24 20:49:36 (GMT)
committer Bruno Coudoin <bcoudoin@src.gnome.org>2007-05-24 20:49:36 (GMT)
commiteb9b606a58d931052b78cdf37fb343468c1a41fc (patch)
tree8fbdbf510a1a8708aeb5f06e76b67e83ddec2f86 /libccc/0.0.4/ccc/cc-view-widget.c
parente8e96543da66584fb1e5f6e3ebf92c5ab39d9700 (diff)
tagging libccc-0.0.4
svn path=/tags/git-migration-vendor/; revision=2668
Diffstat (limited to 'libccc/0.0.4/ccc/cc-view-widget.c')
-rw-r--r--libccc/0.0.4/ccc/cc-view-widget.c1022
1 files changed, 1022 insertions, 0 deletions
diff --git a/libccc/0.0.4/ccc/cc-view-widget.c b/libccc/0.0.4/ccc/cc-view-widget.c
new file mode 100644
index 0000000..5565ec3
--- /dev/null
+++ b/libccc/0.0.4/ccc/cc-view-widget.c
@@ -0,0 +1,1022 @@
+/* this file is part of libccc, criawips' cairo-based canvas
+ *
+ * AUTHORS
+ * Sven Herzberg <herzi@gnome-de.org>
+ *
+ * Copyright (C) 2005,2006 Sven Herzberg
+ *
+ * This program 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 2.1 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <ccc/cc-view-widget.h>
+
+#include <math.h>
+#include <string.h>
+#include <ccc/cc-marshallers.h>
+#include <ccc/cc-item-view.h>
+#include <ccc/cc-view.h>
+
+#include "glib-helpers.h"
+
+// define to get some nice output
+#undef DEBUG_SAVED_FRAMES
+
+struct CcViewWidgetPrivate {
+ gint redraw_timeout;
+ GdkRegion* dirty_region;
+#ifdef DEBUG_SAVED_FRAMES
+ gint updates, requests;
+#endif
+};
+
+/**
+ * cc_view_widget_get_center_view:
+ * @self: a #CcViewWidget
+ *
+ * Find out whether the view is centered.
+ *
+ * Returns %TRUE if @self displays its model centered once there's more space
+ * available than the model requires.
+ */
+gboolean
+cc_view_widget_get_center_view(CcViewWidget const* self) {
+ g_return_val_if_fail(CC_IS_VIEW_WIDGET(self), FALSE);
+
+ return self->center_view;
+}
+
+/**
+ * cc_view_widget_get_zoom_mode:
+ * @self: a #CcViewWidget
+ *
+ * Get the zoom mode of the view widget.
+ *
+ * Returns the zoom mode of the widget.
+ */
+CcZoomMode
+cc_view_widget_get_zoom_mode(CcViewWidget const* self) {
+ g_return_val_if_fail(CC_IS_VIEW_WIDGET(self), CC_ZOOM_PIXELS);
+
+ return self->zoom_mode;
+}
+
+/**
+ * cc_view_widget_new:
+ *
+ * Create a new #CcViewWidget.
+ *
+ * Returns a newly created #CcViewWidget which can be used to display a canvas.
+ */
+GtkWidget*
+cc_view_widget_new(void) {
+ return g_object_new(CC_TYPE_VIEW_WIDGET, NULL);
+}
+
+/**
+ * cc_view_widget_new_root:
+ * @root: a #CcItem, the model of the new view
+ *
+ * Create a new #CcViewWidget and set @root to be the model.
+ *
+ * Returns a new #CcViewWidget with @root as the model.
+ */
+GtkWidget*
+cc_view_widget_new_root(CcItem* root) {
+ GtkWidget* self = cc_view_widget_new();
+
+ cc_view_set_root(CC_VIEW(self), root);
+
+ return self;
+}
+
+static gboolean
+cvw_redraw_timeout(CcViewWidget* self) {
+ gdk_window_invalidate_region(GTK_WIDGET(self)->window, self->priv->dirty_region, FALSE);
+
+ gdk_region_destroy(self->priv->dirty_region);
+ self->priv->dirty_region = NULL;
+
+ self->priv->redraw_timeout = 0;
+#ifdef DEBUG_SAVED_FRAMES
+ self->priv->updates++;
+#endif
+ return FALSE; // we're done (for now)
+}
+
+static void
+cvw_set_dirty(CcViewWidget* self, CcDRect dirty) {
+ if(G_UNLIKELY(!GTK_WIDGET_REALIZED(self))) {
+ return;
+ }
+
+ if(G_UNLIKELY(!self->root)) {
+ return;
+ }
+
+ {
+ GtkWidget* widget = GTK_WIDGET(self);
+ CcDRect visible = {
+ 0.0, 0.0,
+ 1.0 * widget->allocation.width, 1.0 * widget->allocation.height
+ };
+
+ cc_view_world_to_window(CC_VIEW(self), &dirty.x1, &dirty.y1);
+ cc_view_world_to_window(CC_VIEW(self), &dirty.x2, &dirty.y2);
+
+ dirty.x1 = floor(dirty.x1);
+ dirty.y1 = floor(dirty.y1);
+ dirty.x2 = ceil (dirty.x2);
+ dirty.y2 = ceil (dirty.y2);
+
+ if(cc_d_rect_intersect(dirty, visible)) {
+ GdkRectangle rect = {((gint)dirty.x1),
+ ((gint)dirty.y1),
+ ((gint)dirty.x2 - dirty.x1),
+ ((gint)dirty.y2 - dirty.y1)};
+
+ if(G_UNLIKELY(!self->priv->redraw_timeout)) {
+ self->priv->dirty_region = gdk_region_new();
+ self->priv->redraw_timeout = g_timeout_add(40, // ~25 FPS
+ (GSourceFunc)cvw_redraw_timeout,
+ self);
+ }
+
+ gdk_region_union_with_rect(self->priv->dirty_region, &rect);
+#ifdef DEBUG_SAVED_FRAMES
+ self->priv->requests++;
+#endif
+ }
+ }
+}
+
+static CcDRect const*
+cvw_get_real_view(CcViewWidget* self) {
+ static const CcDRect default_view = {0.0, 0.0, 0.0, 0.0};
+ CcDRect const* retval = NULL;
+
+ if(self->scrolled_region) {
+ return self->scrolled_region;
+ } else if(CC_IS_ITEM(self->root)) {
+ retval = cc_item_get_all_bounds(self->root, CC_VIEW(self));
+ }
+
+ if(!retval) {
+ retval = &default_view;
+ }
+
+ return retval;
+}
+
+static gdouble
+cvw_get_real_zoom(CcViewWidget* self) {
+ gdouble zoom_width;
+ gdouble zoom_height;
+ CcDRect const* view = cvw_get_real_view(self);
+
+ if(!view || self->zoom_mode == CC_ZOOM_PIXELS) {
+ return self->zoom;
+ }
+
+ zoom_width = self->zoom * GTK_WIDGET(self)->allocation.width / (view->x2 + 1.0 - view->x1);
+ zoom_height = self->zoom * GTK_WIDGET(self)->allocation.height / (view->y2 + 1.0 - view->y1);
+
+ switch(self->zoom_mode) {
+ case CC_ZOOM_WIDTH:
+ return zoom_width;
+ case CC_ZOOM_HEIGHT:
+ return zoom_height;
+ case CC_ZOOM_AUTO:
+ return MIN(zoom_width, zoom_height);
+ case CC_ZOOM_PIXELS:
+ default:
+ // cannot happen - mention it to make the compiler happy
+ g_assert_not_reached();
+ return 0.0;
+ }
+}
+
+static void
+cvw_update_matrix(CcViewWidget* self) {
+ CcDRect dirty;
+ CcDRect const* view = cvw_get_real_view(self);
+
+ // avoid 0.0 leaving the widget by rounding
+ gdouble h_offset = 0.5,
+ v_offset = 0.5;
+
+ gdouble zoom = cvw_get_real_zoom(self);
+
+ dirty.x1 = 0.0;
+ dirty.x2 = GTK_WIDGET(self)->allocation.width;
+ dirty.y1 = 0.0;
+ dirty.y2 = GTK_WIDGET(self)->allocation.height;
+
+ cairo_matrix_init_scale(&self->display_matrix, zoom, zoom);
+
+ if(self->h_adjustment && self->h_adjustment->page_size < self->h_adjustment->upper - self->h_adjustment->lower) {
+ h_offset -= self->h_adjustment->value;
+ } else if(self->center_view && view) {
+ h_offset += (dirty.x2 / zoom - (view->x2 + view->x1)) / 2.0;
+ } else if(view) {
+ h_offset -= view->x1;
+ }
+
+ if(self->v_adjustment && self->v_adjustment->page_size < self->v_adjustment->upper - self->v_adjustment->lower) {
+ v_offset -= self->v_adjustment->value;
+ } else if(self->center_view && view) {
+ v_offset += (dirty.y2 / zoom - (view->y2 + view->y1)) / 2.0;
+ } else if(view) {
+ v_offset -= view->y1;
+ }
+
+ cairo_matrix_translate(&self->display_matrix, h_offset, v_offset);
+
+ cc_view_window_to_world(CC_VIEW(self), &dirty.x1, &dirty.y1);
+ cc_view_window_to_world(CC_VIEW(self), &dirty.x2, &dirty.y2);
+ cvw_set_dirty(self, dirty);
+}
+
+/**
+ * cc_view_widget_set_center_view:
+ * @self: a #CcViewWidget
+ * @center_view: a #gboolean
+ *
+ * Specify whether the model should be displayed centered if there is as much
+ * space.
+ */
+void
+cc_view_widget_set_center_view(CcViewWidget* self, gboolean center_view) {
+ g_return_if_fail(CC_IS_VIEW_WIDGET(self));
+
+ if(self->center_view == center_view) {
+ return;
+ }
+
+ self->center_view = center_view;
+
+ cvw_update_matrix(self);
+ g_object_notify(G_OBJECT(self), "center-view");
+}
+
+static void
+cvw_update_adjustment_allocation(GtkAdjustment* adjustment, gdouble size, gboolean update_adjustment) {
+ if(!GTK_IS_ADJUSTMENT(adjustment)) {
+ return;
+ }
+
+ g_object_set(adjustment,
+ "page-size", size,
+ "page-increment", size,
+ "step-increment", size > 20 ? 10.0 : 1.0,
+#warning "FIXME: let this scale up to bigger values"
+ NULL);
+
+ if(update_adjustment) {
+ gtk_adjustment_changed(adjustment);
+ }
+
+ gtk_adjustment_set_value(adjustment, MIN(adjustment->value, adjustment->upper - adjustment->page_size));
+}
+
+static void
+cvw_update_hadjustment_allocation(CcViewWidget* self, gboolean update_adjustment) {
+ cvw_update_adjustment_allocation(self->h_adjustment, GTK_WIDGET(self)->allocation.width / cvw_get_real_zoom(self), update_adjustment);
+}
+
+static void
+cvw_update_vadjustment_allocation(CcViewWidget* self, gboolean update_adjustment) {
+ cvw_update_adjustment_allocation(self->v_adjustment, GTK_WIDGET(self)->allocation.height / cvw_get_real_zoom(self), update_adjustment);
+}
+
+/**
+ * cc_view_widget_set_zoom_mode:
+ * @self: a #CcViewWidget
+ * @zoom_mode: a #CcZoomMode
+ *
+ * Set the zoom mode of the view widget.
+ */
+void
+cc_view_widget_set_zoom_mode(CcViewWidget* self, CcZoomMode zoom_mode) {
+ g_return_if_fail(CC_IS_VIEW_WIDGET(self));
+
+ if(self->zoom_mode == zoom_mode) {
+ return;
+ }
+
+ self->zoom_mode = zoom_mode;
+
+ g_object_notify(G_OBJECT(self), "zoom-mode");
+
+ cvw_update_hadjustment_allocation(self, TRUE);
+ cvw_update_vadjustment_allocation(self, TRUE);
+
+ cvw_update_matrix(self);
+}
+
+/* GType */
+static void cvw_init_item_view(CcItemViewIface* iface);
+static void cvw_init_view (CcViewIface * iface);
+G_DEFINE_TYPE_WITH_CODE(CcViewWidget, cc_view_widget, GTK_TYPE_DRAWING_AREA,
+ G_IMPLEMENT_INTERFACE(CC_TYPE_ITEM_VIEW, cvw_init_item_view);
+ G_IMPLEMENT_INTERFACE(CC_TYPE_VIEW, cvw_init_view));
+
+enum {
+ PROP_0,
+ PROP_CENTER_VIEW,
+ PROP_FOCUS,
+ PROP_SCROLLED_REGION,
+ PROP_ROOT,
+ PROP_ZOOM,
+ PROP_ZOOM_MODE
+};
+
+#ifdef DEBUG_SAVED_FRAMES
+static gboolean
+cvw_print_stats(CcViewWidget* self) {
+ if(G_LIKELY(self->priv->requests || self->priv->updates)) {
+ g_print("%d redraws by %d redraw requests\n", self->priv->updates, self->priv->requests);
+ self->priv->updates = self->priv->requests = 0;
+ }
+ return TRUE;
+}
+#endif
+
+static void
+cc_view_widget_init(CcViewWidget* self) {
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE(self, CC_TYPE_VIEW_WIDGET, struct CcViewWidgetPrivate);
+
+ self->center_view = TRUE;
+ gtk_widget_add_events(GTK_WIDGET(self), GDK_ALL_EVENTS_MASK & ~GDK_POINTER_MOTION_HINT_MASK);
+
+#ifdef DEBUG_SAVED_FRAMES
+ g_timeout_add(1000, (GSourceFunc)cvw_print_stats, self);
+#endif
+}
+
+static void
+cvw_dispose(GObject* object) {
+ CcViewWidget* self = CC_VIEW_WIDGET(object);
+ if(self->disposed) {
+ return;
+ }
+ self->disposed = TRUE;
+
+ if(self->root) {
+ cc_item_view_unregister(CC_ITEM_VIEW(self), self->root);
+ g_object_unref(self->root);
+ self->root = NULL;
+ }
+
+ G_OBJECT_CLASS(cc_view_widget_parent_class)->dispose(object);
+}
+
+static void
+cvw_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec) {
+ CcViewWidget* self = CC_VIEW_WIDGET(object);
+
+ switch(prop_id) {
+ case PROP_CENTER_VIEW:
+ g_value_set_boolean(value, cc_view_widget_get_center_view(self));
+ break;
+ case PROP_FOCUS:
+ g_value_set_object(value, self->focus);
+ break;
+ case PROP_ROOT:
+ g_value_set_object(value, self->root);
+ break;
+ case PROP_SCROLLED_REGION:
+ g_value_set_boxed(value, self->scrolled_region);
+ break;
+ case PROP_ZOOM:
+ g_value_set_double(value, self->zoom);
+ break;
+ case PROP_ZOOM_MODE:
+ g_value_set_enum(value, cc_view_widget_get_zoom_mode(self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+cvw_set_focus(CcViewWidget* self, CcItem* focus) {
+ g_return_if_fail(CC_IS_VIEW_WIDGET(self));
+ g_return_if_fail(GTK_WIDGET_CAN_FOCUS(self));
+ g_return_if_fail(!focus || CC_IS_ITEM(focus));
+
+ if(self->focus == focus) {
+ return;
+ }
+
+ if(self->focus) {
+ g_object_unref(self->focus);
+ self->focus = NULL;
+ }
+
+ if(focus) {
+ self->focus = g_object_ref_sink(focus);
+ if(!gtk_widget_is_focus(GTK_WIDGET(self))) {
+ gtk_widget_grab_focus(GTK_WIDGET(self));
+ }
+ }
+
+ g_object_notify(G_OBJECT(self), "focus");
+}
+
+static void
+cvw_set_root(CcViewWidget* self, CcItem* root) {
+ g_return_if_fail(!root || CC_IS_ITEM(root));
+
+ if(self->root == root) {
+ return;
+ }
+
+ if(self->root) {
+ cc_item_view_unregister(CC_ITEM_VIEW(self), self->root);
+ g_object_unref(self->root);
+ self->root = NULL;
+ }
+
+ if(root) {
+ self->root = g_object_ref_sink(root);
+ cc_item_view_register(CC_ITEM_VIEW(self), self->root);
+ }
+
+ g_object_notify(G_OBJECT(self), "root");
+}
+
+static void
+cvw_update_adjustment_root(GtkAdjustment* adjustment, gdouble lower, gdouble upper, gboolean update_adjustment) {
+ g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
+
+ upper += 1.0; // avoid leaving the canvas by rounding
+
+ g_object_set(adjustment,
+ "lower", lower,
+ "upper", upper,
+ NULL);
+
+ if(update_adjustment) {
+ gtk_adjustment_set_value(adjustment, MIN(MAX(adjustment->value, lower), upper));
+
+ gtk_adjustment_changed(adjustment);
+ }
+}
+
+static void
+cvw_update_hadjustment_root(CcViewWidget* self, gboolean update_adjustment) {
+ CcDRect const* view = cvw_get_real_view(self);
+ if(self->h_adjustment && view) {
+ cvw_update_adjustment_root(self->h_adjustment,
+ view->x1,
+ view->x2,
+ update_adjustment);
+ }
+}
+
+static void
+cvw_update_vadjustment_root(CcViewWidget* self, gboolean update_adjustment) {
+ CcDRect const* view = cvw_get_real_view(self);
+ if(self->v_adjustment && view) {
+ cvw_update_adjustment_root(self->v_adjustment,
+ view->y1,
+ view->y2,
+ update_adjustment);
+ }
+}
+
+static void
+cvw_set_scrolled_region(CcViewWidget* self, CcDRect* scrolled_region) {
+ if(self->scrolled_region == scrolled_region) {
+ return;
+ }
+
+ g_free(self->scrolled_region);
+ self->scrolled_region = NULL;
+
+ if(scrolled_region) {
+ self->scrolled_region = cc_d_rect_copy(scrolled_region);
+ }
+
+ g_object_notify(G_OBJECT(self), "scrolled-region");
+ cvw_update_hadjustment_root(self, TRUE);
+ cvw_update_vadjustment_root(self, TRUE);
+ cvw_update_matrix(self);
+ gtk_widget_queue_draw(GTK_WIDGET(self));
+}
+
+static void
+cvw_set_zoom(CcViewWidget* self, gdouble zoom) {
+ if(self->zoom == zoom) {
+ return;
+ }
+
+ self->zoom = zoom;
+
+ g_object_notify(G_OBJECT(self), "zoom");
+
+ cvw_update_hadjustment_allocation(self, TRUE);
+ cvw_update_vadjustment_allocation(self, TRUE);
+
+ cvw_update_matrix(self);
+}
+
+static void
+cvw_set_property(GObject* object, guint prop_id, GValue const* value, GParamSpec* pspec) {
+ CcViewWidget* self = CC_VIEW_WIDGET(object);
+
+ switch(prop_id) {
+ case PROP_CENTER_VIEW:
+ cc_view_widget_set_center_view(self, g_value_get_boolean(value));
+ break;
+ case PROP_FOCUS:
+ cvw_set_focus(self, g_value_get_object(value));
+ break;
+ case PROP_ROOT:
+ cvw_set_root(self, g_value_get_object(value));
+ break;
+ case PROP_SCROLLED_REGION:
+ cvw_set_scrolled_region(self, g_value_get_boxed(value));
+ break;
+ case PROP_ZOOM:
+ cvw_set_zoom(self, g_value_get_double(value));
+ break;
+ case PROP_ZOOM_MODE:
+ cc_view_widget_set_zoom_mode(self, g_value_get_enum(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+cvw_event(GtkWidget* widget, GdkEvent* event) {
+ CcViewWidget* self = CC_VIEW_WIDGET(widget);
+ CcView * view = CC_VIEW(widget);
+ gboolean retval = FALSE;
+ GdkEvent * item_event = gdk_event_copy(event);
+ CcItem * item = NULL;
+#if 0
+ {
+ GEnumClass* c = g_type_class_ref(GDK_TYPE_EVENT_TYPE);
+ GEnumValue* v = g_enum_get_value(c, event->type);
+ g_message("cvw_event(): %s", v->value_name);
+ g_type_class_unref(c);
+ }
+#endif
+ switch(event->type) {
+ case GDK_BUTTON_PRESS:
+ case GDK_2BUTTON_PRESS:
+ case GDK_3BUTTON_PRESS:
+ case GDK_BUTTON_RELEASE:
+ cc_view_window_to_world(CC_VIEW(self), &item_event->button.x, &item_event->button.y);
+ if(!self->root) {
+ break;
+ }
+ if(self->grabbed) {
+ item = self->grabbed;
+ } else {
+ cc_item_distance(self->root, item_event->button.x, item_event->button.y, &item);
+ }
+ if(item) {
+ g_signal_emit_by_name(item, "event", view, item_event, &retval);
+ }
+ break;
+ case GDK_ENTER_NOTIFY:
+ // we don't need this one
+ break;
+ case GDK_LEAVE_NOTIFY:
+ // unset the current item, but not if it's just grabbed
+ if(self->current && self->current != self->grabbed) {
+ cc_view_world_to_window(CC_VIEW(self), &item_event->crossing.x, &item_event->crossing.y);
+ g_signal_emit_by_name(self->current, "event", view, item_event, &retval);
+ self->current = NULL;
+ }
+ break;
+ case GDK_MOTION_NOTIFY:
+ if(self->root) {
+ GdkEventCrossing cr = {
+ GDK_LEAVE_NOTIFY,
+ event->motion.window,
+ FALSE,
+ event->motion.window,
+ event->motion.time,
+ event->motion.x, event->motion.y,
+ event->motion.x, event->motion.y,
+ GDK_CROSSING_NORMAL,
+ GDK_NOTIFY_UNKNOWN, // FIXME: implement this one
+ FALSE, // FIXME: implement this one
+ event->motion.state
+ };
+ gboolean retval2 = FALSE;
+
+ cc_view_window_to_world(CC_VIEW(self), &item_event->motion.x, &item_event->motion.y);
+ if(self->grabbed) {
+ if(0.0 < cc_item_distance(self->grabbed, item_event->motion.x, item_event->motion.y, &item)) {
+ if(self->over_grabbed) {
+ g_signal_emit_by_name(self->grabbed, "event", view, &cr, &retval2);
+ self->over_grabbed = FALSE;
+ }
+ } else {
+ if(!self->over_grabbed) {
+ retval2 = FALSE;
+ cr.type = GDK_ENTER_NOTIFY;
+ g_signal_emit_by_name(self->grabbed, "event", view, &cr, &retval2);
+ }
+ }
+ item = self->grabbed;
+ } else {
+ cc_item_distance(self->root, item_event->motion.x, item_event->motion.y, &item);
+ if(item != self->current) {
+ cc_view_window_to_world(CC_VIEW(self), &cr.x, &cr.y);
+ if(self->current) {
+ g_signal_emit_by_name(self->current, "event", view, &cr, &retval2);
+ self->current = NULL;
+ }
+ if(item) {
+ retval2 = FALSE;
+ cr.type = GDK_ENTER_NOTIFY;
+ self->current = item;
+ g_signal_emit_by_name(self->current, "event", view, &cr, &retval2);
+ }
+ }
+ }
+ if(item) {
+ g_signal_emit_by_name(item, "event", view, item_event, &retval);
+ }
+ gdk_window_get_pointer(GTK_WIDGET(self)->window, NULL, NULL, NULL);
+ }
+ break;
+ case GDK_SCROLL:
+ // dispatch this one on our own, heh
+ break;
+ case GDK_GRAB_BROKEN:
+ // read documentation and find out whether we need this one
+ break;
+ case GDK_KEY_PRESS:
+ case GDK_KEY_RELEASE:
+ // we're going to deliver these down to the focused child
+ if(self->focus) {
+ g_signal_emit_by_name(self->focus, "event", view, item_event, &retval);
+ }
+ break;
+ case GDK_FOCUS_CHANGE:
+ if(!item_event->focus_change.in && self->focus) {
+ g_signal_emit_by_name(self->focus, "event", view, item_event, &retval);
+ cvw_set_focus(self, NULL);
+ }
+ case GDK_CONFIGURE:
+ case GDK_EXPOSE:
+ case GDK_MAP:
+ case GDK_PROXIMITY_IN:
+ case GDK_PROXIMITY_OUT:
+ case GDK_UNMAP:
+ case GDK_VISIBILITY_NOTIFY:
+ // we don't need those
+ break;
+ default:
+ {
+ GTypeClass* class = g_type_class_ref(GDK_TYPE_EVENT_TYPE);
+ GEnumValue* value = g_enum_get_value(G_ENUM_CLASS(class), event->type);
+ g_message("CcViewWidget.event(): Unhandled event of type '%s'", value->value_name);
+ g_type_class_unref(class);
+ }
+ break;
+ }
+
+ if(GTK_WIDGET_CLASS(cc_view_widget_parent_class)->event) {
+ retval |= GTK_WIDGET_CLASS(cc_view_widget_parent_class)->event(widget, event);
+ }
+
+ gdk_event_free(item_event);
+
+ return retval;
+}
+
+static gboolean
+cvw_expose_event(GtkWidget* widget, GdkEventExpose* ev) {
+ CcViewWidget* self = CC_VIEW_WIDGET(widget);
+ CcDRect const* all_bounds;
+
+ g_return_val_if_fail(!self->root || CC_IS_ITEM(self->root), FALSE);
+
+ all_bounds = !self->root ? NULL : cc_item_get_all_bounds(self->root, CC_VIEW(widget));
+ if(all_bounds) {
+ // draw only if the area that should be repainted overlaps with the canvas area
+ GdkRectangle rect;
+ gdouble x = all_bounds->x1,
+ y = all_bounds->y1,
+ w = all_bounds->x2,
+ h = all_bounds->y2;
+
+ cc_view_world_to_window(CC_VIEW(self), &x, &y);
+ cc_view_world_to_window(CC_VIEW(self), &w, &h);
+
+ w -= x;
+ h -= y;
+
+ rect.x = x;
+ rect.y = y;
+ rect.width = w;
+ rect.height = h;
+
+ if(GDK_OVERLAP_RECTANGLE_OUT != gdk_region_rect_in(ev->region, &rect)) {
+ cairo_t* cr = gdk_cairo_create(widget->window);
+ gdk_cairo_region(cr, ev->region);
+ cairo_clip(cr);
+ cairo_save(cr);
+ cc_item_render(self->root, CC_VIEW(self), cr);
+ cairo_restore(cr);
+ cairo_destroy(cr);
+ }
+ }
+
+ // we don't chain up by intention
+ return FALSE;
+}
+
+static gboolean
+cvw_focus(GtkWidget* widget, GtkDirectionType dir) {
+ CcViewWidget* self;
+ gboolean retval = FALSE;
+ CcItem* victim;
+
+ if(!GTK_WIDGET_CAN_FOCUS(widget) || !self->root) {
+ return retval;
+ }
+
+ self = CC_VIEW_WIDGET(widget);
+ victim = self->focus ? self->focus : self->root;
+ g_signal_emit_by_name(victim, "focus", self, dir, &retval);
+
+ g_return_val_if_fail(!retval || gtk_widget_is_focus(widget), retval);
+
+ // This function is built like gtk_widget_real_focus, so no chaining up here.
+ return retval;
+}
+
+
+static void
+cvw_size_request(GtkWidget* widget, GtkRequisition* requisition) {
+ CcViewWidget* self = CC_VIEW_WIDGET(widget);
+
+ CcDRect const* all_bounds = !self->root ? NULL : cc_item_get_all_bounds(self->root, CC_VIEW(self));
+
+ if(!self->h_adjustment && all_bounds) {
+ requisition->width = self->zoom * all_bounds->x2 + 1.0;
+ } else {
+ requisition->width = 0.0;
+ }
+
+ if(!self->v_adjustment && all_bounds) {
+ requisition->height = self->zoom * all_bounds->y2 + 1.0;
+ } else {
+ requisition->width = 0.0;
+ }
+}
+
+static void
+cvw_size_allocate(GtkWidget* widget, GtkAllocation* allocation) {
+ CcViewWidget* self = CC_VIEW_WIDGET(widget);
+
+ GTK_WIDGET_CLASS(cc_view_widget_parent_class)->size_allocate(widget, allocation);
+
+ cvw_update_hadjustment_allocation(self, TRUE);
+ cvw_update_vadjustment_allocation(self, TRUE);
+
+ cvw_update_matrix(self);
+}
+
+static void
+cvw_set_adjustment(CcViewWidget* self, GtkAdjustment* new_adjustment, GtkAdjustment**old_adjustment) {
+ g_return_if_fail(old_adjustment);
+
+ if(!new_adjustment) {
+ new_adjustment = GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
+ }
+
+ if(*old_adjustment != new_adjustment) {
+ if(*old_adjustment) {
+ g_signal_handlers_disconnect_by_func(*old_adjustment, cvw_update_matrix, self);
+ g_object_unref(*old_adjustment);
+ }
+
+ *old_adjustment = g_object_ref(new_adjustment);
+ gtk_object_sink(GTK_OBJECT(*old_adjustment));
+
+ g_signal_connect_swapped(*old_adjustment, "changed",
+ G_CALLBACK(cvw_update_matrix), self);
+ g_signal_connect_swapped(*old_adjustment, "value-changed",
+ G_CALLBACK(cvw_update_matrix), self);
+ cvw_update_matrix(self);
+ }
+}
+
+static void
+cvw_set_hadjustment(CcViewWidget* self, GtkAdjustment* hadjustment) {
+ cvw_set_adjustment(self, hadjustment, &self->h_adjustment);
+
+ if(self->h_adjustment) {
+ cvw_update_hadjustment_allocation(self, FALSE);
+ cvw_update_hadjustment_root(self, TRUE);
+ }
+}
+
+static void
+cvw_set_vadjustment(CcViewWidget* self, GtkAdjustment* vadjustment) {
+ cvw_set_adjustment(self, vadjustment, &self->v_adjustment);
+
+ if(self->v_adjustment) {
+ cvw_update_vadjustment_allocation(self, FALSE);
+ cvw_update_vadjustment_root(self, TRUE);
+ }
+}
+
+static void
+cvw_set_scroll_adjustments(CcViewWidget* self, GtkAdjustment* hadjustment, GtkAdjustment* vadjustment) {
+ cvw_set_hadjustment(self, hadjustment);
+ cvw_set_vadjustment(self, vadjustment);
+}
+
+static void
+cc_view_widget_class_init(CcViewWidgetClass* self_class) {
+ GObjectClass * go_class;
+ GtkWidgetClass* gw_class;
+
+ /* GObjectClass */
+ go_class = G_OBJECT_CLASS(self_class);
+ go_class->dispose = cvw_dispose;
+ go_class->get_property = cvw_get_property;
+ go_class->set_property = cvw_set_property;
+
+ g_object_class_install_property(go_class,
+ PROP_CENTER_VIEW,
+ g_param_spec_boolean("center-view",
+ "Center View",
+ "Display the model centered if there is more space than "
+ "the model needs.",
+ TRUE,
+ G_PARAM_READWRITE));
+
+ /* GtkWidgetClass */
+ gw_class = GTK_WIDGET_CLASS(self_class);
+ gw_class->event = cvw_event;
+ gw_class->expose_event = cvw_expose_event;
+ gw_class->focus = cvw_focus;
+ gw_class->size_allocate = cvw_size_allocate;
+ gw_class->size_request = cvw_size_request;
+
+ /**
+ * CcViewWidget::set-scroll-adjustments:
+ * @hadjustment: a #GtkAdjustment
+ * @vadjustment: a #GtkAdjustment
+ *
+ * This signal gets emitted when the widget is being added to a
+ * scrollable container.
+ */
+ gw_class->set_scroll_adjustments_signal =
+ g_signal_new("set-scroll-adjustments", CC_TYPE_VIEW_WIDGET,
+ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(CcViewWidgetClass, set_scroll_adjustments),
+ NULL, NULL,
+ cc_marshal_VOID__BOXED_BOXED,
+ G_TYPE_NONE, 2,
+ GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT);
+
+ /* CcViewIface */
+ _cc_view_install_property(go_class, PROP_ROOT, PROP_SCROLLED_REGION, PROP_ZOOM, PROP_ZOOM_MODE, PROP_FOCUS);
+
+ /* CcViewWidgetClass */
+ self_class->set_scroll_adjustments = cvw_set_scroll_adjustments;
+
+ g_type_class_add_private(self_class, sizeof(struct CcViewWidgetPrivate));
+}
+
+/* CcItemViewIface */
+static void
+cvw_item_dirty(CcItemView* iview, CcItem* item, CcView const* view, CcDRect const* dirty) {
+ if(dirty) {
+ cvw_set_dirty(CC_VIEW_WIDGET(view), *dirty);
+ }
+}
+
+static void
+cvw_notify_all_bounds(CcItemView* iview, CcItem* item, CcView* view, CcDRect const* all_bounds) {
+ CcViewWidget* self = CC_VIEW_WIDGET(iview);
+ cvw_update_hadjustment_root(self, TRUE);
+ cvw_update_vadjustment_root(self, TRUE);
+
+ //g_return_if_fail(CC_IS_VIEW(view));
+ //g_return_if_fail(CC_VIEW_WIDGET(iview) == CC_VIEW(view));
+
+ if(all_bounds) {
+ cvw_set_dirty(self, *all_bounds);
+ }
+ cvw_update_matrix(self);
+ gtk_widget_queue_resize_no_redraw(GTK_WIDGET(iview));
+}
+
+static void
+cvw_init_item_view(CcItemViewIface* iface) {
+ iface->item_dirty = cvw_item_dirty;
+ iface->notify_all_bounds = cvw_notify_all_bounds;
+}
+
+/* CcViewIface */
+static GdkGrabStatus
+cvw_grab_item(CcView* view, CcItem* item, GdkEventMask mask, GdkCursor* cursor, guint32 time) {
+ GdkGrabStatus retval;
+ CcViewWidget* self = CC_VIEW_WIDGET(view);
+
+ g_return_val_if_fail(CC_IS_VIEW_WIDGET(view), GDK_GRAB_NOT_VIEWABLE);
+ g_return_val_if_fail(CC_IS_ITEM(item), GDK_GRAB_NOT_VIEWABLE);
+
+ if(self->grabbed) {
+ return GDK_GRAB_ALREADY_GRABBED;
+ }
+
+ // FIXME: check whether the item is visible
+
+ retval = gdk_pointer_grab(GTK_WIDGET(self)->window,
+ FALSE,
+ mask,
+ NULL,
+ cursor,
+ time);
+
+ if(retval != GDK_GRAB_SUCCESS) {
+ return retval;
+ }
+
+ self->grabbed = item;
+ self->grab_mask = mask;
+ self->over_grabbed = (self->current && cc_item_is_child_of(self->current, self->grabbed)) || self->current == self->grabbed;
+ self->current = item; /* So all events go to the grabbed item */
+
+ return retval;
+}
+
+static void
+cvw_ungrab_item(CcView* view, CcItem* item, guint32 time) {
+ CcViewWidget* self = CC_VIEW_WIDGET(view);
+ g_return_if_fail(CC_IS_VIEW_WIDGET(view));
+ g_return_if_fail(CC_IS_ITEM(item));
+
+ if(self->grabbed != item) {
+ return;
+ }
+
+ self->grabbed = NULL;
+
+ if(!self->over_grabbed) {
+ self->current = NULL;
+ }
+
+ gdk_pointer_ungrab(time);
+}
+
+static void
+cvw_window_to_world(CcView* view, gdouble* x, gdouble* y) {
+ cairo_matrix_t inverse;
+
+ memcpy(&inverse, &CC_VIEW_WIDGET(view)->display_matrix, sizeof(cairo_matrix_t));
+
+ cairo_matrix_invert(&inverse);
+ cairo_matrix_transform_point(&inverse, x, y);
+}
+
+static void
+cvw_world_to_window(CcView* view, gdouble* x, gdouble* y) {
+ gdouble tx, ty;
+ cairo_matrix_transform_point(&CC_VIEW_WIDGET(view)->display_matrix, x ? x : &tx, y ? y : &ty);
+}
+
+static void
+cvw_world_to_window_distance(CcView* view, gdouble* x, gdouble* y) {
+ gdouble tx, ty;
+ cairo_matrix_transform_distance(&CC_VIEW_WIDGET(view)->display_matrix, x ? x : &tx, y ? y : &ty);
+}
+
+static void
+cvw_init_view(CcViewIface* iface) {
+ iface->grab_item = cvw_grab_item;
+ iface->ungrab_item = cvw_ungrab_item;
+ iface->window_to_world = cvw_window_to_world;
+ iface->world_to_window = cvw_world_to_window;
+ iface->world_to_window_distance = cvw_world_to_window_distance;
+}
+