summaryrefslogtreecommitdiffstats
path: root/src/Canvas.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/Canvas.cpp')
-rw-r--r--src/Canvas.cpp4184
1 files changed, 0 insertions, 4184 deletions
diff --git a/src/Canvas.cpp b/src/Canvas.cpp
deleted file mode 100644
index 6a18cdc..0000000
--- a/src/Canvas.cpp
+++ /dev/null
@@ -1,4184 +0,0 @@
-/* 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/>.
- */
-
-/* Parts based on GnomeCanvas, by Federico Mena <federico@nuclecu.unam.mx>
- * and Raph Levien <raph@gimp.org>
- * Copyright 1997-2000 Free Software Foundation
- */
-
-#define _POSIX_C_SOURCE 200809L // strdup
-#define _XOPEN_SOURCE 600 // isascii on BSD
-
-#include <math.h>
-#include <stdio.h>
-#include <string.h>
-
-#include <algorithm>
-#include <cassert>
-#include <cmath>
-#include <iostream>
-#include <map>
-#include <set>
-#include <sstream>
-#include <string>
-#include <vector>
-
-#include <cairo.h>
-#include <gdk/gdkkeysyms.h>
-#include <gtk/gtk.h>
-#include <gtk/gtkstyle.h>
-#include <gtkmm/widget.h>
-
-#include "ganv/Canvas.hpp"
-#include "ganv/Circle.hpp"
-#include "ganv/Edge.hpp"
-#include "ganv/Module.hpp"
-#include "ganv/Port.hpp"
-#include "ganv/box.h"
-#include "ganv/canvas.h"
-#include "ganv/edge.h"
-#include "ganv/group.h"
-#include "ganv/node.h"
-#include "ganv_config.h"
-
-#include "./color.h"
-#include "./ganv-marshal.h"
-#include "./ganv-private.h"
-#include "./gettext.h"
-
-#ifdef HAVE_AGRAPH
-// Deal with graphviz API amateur hour...
-# define _DLL_BLD 0
-# define _dll_import 0
-# define _BLD_cdt 0
-# define _PACKAGE_ast 0
-# include <gvc.h>
-#endif
-#ifdef GANV_FDGL
-# include "./fdgl.hpp"
-#endif
-
-#define CANVAS_IDLE_PRIORITY (GDK_PRIORITY_REDRAW - 5)
-
-static const double GANV_CANVAS_PAD = 8.0;
-
-typedef struct {
- int x;
- int y;
- int width;
- int height;
-} IRect;
-
-extern "C" {
-static void add_idle(GanvCanvas* canvas);
-static void ganv_canvas_destroy(GtkObject* object);
-static void ganv_canvas_map(GtkWidget* widget);
-static void ganv_canvas_unmap(GtkWidget* widget);
-static void ganv_canvas_realize(GtkWidget* widget);
-static void ganv_canvas_unrealize(GtkWidget* widget);
-static void ganv_canvas_size_allocate(GtkWidget* widget,
- GtkAllocation* allocation);
-static gint ganv_canvas_button(GtkWidget* widget,
- GdkEventButton* event);
-static gint ganv_canvas_motion(GtkWidget* widget,
- GdkEventMotion* event);
-static gint ganv_canvas_expose(GtkWidget* widget,
- GdkEventExpose* event);
-static gboolean ganv_canvas_key(GtkWidget* widget,
- GdkEventKey* event);
-static gboolean ganv_canvas_scroll(GtkWidget* widget,
- GdkEventScroll* event);
-static gint ganv_canvas_crossing(GtkWidget* widget,
- GdkEventCrossing* event);
-static gint ganv_canvas_focus_in(GtkWidget* widget,
- GdkEventFocus* event);
-static gint ganv_canvas_focus_out(GtkWidget* widget,
- GdkEventFocus* event);
-
-static GtkLayoutClass* canvas_parent_class;
-}
-
-static guint signal_connect;
-static guint signal_disconnect;
-
-static GEnumValue dir_values[3];
-
-typedef std::set<GanvNode*> Items;
-
-#define FOREACH_ITEM(items, i) \
- for (Items::const_iterator i = items.begin(); i != items.end(); ++i)
-
-#define FOREACH_ITEM_MUT(items, i) \
- for (Items::iterator i = items.begin(); i != items.end(); ++i)
-
-#define FOREACH_EDGE(edges, i) \
- for (GanvCanvasImpl::Edges::const_iterator i = edges.begin(); \
- i != edges.end(); \
- ++i)
-
-#define FOREACH_EDGE_MUT(edges, i) \
- for (GanvCanvasImpl::Edges::iterator i = edges.begin(); \
- i != edges.end(); \
- ++i)
-
-#define FOREACH_SELECTED_EDGE(edges, i) \
- for (GanvCanvasImpl::SelectedEdges::const_iterator i = edges.begin(); \
- i != edges.end(); \
- ++i)
-
-#define FOREACH_SELECTED_PORT(p) \
- for (SelectedPorts::iterator p = _selected_ports.begin(); \
- p != _selected_ports.end(); ++p)
-
-#ifdef HAVE_AGRAPH
-class GVNodes : public std::map<GanvNode*, Agnode_t*> {
-public:
- GVNodes() : gvc(0), G(0) {}
-
- void cleanup() {
- gvFreeLayout(gvc, G);
- agclose (G);
- gvc = 0;
- G = 0;
- }
-
- GVC_t* gvc;
- Agraph_t* G;
-};
-#endif
-
-static const uint32_t SELECT_RECT_FILL_COLOUR = 0x2E444577;
-static const uint32_t SELECT_RECT_BORDER_COLOUR = 0x2E4445FF;
-
-/* Order edges by (tail, head) */
-struct TailHeadOrder {
- inline bool operator()(const GanvEdge* a, const GanvEdge* b) const {
- return ((a->impl->tail < b->impl->tail)
- || (a->impl->tail == b->impl->tail
- && a->impl->head < b->impl->head));
- }
-};
-
-/* Order edges by (head, tail) */
-struct HeadTailOrder {
- inline bool operator()(const GanvEdge* a, const GanvEdge* b) const {
- return ((a->impl->head < b->impl->head)
- || (a->impl->head == b->impl->head
- && a->impl->tail < b->impl->tail));
- }
-};
-
-/* Callback used when the root item of a canvas is destroyed. The user should
- * never ever do this, so we panic if this happens.
- */
-static void
-panic_root_destroyed(GtkObject* object, gpointer data)
-{
- g_error("Eeeek, root item %p of canvas %p was destroyed!", (void*)object, data);
-}
-
-struct GanvCanvasImpl {
- GanvCanvasImpl(GanvCanvas* canvas)
- : _gcanvas(canvas)
- , _wrapper(NULL)
- , _connect_port(NULL)
- , _last_selected_port(NULL)
- , _drag_edge(NULL)
- , _drag_node(NULL)
- , _select_rect(NULL)
- , _select_start_x(0.0)
- , _select_start_y(0.0)
- , _drag_state(NOT_DRAGGING)
- {
- this->root = GANV_ITEM(g_object_new(ganv_group_get_type(), NULL));
- this->root->impl->canvas = canvas;
- g_object_ref_sink(this->root);
-
- this->direction = GANV_DIRECTION_RIGHT;
- this->width = 0;
- this->height = 0;
-
- this->redraw_region = NULL;
- this->current_item = NULL;
- this->new_current_item = NULL;
- this->grabbed_item = NULL;
- this->focused_item = NULL;
- this->pixmap_gc = NULL;
-
- this->pick_event.type = GDK_LEAVE_NOTIFY;
- this->pick_event.crossing.x = 0;
- this->pick_event.crossing.y = 0;
-
- this->scroll_x1 = 0.0;
- this->scroll_y1 = 0.0;
- this->scroll_x2 = canvas->layout.width;
- this->scroll_y2 = canvas->layout.height;
-
- this->pixels_per_unit = 1.0;
- this->font_size = ganv_canvas_get_default_font_size(canvas);
-
- this->idle_id = 0;
- this->root_destroy_id = g_signal_connect(
- this->root, "destroy", G_CALLBACK(panic_root_destroyed), canvas);
-
- this->redraw_x1 = 0;
- this->redraw_y1 = 0;
- this->redraw_x2 = 0;
- this->redraw_y2 = 0;
-
- this->draw_xofs = 0;
- this->draw_yofs = 0;
- this->zoom_xofs = 0;
- this->zoom_yofs = 0;
-
- this->state = 0;
- this->grabbed_event_mask = 0;
-
- this->center_scroll_region = FALSE;
- this->need_update = FALSE;
- this->need_redraw = FALSE;
- this->need_repick = TRUE;
- this->left_grabbed_item = FALSE;
- this->in_repick = FALSE;
- this->locked = FALSE;
- this->exporting = FALSE;
-
-#ifdef GANV_FDGL
- this->layout_idle_id = 0;
- this->layout_energy = 0.4;
- this->sprung_layout = FALSE;
-#endif
-
- _animate_idle_id = 0;
-
- _port_order.port_cmp = NULL;
- _port_order.data = NULL;
-
- gtk_layout_set_hadjustment(GTK_LAYOUT(canvas), NULL);
- gtk_layout_set_vadjustment(GTK_LAYOUT(canvas), NULL);
-
- _move_cursor = gdk_cursor_new(GDK_FLEUR);
- }
-
- ~GanvCanvasImpl()
- {
- if (_animate_idle_id) {
- g_source_remove(_animate_idle_id);
- _animate_idle_id = 0;
- }
-
- while (g_idle_remove_by_data(this)) {}
- ganv_canvas_clear(_gcanvas);
- gdk_cursor_unref(_move_cursor);
- }
-
- static gboolean on_animate_timeout(gpointer impl);
-
-#ifdef GANV_FDGL
- static gboolean on_layout_timeout(gpointer impl) {
- return ((GanvCanvasImpl*)impl)->layout_iteration();
- }
-
- static void on_layout_done(gpointer impl) {
- ((GanvCanvasImpl*)impl)->layout_idle_id = 0;
- }
-
- gboolean layout_iteration();
- gboolean layout_calculate(double dur, bool update);
-#endif
-
- void unselect_ports();
-
-#ifdef HAVE_AGRAPH
- GVNodes layout_dot(const std::string& filename);
-#endif
-
- typedef std::set<GanvEdge*, TailHeadOrder> Edges;
- typedef std::set<GanvEdge*, HeadTailOrder> DstEdges;
- typedef std::set<GanvEdge*> SelectedEdges;
- typedef std::set<GanvPort*> SelectedPorts;
-
- Edges::const_iterator first_edge_from(const GanvNode* src);
- DstEdges::const_iterator first_edge_to(const GanvNode* dst);
-
- void select_port(GanvPort* p, bool unique=false);
- void select_port_toggle(GanvPort* p, int mod_state);
- void unselect_port(GanvPort* p);
- void selection_joined_with(GanvPort* port);
- void join_selection();
-
- GanvNode* get_node_at(double x, double y);
-
- bool on_event(GdkEvent* event);
-
- bool scroll_drag_handler(GdkEvent* event);
- bool select_drag_handler(GdkEvent* event);
- bool connect_drag_handler(GdkEvent* event);
- void end_connect_drag();
-
- /*
- Event handler for ports.
-
- This must be implemented as a Canvas method since port event handling
- depends on shared data (for selection and connecting). This function
- should only be used by Port implementations.
- */
- bool port_event(GdkEvent* event, GanvPort* port);
-
- void ports_joined(GanvPort* port1, GanvPort* port2);
- void port_clicked(GdkEvent* event, GanvPort* port);
- void highlight_port(GanvPort* port, bool highlight);
-
- void move_contents_to_internal(double x, double y, double min_x, double min_y);
-
- GanvCanvas* _gcanvas;
- Ganv::Canvas* _wrapper;
-
- Items _items; ///< Items on this canvas
- Edges _edges; ///< Edges ordered (src, dst)
- DstEdges _dst_edges; ///< Edges ordered (dst, src)
- Items _selected_items; ///< Currently selected items
- SelectedEdges _selected_edges; ///< Currently selected edges
-
- SelectedPorts _selected_ports; ///< Selected ports (hilited red)
- GanvPort* _connect_port; ///< Port for which a edge is being made
- GanvPort* _last_selected_port;
- GanvEdge* _drag_edge;
- GanvNode* _drag_node;
-
- GanvBox* _select_rect; ///< Rectangle for drag selection
- double _select_start_x; ///< Selection drag start x coordinate
- double _select_start_y; ///< Selection drag start y coordinate
-
- enum DragState { NOT_DRAGGING, EDGE, SCROLL, SELECT };
- DragState _drag_state;
-
- GdkCursor* _move_cursor;
- guint _animate_idle_id;
-
- PortOrderCtx _port_order;
-
- /* Root canvas item */
- GanvItem* root;
-
- /* Flow direction */
- GanvDirection direction;
-
- /* Canvas width */
- double width;
-
- /* Canvas height */
- double height;
-
- /* Region that needs redrawing (list of rectangles) */
- GSList* redraw_region;
-
- /* The item containing the mouse pointer, or NULL if none */
- GanvItem* current_item;
-
- /* Item that is about to become current (used to track deletions and such) */
- GanvItem* new_current_item;
-
- /* Item that holds a pointer grab, or NULL if none */
- GanvItem* grabbed_item;
-
- /* If non-NULL, the currently focused item */
- GanvItem* focused_item;
-
- /* GC for temporary draw pixmap */
- GdkGC* pixmap_gc;
-
- /* Event on which selection of current item is based */
- GdkEvent pick_event;
-
- /* Scrolling region */
- double scroll_x1;
- double scroll_y1;
- double scroll_x2;
- double scroll_y2;
-
- /* Scaling factor to be used for display */
- double pixels_per_unit;
-
- /* Font size in points */
- double font_size;
-
- /* Idle handler ID */
- guint idle_id;
-
- /* Signal handler ID for destruction of the root item */
- guint root_destroy_id;
-
- /* Area that is being redrawn. Contains (x1, y1) but not (x2, y2).
- * Specified in canvas pixel coordinates.
- */
- int redraw_x1;
- int redraw_y1;
- int redraw_x2;
- int redraw_y2;
-
- /* Offsets of the temprary drawing pixmap */
- int draw_xofs;
- int draw_yofs;
-
- /* Internal pixel offsets when zoomed out */
- int zoom_xofs;
- int zoom_yofs;
-
- /* Last known modifier state, for deferred repick when a button is down */
- int state;
-
- /* Event mask specified when grabbing an item */
- guint grabbed_event_mask;
-
- /* Whether the canvas should center the scroll region in the middle of
- * the window if the scroll region is smaller than the window.
- */
- gboolean center_scroll_region;
-
- /* Whether items need update at next idle loop iteration */
- gboolean need_update;
-
- /* Whether the canvas needs redrawing at the next idle loop iteration */
- gboolean need_redraw;
-
- /* Whether current item will be repicked at next idle loop iteration */
- gboolean need_repick;
-
- /* For use by internal pick_current_item() function */
- gboolean left_grabbed_item;
-
- /* For use by internal pick_current_item() function */
- gboolean in_repick;
-
- /* Disable changes to canvas */
- gboolean locked;
-
- /* True if the current draw is an export */
- gboolean exporting;
-
-#ifdef GANV_FDGL
- guint layout_idle_id;
- gdouble layout_energy;
- gboolean sprung_layout;
-#endif
-};
-
-typedef struct {
- GanvItem item;
- GanvEdgePrivate* impl;
- GanvEdgePrivate impl_data;
-} GanvEdgeKey;
-
-static void
-make_edge_search_key(GanvEdgeKey* key,
- const GanvNode* tail,
- const GanvNode* head)
-{
- memset(key, '\0', sizeof(GanvEdgeKey));
- key->impl = &key->impl_data;
- key->impl->tail = const_cast<GanvNode*>(tail);
- key->impl->head = const_cast<GanvNode*>(head);
-}
-
-GanvCanvasImpl::Edges::const_iterator
-GanvCanvasImpl::first_edge_from(const GanvNode* tail)
-{
- GanvEdgeKey key;
- make_edge_search_key(&key, tail, NULL);
- return _edges.lower_bound((GanvEdge*)&key);
-}
-
-GanvCanvasImpl::DstEdges::const_iterator
-GanvCanvasImpl::first_edge_to(const GanvNode* head)
-{
- GanvEdgeKey key;
- make_edge_search_key(&key, NULL, head);
- return _dst_edges.lower_bound((GanvEdge*)&key);
-}
-
-static void
-select_if_tail_is_selected(GanvEdge* edge, void* data)
-{
- GanvNode* tail = edge->impl->tail;
- gboolean selected;
- g_object_get(tail, "selected", &selected, NULL);
- if (!selected && GANV_IS_PORT(tail)) {
- g_object_get(ganv_port_get_module(GANV_PORT(tail)),
- "selected", &selected, NULL);
- }
-
- if (selected) {
- ganv_edge_select(edge);
- }
-}
-
-static void
-select_if_head_is_selected(GanvEdge* edge, void* data)
-{
- GanvNode* head = edge->impl->head;
- gboolean selected;
- g_object_get(head, "selected", &selected, NULL);
- if (!selected && GANV_IS_PORT(head)) {
- g_object_get(ganv_port_get_module(GANV_PORT(head)),
- "selected", &selected, NULL);
- }
-
- if (selected) {
- ganv_edge_set_selected(edge, TRUE);
- }
-}
-
-static void
-select_edges(GanvPort* port, void* data)
-{
- GanvCanvasImpl* impl = (GanvCanvasImpl*)data;
- if (port->impl->is_input) {
- ganv_canvas_for_each_edge_to(impl->_gcanvas,
- GANV_NODE(port),
- select_if_tail_is_selected,
- NULL);
- } else {
- ganv_canvas_for_each_edge_from(impl->_gcanvas,
- GANV_NODE(port),
- select_if_head_is_selected,
- NULL);
- }
-}
-
-#ifdef HAVE_AGRAPH
-static void
-gv_set(void* subject, const char* key, double value)
-{
- std::ostringstream ss;
- ss << value;
- agsafeset(subject, (char*)key, (char*)ss.str().c_str(), (char*)"");
-}
-
-GVNodes
-GanvCanvasImpl::layout_dot(const std::string& filename)
-{
- GVNodes nodes;
-
- const double dpi = gdk_screen_get_resolution(gdk_screen_get_default());
-
- GVC_t* gvc = gvContext();
-
- Agraph_t* G = agopen((char*)"g", Agdirected, NULL);
-
- agsafeset(G, (char*)"splines", (char*)"false", (char*)"");
- agsafeset(G, (char*)"compound", (char*)"true", (char*)"");
- agsafeset(G, (char*)"remincross", (char*)"true", (char*)"");
- agsafeset(G, (char*)"overlap", (char*)"scale", (char*)"");
- agsafeset(G, (char*)"nodesep", (char*)"0.05", (char*)"");
- gv_set(G, "fontsize", ganv_canvas_get_font_size(_gcanvas));
- gv_set(G, "dpi", dpi);
-
- nodes.gvc = gvc;
- nodes.G = G;
-
- const bool flow_right = _gcanvas->impl->direction;
- if (flow_right) {
- agattr(G, AGRAPH, (char*)"rankdir", (char*)"LR");
- } else {
- agattr(G, AGRAPH, (char*)"rankdir", (char*)"TD");
- }
-
- unsigned id = 0;
- std::ostringstream ss;
- FOREACH_ITEM(_items, i) {
- ss.str("");
- ss << "n" << id++;
- const std::string node_id = ss.str();
-
- Agnode_t* node = agnode(G, strdup(node_id.c_str()), true);
- nodes.insert(std::make_pair(*i, node));
-
- if (GANV_IS_MODULE(*i)) {
- GanvModule* const m = GANV_MODULE(*i);
-
- agsafeset(node, (char*)"shape", (char*)"plaintext", (char*)"");
- gv_set(node, "width", ganv_box_get_width(GANV_BOX(*i)) / dpi);
- gv_set(node, "height", ganv_box_get_height(GANV_BOX(*i)) / dpi);
-
- std::string inputs; // Down flow
- std::string outputs; // Down flow
- std::string ports; // Right flow
- unsigned n_inputs = 0;
- unsigned n_outputs = 0;
- for (size_t i = 0; i < ganv_module_num_ports(m); ++i) {
- GanvPort* port = ganv_module_get_port(m, i);
- ss.str("");
- ss << port;
-
- if (port->impl->is_input) {
- ++n_inputs;
- } else {
- ++n_outputs;
- }
-
- std::string cell = std::string("<TD PORT=\"") + ss.str() + "\"";
-
- cell += " FIXEDSIZE=\"TRUE\"";
- ss.str("");
- ss << ganv_box_get_width(GANV_BOX(port));// / dpp * 1.3333333;
- cell += " WIDTH=\"" + ss.str() + "\"";
-
- ss.str("");
- ss << ganv_box_get_height(GANV_BOX(port));// / dpp * 1.333333;
- cell += " HEIGHT=\"" + ss.str() + "\"";
-
- cell += ">";
- const char* label = ganv_node_get_label(GANV_NODE(port));
- if (label && flow_right) {
- cell += label;
- }
- cell += "</TD>";
-
- if (flow_right) {
- ports += "<TR>" + cell + "</TR>";
- } else if (port->impl->is_input) {
- inputs += cell;
- } else {
- outputs += cell;
- }
-
- nodes.insert(std::make_pair(GANV_NODE(port), node));
- }
-
- const unsigned n_cols = std::max(n_inputs, n_outputs);
-
- std::string html = "<TABLE CELLPADDING=\"0\" CELLSPACING=\"0\">";
-
- // Input row (down flow only)
- if (!inputs.empty()) {
- for (unsigned i = n_inputs; i < n_cols + 1; ++i) {
- inputs += "<TD BORDER=\"0\"></TD>";
- }
- html += std::string("<TR>") + inputs + "</TR>";
- }
-
- // Label row
- std::stringstream colspan;
- colspan << (flow_right ? 1 : (n_cols + 1));
- html += std::string("<TR><TD BORDER=\"0\" CELLPADDING=\"2\" COLSPAN=\"")
- + colspan.str()
- + "\">";
- const char* label = ganv_node_get_label(GANV_NODE(m));
- if (label) {
- html += label;
- }
- html += "</TD></TR>";
-
- // Ports rows (right flow only)
- if (!ports.empty()) {
- html += ports;
- }
-
- // Output row (down flow only)
- if (!outputs.empty()) {
- for (unsigned i = n_outputs; i < n_cols + 1; ++i) {
- outputs += "<TD BORDER=\"0\"></TD>";
- }
- html += std::string("<TR>") + outputs + "</TR>";
- }
- html += "</TABLE>";
-
- char* html_label_str = agstrdup_html(G, (char*)html.c_str());
-
- agsafeset(node, (char*)"label", (char*)html_label_str, (char*)"");
- } else if (GANV_IS_CIRCLE(*i)) {
- agsafeset(node, (char*)"shape", (char*)"circle", (char*)"");
- agsafeset(node, (char*)"fixedsize", (char*)"true", (char*)"");
- agsafeset(node, (char*)"margin", (char*)"0.0,0.0", (char*)"");
-
- const double radius = ganv_circle_get_radius(GANV_CIRCLE(*i));
- const double penwidth = ganv_node_get_border_width(GANV_NODE(*i));
- const double span = (radius + penwidth) * 2.3 / dpi;
- gv_set(node, (char*)"width", span);
- gv_set(node, (char*)"height", span);
- gv_set(node, (char*)"penwidth", penwidth);
-
- if (ganv_node_get_dash_length(GANV_NODE(*i)) > 0.0) {
- agsafeset(node, (char*)"style", (char*)"dashed", (char*)"");
- }
-
- const char* label = ganv_node_get_label(GANV_NODE(*i));
- if (label) {
- agsafeset(node, (char*)"label", (char*)label, (char*)"");
- } else {
- agsafeset(node, (char*)"label", (char*)"", (char*)"");
- }
- } else {
- std::cerr << "Unable to arrange item of unknown type" << std::endl;
- }
- }
-
- FOREACH_EDGE(_edges, i) {
- const GanvEdge* const edge = *i;
- GVNodes::iterator tail_i = nodes.find(edge->impl->tail);
- GVNodes::iterator head_i = nodes.find(edge->impl->head);
-
- if (tail_i != nodes.end() && head_i != nodes.end()) {
- Agedge_t* e = agedge(G, tail_i->second, head_i->second, NULL, true);
- if (GANV_IS_PORT(edge->impl->tail)) {
- ss.str((char*)"");
- ss << edge->impl->tail << (flow_right ? ":e" : ":s");
- agsafeset(e, (char*)"tailport", (char*)ss.str().c_str(), (char*)"");
- }
- if (GANV_IS_PORT(edge->impl->head)) {
- ss.str((char*)"");
- ss << edge->impl->head << (flow_right ? ":w" : ":n");
- agsafeset(e, (char*)"headport", (char*)ss.str().c_str(), (char*)"");
- }
- if (!ganv_edge_get_constraining(edge)) {
- agsafeset(e, (char*)"constraint", (char*)"false", (char*)"");
- }
- } else {
- std::cerr << "Unable to find graphviz node" << std::endl;
- }
- }
-
- // Add edges between partners to have them lined up as if connected
- for (GVNodes::iterator i = nodes.begin(); i != nodes.end(); ++i) {
- GanvNode* partner = ganv_node_get_partner(i->first);
- if (partner) {
- GVNodes::iterator p = nodes.find(partner);
- if (p != nodes.end()) {
- Agedge_t* e = agedge(G, i->second, p->second, NULL, true);
- agsafeset(e, (char*)"style", (char*)"invis", (char*)"");
- }
- }
- }
-
- gvLayout(gvc, G, (char*)"dot");
- FILE* tmp = fopen("/dev/null", "w");
- gvRender(gvc, G, (char*)"dot", tmp);
- fclose(tmp);
-
- if (filename != "") {
- FILE* fd = fopen(filename.c_str(), "w");
- gvRender(gvc, G, (char*)"dot", fd);
- fclose(fd);
- }
-
- return nodes;
-}
-#endif
-
-inline uint64_t
-get_monotonic_time()
-{
-#if GLIB_CHECK_VERSION(2, 28, 0)
- return g_get_monotonic_time();
-#else
- GTimeVal time;
- g_get_current_time(&time);
- return time.tv_sec + time.tv_usec;
-#endif
-}
-
-#ifdef GANV_FDGL
-
-inline Region
-get_region(GanvNode* node)
-{
- GanvItem* item = &node->item;
-
- double x1, y1, x2, y2;
- ganv_item_get_bounds(item, &x1, &y1, &x2, &y2);
-
- Region reg;
- ganv_item_get_bounds(item, &reg.pos.x, &reg.pos.y, &reg.area.x, &reg.area.y);
- reg.area.x = x2 - x1;
- reg.area.y = y2 - y1;
- reg.pos.x = item->impl->x + (reg.area.x / 2.0);
- reg.pos.y = item->impl->y + (reg.area.y / 2.0);
-
- // No need for i2w here since we only care about top-level items
- return reg;
-}
-
-inline void
-apply_force(GanvNode* a, GanvNode* b, const Vector& f)
-{
- a->impl->force = vec_add(a->impl->force, f);
- b->impl->force = vec_sub(b->impl->force, f);
-}
-
-gboolean
-GanvCanvasImpl::layout_iteration()
-{
- if (_drag_state == EDGE) {
- return FALSE; // Canvas is locked, halt layout process
- } else if (!sprung_layout) {
- return FALSE; // We shouldn't be running at all
- }
-
- static const double T_PER_US = .0001; // Sym time per real microsecond
-
- static uint64_t prev = 0; // Previous iteration time
-
- const uint64_t now = get_monotonic_time();
- const double time_to_run = std::min((now - prev) * T_PER_US, 10.0);
-
- prev = now;
-
- const double QUANTUM = 0.05;
- double sym_time = 0.0;
- while (sym_time + QUANTUM < time_to_run) {
- if (!layout_calculate(QUANTUM, FALSE)) {
- break;
- }
- sym_time += QUANTUM;
- }
-
- return layout_calculate(QUANTUM, TRUE);
-}
-
-gboolean
-GanvCanvasImpl::layout_calculate(double dur, bool update)
-{
- // A light directional force to push sources to the top left
- static const double DIR_MAGNITUDE = -1000.0;
- Vector dir = { 0.0, 0.0 };
- switch (_gcanvas->impl->direction) {
- case GANV_DIRECTION_RIGHT: dir.x = DIR_MAGNITUDE; break;
- case GANV_DIRECTION_DOWN: dir.y = DIR_MAGNITUDE; break;
- }
-
- // Calculate attractive spring forces for edges
- FOREACH_EDGE(_edges, i) {
- const GanvEdge* const edge = *i;
- if (!ganv_edge_get_constraining(edge)) {
- continue;
- }
-
- GanvNode* tail = ganv_edge_get_tail(edge);
- GanvNode* head = ganv_edge_get_head(edge);
- if (GANV_IS_PORT(tail)) {
- tail = GANV_NODE(ganv_port_get_module(GANV_PORT(tail)));
- }
- if (GANV_IS_PORT(head)) {
- head = GANV_NODE(ganv_port_get_module(GANV_PORT(head)));
- }
- if (tail == head) {
- continue;
- }
-
- head->impl->connected = tail->impl->connected = TRUE;
-
- GanvEdgeCoords coords;
- ganv_edge_get_coords(edge, &coords);
-
- const Vector tpos = { coords.x1, coords.y1 };
- const Vector hpos = { coords.x2, coords.y2 };
- apply_force(tail, head, edge_force(dir, hpos, tpos));
- }
-
- // Calculate repelling forces between nodes
- FOREACH_ITEM(_items, i) {
- if (!GANV_IS_MODULE(*i) && !GANV_IS_CIRCLE(*i)) {
- continue;
- }
-
- GanvNode* const node = *i;
- GanvNode* partner = ganv_node_get_partner(node);
- if (!partner && !node->impl->connected) {
- continue;
- }
-
- const Region reg = get_region(node);
- if (partner) {
- // Add fake long spring to partner to line up as if connected
- const Region preg = get_region(partner);
- apply_force(node, partner, edge_force(dir, preg.pos, reg.pos));
- }
-
- /* Add tide force which pulls all objects as if the layout is happening
- on a flowing river surface. This prevents disconnected components
- from being ejected, since at some point the tide force will be
- greater than distant repelling charges. */
- const Vector mouth = { -100000.0, -100000.0 };
- node->impl->force = vec_add(
- node->impl->force,
- tide_force(mouth, reg.pos, 4000000000000.0));
-
- // Add slight noise to force to limit oscillation
- const Vector noise = { rand() / (float)RAND_MAX * 128.0,
- rand() / (float)RAND_MAX * 128.0 };
- node->impl->force = vec_add(noise, node->impl->force);
-
- FOREACH_ITEM(_items, j) {
- if (i == j || (!GANV_IS_MODULE(*i) && !GANV_IS_CIRCLE(*i))) {
- continue;
- }
- apply_force(node, *j, repel_force(reg, get_region(*j)));
- }
- }
-
- // Update positions based on calculated forces
- size_t n_moved = 0;
- FOREACH_ITEM(_items, i) {
- if (!GANV_IS_MODULE(*i) && !GANV_IS_CIRCLE(*i)) {
- continue;
- }
-
- GanvNode* const node = *i;
-
- if (node->impl->grabbed ||
- (!node->impl->connected && !ganv_node_get_partner(node))) {
- node->impl->vel.x = 0.0;
- node->impl->vel.y = 0.0;
- } else {
- node->impl->vel = vec_add(node->impl->vel,
- vec_mult(node->impl->force, dur));
- node->impl->vel = vec_mult(node->impl->vel, layout_energy);
-
- static const double MAX_VEL = 1000.0;
- static const double MIN_COORD = 4.0;
-
- // Clamp velocity
- const double vel_mag = vec_mag(node->impl->vel);
- if (vel_mag > MAX_VEL) {
- node->impl->vel = vec_mult(
- vec_mult(node->impl->vel, 1.0 / vel_mag),
- MAX_VEL);
- }
-
- // Update position
- GanvItem* item = &node->item;
- const double x0 = item->impl->x;
- const double y0 = item->impl->y;
- const Vector dpos = vec_mult(node->impl->vel, dur);
-
- item->impl->x = std::max(MIN_COORD, item->impl->x + dpos.x);
- item->impl->y = std::max(MIN_COORD, item->impl->y + dpos.y);
-
- if (update) {
- ganv_item_request_update(item);
- item->impl->canvas->impl->need_repick = TRUE;
- }
-
- if (lrint(x0) != lrint(item->impl->x) || lrint(y0) != lrint(item->impl->y)) {
- ++n_moved;
- }
- }
-
- // Reset forces for next time
- node->impl->force.x = 0.0;
- node->impl->force.y = 0.0;
- node->impl->connected = FALSE;
- }
-
- if (update) {
- // Now update edge positions to reflect new node positions
- FOREACH_EDGE(_edges, i) {
- GanvEdge* const edge = *i;
- ganv_edge_update_location(edge);
- }
- }
-
- layout_energy *= 0.999;
- return n_moved > 0;
-}
-
-#endif // GANV_FDGL
-
-void
-GanvCanvasImpl::select_port(GanvPort* p, bool unique)
-{
- if (unique) {
- unselect_ports();
- }
- g_object_set(G_OBJECT(p), "selected", TRUE, NULL);
- _selected_ports.insert(p);
- _last_selected_port = p;
-}
-
-void
-GanvCanvasImpl::select_port_toggle(GanvPort* port, int mod_state)
-{
- gboolean selected;
- g_object_get(G_OBJECT(port), "selected", &selected, NULL);
- if ((mod_state & GDK_CONTROL_MASK)) {
- if (selected)
- unselect_port(port);
- else
- select_port(port);
- } else if ((mod_state & GDK_SHIFT_MASK)) {
- GanvModule* const m = ganv_port_get_module(port);
- if (_last_selected_port && m
- && ganv_port_get_module(_last_selected_port) == m) {
- // Pivot around _last_selected_port in a single pass over module ports each click
- GanvPort* old_last_selected = _last_selected_port;
- GanvPort* first = NULL;
- bool done = false;
- for (size_t i = 0; i < ganv_module_num_ports(m); ++i) {
- GanvPort* const p = ganv_module_get_port(m, i);
- if (!first && !done && (p == _last_selected_port || p == port)) {
- first = p;
- }
-
- if (first && !done && p->impl->is_input == first->impl->is_input) {
- select_port(p, false);
- } else {
- unselect_port(p);
- }
-
- if (p != first && (p == old_last_selected || p == port)) {
- done = true;
- }
- }
- _last_selected_port = old_last_selected;
- } else {
- if (selected) {
- unselect_port(port);
- } else {
- select_port(port);
- }
- }
- } else {
- if (selected) {
- unselect_ports();
- } else {
- select_port(port, true);
- }
- }
-}
-
-void
-GanvCanvasImpl::unselect_port(GanvPort* p)
-{
- _selected_ports.erase(p);
- g_object_set(G_OBJECT(p), "selected", FALSE, NULL);
- if (_last_selected_port == p) {
- _last_selected_port = NULL;
- }
-}
-
-void
-GanvCanvasImpl::selection_joined_with(GanvPort* port)
-{
- FOREACH_SELECTED_PORT(i)
- ports_joined(*i, port);
-}
-
-void
-GanvCanvasImpl::join_selection()
-{
- std::vector<GanvPort*> inputs;
- std::vector<GanvPort*> outputs;
- FOREACH_SELECTED_PORT(i) {
- if ((*i)->impl->is_input) {
- inputs.push_back(*i);
- } else {
- outputs.push_back(*i);
- }
- }
-
- if (inputs.size() == 1) { // 1 -> n
- for (size_t i = 0; i < outputs.size(); ++i)
- ports_joined(inputs[0], outputs[i]);
- } else if (outputs.size() == 1) { // n -> 1
- for (size_t i = 0; i < inputs.size(); ++i)
- ports_joined(inputs[i], outputs[0]);
- } else { // n -> m
- size_t num_to_connect = std::min(inputs.size(), outputs.size());
- for (size_t i = 0; i < num_to_connect; ++i) {
- ports_joined(inputs[i], outputs[i]);
- }
- }
-}
-
-GanvNode*
-GanvCanvasImpl::get_node_at(double x, double y)
-{
- GanvItem* item = ganv_canvas_get_item_at(GANV_CANVAS(_gcanvas), x, y);
- while (item) {
- if (GANV_IS_NODE(item)) {
- return GANV_NODE(item);
- } else {
- item = item->impl->parent;
- }
- }
-
- return NULL;
-}
-
-bool
-GanvCanvasImpl::on_event(GdkEvent* event)
-{
- static const int scroll_increment = 10;
- int scroll_x, scroll_y;
-
- bool handled = false;
- switch (event->type) {
- case GDK_KEY_PRESS:
- handled = true;
- ganv_canvas_get_scroll_offsets(GANV_CANVAS(_gcanvas), &scroll_x, &scroll_y);
- switch (event->key.keyval) {
- case GDK_Up:
- scroll_y -= scroll_increment;
- break;
- case GDK_Down:
- scroll_y += scroll_increment;
- break;
- case GDK_Left:
- scroll_x -= scroll_increment;
- break;
- case GDK_Right:
- scroll_x += scroll_increment;
- break;
- case GDK_Return:
- if (_selected_ports.size() > 1) {
- join_selection();
- ganv_canvas_clear_selection(_gcanvas);
- }
- break;
- default:
- handled = false;
- }
- if (handled) {
- ganv_canvas_scroll_to(GANV_CANVAS(_gcanvas), scroll_x, scroll_y);
- return true;
- }
- break;
-
- case GDK_SCROLL:
- if ((event->scroll.state & GDK_CONTROL_MASK)) {
- const double zoom = ganv_canvas_get_zoom(_gcanvas);
- if (event->scroll.direction == GDK_SCROLL_UP) {
- ganv_canvas_set_zoom(_gcanvas, zoom * 1.25);
- return true;
- } else if (event->scroll.direction == GDK_SCROLL_DOWN) {
- ganv_canvas_set_zoom(_gcanvas, zoom * 0.75);
- return true;
- }
- }
- break;
-
- default:
- break;
- }
-
- return scroll_drag_handler(event)
- || select_drag_handler(event)
- || connect_drag_handler(event);
-}
-
-bool
-GanvCanvasImpl::scroll_drag_handler(GdkEvent* event)
-{
- bool handled = true;
-
- static int original_scroll_x = 0;
- static int original_scroll_y = 0;
- static double origin_x = 0;
- static double origin_y = 0;
- static double scroll_offset_x = 0;
- static double scroll_offset_y = 0;
- static double last_x = 0;
- static double last_y = 0;
-
- GanvItem* root = ganv_canvas_root(_gcanvas);
-
- if (event->type == GDK_BUTTON_PRESS && event->button.button == 2) {
- ganv_canvas_grab_item(
- root,
- GDK_POINTER_MOTION_MASK|GDK_BUTTON_RELEASE_MASK,
- NULL, event->button.time);
- ganv_canvas_get_scroll_offsets(GANV_CANVAS(_gcanvas), &original_scroll_x, &original_scroll_y);
- scroll_offset_x = 0;
- scroll_offset_y = 0;
- origin_x = event->button.x_root;
- origin_y = event->button.y_root;
- //cerr << "Origin: (" << origin_x << "," << origin_y << ")\n";
- last_x = origin_x;
- last_y = origin_y;
- _drag_state = SCROLL;
-
- } else if (event->type == GDK_MOTION_NOTIFY && _drag_state == SCROLL) {
- const double x = event->motion.x_root;
- const double y = event->motion.y_root;
- const double x_offset = last_x - x;
- const double y_offset = last_y - y;
-
- //cerr << "Coord: (" << x << "," << y << ")\n";
- //cerr << "Offset: (" << x_offset << "," << y_offset << ")\n";
-
- scroll_offset_x += x_offset;
- scroll_offset_y += y_offset;
- ganv_canvas_scroll_to(GANV_CANVAS(_gcanvas),
- lrint(original_scroll_x + scroll_offset_x),
- lrint(original_scroll_y + scroll_offset_y));
- last_x = x;
- last_y = y;
- } else if (event->type == GDK_BUTTON_RELEASE && _drag_state == SCROLL) {
- ganv_canvas_ungrab_item(root, event->button.time);
- _drag_state = NOT_DRAGGING;
- } else {
- handled = false;
- }
-
- return handled;
-}
-
-static void
-get_motion_coords(GdkEventMotion* motion, double* x, double* y)
-{
- if (motion->is_hint) {
- gint px;
- gint py;
- GdkModifierType state;
- gdk_window_get_pointer(motion->window, &px, &py, &state);
- *x = px;
- *y = py;
- } else {
- *x = motion->x;
- *y = motion->y;
- }
-}
-
-bool
-GanvCanvasImpl::select_drag_handler(GdkEvent* event)
-{
- GanvItem* root = ganv_canvas_root(_gcanvas);
- if (event->type == GDK_BUTTON_PRESS && event->button.button == 1) {
- assert(_select_rect == NULL);
- _drag_state = SELECT;
- if ( !(event->button.state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) )
- ganv_canvas_clear_selection(_gcanvas);
- _select_rect = GANV_BOX(
- ganv_item_new(
- root,
- ganv_box_get_type(),
- "x1", event->button.x,
- "y1", event->button.y,
- "x2", event->button.x,
- "y2", event->button.y,
- "fill-color", SELECT_RECT_FILL_COLOUR,
- "border-color", SELECT_RECT_BORDER_COLOUR,
- NULL));
- _select_start_x = event->button.x;
- _select_start_y = event->button.y;
- ganv_canvas_grab_item(
- root, GDK_POINTER_MOTION_MASK|GDK_BUTTON_RELEASE_MASK,
- NULL, event->button.time);
- return true;
- } else if (event->type == GDK_MOTION_NOTIFY && _drag_state == SELECT) {
- assert(_select_rect);
- double x, y;
- get_motion_coords(&event->motion, &x, &y);
- _select_rect->impl->coords.x1 = MIN(_select_start_x, x);
- _select_rect->impl->coords.y1 = MIN(_select_start_y, y);
- _select_rect->impl->coords.x2 = MAX(_select_start_x, x);
- _select_rect->impl->coords.y2 = MAX(_select_start_y, y);
- ganv_item_request_update(&_select_rect->node.item);
- return true;
- } else if (event->type == GDK_BUTTON_RELEASE && _drag_state == SELECT) {
- // Normalize select rect
- ganv_box_normalize(_select_rect);
-
- // Select all modules within rect
- FOREACH_ITEM(_items, i) {
- GanvNode* node = *i;
- if ((void*)node != (void*)_select_rect &&
- ganv_node_is_within(
- node,
- ganv_box_get_x1(_select_rect),
- ganv_box_get_y1(_select_rect),
- ganv_box_get_x2(_select_rect),
- ganv_box_get_y2(_select_rect))) {
- gboolean selected;
- g_object_get(G_OBJECT(node), "selected", &selected, NULL);
- if (selected) {
- ganv_canvas_unselect_node(_gcanvas, node);
- } else {
- ganv_canvas_select_node(_gcanvas, node);
- }
- }
- }
-
- // Select all edges with handles within rect
- FOREACH_EDGE(_edges, i) {
- if (ganv_edge_is_within(
- (*i),
- ganv_box_get_x1(_select_rect),
- ganv_box_get_y1(_select_rect),
- ganv_box_get_x2(_select_rect),
- ganv_box_get_y2(_select_rect))) {
- ganv_canvas_select_edge(_gcanvas, *i);
- }
- }
-
- ganv_canvas_ungrab_item(root, event->button.time);
-
- gtk_object_destroy(GTK_OBJECT(_select_rect));
- _select_rect = NULL;
- _drag_state = NOT_DRAGGING;
- return true;
- }
- return false;
-}
-
-bool
-GanvCanvasImpl::connect_drag_handler(GdkEvent* event)
-{
- static bool snapped = false;
-
- if (_drag_state != EDGE) {
- return false;
- }
-
- if (event->type == GDK_MOTION_NOTIFY) {
- double x, y;
- get_motion_coords(&event->motion, &x, &y);
-
- if (!_drag_edge) {
- // Create drag edge
- assert(!_drag_node);
- assert(_connect_port);
-
- _drag_node = GANV_NODE(
- ganv_item_new(
- GANV_ITEM(ganv_canvas_root(GANV_CANVAS(_gcanvas))),
- ganv_node_get_type(),
- "x", x,
- "y", y,
- NULL));
-
- _drag_edge = ganv_edge_new(
- _gcanvas,
- GANV_NODE(_connect_port),
- _drag_node,
- "color", GANV_NODE(_connect_port)->impl->fill_color,
- "curved", TRUE,
- "ghost", TRUE,
- NULL);
- }
-
- GanvNode* joinee = get_node_at(x, y);
- if (joinee && ganv_node_can_head(joinee) && joinee != _drag_node) {
- // Snap to item
- snapped = true;
- ganv_item_set(&_drag_edge->item, "head", joinee, NULL);
- } else if (snapped) {
- // Unsnap from item
- snapped = false;
- ganv_item_set(&_drag_edge->item, "head", _drag_node, NULL);
- }
-
- // Update drag edge for pointer position
- ganv_node_move_to(_drag_node, x, y);
- ganv_item_request_update(GANV_ITEM(_drag_node));
- ganv_item_request_update(GANV_ITEM(_drag_edge));
-
- return true;
-
- } else if (event->type == GDK_BUTTON_RELEASE) {
- ganv_canvas_ungrab_item(root, event->button.time);
-
- double x = event->button.x;
- double y = event->button.y;
-
- GanvNode* joinee = get_node_at(x, y);
-
- if (GANV_IS_PORT(joinee)) {
- if (joinee == GANV_NODE(_connect_port)) {
- // Drag ended on the same port it started on, port clicked
- if (_selected_ports.empty()) {
- // No selected ports, port clicked
- select_port(_connect_port);
- } else {
- // Connect to selected ports
- selection_joined_with(_connect_port);
- _connect_port = NULL;
- }
- } else { // drag ended on different port
- ports_joined(_connect_port, GANV_PORT(joinee));
- _connect_port = NULL;
- }
- }
-
- end_connect_drag();
- return true;
- }
-
- return false;
-}
-
-void
-GanvCanvasImpl::end_connect_drag()
-{
- if (_connect_port) {
- highlight_port(_connect_port, false);
- }
- gtk_object_destroy(GTK_OBJECT(_drag_edge));
- gtk_object_destroy(GTK_OBJECT(_drag_node));
- _drag_state = NOT_DRAGGING;
- _connect_port = NULL;
- _drag_edge = NULL;
- _drag_node = NULL;
-}
-
-bool
-GanvCanvasImpl::port_event(GdkEvent* event, GanvPort* port)
-{
- static bool port_pressed = true;
- static bool port_dragging = false;
- static bool control_dragging = false;
- static double control_start_x = 0;
- static double control_start_y = 0;
- static float control_start_value = 0;
-
- switch (event->type) {
- case GDK_BUTTON_PRESS:
- if (event->button.button == 1) {
- GanvModule* const module = ganv_port_get_module(port);
- double port_x = event->button.x;
- double port_y = event->button.y;
- ganv_item_w2i(GANV_ITEM(port), &port_x, &port_y);
-
- if (_selected_ports.empty() && module && port->impl->control &&
- (port->impl->is_input ||
- (port->impl->is_controllable &&
- port_x < ganv_box_get_width(GANV_BOX(port)) / 2.0))) {
- if (port->impl->control->is_toggle) {
- if (port->impl->control->value >= 0.5) {
- ganv_port_set_control_value_internal(port, 0.0);
- } else {
- ganv_port_set_control_value_internal(port, 1.0);
- }
- } else {
- control_dragging = port_pressed = true;
- control_start_x = event->button.x_root;
- control_start_y = event->button.y_root;
- control_start_value = ganv_port_get_control_value(port);
- ganv_canvas_grab_item(
- GANV_ITEM(port),
- GDK_POINTER_MOTION_MASK|GDK_BUTTON_RELEASE_MASK,
- NULL, event->button.time);
- GANV_NODE(port)->impl->grabbed = TRUE;
-
- }
- } else if (!port->impl->is_input) {
- port_dragging = port_pressed = true;
- ganv_canvas_grab_item(
- GANV_ITEM(port),
- GDK_BUTTON_RELEASE_MASK|GDK_POINTER_MOTION_MASK|
- GDK_ENTER_NOTIFY_MASK|GDK_LEAVE_NOTIFY_MASK,
- NULL, event->button.time);
- } else {
- port_pressed = true;
- ganv_canvas_grab_item(GANV_ITEM(port),
- GDK_BUTTON_RELEASE_MASK,
- NULL, event->button.time);
- }
- return true;
- }
- break;
-
- case GDK_MOTION_NOTIFY:
- if (control_dragging) {
- const double mouse_x = event->button.x_root;
- const double mouse_y = event->button.y_root;
- GdkScreen* screen = gdk_screen_get_default();
- const int screen_width = gdk_screen_get_width(screen);
- const int screen_height = gdk_screen_get_height(screen);
- const double drag_dx = mouse_x - control_start_x;
- const double drag_dy = mouse_y - control_start_y;
- const double ythresh = 0.2; // Minimum y fraction for fine
-
- const double range_x = ((drag_dx > 0)
- ? (screen_width - control_start_x)
- : control_start_x) - GANV_CANVAS_PAD;
-
- const double range_y = ((drag_dy > 0)
- ? (screen_height - control_start_y)
- : control_start_y);
-
- const double dx = drag_dx / range_x;
- const double dy = fabs(drag_dy / range_y);
-
- const double value_range = (drag_dx > 0)
- ? port->impl->control->max - control_start_value
- : control_start_value - port->impl->control->min;
-
- const double sens = (dy < ythresh)
- ? 1.0
- : 1.0 - fabs(drag_dy / (range_y + ythresh));
-
- const double dvalue = (dx * value_range) * sens;
- double value = control_start_value + dvalue;
- if (value < port->impl->control->min) {
- value = port->impl->control->min;
- } else if (value > port->impl->control->max) {
- value = port->impl->control->max;
- }
- ganv_port_set_control_value_internal(port, value);
- return true;
- } else if (port_dragging) {
- return true;
- }
- break;
-
- case GDK_BUTTON_RELEASE:
- if (port_pressed) {
- ganv_canvas_ungrab_item(GANV_ITEM(port), event->button.time);
- }
-
- if (port_dragging) {
- if (_connect_port) { // dragging
- ports_joined(port, _connect_port);
- } else {
- port_clicked(event, port);
- }
- port_dragging = false;
- } else if (control_dragging) {
- control_dragging = false;
- GANV_NODE(port)->impl->grabbed = FALSE;
- if (event->button.x_root == control_start_x &&
- event->button.y_root == control_start_y) {
- select_port_toggle(port, event->button.state);
- }
- } else {
- port_clicked(event, port);
- }
- return true;
-
- case GDK_ENTER_NOTIFY:
- gboolean selected;
- g_object_get(G_OBJECT(port), "selected", &selected, NULL);
- if (!control_dragging && !selected) {
- highlight_port(port, true);
- return true;
- }
- break;
-
- case GDK_LEAVE_NOTIFY:
- if (port_dragging) {
- _drag_state = GanvCanvasImpl::EDGE;
- _connect_port = port;
- port_dragging = false;
- ganv_canvas_ungrab_item(GANV_ITEM(port), event->crossing.time);
- ganv_canvas_grab_item(
- root,
- GDK_BUTTON_PRESS_MASK|GDK_POINTER_MOTION_MASK|GDK_BUTTON_RELEASE_MASK,
- NULL, event->crossing.time);
- return true;
- } else if (!control_dragging) {
- highlight_port(port, false);
- return true;
- }
- break;
-
- default:
- break;
- }
-
- return false;
-}
-
-/* Called when two ports are 'joined' (connected or disconnected) */
-void
-GanvCanvasImpl::ports_joined(GanvPort* port1, GanvPort* port2)
-{
- if (port1 == port2 || !port1 || !port2 || !port1->impl || !port2->impl) {
- return;
- }
-
- highlight_port(port1, false);
- highlight_port(port2, false);
-
- GanvNode* src_node;
- GanvNode* dst_node;
-
- if (port2->impl->is_input && !port1->impl->is_input) {
- src_node = GANV_NODE(port1);
- dst_node = GANV_NODE(port2);
- } else if (!port2->impl->is_input && port1->impl->is_input) {
- src_node = GANV_NODE(port2);
- dst_node = GANV_NODE(port1);
- } else {
- return;
- }
-
- if (!ganv_canvas_get_edge(_gcanvas, src_node, dst_node)) {
- g_signal_emit(_gcanvas, signal_connect, 0,
- src_node, dst_node, NULL);
- } else {
- g_signal_emit(_gcanvas, signal_disconnect, 0,
- src_node, dst_node, NULL);
- }
-}
-
-void
-GanvCanvasImpl::port_clicked(GdkEvent* event, GanvPort* port)
-{
- const bool modded = event->button.state & (GDK_SHIFT_MASK|GDK_CONTROL_MASK);
- if (!modded && _last_selected_port &&
- _last_selected_port->impl->is_input != port->impl->is_input) {
- selection_joined_with(port);
- } else {
- select_port_toggle(port, event->button.state);
- }
-}
-
-void
-GanvCanvasImpl::highlight_port(GanvPort* port, bool highlight)
-{
- g_object_set(G_OBJECT(port), "highlighted", highlight, NULL);
- ganv_canvas_for_each_edge_on(_gcanvas,
- GANV_NODE(port),
- (highlight
- ? (GanvEdgeFunc)ganv_edge_highlight
- : (GanvEdgeFunc)ganv_edge_unhighlight),
- NULL);
-}
-
-/* Update animated "rubber band" selection effect. */
-gboolean
-GanvCanvasImpl::on_animate_timeout(gpointer data)
-{
- GanvCanvasImpl* impl = (GanvCanvasImpl*)data;
- if (!impl->pixmap_gc) {
- return FALSE; // Unrealized
- }
-
- const double seconds = get_monotonic_time() / 1000000.0;
-
- FOREACH_ITEM(impl->_selected_items, s) {
- ganv_node_tick(*s, seconds);
- }
-
- for (SelectedPorts::iterator p = impl->_selected_ports.begin();
- p != impl->_selected_ports.end();
- ++p) {
- ganv_node_tick(GANV_NODE(*p), seconds);
- }
-
- FOREACH_EDGE(impl->_selected_edges, c) {
- ganv_edge_tick(*c, seconds);
- }
-
- return TRUE;
-}
-
-void
-GanvCanvasImpl::move_contents_to_internal(double x, double y, double min_x, double min_y)
-{
- FOREACH_ITEM(_items, i) {
- ganv_node_move(*i,
- x - min_x,
- y - min_y);
- }
-}
-
-void
-GanvCanvasImpl::unselect_ports()
-{
- for (GanvCanvasImpl::SelectedPorts::iterator i = _selected_ports.begin();
- i != _selected_ports.end(); ++i)
- g_object_set(G_OBJECT(*i), "selected", FALSE, NULL);
-
- _selected_ports.clear();
- _last_selected_port = NULL;
-}
-
-namespace Ganv {
-
-static gboolean
-on_event_after(GanvItem* canvasitem,
- GdkEvent* ev,
- void* canvas)
-{
- return ((Canvas*)canvas)->signal_event.emit(ev);
-}
-
-static void
-on_connect(GanvCanvas* canvas, GanvNode* tail, GanvNode* head, void* data)
-{
- Canvas* canvasmm = (Canvas*)data;
- canvasmm->signal_connect.emit(Glib::wrap(tail), Glib::wrap(head));
-}
-
-static void
-on_disconnect(GanvCanvas* canvas, GanvNode* tail, GanvNode* head, void* data)
-{
- Canvas* canvasmm = (Canvas*)data;
- canvasmm->signal_disconnect.emit(Glib::wrap(tail), Glib::wrap(head));
-}
-
-Canvas::Canvas(double width, double height)
- : _gobj(GANV_CANVAS(ganv_canvas_new(width, height)))
-{
- ganv_canvas_set_wrapper(_gobj, this);
-
- g_signal_connect_after(ganv_canvas_root(_gobj), "event",
- G_CALLBACK(on_event_after), this);
- g_signal_connect(gobj(), "connect",
- G_CALLBACK(on_connect), this);
- g_signal_connect(gobj(), "disconnect",
- G_CALLBACK(on_disconnect), this);
-}
-
-Canvas::~Canvas()
-{
- delete _gobj->impl;
-}
-
-void
-Canvas::remove_edge_between(Node* item1, Node* item2)
-{
- GanvEdge* edge = ganv_canvas_get_edge(_gobj, item1->gobj(), item2->gobj());
- if (edge) {
- ganv_canvas_remove_edge(_gobj, edge);
- }
-}
-
-void
-Canvas::remove_edge(Edge* edge)
-{
- ganv_canvas_remove_edge(_gobj, edge->gobj());
-}
-
-Item*
-Canvas::get_item_at(double x, double y) const
-{
- GanvItem* item = ganv_canvas_get_item_at(_gobj, x, y);
- if (item) {
- return Glib::wrap(item);
- }
- return NULL;
-}
-
-Edge*
-Canvas::get_edge(Node* tail, Node* head) const
-{
- GanvEdge* e = ganv_canvas_get_edge(_gobj, tail->gobj(), head->gobj());
- if (e) {
- return Glib::wrap(e);
- }
- return NULL;
-}
-
-} // namespace Ganv
-
-extern "C" {
-
-#include "ganv/canvas.h"
-
-#include "./boilerplate.h"
-#include "./color.h"
-#include "./gettext.h"
-
-G_DEFINE_TYPE_WITH_CODE(GanvCanvas, ganv_canvas, GTK_TYPE_LAYOUT,
- G_ADD_PRIVATE(GanvCanvas))
-
-enum {
- PROP_0,
- PROP_WIDTH,
- PROP_HEIGHT,
- PROP_DIRECTION,
- PROP_FONT_SIZE,
- PROP_LOCKED,
- PROP_FOCUSED_ITEM
-};
-
-static gboolean
-on_canvas_event(GanvItem* canvasitem,
- GdkEvent* ev,
- void* impl)
-{
- return ((GanvCanvasImpl*)impl)->on_event(ev);
-}
-
-static void
-ganv_canvas_init(GanvCanvas* canvas)
-{
- GTK_WIDGET_SET_FLAGS(canvas, GTK_CAN_FOCUS);
-
- canvas->impl = new GanvCanvasImpl(canvas);
-
- g_signal_connect(G_OBJECT(ganv_canvas_root(canvas)),
- "event", G_CALLBACK(on_canvas_event), canvas->impl);
-}
-
-static void
-ganv_canvas_set_property(GObject* object,
- guint prop_id,
- const GValue* value,
- GParamSpec* pspec)
-{
- g_return_if_fail(object != NULL);
- g_return_if_fail(GANV_IS_CANVAS(object));
-
- GanvCanvas* canvas = GANV_CANVAS(object);
-
- switch (prop_id) {
- case PROP_WIDTH:
- ganv_canvas_resize(canvas, g_value_get_double(value), canvas->impl->height);
- break;
- case PROP_HEIGHT:
- ganv_canvas_resize(canvas, canvas->impl->width, g_value_get_double(value));
- break;
- case PROP_DIRECTION:
- ganv_canvas_set_direction(canvas, (GanvDirection)g_value_get_enum(value));
- break;
- case PROP_FONT_SIZE:
- ganv_canvas_set_font_size(canvas, g_value_get_double(value));
- break;
- case PROP_LOCKED:
- canvas->impl->locked = g_value_get_boolean(value);
- break;
- case PROP_FOCUSED_ITEM:
- canvas->impl->focused_item = GANV_ITEM(g_value_get_object(value));
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
- break;
- }
-}
-
-static void
-ganv_canvas_get_property(GObject* object,
- guint prop_id,
- GValue* value,
- GParamSpec* pspec)
-{
- g_return_if_fail(object != NULL);
- g_return_if_fail(GANV_IS_CANVAS(object));
-
- GanvCanvas* canvas = GANV_CANVAS(object);
-
- switch (prop_id) {
- GET_CASE(WIDTH, double, canvas->impl->width)
- GET_CASE(HEIGHT, double, canvas->impl->height)
- GET_CASE(LOCKED, boolean, canvas->impl->locked);
- case PROP_FOCUSED_ITEM:
- g_value_set_object(value, GANV_CANVAS(object)->impl->focused_item);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
- break;
- }
-}
-
-static void
-ganv_canvas_class_init(GanvCanvasClass* klass)
-{
- GObjectClass* gobject_class = (GObjectClass*)klass;
- GtkObjectClass* object_class = (GtkObjectClass*)klass;
- GtkWidgetClass* widget_class = (GtkWidgetClass*)klass;
-
- canvas_parent_class = GTK_LAYOUT_CLASS(g_type_class_peek_parent(klass));
-
- gobject_class->set_property = ganv_canvas_set_property;
- gobject_class->get_property = ganv_canvas_get_property;
-
- object_class->destroy = ganv_canvas_destroy;
-
- widget_class->map = ganv_canvas_map;
- widget_class->unmap = ganv_canvas_unmap;
- widget_class->realize = ganv_canvas_realize;
- widget_class->unrealize = ganv_canvas_unrealize;
- widget_class->size_allocate = ganv_canvas_size_allocate;
- widget_class->button_press_event = ganv_canvas_button;
- widget_class->button_release_event = ganv_canvas_button;
- widget_class->motion_notify_event = ganv_canvas_motion;
- widget_class->expose_event = ganv_canvas_expose;
- widget_class->key_press_event = ganv_canvas_key;
- widget_class->key_release_event = ganv_canvas_key;
- widget_class->enter_notify_event = ganv_canvas_crossing;
- widget_class->leave_notify_event = ganv_canvas_crossing;
- widget_class->focus_in_event = ganv_canvas_focus_in;
- widget_class->focus_out_event = ganv_canvas_focus_out;
- widget_class->scroll_event = ganv_canvas_scroll;
-
- g_object_class_install_property(
- gobject_class, PROP_FOCUSED_ITEM, g_param_spec_object(
- "focused-item",
- _("Focused item"),
- _("The item that currently has keyboard focus."),
- GANV_TYPE_ITEM,
- (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE)));
-
- g_object_class_install_property(
- gobject_class, PROP_WIDTH, g_param_spec_double(
- "width",
- _("Width"),
- _("The width of the canvas."),
- 0.0, G_MAXDOUBLE,
- 800.0,
- (GParamFlags)G_PARAM_READWRITE));
-
- g_object_class_install_property(
- gobject_class, PROP_HEIGHT, g_param_spec_double(
- "height",
- _("Height"),
- _("The height of the canvas"),
- 0.0, G_MAXDOUBLE,
- 600.0,
- (GParamFlags)G_PARAM_READWRITE));
-
- GEnumValue down_dir = { GANV_DIRECTION_DOWN, "down", "down" };
- GEnumValue right_dir = { GANV_DIRECTION_RIGHT, "right", "right" };
- GEnumValue null_dir = { 0, 0, 0 };
- dir_values[0] = down_dir;
- dir_values[1] = right_dir;
- dir_values[2] = null_dir;
- GType dir_type = g_enum_register_static("GanvDirection",
- dir_values);
-
- g_object_class_install_property(
- gobject_class, PROP_DIRECTION, g_param_spec_enum(
- "direction",
- _("Direction"),
- _("The direction of the signal flow on the canvas."),
- dir_type,
- GANV_DIRECTION_RIGHT,
- (GParamFlags)G_PARAM_READWRITE));
-
- g_object_class_install_property(
- gobject_class, PROP_FONT_SIZE, g_param_spec_double(
- "font-size",
- _("Font size"),
- _("The default font size for the canvas"),
- 0.0, G_MAXDOUBLE,
- 12.0,
- (GParamFlags)G_PARAM_READWRITE));
-
- g_object_class_install_property(
- gobject_class, PROP_LOCKED, g_param_spec_boolean(
- "locked",
- _("Locked"),
- _("If true, nodes on the canvas can not be moved by the user."),
- FALSE,
- (GParamFlags)G_PARAM_READWRITE));
-
- signal_connect = g_signal_new("connect",
- ganv_canvas_get_type(),
- G_SIGNAL_RUN_FIRST,
- 0, NULL, NULL,
- ganv_marshal_VOID__OBJECT_OBJECT,
- G_TYPE_NONE,
- 2,
- ganv_node_get_type(),
- ganv_node_get_type(),
- 0);
-
- signal_disconnect = g_signal_new("disconnect",
- ganv_canvas_get_type(),
- G_SIGNAL_RUN_FIRST,
- 0, NULL, NULL,
- ganv_marshal_VOID__OBJECT_OBJECT,
- G_TYPE_NONE,
- 2,
- ganv_node_get_type(),
- ganv_node_get_type(),
- 0);
-}
-
-void
-ganv_canvas_resize(GanvCanvas* canvas, double width, double height)
-{
- if (width != canvas->impl->width || height != canvas->impl->height) {
- canvas->impl->width = width;
- canvas->impl->height = height;
- ganv_canvas_set_scroll_region(canvas, 0.0, 0.0, width, height);
- }
-}
-
-void
-ganv_canvas_contents_changed(GanvCanvas* canvas)
-{
-#ifdef GANV_FDGL
- if (!canvas->impl->layout_idle_id && canvas->impl->sprung_layout) {
- canvas->impl->layout_energy = 0.4;
- canvas->impl->layout_idle_id = g_timeout_add_full(
- G_PRIORITY_DEFAULT_IDLE,
- 33,
- GanvCanvasImpl::on_layout_timeout,
- canvas->impl,
- GanvCanvasImpl::on_layout_done);
- }
-#endif
-}
-
-double
-ganv_canvas_get_default_font_size(const GanvCanvas* canvas)
-{
- GtkStyle* style = gtk_rc_get_style(GTK_WIDGET(canvas));
- const PangoFontDescription* font = style->font_desc;
- return pango_font_description_get_size(font) / (double)PANGO_SCALE;
-}
-
-double
-ganv_canvas_get_font_size(const GanvCanvas* canvas)
-{
- return canvas->impl->font_size;
-}
-
-void
-ganv_canvas_set_zoom(GanvCanvas* canvas, double zoom)
-{
- g_return_if_fail(GANV_IS_CANVAS(canvas));
-
- zoom = std::max(zoom, 0.01);
- if (zoom == canvas->impl->pixels_per_unit) {
- return;
- }
-
- const int anchor_x = (canvas->impl->center_scroll_region)
- ? GTK_WIDGET(canvas)->allocation.width / 2
- : 0;
- const int anchor_y = (canvas->impl->center_scroll_region)
- ? GTK_WIDGET(canvas)->allocation.height / 2
- : 0;
-
- /* Find the coordinates of the anchor point in units. */
- const double ax = (canvas->layout.hadjustment)
- ? ((canvas->layout.hadjustment->value + anchor_x)
- / canvas->impl->pixels_per_unit
- + canvas->impl->scroll_x1 + canvas->impl->zoom_xofs)
- : ((0.0 + anchor_x) / canvas->impl->pixels_per_unit
- + canvas->impl->scroll_x1 + canvas->impl->zoom_xofs);
- const double ay = (canvas->layout.hadjustment)
- ? ((canvas->layout.vadjustment->value + anchor_y)
- / canvas->impl->pixels_per_unit
- + canvas->impl->scroll_y1 + canvas->impl->zoom_yofs)
- : ((0.0 + anchor_y) / canvas->impl->pixels_per_unit
- + canvas->impl->scroll_y1 + canvas->impl->zoom_yofs);
-
- /* Now calculate the new offset of the upper left corner. */
- const int x1 = ((ax - canvas->impl->scroll_x1) * zoom) - anchor_x;
- const int y1 = ((ay - canvas->impl->scroll_y1) * zoom) - anchor_y;
-
- canvas->impl->pixels_per_unit = zoom;
- ganv_canvas_scroll_to(canvas, x1, y1);
-
- ganv_canvas_request_update(canvas);
- gtk_widget_queue_draw(GTK_WIDGET(canvas));
-
- canvas->impl->need_repick = TRUE;
-}
-
-void
-ganv_canvas_set_font_size(GanvCanvas* canvas, double points)
-{
- points = std::max(points, 1.0);
- if (points != canvas->impl->font_size) {
- canvas->impl->font_size = points;
- FOREACH_ITEM(canvas->impl->_items, i) {
- ganv_node_redraw_text(*i);
- }
- }
-}
-
-void
-ganv_canvas_zoom_full(GanvCanvas* canvas)
-{
- if (canvas->impl->_items.empty())
- return;
-
- int win_width, win_height;
- GdkWindow* win = gtk_widget_get_window(
- GTK_WIDGET(canvas->impl->_gcanvas));
- gdk_window_get_size(win, &win_width, &win_height);
-
- // Box containing all canvas items
- double left = DBL_MAX;
- double right = DBL_MIN;
- double top = DBL_MIN;
- double bottom = DBL_MAX;
-
- FOREACH_ITEM(canvas->impl->_items, i) {
- GanvItem* const item = GANV_ITEM(*i);
- const double x = item->impl->x;
- const double y = item->impl->y;
- if (GANV_IS_CIRCLE(*i)) {
- const double r = GANV_CIRCLE(*i)->impl->coords.radius;
- left = MIN(left, x - r);
- right = MAX(right, x + r);
- bottom = MIN(bottom, y - r);
- top = MAX(top, y + r);
- } else {
- left = MIN(left, x);
- right = MAX(right, x + ganv_box_get_width(GANV_BOX(*i)));
- bottom = MIN(bottom, y);
- top = MAX(top, y + ganv_box_get_height(GANV_BOX(*i)));
- }
- }
-
- static const double pad = GANV_CANVAS_PAD;
-
- const double new_zoom = std::min(
- ((double)win_width / (double)(right - left + pad*2.0)),
- ((double)win_height / (double)(top - bottom + pad*2.0)));
-
- ganv_canvas_set_zoom(canvas, new_zoom);
-
- int scroll_x, scroll_y;
- ganv_canvas_w2c(canvas->impl->_gcanvas,
- lrintf(left - pad), lrintf(bottom - pad),
- &scroll_x, &scroll_y);
-
- ganv_canvas_scroll_to(canvas->impl->_gcanvas,
- scroll_x, scroll_y);
-}
-
-static void
-set_node_direction(GanvNode* node, void* data)
-{
- if (GANV_IS_MODULE(node)) {
- ganv_module_set_direction(GANV_MODULE(node), *(GanvDirection*)data);
- }
-}
-
-GanvDirection
-ganv_canvas_get_direction(GanvCanvas* canvas)
-{
- return canvas->impl->direction;
-}
-
-void
-ganv_canvas_set_direction(GanvCanvas* canvas, GanvDirection dir)
-{
- if (canvas->impl->direction != dir) {
- canvas->impl->direction = dir;
- ganv_canvas_for_each_node(canvas, set_node_direction, &dir);
- ganv_canvas_contents_changed(canvas);
- }
-}
-
-void
-ganv_canvas_clear_selection(GanvCanvas* canvas)
-{
- canvas->impl->unselect_ports();
-
- Items items(canvas->impl->_selected_items);
- canvas->impl->_selected_items.clear();
- FOREACH_ITEM(items, i) {
- ganv_item_set(GANV_ITEM(*i), "selected", FALSE, NULL);
- }
-
- GanvCanvasImpl::SelectedEdges edges(canvas->impl->_selected_edges);
- canvas->impl->_selected_edges.clear();
- FOREACH_SELECTED_EDGE(edges, c) {
- ganv_item_set(GANV_ITEM(*c), "selected", FALSE, NULL);
- }
-}
-
-void
-ganv_canvas_move_selected_items(GanvCanvas* canvas,
- double dx,
- double dy)
-{
- FOREACH_ITEM(canvas->impl->_selected_items, i) {
- if ((*i)->item.impl->parent == canvas->impl->root) {
- ganv_node_move(*i, dx, dy);
- }
- }
-}
-
-void
-ganv_canvas_selection_move_finished(GanvCanvas* canvas)
-{
- FOREACH_ITEM(canvas->impl->_selected_items, i) {
- const double x = GANV_ITEM(*i)->impl->x;
- const double y = GANV_ITEM(*i)->impl->y;
- g_signal_emit(*i, signal_moved, 0, x, y, NULL);
- }
-}
-
-static void
-select_if_ends_are_selected(GanvEdge* edge, void* data)
-{
- if (ganv_node_is_selected(ganv_edge_get_tail(edge)) &&
- ganv_node_is_selected(ganv_edge_get_head(edge))) {
- ganv_edge_set_selected(edge, TRUE);
- }
-}
-
-static void
-unselect_edges(GanvPort* port, void* data)
-{
- GanvCanvasImpl* impl = (GanvCanvasImpl*)data;
- if (port->impl->is_input) {
- ganv_canvas_for_each_edge_to(impl->_gcanvas,
- GANV_NODE(port),
- (GanvEdgeFunc)ganv_edge_unselect,
- NULL);
- } else {
- ganv_canvas_for_each_edge_from(impl->_gcanvas,
- GANV_NODE(port),
- (GanvEdgeFunc)ganv_edge_unselect,
- NULL);
- }
-}
-
-void
-ganv_canvas_select_node(GanvCanvas* canvas,
- GanvNode* node)
-{
- canvas->impl->_selected_items.insert(node);
-
- // Select any connections to or from this node
- if (GANV_IS_MODULE(node)) {
- ganv_module_for_each_port(GANV_MODULE(node), select_edges, canvas->impl);
- } else {
- ganv_canvas_for_each_edge_on(
- canvas, node, select_if_ends_are_selected, canvas->impl);
- }
-
- g_object_set(node, "selected", TRUE, NULL);
-}
-
-void
-ganv_canvas_unselect_node(GanvCanvas* canvas,
- GanvNode* node)
-{
- // Unselect any connections to or from canvas->impl node
- if (GANV_IS_MODULE(node)) {
- ganv_module_for_each_port(GANV_MODULE(node), unselect_edges, canvas->impl);
- } else {
- ganv_canvas_for_each_edge_on(
- canvas, node, (GanvEdgeFunc)ganv_edge_unselect, NULL);
- }
-
- // Unselect item
- canvas->impl->_selected_items.erase(node);
- g_object_set(node, "selected", FALSE, NULL);
-}
-
-void
-ganv_canvas_add_node(GanvCanvas* canvas,
- GanvNode* node)
-{
- GanvItem* item = GANV_ITEM(node);
- if (item->impl->parent == ganv_canvas_root(canvas)) {
- canvas->impl->_items.insert(node);
- }
-}
-
-void
-ganv_canvas_remove_node(GanvCanvas* canvas,
- GanvNode* node)
-{
- if (node == (GanvNode*)canvas->impl->_connect_port) {
- if (canvas->impl->_drag_state == GanvCanvasImpl::EDGE) {
- ganv_canvas_ungrab_item(ganv_canvas_root(canvas), 0);
- canvas->impl->end_connect_drag();
- }
- canvas->impl->_connect_port = NULL;
- }
-
- // Remove from selection
- canvas->impl->_selected_items.erase(node);
-
- // Remove children ports from selection if item is a module
- if (GANV_IS_MODULE(node)) {
- GanvModule* const module = GANV_MODULE(node);
- for (unsigned i = 0; i < ganv_module_num_ports(module); ++i) {
- canvas->impl->unselect_port(ganv_module_get_port(module, i));
- }
- }
-
- // Remove from items
- canvas->impl->_items.erase(node);
-}
-
-GanvEdge*
-ganv_canvas_get_edge(GanvCanvas* canvas,
- GanvNode* tail,
- GanvNode* head)
-{
- GanvEdgeKey key;
- make_edge_search_key(&key, tail, head);
- GanvCanvasImpl::Edges::const_iterator i = canvas->impl->_edges.find((GanvEdge*)&key);
- return (i != canvas->impl->_edges.end()) ? *i : NULL;
-}
-
-void
-ganv_canvas_remove_edge_between(GanvCanvas* canvas,
- GanvNode* tail,
- GanvNode* head)
-{
- ganv_canvas_remove_edge(canvas, ganv_canvas_get_edge(canvas, tail, head));
-}
-
-void
-ganv_canvas_disconnect_edge(GanvCanvas* canvas,
- GanvEdge* edge)
-{
- g_signal_emit(canvas, signal_disconnect, 0,
- edge->impl->tail, edge->impl->head, NULL);
-}
-
-void
-ganv_canvas_add_edge(GanvCanvas* canvas,
- GanvEdge* edge)
-{
- canvas->impl->_edges.insert(edge);
- canvas->impl->_dst_edges.insert(edge);
- ganv_canvas_contents_changed(canvas);
-}
-
-void
-ganv_canvas_remove_edge(GanvCanvas* canvas,
- GanvEdge* edge)
-{
- if (edge) {
- canvas->impl->_selected_edges.erase(edge);
- canvas->impl->_edges.erase(edge);
- canvas->impl->_dst_edges.erase(edge);
- ganv_edge_request_redraw(GANV_ITEM(edge), &edge->impl->coords);
- gtk_object_destroy(GTK_OBJECT(edge));
- ganv_canvas_contents_changed(canvas);
- }
-}
-
-void
-ganv_canvas_select_edge(GanvCanvas* canvas,
- GanvEdge* edge)
-{
- ganv_item_set(GANV_ITEM(edge), "selected", TRUE, NULL);
- canvas->impl->_selected_edges.insert(edge);
-}
-
-void
-ganv_canvas_unselect_edge(GanvCanvas* canvas,
- GanvEdge* edge)
-{
- ganv_item_set(GANV_ITEM(edge), "selected", FALSE, NULL);
- canvas->impl->_selected_edges.erase(edge);
-}
-
-void
-ganv_canvas_for_each_node(GanvCanvas* canvas,
- GanvNodeFunc f,
- void* data)
-{
- FOREACH_ITEM(canvas->impl->_items, i) {
- f(*i, data);
- }
-}
-
-void
-ganv_canvas_for_each_selected_node(GanvCanvas* canvas,
- GanvNodeFunc f,
- void* data)
-{
- FOREACH_ITEM(canvas->impl->_selected_items, i) {
- f(*i, data);
- }
-}
-
-gboolean
-ganv_canvas_empty(const GanvCanvas* canvas)
-{
- return canvas->impl->_items.empty();
-}
-
-void
-ganv_canvas_for_each_edge(GanvCanvas* canvas,
- GanvEdgeFunc f,
- void* data)
-{
- GanvCanvasImpl* impl = canvas->impl;
- for (GanvCanvasImpl::Edges::const_iterator i = impl->_edges.begin();
- i != impl->_edges.end();) {
- GanvCanvasImpl::Edges::const_iterator next = i;
- ++next;
- f((*i), data);
- i = next;
- }
-}
-
-void
-ganv_canvas_for_each_edge_from(GanvCanvas* canvas,
- const GanvNode* tail,
- GanvEdgeFunc f,
- void* data)
-{
- GanvCanvasImpl* impl = canvas->impl;
- for (GanvCanvasImpl::Edges::const_iterator i = impl->first_edge_from(tail);
- i != impl->_edges.end() && (*i)->impl->tail == tail;) {
- GanvCanvasImpl::Edges::const_iterator next = i;
- ++next;
- f((*i), data);
- i = next;
- }
-}
-
-void
-ganv_canvas_for_each_edge_to(GanvCanvas* canvas,
- const GanvNode* head,
- GanvEdgeFunc f,
- void* data)
-{
- GanvCanvasImpl* impl = canvas->impl;
- for (GanvCanvasImpl::Edges::const_iterator i = impl->first_edge_to(head);
- i != impl->_dst_edges.end() && (*i)->impl->head == head;) {
- GanvCanvasImpl::Edges::const_iterator next = i;
- ++next;
- f((*i), data);
- i = next;
- }
-}
-
-void
-ganv_canvas_for_each_edge_on(GanvCanvas* canvas,
- const GanvNode* node,
- GanvEdgeFunc f,
- void* data)
-{
- ganv_canvas_for_each_edge_from(canvas, node, f, data);
- ganv_canvas_for_each_edge_to(canvas, node, f, data);
-}
-
-void
-ganv_canvas_for_each_selected_edge(GanvCanvas* canvas,
- GanvEdgeFunc f,
- void* data)
-{
- FOREACH_EDGE(canvas->impl->_selected_edges, i) {
- f((*i), data);
- }
-}
-
-GdkCursor*
-ganv_canvas_get_move_cursor(const GanvCanvas* canvas)
-{
- return canvas->impl->_move_cursor;
-}
-
-gboolean
-ganv_canvas_port_event(GanvCanvas* canvas,
- GanvPort* port,
- GdkEvent* event)
-{
- return canvas->impl->port_event(event, port);
-}
-
-void
-ganv_canvas_clear(GanvCanvas* canvas)
-{
- canvas->impl->_selected_items.clear();
- canvas->impl->_selected_edges.clear();
-
- Items items = canvas->impl->_items; // copy
- FOREACH_ITEM(items, i) {
- gtk_object_destroy(GTK_OBJECT(*i));
- }
- canvas->impl->_items.clear();
-
- GanvCanvasImpl::Edges edges = canvas->impl->_edges; // copy
- FOREACH_EDGE(edges, i) {
- gtk_object_destroy(GTK_OBJECT(*i));
- }
- canvas->impl->_edges.clear();
- canvas->impl->_dst_edges.clear();
-
- canvas->impl->_selected_ports.clear();
- canvas->impl->_connect_port = NULL;
-}
-
-void
-ganv_canvas_select_all(GanvCanvas* canvas)
-{
- ganv_canvas_clear_selection(canvas);
- FOREACH_ITEM(canvas->impl->_items, i) {
- ganv_canvas_select_node(canvas, *i);
- }
-}
-
-double
-ganv_canvas_get_zoom(const GanvCanvas* canvas)
-{
- return canvas->impl->pixels_per_unit;
-}
-
-void
-ganv_canvas_move_contents_to(GanvCanvas* canvas, double x, double y)
-{
- double min_x=HUGE_VAL, min_y=HUGE_VAL;
- FOREACH_ITEM(canvas->impl->_items, i) {
- const double x = GANV_ITEM(*i)->impl->x;
- const double y = GANV_ITEM(*i)->impl->y;
- min_x = std::min(min_x, x);
- min_y = std::min(min_y, y);
- }
- canvas->impl->move_contents_to_internal(x, y, min_x, min_y);
-}
-
-void
-ganv_canvas_arrange(GanvCanvas* canvas)
-{
-#ifdef HAVE_AGRAPH
- GVNodes nodes = canvas->impl->layout_dot((char*)"");
-
- double least_x=HUGE_VAL, least_y=HUGE_VAL, most_x=0, most_y=0;
-
- // Set numeric locale to POSIX for reading graphviz output with strtod
- char* locale = strdup(setlocale(LC_NUMERIC, NULL));
- setlocale(LC_NUMERIC, "POSIX");
-
- const double dpi = gdk_screen_get_resolution(gdk_screen_get_default());
- const double dpp = dpi / 72.0;
-
- // Arrange to graphviz coordinates
- for (GVNodes::iterator i = nodes.begin(); i != nodes.end(); ++i) {
- if (GANV_ITEM(i->first)->impl->parent != GANV_ITEM(ganv_canvas_root(canvas))) {
- continue;
- }
- const std::string pos = agget(i->second, (char*)"pos");
- const std::string x_str = pos.substr(0, pos.find(","));
- const std::string y_str = pos.substr(pos.find(",") + 1);
- const double cx = lrint(strtod(x_str.c_str(), NULL) * dpp);
- const double cy = lrint(strtod(y_str.c_str(), NULL) * dpp);
-
- double w, h;
- if (GANV_IS_BOX(i->first)) {
- w = ganv_box_get_width(GANV_BOX(i->first));
- h = ganv_box_get_height(GANV_BOX(i->first));
- } else {
- w = h = ganv_circle_get_radius(GANV_CIRCLE(i->first)) * 2.3;
- }
-
- /* Dot node positions are supposedly node centers, but things only
- match up if x is interpreted as center and y as top...
- */
- double x = cx - (w / 2.0);
- double y = -cy - (h / 2.0);
-
- ganv_node_move_to(i->first, x, y);
-
- if (GANV_IS_CIRCLE(i->first)) {
- // Offset least x and y to avoid cutting off circles at origin
- const double r = ganv_circle_get_radius(GANV_CIRCLE(i->first));
- x -= r;
- y -= r;
- }
-
- least_x = std::min(least_x, x);
- least_y = std::min(least_y, y);
- most_x = std::max(most_x, x + w);
- most_y = std::max(most_y, y + h);
- }
-
- // Reset numeric locale to original value
- setlocale(LC_NUMERIC, locale);
- free(locale);
-
- const double graph_width = most_x - least_x;
- const double graph_height = most_y - least_y;
-
- //cerr << "CWH: " << _width << ", " << _height << endl;
- //cerr << "GWH: " << graph_width << ", " << graph_height << endl;
-
- double old_width, old_height;
- g_object_get(G_OBJECT(canvas),
- "width", &old_width,
- "height", &old_height,
- NULL);
-
- const double new_width = std::max(graph_width + 10.0, old_width);
- const double new_height = std::max(graph_height + 10.0, old_height);
- if (new_width != old_width || new_height != old_height) {
- ganv_canvas_resize(canvas, new_width, new_height);
- }
- nodes.cleanup();
-
- static const double border_width = GANV_CANVAS_PAD;
- canvas->impl->move_contents_to_internal(border_width, border_width, least_x, least_y);
- ganv_canvas_scroll_to(canvas->impl->_gcanvas, 0, 0);
-
- FOREACH_ITEM(canvas->impl->_items, i) {
- const double x = GANV_ITEM(*i)->impl->x;
- const double y = GANV_ITEM(*i)->impl->y;
- g_signal_emit(*i, signal_moved, 0, x, y, NULL);
- }
-#endif
-}
-
-int
-ganv_canvas_export_image(GanvCanvas* canvas,
- const char* filename,
- gboolean draw_background)
-{
- const char* ext = strrchr(filename, '.');
- if (!ext) {
- return 1;
- } else if (!strcmp(ext, ".dot")) {
- ganv_canvas_export_dot(canvas, filename);
- return 0;
- }
-
- cairo_surface_t* rec_surface = cairo_recording_surface_create(
- CAIRO_CONTENT_COLOR_ALPHA, NULL);
-
- // Draw to recording surface
- cairo_t* cr = cairo_create(rec_surface);
- canvas->impl->exporting = TRUE;
- (*GANV_ITEM_GET_CLASS(canvas->impl->root)->draw)(
- canvas->impl->root, cr,
- 0, 0, canvas->impl->width, canvas->impl->height);
- canvas->impl->exporting = FALSE;
- cairo_destroy(cr);
-
- // Get draw extent
- double x, y, w, h;
- cairo_recording_surface_ink_extents(rec_surface, &x, &y, &w, &h);
-
- // Create image surface with the appropriate size
- const double pad = GANV_CANVAS_PAD;
- const double img_w = w + pad * 2;
- const double img_h = h + pad * 2;
- cairo_surface_t* img = NULL;
- if (!strcmp(ext, ".svg")) {
- img = cairo_svg_surface_create(filename, img_w, img_h);
- } else if (!strcmp(ext, ".pdf")) {
- img = cairo_pdf_surface_create(filename, img_w, img_h);
- } else if (!strcmp(ext, ".ps")) {
- img = cairo_ps_surface_create(filename, img_w, img_h);
- } else {
- cairo_surface_destroy(rec_surface);
- return 1;
- }
-
- // Draw recording to image surface
- cr = cairo_create(img);
- if (draw_background) {
- double r, g, b, a;
- color_to_rgba(DEFAULT_BACKGROUND_COLOR, &r, &g, &b, &a);
- cairo_set_source_rgba(cr, r, g, b, a);
- cairo_rectangle(cr, 0, 0, w + 2 * pad, h + 2 * pad);
- cairo_fill(cr);
- }
- cairo_set_source_surface(cr, rec_surface, -x + pad, -y + pad);
- cairo_paint(cr);
- cairo_destroy(cr);
- cairo_surface_destroy(rec_surface);
- cairo_surface_destroy(img);
- return 0;
-}
-
-void
-ganv_canvas_export_dot(GanvCanvas* canvas, const char* filename)
-{
-#ifdef HAVE_AGRAPH
- GVNodes nodes = canvas->impl->layout_dot(filename);
- nodes.cleanup();
-#endif
-}
-
-gboolean
-ganv_canvas_supports_sprung_layout(const GanvCanvas* canvas)
-{
-#ifdef GANV_FDGL
- return TRUE;
-#else
- return FALSE;
-#endif
-}
-
-gboolean
-ganv_canvas_set_sprung_layout(GanvCanvas* canvas, gboolean sprung_layout)
-{
-#ifndef GANV_FDGL
- return FALSE;
-#else
- canvas->impl->sprung_layout = sprung_layout;
- ganv_canvas_contents_changed(canvas);
- return TRUE;
-#endif
-}
-
-gboolean
-ganv_canvas_get_locked(const GanvCanvas* canvas)
-{
- return canvas->impl->locked;
-}
-
-/* Convenience function to remove the idle handler of a canvas */
-static void
-remove_idle(GanvCanvas* canvas)
-{
- if (canvas->impl->idle_id == 0) {
- return;
- }
-
- g_source_remove(canvas->impl->idle_id);
- canvas->impl->idle_id = 0;
-}
-
-/* Removes the transient state of the canvas (idle handler, grabs). */
-static void
-shutdown_transients(GanvCanvas* canvas)
-{
- /* We turn off the need_redraw flag, since if the canvas is mapped again
- * it will request a redraw anyways. We do not turn off the need_update
- * flag, though, because updates are not queued when the canvas remaps
- * itself.
- */
- if (canvas->impl->need_redraw) {
- canvas->impl->need_redraw = FALSE;
- g_slist_foreach(canvas->impl->redraw_region, (GFunc)g_free, NULL);
- g_slist_free(canvas->impl->redraw_region);
- canvas->impl->redraw_region = NULL;
- canvas->impl->redraw_x1 = 0;
- canvas->impl->redraw_y1 = 0;
- canvas->impl->redraw_x2 = 0;
- canvas->impl->redraw_y2 = 0;
- }
-
- if (canvas->impl->grabbed_item) {
- canvas->impl->grabbed_item = NULL;
- gdk_pointer_ungrab(GDK_CURRENT_TIME);
- }
-
- remove_idle(canvas);
-}
-
-/* Destroy handler for GanvCanvas */
-static void
-ganv_canvas_destroy(GtkObject* object)
-{
- g_return_if_fail(GANV_IS_CANVAS(object));
-
- /* remember, destroy can be run multiple times! */
-
- GanvCanvas* canvas = GANV_CANVAS(object);
-
- if (canvas->impl->root_destroy_id) {
- g_signal_handler_disconnect(canvas->impl->root, canvas->impl->root_destroy_id);
- canvas->impl->root_destroy_id = 0;
- }
- if (canvas->impl->root) {
- gtk_object_destroy(GTK_OBJECT(canvas->impl->root));
- g_object_unref(G_OBJECT(canvas->impl->root));
- canvas->impl->root = NULL;
- }
-
- shutdown_transients(canvas);
-
- if (GTK_OBJECT_CLASS(canvas_parent_class)->destroy) {
- (*GTK_OBJECT_CLASS(canvas_parent_class)->destroy)(object);
- }
-}
-
-GanvCanvas*
-ganv_canvas_new(double width, double height)
-{
- GanvCanvas* canvas = GANV_CANVAS(
- g_object_new(ganv_canvas_get_type(),
- "width", width,
- "height", height,
- NULL));
-
- ganv_canvas_set_scroll_region(canvas, 0.0, 0.0, width, height);
-
- return canvas;
-}
-
-void
-ganv_canvas_set_wrapper(GanvCanvas* canvas, void* wrapper)
-{
- canvas->impl->_wrapper = (Ganv::Canvas*)wrapper;
-}
-
-void*
-ganv_canvas_get_wrapper(GanvCanvas* canvas)
-{
- return canvas->impl->_wrapper;
-}
-
-/* Map handler for the canvas */
-static void
-ganv_canvas_map(GtkWidget* widget)
-{
- g_return_if_fail(GANV_IS_CANVAS(widget));
-
- /* Normal widget mapping stuff */
-
- if (GTK_WIDGET_CLASS(canvas_parent_class)->map) {
- (*GTK_WIDGET_CLASS(canvas_parent_class)->map)(widget);
- }
-
- GanvCanvas* canvas = GANV_CANVAS(widget);
-
- if (canvas->impl->need_update) {
- add_idle(canvas);
- }
-
- /* Map items */
-
- if (GANV_ITEM_GET_CLASS(canvas->impl->root)->map) {
- (*GANV_ITEM_GET_CLASS(canvas->impl->root)->map)(canvas->impl->root);
- }
-}
-
-/* Unmap handler for the canvas */
-static void
-ganv_canvas_unmap(GtkWidget* widget)
-{
- g_return_if_fail(GANV_IS_CANVAS(widget));
-
- GanvCanvas* canvas = GANV_CANVAS(widget);
-
- shutdown_transients(canvas);
-
- /* Unmap items */
-
- if (GANV_ITEM_GET_CLASS(canvas->impl->root)->unmap) {
- (*GANV_ITEM_GET_CLASS(canvas->impl->root)->unmap)(canvas->impl->root);
- }
-
- /* Normal widget unmapping stuff */
-
- if (GTK_WIDGET_CLASS(canvas_parent_class)->unmap) {
- (*GTK_WIDGET_CLASS(canvas_parent_class)->unmap)(widget);
- }
-}
-
-/* Realize handler for the canvas */
-static void
-ganv_canvas_realize(GtkWidget* widget)
-{
- g_return_if_fail(GANV_IS_CANVAS(widget));
-
- /* Normal widget realization stuff */
-
- if (GTK_WIDGET_CLASS(canvas_parent_class)->realize) {
- (*GTK_WIDGET_CLASS(canvas_parent_class)->realize)(widget);
- }
-
- GanvCanvas* canvas = GANV_CANVAS(widget);
-
- gdk_window_set_events(
- canvas->layout.bin_window,
- (GdkEventMask)(gdk_window_get_events(canvas->layout.bin_window)
- | GDK_EXPOSURE_MASK
- | GDK_BUTTON_PRESS_MASK
- | GDK_BUTTON_RELEASE_MASK
- | GDK_POINTER_MOTION_MASK
- | GDK_KEY_PRESS_MASK
- | GDK_KEY_RELEASE_MASK
- | GDK_ENTER_NOTIFY_MASK
- | GDK_LEAVE_NOTIFY_MASK
- | GDK_FOCUS_CHANGE_MASK));
-
- /* Create our own temporary pixmap gc and realize all the items */
-
- canvas->impl->pixmap_gc = gdk_gc_new(canvas->layout.bin_window);
-
- (*GANV_ITEM_GET_CLASS(canvas->impl->root)->realize)(canvas->impl->root);
-
- canvas->impl->_animate_idle_id = g_timeout_add(
- 120, GanvCanvasImpl::on_animate_timeout, canvas->impl);
-}
-
-/* Unrealize handler for the canvas */
-static void
-ganv_canvas_unrealize(GtkWidget* widget)
-{
- g_return_if_fail(GANV_IS_CANVAS(widget));
-
- GanvCanvas* canvas = GANV_CANVAS(widget);
-
- if (canvas->impl->_animate_idle_id) {
- g_source_remove(canvas->impl->_animate_idle_id);
- canvas->impl->_animate_idle_id = 0;
- }
- while (g_idle_remove_by_data(canvas->impl)) {}
-
- shutdown_transients(canvas);
-
- /* Unrealize items and parent widget */
-
- (*GANV_ITEM_GET_CLASS(canvas->impl->root)->unrealize)(canvas->impl->root);
-
- g_object_unref(canvas->impl->pixmap_gc);
- canvas->impl->pixmap_gc = NULL;
-
- if (GTK_WIDGET_CLASS(canvas_parent_class)->unrealize) {
- (*GTK_WIDGET_CLASS(canvas_parent_class)->unrealize)(widget);
- }
-}
-
-/* Handles scrolling of the canvas. Adjusts the scrolling and zooming offset to
- * keep as much as possible of the canvas scrolling region in view.
- */
-static void
-scroll_to(GanvCanvas* canvas, int cx, int cy)
-{
- int scroll_width, scroll_height;
- int right_limit, bottom_limit;
- int old_zoom_xofs, old_zoom_yofs;
- int changed_x = FALSE, changed_y = FALSE;
- int canvas_width, canvas_height;
-
- canvas_width = GTK_WIDGET(canvas)->allocation.width;
- canvas_height = GTK_WIDGET(canvas)->allocation.height;
-
- scroll_width = floor((canvas->impl->scroll_x2 - canvas->impl->scroll_x1) * canvas->impl->pixels_per_unit
- + 0.5);
- scroll_height = floor((canvas->impl->scroll_y2 - canvas->impl->scroll_y1) * canvas->impl->pixels_per_unit
- + 0.5);
-
- right_limit = scroll_width - canvas_width;
- bottom_limit = scroll_height - canvas_height;
-
- old_zoom_xofs = canvas->impl->zoom_xofs;
- old_zoom_yofs = canvas->impl->zoom_yofs;
-
- if (right_limit < 0) {
- cx = 0;
-
- if (canvas->impl->center_scroll_region) {
- canvas->impl->zoom_xofs = (canvas_width - scroll_width) / 2;
- scroll_width = canvas_width;
- } else {
- canvas->impl->zoom_xofs = 0;
- }
- } else if (cx < 0) {
- cx = 0;
- canvas->impl->zoom_xofs = 0;
- } else if (cx > right_limit) {
- cx = right_limit;
- canvas->impl->zoom_xofs = 0;
- } else {
- canvas->impl->zoom_xofs = 0;
- }
-
- if (bottom_limit < 0) {
- cy = 0;
-
- if (canvas->impl->center_scroll_region) {
- canvas->impl->zoom_yofs = (canvas_height - scroll_height) / 2;
- scroll_height = canvas_height;
- } else {
- canvas->impl->zoom_yofs = 0;
- }
- } else if (cy < 0) {
- cy = 0;
- canvas->impl->zoom_yofs = 0;
- } else if (cy > bottom_limit) {
- cy = bottom_limit;
- canvas->impl->zoom_yofs = 0;
- } else {
- canvas->impl->zoom_yofs = 0;
- }
-
- if ((canvas->impl->zoom_xofs != old_zoom_xofs) || (canvas->impl->zoom_yofs != old_zoom_yofs)) {
- ganv_canvas_request_update(canvas);
- gtk_widget_queue_draw(GTK_WIDGET(canvas));
- }
-
- if (canvas->layout.hadjustment && ( ((int)canvas->layout.hadjustment->value) != cx) ) {
- canvas->layout.hadjustment->value = cx;
- changed_x = TRUE;
- }
-
- if (canvas->layout.vadjustment && ( ((int)canvas->layout.vadjustment->value) != cy) ) {
- canvas->layout.vadjustment->value = cy;
- changed_y = TRUE;
- }
-
- if ((scroll_width != (int)canvas->layout.width)
- || (scroll_height != (int)canvas->layout.height)) {
- gtk_layout_set_size(GTK_LAYOUT(canvas), scroll_width, scroll_height);
- }
-
- /* Signal GtkLayout that it should do a redraw. */
-
- if (changed_x) {
- g_signal_emit_by_name(canvas->layout.hadjustment, "value_changed");
- }
-
- if (changed_y) {
- g_signal_emit_by_name(canvas->layout.vadjustment, "value_changed");
- }
-}
-
-/* Size allocation handler for the canvas */
-static void
-ganv_canvas_size_allocate(GtkWidget* widget, GtkAllocation* allocation)
-{
- g_return_if_fail(GANV_IS_CANVAS(widget));
- g_return_if_fail(allocation != NULL);
-
- if (GTK_WIDGET_CLASS(canvas_parent_class)->size_allocate) {
- (*GTK_WIDGET_CLASS(canvas_parent_class)->size_allocate)(widget, allocation);
- }
-
- GanvCanvas* canvas = GANV_CANVAS(widget);
-
- /* Recenter the view, if appropriate */
-
- canvas->layout.hadjustment->page_size = allocation->width;
- canvas->layout.hadjustment->page_increment = allocation->width / 2;
-
- canvas->layout.vadjustment->page_size = allocation->height;
- canvas->layout.vadjustment->page_increment = allocation->height / 2;
-
- scroll_to(canvas,
- canvas->layout.hadjustment->value,
- canvas->layout.vadjustment->value);
-
- g_signal_emit_by_name(canvas->layout.hadjustment, "changed");
- g_signal_emit_by_name(canvas->layout.vadjustment, "changed");
-}
-
-/* Returns whether the item is an inferior of or is equal to the parent. */
-static gboolean
-is_descendant(GanvItem* item, GanvItem* parent)
-{
- for (; item; item = item->impl->parent) {
- if (item == parent) {
- return TRUE;
- }
- }
-
- return FALSE;
-}
-
-
-/* Emits an event for an item in the canvas, be it the current item, grabbed
- * item, or focused item, as appropriate.
- */
-int
-ganv_canvas_emit_event(GanvCanvas* canvas, GdkEvent* event)
-{
- GdkEvent* ev;
- gint finished;
- GanvItem* item;
- GanvItem* parent;
- guint mask;
-
- /* Perform checks for grabbed items */
-
- if (canvas->impl->grabbed_item
- && !is_descendant(canvas->impl->current_item, canvas->impl->grabbed_item)) {
- /* I think this warning is annoying and I don't know what it's for
- * so I'll disable it for now.
- */
- /* g_warning ("emit_event() returning FALSE!\n");*/
- return FALSE;
- }
-
- if (canvas->impl->grabbed_item) {
- switch (event->type) {
- case GDK_ENTER_NOTIFY:
- mask = GDK_ENTER_NOTIFY_MASK;
- break;
-
- case GDK_LEAVE_NOTIFY:
- mask = GDK_LEAVE_NOTIFY_MASK;
- break;
-
- case GDK_MOTION_NOTIFY:
- mask = GDK_POINTER_MOTION_MASK;
- break;
-
- case GDK_BUTTON_PRESS:
- case GDK_2BUTTON_PRESS:
- case GDK_3BUTTON_PRESS:
- mask = GDK_BUTTON_PRESS_MASK;
- break;
-
- case GDK_BUTTON_RELEASE:
- mask = GDK_BUTTON_RELEASE_MASK;
- break;
-
- case GDK_KEY_PRESS:
- mask = GDK_KEY_PRESS_MASK;
- break;
-
- case GDK_KEY_RELEASE:
- mask = GDK_KEY_RELEASE_MASK;
- break;
-
- case GDK_SCROLL:
- mask = GDK_SCROLL_MASK;
- break;
-
- default:
- mask = 0;
- break;
- }
-
- if (!(mask & canvas->impl->grabbed_event_mask)) {
- return FALSE;
- }
- }
-
- /* Convert to world coordinates -- we have two cases because of diferent
- * offsets of the fields in the event structures.
- */
-
- ev = gdk_event_copy(event);
-
- switch (ev->type) {
- case GDK_ENTER_NOTIFY:
- case GDK_LEAVE_NOTIFY:
- ganv_canvas_window_to_world(canvas,
- ev->crossing.x, ev->crossing.y,
- &ev->crossing.x, &ev->crossing.y);
- break;
-
- case GDK_MOTION_NOTIFY:
- case GDK_BUTTON_PRESS:
- case GDK_2BUTTON_PRESS:
- case GDK_3BUTTON_PRESS:
- case GDK_BUTTON_RELEASE:
- ganv_canvas_window_to_world(canvas,
- ev->motion.x, ev->motion.y,
- &ev->motion.x, &ev->motion.y);
- break;
-
- default:
- break;
- }
-
- /* Choose where we send the event */
-
- item = canvas->impl->current_item;
-
- if (canvas->impl->focused_item
- && ((event->type == GDK_KEY_PRESS)
- || (event->type == GDK_KEY_RELEASE)
- || (event->type == GDK_FOCUS_CHANGE))) {
- item = canvas->impl->focused_item;
- }
-
- /* The event is propagated up the hierarchy (for if someone connected to
- * a group instead of a leaf event), and emission is stopped if a
- * handler returns TRUE, just like for GtkWidget events.
- */
-
- finished = FALSE;
-
- while (item && !finished) {
- g_object_ref(G_OBJECT(item));
-
- ganv_item_emit_event(item, ev, &finished);
-
- parent = item->impl->parent;
- g_object_unref(G_OBJECT(item));
-
- item = parent;
- }
-
- gdk_event_free(ev);
-
- return finished;
-}
-
-void
-ganv_canvas_set_need_repick(GanvCanvas* canvas)
-{
- canvas->impl->need_repick = TRUE;
-}
-
-void
-ganv_canvas_forget_item(GanvCanvas* canvas, GanvItem* item)
-{
- if (canvas->impl && item == canvas->impl->current_item) {
- canvas->impl->current_item = NULL;
- canvas->impl->need_repick = TRUE;
- }
-
- if (canvas->impl && item == canvas->impl->new_current_item) {
- canvas->impl->new_current_item = NULL;
- canvas->impl->need_repick = TRUE;
- }
-
- if (canvas->impl && item == canvas->impl->grabbed_item) {
- canvas->impl->grabbed_item = NULL;
- gdk_pointer_ungrab(GDK_CURRENT_TIME);
- }
-
- if (canvas->impl && item == canvas->impl->focused_item) {
- canvas->impl->focused_item = NULL;
- }
-}
-
-void
-ganv_canvas_grab_focus(GanvCanvas* canvas, GanvItem* item)
-{
- g_return_if_fail(GANV_IS_ITEM(item));
- g_return_if_fail(GTK_WIDGET_CAN_FOCUS(GTK_WIDGET(canvas)));
-
- GanvItem* focused_item = canvas->impl->focused_item;
- GdkEvent ev;
-
- if (focused_item) {
- ev.focus_change.type = GDK_FOCUS_CHANGE;
- ev.focus_change.window = canvas->layout.bin_window;
- ev.focus_change.send_event = FALSE;
- ev.focus_change.in = FALSE;
-
- ganv_canvas_emit_event(canvas, &ev);
- }
-
- canvas->impl->focused_item = item;
- gtk_widget_grab_focus(GTK_WIDGET(canvas));
-
- if (focused_item) {
- ev.focus_change.type = GDK_FOCUS_CHANGE;
- ev.focus_change.window = canvas->layout.bin_window;
- ev.focus_change.send_event = FALSE;
- ev.focus_change.in = TRUE;
-
- ganv_canvas_emit_event(canvas, &ev);
- }
-}
-
-/**
- * ganv_canvas_grab_item:
- * @item: A canvas item.
- * @event_mask: Mask of events that will be sent to this item.
- * @cursor: If non-NULL, the cursor that will be used while the grab is active.
- * @etime: The timestamp required for grabbing the mouse, or GDK_CURRENT_TIME.
- *
- * Specifies that all events that match the specified event mask should be sent
- * to the specified item, and also grabs the mouse by calling
- * gdk_pointer_grab(). The event mask is also used when grabbing the pointer.
- * If @cursor is not NULL, then that cursor is used while the grab is active.
- * The @etime parameter is the timestamp required for grabbing the mouse.
- *
- * Return value: If an item was already grabbed, it returns %GDK_GRAB_ALREADY_GRABBED. If
- * the specified item was hidden by calling ganv_item_hide(), then it
- * returns %GDK_GRAB_NOT_VIEWABLE. Else, it returns the result of calling
- * gdk_pointer_grab().
- **/
-int
-ganv_canvas_grab_item(GanvItem* item, guint event_mask, GdkCursor* cursor, guint32 etime)
-{
- g_return_val_if_fail(GANV_IS_ITEM(item), GDK_GRAB_NOT_VIEWABLE);
- g_return_val_if_fail(GTK_WIDGET_MAPPED(item->impl->canvas), GDK_GRAB_NOT_VIEWABLE);
-
- if (item->impl->canvas->impl->grabbed_item) {
- return GDK_GRAB_ALREADY_GRABBED;
- }
-
- if (!(item->object.flags & GANV_ITEM_VISIBLE)) {
- return GDK_GRAB_NOT_VIEWABLE;
- }
-
- int retval = gdk_pointer_grab(item->impl->canvas->layout.bin_window,
- FALSE,
- (GdkEventMask)event_mask,
- NULL,
- cursor,
- etime);
-
- if (retval != GDK_GRAB_SUCCESS) {
- return retval;
- }
-
- item->impl->canvas->impl->grabbed_item = item;
- item->impl->canvas->impl->grabbed_event_mask = event_mask;
- item->impl->canvas->impl->current_item = item; /* So that events go to the grabbed item */
-
- return retval;
-}
-
-/**
- * ganv_canvas_ungrab_item:
- * @item: A canvas item that holds a grab.
- * @etime: The timestamp for ungrabbing the mouse.
- *
- * Ungrabs the item, which must have been grabbed in the canvas, and ungrabs the
- * mouse.
- **/
-void
-ganv_canvas_ungrab_item(GanvItem* item, guint32 etime)
-{
- g_return_if_fail(GANV_IS_ITEM(item));
-
- if (item->impl->canvas->impl->grabbed_item != item) {
- return;
- }
-
- item->impl->canvas->impl->grabbed_item = NULL;
-
- gdk_pointer_ungrab(etime);
-}
-
-void
-ganv_canvas_get_zoom_offsets(GanvCanvas* canvas, int* x, int* y)
-{
- *x = canvas->impl->zoom_xofs;
- *y = canvas->impl->zoom_yofs;
-}
-
-/* Re-picks the current item in the canvas, based on the event's coordinates.
- * Also emits enter/leave events for items as appropriate.
- */
-static int
-pick_current_item(GanvCanvas* canvas, GdkEvent* event)
-{
- int retval = FALSE;
-
- /* If a button is down, we'll perform enter and leave events on the
- * current item, but not enter on any other item. This is more or less
- * like X pointer grabbing for canvas items.
- */
- int button_down = canvas->impl->state & (GDK_BUTTON1_MASK
- | GDK_BUTTON2_MASK
- | GDK_BUTTON3_MASK
- | GDK_BUTTON4_MASK
- | GDK_BUTTON5_MASK);
- if (!button_down) {
- canvas->impl->left_grabbed_item = FALSE;
- }
-
- /* Save the event in the canvas. This is used to synthesize enter and
- * leave events in case the current item changes. It is also used to
- * re-pick the current item if the current one gets deleted. Also,
- * synthesize an enter event.
- */
- if (event != &canvas->impl->pick_event) {
- if ((event->type == GDK_MOTION_NOTIFY) || (event->type == GDK_BUTTON_RELEASE)) {
- /* these fields have the same offsets in both types of events */
-
- canvas->impl->pick_event.crossing.type = GDK_ENTER_NOTIFY;
- canvas->impl->pick_event.crossing.window = event->motion.window;
- canvas->impl->pick_event.crossing.send_event = event->motion.send_event;
- canvas->impl->pick_event.crossing.subwindow = NULL;
- canvas->impl->pick_event.crossing.x = event->motion.x;
- canvas->impl->pick_event.crossing.y = event->motion.y;
- canvas->impl->pick_event.crossing.mode = GDK_CROSSING_NORMAL;
- canvas->impl->pick_event.crossing.detail = GDK_NOTIFY_NONLINEAR;
- canvas->impl->pick_event.crossing.focus = FALSE;
- canvas->impl->pick_event.crossing.state = event->motion.state;
-
- /* these fields don't have the same offsets in both types of events */
-
- if (event->type == GDK_MOTION_NOTIFY) {
- canvas->impl->pick_event.crossing.x_root = event->motion.x_root;
- canvas->impl->pick_event.crossing.y_root = event->motion.y_root;
- } else {
- canvas->impl->pick_event.crossing.x_root = event->button.x_root;
- canvas->impl->pick_event.crossing.y_root = event->button.y_root;
- }
- } else {
- canvas->impl->pick_event = *event;
- }
- }
-
- /* Don't do anything else if this is a recursive call */
-
- if (canvas->impl->in_repick) {
- return retval;
- }
-
- /* LeaveNotify means that there is no current item, so we don't look for one */
-
- if (canvas->impl->pick_event.type != GDK_LEAVE_NOTIFY) {
- /* these fields don't have the same offsets in both types of events */
-
- double x, y;
- if (canvas->impl->pick_event.type == GDK_ENTER_NOTIFY) {
- x = canvas->impl->pick_event.crossing.x - canvas->impl->zoom_xofs;
- y = canvas->impl->pick_event.crossing.y - canvas->impl->zoom_yofs;
- } else {
- x = canvas->impl->pick_event.motion.x - canvas->impl->zoom_xofs;
- y = canvas->impl->pick_event.motion.y - canvas->impl->zoom_yofs;
- }
-
- /* world coords */
-
- x = canvas->impl->scroll_x1 + x / canvas->impl->pixels_per_unit;
- y = canvas->impl->scroll_y1 + y / canvas->impl->pixels_per_unit;
-
- /* find the closest item */
-
- if (canvas->impl->root->object.flags & GANV_ITEM_VISIBLE) {
- GANV_ITEM_GET_CLASS(canvas->impl->root)->point(
- canvas->impl->root,
- x - canvas->impl->root->impl->x, y - canvas->impl->root->impl->y,
- &canvas->impl->new_current_item);
- } else {
- canvas->impl->new_current_item = NULL;
- }
- } else {
- canvas->impl->new_current_item = NULL;
- }
-
- if ((canvas->impl->new_current_item == canvas->impl->current_item) && !canvas->impl->left_grabbed_item) {
- return retval; /* current item did not change */
-
- }
- /* Synthesize events for old and new current items */
-
- if ((canvas->impl->new_current_item != canvas->impl->current_item)
- && (canvas->impl->current_item != NULL)
- && !canvas->impl->left_grabbed_item) {
- GdkEvent new_event;
-
- new_event = canvas->impl->pick_event;
- new_event.type = GDK_LEAVE_NOTIFY;
-
- new_event.crossing.detail = GDK_NOTIFY_ANCESTOR;
- new_event.crossing.subwindow = NULL;
- canvas->impl->in_repick = TRUE;
- retval = ganv_canvas_emit_event(canvas, &new_event);
- canvas->impl->in_repick = FALSE;
- }
-
- /* new_current_item may have been set to NULL during the call to ganv_canvas_emit_event() above */
-
- if ((canvas->impl->new_current_item != canvas->impl->current_item) && button_down) {
- canvas->impl->left_grabbed_item = TRUE;
- return retval;
- }
-
- /* Handle the rest of cases */
-
- canvas->impl->left_grabbed_item = FALSE;
- canvas->impl->current_item = canvas->impl->new_current_item;
-
- if (canvas->impl->current_item != NULL) {
- GdkEvent new_event;
-
- new_event = canvas->impl->pick_event;
- new_event.type = GDK_ENTER_NOTIFY;
- new_event.crossing.detail = GDK_NOTIFY_ANCESTOR;
- new_event.crossing.subwindow = NULL;
- retval = ganv_canvas_emit_event(canvas, &new_event);
- }
-
- return retval;
-}
-
-/* Button event handler for the canvas */
-static gint
-ganv_canvas_button(GtkWidget* widget, GdkEventButton* event)
-{
- int mask;
- int retval;
-
- g_return_val_if_fail(GANV_IS_CANVAS(widget), FALSE);
- g_return_val_if_fail(event != NULL, FALSE);
-
- retval = FALSE;
-
- GanvCanvas* canvas = GANV_CANVAS(widget);
-
- /*
- * dispatch normally regardless of the event's window if an item has
- * has a pointer grab in effect
- */
- if (!canvas->impl->grabbed_item && ( event->window != canvas->layout.bin_window) ) {
- return retval;
- }
-
- switch (event->button) {
- case 1:
- mask = GDK_BUTTON1_MASK;
- break;
- case 2:
- mask = GDK_BUTTON2_MASK;
- break;
- case 3:
- mask = GDK_BUTTON3_MASK;
- break;
- case 4:
- mask = GDK_BUTTON4_MASK;
- break;
- case 5:
- mask = GDK_BUTTON5_MASK;
- break;
- default:
- mask = 0;
- }
-
- switch (event->type) {
- case GDK_BUTTON_PRESS:
- case GDK_2BUTTON_PRESS:
- case GDK_3BUTTON_PRESS:
- /* Pick the current item as if the button were not pressed, and
- * then process the event.
- */
- canvas->impl->state = event->state;
- pick_current_item(canvas, (GdkEvent*)event);
- canvas->impl->state ^= mask;
- retval = ganv_canvas_emit_event(canvas, (GdkEvent*)event);
- break;
-
- case GDK_BUTTON_RELEASE:
- /* Process the event as if the button were pressed, then repick
- * after the button has been released
- */
- canvas->impl->state = event->state;
- retval = ganv_canvas_emit_event(canvas, (GdkEvent*)event);
- event->state ^= mask;
- canvas->impl->state = event->state;
- pick_current_item(canvas, (GdkEvent*)event);
- event->state ^= mask;
- break;
-
- default:
- g_assert_not_reached();
- }
-
- return retval;
-}
-
-/* Motion event handler for the canvas */
-static gint
-ganv_canvas_motion(GtkWidget* widget, GdkEventMotion* event)
-{
- g_return_val_if_fail(GANV_IS_CANVAS(widget), FALSE);
- g_return_val_if_fail(event != NULL, FALSE);
-
- GanvCanvas* canvas = GANV_CANVAS(widget);
-
- if (event->window != canvas->layout.bin_window) {
- return FALSE;
- }
-
- canvas->impl->state = event->state;
- pick_current_item(canvas, (GdkEvent*)event);
- return ganv_canvas_emit_event(canvas, (GdkEvent*)event);
-}
-
-static gboolean
-ganv_canvas_scroll(GtkWidget* widget, GdkEventScroll* event)
-{
- g_return_val_if_fail(GANV_IS_CANVAS(widget), FALSE);
- g_return_val_if_fail(event != NULL, FALSE);
-
- GanvCanvas* canvas = GANV_CANVAS(widget);
-
- if (event->window != canvas->layout.bin_window) {
- return FALSE;
- }
-
- canvas->impl->state = event->state;
- pick_current_item(canvas, (GdkEvent*)event);
- return ganv_canvas_emit_event(canvas, (GdkEvent*)event);
-}
-
-/* Key event handler for the canvas */
-static gboolean
-ganv_canvas_key(GtkWidget* widget, GdkEventKey* event)
-{
- g_return_val_if_fail(GANV_IS_CANVAS(widget), FALSE);
- g_return_val_if_fail(event != NULL, FALSE);
-
- GanvCanvas* canvas = GANV_CANVAS(widget);
-
- if (!ganv_canvas_emit_event(canvas, (GdkEvent*)event)) {
- GtkWidgetClass* widget_class;
-
- widget_class = GTK_WIDGET_CLASS(canvas_parent_class);
-
- if (event->type == GDK_KEY_PRESS) {
- if (widget_class->key_press_event) {
- return (*widget_class->key_press_event)(widget, event);
- }
- } else if (event->type == GDK_KEY_RELEASE) {
- if (widget_class->key_release_event) {
- return (*widget_class->key_release_event)(widget, event);
- }
- } else {
- g_assert_not_reached();
- }
-
- return FALSE;
- } else {
- return TRUE;
- }
-}
-
-/* Crossing event handler for the canvas */
-static gint
-ganv_canvas_crossing(GtkWidget* widget, GdkEventCrossing* event)
-{
- g_return_val_if_fail(GANV_IS_CANVAS(widget), FALSE);
- g_return_val_if_fail(event != NULL, FALSE);
-
- GanvCanvas* canvas = GANV_CANVAS(widget);
-
- if (event->window != canvas->layout.bin_window) {
- return FALSE;
- }
-
- canvas->impl->state = event->state;
- return pick_current_item(canvas, (GdkEvent*)event);
-}
-
-/* Focus in handler for the canvas */
-static gint
-ganv_canvas_focus_in(GtkWidget* widget, GdkEventFocus* event)
-{
- GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
-
- GanvCanvas* canvas = GANV_CANVAS(widget);
-
- if (canvas->impl->focused_item) {
- return ganv_canvas_emit_event(canvas, (GdkEvent*)event);
- } else {
- return FALSE;
- }
-}
-
-/* Focus out handler for the canvas */
-static gint
-ganv_canvas_focus_out(GtkWidget* widget, GdkEventFocus* event)
-{
- GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
-
- GanvCanvas* canvas = GANV_CANVAS(widget);
-
- if (canvas->impl->focused_item) {
- return ganv_canvas_emit_event(canvas, (GdkEvent*)event);
- } else {
- return FALSE;
- }
-}
-
-#define REDRAW_QUANTUM_SIZE 512
-
-static void
-ganv_canvas_paint_rect(GanvCanvas* canvas, gint x0, gint y0, gint x1, gint y1)
-{
- gint draw_x1, draw_y1;
- gint draw_x2, draw_y2;
- gint draw_width, draw_height;
-
- g_return_if_fail(!canvas->impl->need_update);
-
- draw_x1 = MAX(x0, canvas->layout.hadjustment->value - canvas->impl->zoom_xofs);
- draw_y1 = MAX(y0, canvas->layout.vadjustment->value - canvas->impl->zoom_yofs);
- draw_x2 = MIN(draw_x1 + GTK_WIDGET(canvas)->allocation.width, x1);
- draw_y2 = MIN(draw_y1 + GTK_WIDGET(canvas)->allocation.height, y1);
-
- draw_width = draw_x2 - draw_x1;
- draw_height = draw_y2 - draw_y1;
-
- if ((draw_width < 1) || (draw_height < 1)) {
- return;
- }
-
- canvas->impl->redraw_x1 = draw_x1;
- canvas->impl->redraw_y1 = draw_y1;
- canvas->impl->redraw_x2 = draw_x2;
- canvas->impl->redraw_y2 = draw_y2;
- canvas->impl->draw_xofs = draw_x1;
- canvas->impl->draw_yofs = draw_y1;
-
- cairo_t* cr = gdk_cairo_create(canvas->layout.bin_window);
-
- double win_x, win_y;
- ganv_canvas_window_to_world(canvas, 0, 0, &win_x, &win_y);
- cairo_translate(cr, -win_x, -win_y);
- cairo_scale(cr, canvas->impl->pixels_per_unit, canvas->impl->pixels_per_unit);
-
- if (canvas->impl->root->object.flags & GANV_ITEM_VISIBLE) {
- double wx1, wy1, ww, wh;
- ganv_canvas_c2w(canvas, draw_x1, draw_y1, &wx1, &wy1);
- ganv_canvas_c2w(canvas, draw_width, draw_height, &ww, &wh);
-
- // Draw background
- double r, g, b, a;
- color_to_rgba(DEFAULT_BACKGROUND_COLOR, &r, &g, &b, &a);
- cairo_set_source_rgba(cr, r, g, b, a);
- cairo_rectangle(cr, wx1, wy1, ww, wh);
- cairo_fill(cr);
-
- // Draw root group
- (*GANV_ITEM_GET_CLASS(canvas->impl->root)->draw)(
- canvas->impl->root, cr,
- wx1, wy1, ww, wh);
- }
-
- cairo_destroy(cr);
-}
-
-/* Expose handler for the canvas */
-static gint
-ganv_canvas_expose(GtkWidget* widget, GdkEventExpose* event)
-{
- GanvCanvas* canvas = GANV_CANVAS(widget);
- if (!GTK_WIDGET_DRAWABLE(widget) ||
- (event->window != canvas->layout.bin_window)) {
- return FALSE;
- }
-
- /* Find a single bounding rectangle for all rectangles in the region.
- Since drawing the root group is O(n) and thus very expensive for large
- canvases, it's much faster to do a single paint than many, even though
- more area may be painted that way. With a better group implementation,
- it would likely be better to paint each changed rectangle separately. */
- GdkRectangle clip;
- gdk_region_get_clipbox(event->region, &clip);
-
- const int x2 = clip.x + clip.width;
- const int y2 = clip.y + clip.height;
-
- if (canvas->impl->need_update || canvas->impl->need_redraw) {
- /* Update or drawing is scheduled, so just mark exposed area as dirty */
- ganv_canvas_request_redraw_c(canvas, clip.x, clip.y, x2, y2);
- } else {
- /* No pending updates, draw exposed area immediately */
- ganv_canvas_paint_rect(canvas, clip.x, clip.y, x2, y2);
-
- /* And call expose on parent container class */
- if (GTK_WIDGET_CLASS(canvas_parent_class)->expose_event) {
- (*GTK_WIDGET_CLASS(canvas_parent_class)->expose_event)(
- widget, event);
- }
- }
-
- return FALSE;
-}
-
-/* Repaints the areas in the canvas that need it */
-static void
-paint(GanvCanvas* canvas)
-{
- for (GSList* l = canvas->impl->redraw_region; l; l = l->next) {
- IRect* rect = (IRect*)l->data;
-
- const GdkRectangle gdkrect = {
- rect->x + canvas->impl->zoom_xofs,
- rect->y + canvas->impl->zoom_yofs,
- rect->width,
- rect->height
- };
-
- gdk_window_invalidate_rect(canvas->layout.bin_window, &gdkrect, FALSE);
- g_free(rect);
- }
-
- g_slist_free(canvas->impl->redraw_region);
- canvas->impl->redraw_region = NULL;
- canvas->impl->need_redraw = FALSE;
-
- canvas->impl->redraw_x1 = 0;
- canvas->impl->redraw_y1 = 0;
- canvas->impl->redraw_x2 = 0;
- canvas->impl->redraw_y2 = 0;
-}
-
-static void
-do_update(GanvCanvas* canvas)
-{
- /* Cause the update if necessary */
-
-update_again:
- if (canvas->impl->need_update) {
- ganv_item_invoke_update(canvas->impl->root, 0);
-
- canvas->impl->need_update = FALSE;
- }
-
- /* Pick new current item */
-
- while (canvas->impl->need_repick) {
- canvas->impl->need_repick = FALSE;
- pick_current_item(canvas, &canvas->impl->pick_event);
- }
-
- /* it is possible that during picking we emitted an event in which
- the user then called some function which then requested update
- of something. Without this we'd be left in a state where
- need_update would have been left TRUE and the canvas would have
- been left unpainted. */
- if (canvas->impl->need_update) {
- goto update_again;
- }
-
- /* Paint if able to */
-
- if (GTK_WIDGET_DRAWABLE(canvas) && canvas->impl->need_redraw) {
- paint(canvas);
- }
-}
-
-/* Idle handler for the canvas. It deals with pending updates and redraws. */
-static gboolean
-idle_handler(gpointer data)
-{
- GDK_THREADS_ENTER();
-
- GanvCanvas* canvas = GANV_CANVAS(data);
-
- do_update(canvas);
-
- /* Reset idle id */
- canvas->impl->idle_id = 0;
-
- GDK_THREADS_LEAVE();
-
- return FALSE;
-}
-
-/* Convenience function to add an idle handler to a canvas */
-static void
-add_idle(GanvCanvas* canvas)
-{
- g_assert(canvas->impl->need_update || canvas->impl->need_redraw);
-
- if (!canvas->impl->idle_id) {
- canvas->impl->idle_id = g_idle_add_full(CANVAS_IDLE_PRIORITY,
- idle_handler,
- canvas,
- NULL);
- }
-
- /* canvas->idle_id = gtk_idle_add (idle_handler, canvas); */
-}
-
-GanvItem*
-ganv_canvas_root(GanvCanvas* canvas)
-{
- g_return_val_if_fail(GANV_IS_CANVAS(canvas), NULL);
-
- return canvas->impl->root;
-}
-
-void
-ganv_canvas_set_scroll_region(GanvCanvas* canvas,
- double x1, double y1, double x2, double y2)
-{
- double wxofs, wyofs;
- int xofs, yofs;
-
- g_return_if_fail(GANV_IS_CANVAS(canvas));
-
- /*
- * Set the new scrolling region. If possible, do not move the visible contents of the
- * canvas.
- */
-
- ganv_canvas_c2w(canvas,
- GTK_LAYOUT(canvas)->hadjustment->value + canvas->impl->zoom_xofs,
- GTK_LAYOUT(canvas)->vadjustment->value + canvas->impl->zoom_yofs,
- /*canvas->impl->zoom_xofs,
- canvas->impl->zoom_yofs,*/
- &wxofs, &wyofs);
-
- canvas->impl->scroll_x1 = x1;
- canvas->impl->scroll_y1 = y1;
- canvas->impl->scroll_x2 = x2;
- canvas->impl->scroll_y2 = y2;
-
- ganv_canvas_w2c(canvas, wxofs, wyofs, &xofs, &yofs);
-
- scroll_to(canvas, xofs, yofs);
-
- canvas->impl->need_repick = TRUE;
-#if 0
- /* todo: should be requesting update */
- (*GANV_ITEM_CLASS(canvas->impl->root->object.klass)->update)(
- canvas->impl->root, NULL, NULL, 0);
-#endif
-}
-
-void
-ganv_canvas_get_scroll_region(GanvCanvas* canvas,
- double* x1, double* y1, double* x2, double* y2)
-{
- g_return_if_fail(GANV_IS_CANVAS(canvas));
-
- if (x1) {
- *x1 = canvas->impl->scroll_x1;
- }
-
- if (y1) {
- *y1 = canvas->impl->scroll_y1;
- }
-
- if (x2) {
- *x2 = canvas->impl->scroll_x2;
- }
-
- if (y2) {
- *y2 = canvas->impl->scroll_y2;
- }
-}
-
-void
-ganv_canvas_set_center_scroll_region(GanvCanvas* canvas, gboolean center_scroll_region)
-{
- g_return_if_fail(GANV_IS_CANVAS(canvas));
-
- canvas->impl->center_scroll_region = center_scroll_region != 0;
-
- scroll_to(canvas,
- canvas->layout.hadjustment->value,
- canvas->layout.vadjustment->value);
-}
-
-gboolean
-ganv_canvas_get_center_scroll_region(const GanvCanvas* canvas)
-{
- g_return_val_if_fail(GANV_IS_CANVAS(canvas), FALSE);
-
- return canvas->impl->center_scroll_region ? TRUE : FALSE;
-}
-
-void
-ganv_canvas_scroll_to(GanvCanvas* canvas, int cx, int cy)
-{
- g_return_if_fail(GANV_IS_CANVAS(canvas));
-
- scroll_to(canvas, cx, cy);
-}
-
-void
-ganv_canvas_get_scroll_offsets(const GanvCanvas* canvas, int* cx, int* cy)
-{
- g_return_if_fail(GANV_IS_CANVAS(canvas));
-
- if (cx) {
- *cx = canvas->layout.hadjustment->value;
- }
-
- if (cy) {
- *cy = canvas->layout.vadjustment->value;
- }
-}
-
-GanvItem*
-ganv_canvas_get_item_at(GanvCanvas* canvas, double x, double y)
-{
- g_return_val_if_fail(GANV_IS_CANVAS(canvas), NULL);
-
- GanvItem* item = NULL;
- double dist = GANV_ITEM_GET_CLASS(canvas->impl->root)->point(
- canvas->impl->root,
- x - canvas->impl->root->impl->x,
- y - canvas->impl->root->impl->y,
- &item);
- if ((int)(dist * canvas->impl->pixels_per_unit + 0.5) <= GANV_CLOSE_ENOUGH) {
- return item;
- } else {
- return NULL;
- }
-}
-
-void
-ganv_canvas_request_update(GanvCanvas* canvas)
-{
- if (canvas->impl->need_update) {
- return;
- }
-
- canvas->impl->need_update = TRUE;
- if (GTK_WIDGET_MAPPED((GtkWidget*)canvas)) {
- add_idle(canvas);
- }
-}
-
-static inline gboolean
-rect_overlaps(const IRect* a, const IRect* b)
-{
- if ((a->x > b->x + b->width) ||
- (a->y > b->y + b->height) ||
- (a->x + a->width < b->x) ||
- (a->y + a->height < b->y)) {
- return FALSE;
- }
- return TRUE;
-}
-
-static inline gboolean
-rect_is_visible(GanvCanvas* canvas, const IRect* r)
-{
- const IRect rect = {
- (int)(canvas->layout.hadjustment->value - canvas->impl->zoom_xofs),
- (int)(canvas->layout.vadjustment->value - canvas->impl->zoom_yofs),
- GTK_WIDGET(canvas)->allocation.width,
- GTK_WIDGET(canvas)->allocation.height
- };
-
- return rect_overlaps(&rect, r);
-}
-
-void
-ganv_canvas_request_redraw_c(GanvCanvas* canvas,
- int x1, int y1, int x2, int y2)
-{
- g_return_if_fail(GANV_IS_CANVAS(canvas));
-
- if (!GTK_WIDGET_DRAWABLE(canvas) || (x1 >= x2) || (y1 >= y2)) {
- return;
- }
-
- const IRect rect = { x1, y1, x2 - x1, y2 - y1 };
-
- if (!rect_is_visible(canvas, &rect)) {
- return;
- }
-
- IRect* r = (IRect*)g_malloc(sizeof(IRect));
- *r = rect;
-
- canvas->impl->redraw_region = g_slist_prepend(canvas->impl->redraw_region, r);
- canvas->impl->need_redraw = TRUE;
-
- if (canvas->impl->idle_id == 0) {
- add_idle(canvas);
- }
-}
-
-/* Request a redraw of the specified rectangle in world coordinates */
-void
-ganv_canvas_request_redraw_w(GanvCanvas* canvas,
- double x1, double y1, double x2, double y2)
-{
- int cx1, cx2, cy1, cy2;
- ganv_canvas_w2c(canvas, x1, y1, &cx1, &cy1);
- ganv_canvas_w2c(canvas, x2, y2, &cx2, &cy2);
- ganv_canvas_request_redraw_c(canvas, cx1, cy1, cx2, cy2);
-}
-
-void
-ganv_canvas_w2c_affine(GanvCanvas* canvas, cairo_matrix_t* matrix)
-{
- g_return_if_fail(GANV_IS_CANVAS(canvas));
- g_return_if_fail(matrix != NULL);
-
- cairo_matrix_init_translate(matrix,
- -canvas->impl->scroll_x1,
- -canvas->impl->scroll_y1);
-
- cairo_matrix_scale(matrix,
- canvas->impl->pixels_per_unit,
- canvas->impl->pixels_per_unit);
-}
-
-void
-ganv_canvas_w2c(GanvCanvas* canvas, double wx, double wy, int* cx, int* cy)
-{
- g_return_if_fail(GANV_IS_CANVAS(canvas));
-
- cairo_matrix_t matrix;
- ganv_canvas_w2c_affine(canvas, &matrix);
-
- cairo_matrix_transform_point(&matrix, &wx, &wy);
-
- if (cx) {
- *cx = floor(wx + 0.5);
- }
- if (cy) {
- *cy = floor(wy + 0.5);
- }
-}
-
-void
-ganv_canvas_w2c_d(GanvCanvas* canvas, double wx, double wy, double* cx, double* cy)
-{
- g_return_if_fail(GANV_IS_CANVAS(canvas));
-
- cairo_matrix_t matrix;
- ganv_canvas_w2c_affine(canvas, &matrix);
-
- cairo_matrix_transform_point(&matrix, &wx, &wy);
-
- if (cx) {
- *cx = wx;
- }
- if (cy) {
- *cy = wy;
- }
-}
-
-void
-ganv_canvas_c2w(GanvCanvas* canvas, int cx, int cy, double* wx, double* wy)
-{
- g_return_if_fail(GANV_IS_CANVAS(canvas));
-
- cairo_matrix_t matrix;
- ganv_canvas_w2c_affine(canvas, &matrix);
- cairo_matrix_invert(&matrix);
-
- double x = cx;
- double y = cy;
- cairo_matrix_transform_point(&matrix, &x, &y);
-
- if (wx) {
- *wx = x;
- }
- if (wy) {
- *wy = y;
- }
-}
-
-void
-ganv_canvas_window_to_world(GanvCanvas* canvas, double winx, double winy,
- double* worldx, double* worldy)
-{
- g_return_if_fail(GANV_IS_CANVAS(canvas));
-
- if (worldx) {
- *worldx = canvas->impl->scroll_x1 + ((winx - canvas->impl->zoom_xofs)
- / canvas->impl->pixels_per_unit);
- }
-
- if (worldy) {
- *worldy = canvas->impl->scroll_y1 + ((winy - canvas->impl->zoom_yofs)
- / canvas->impl->pixels_per_unit);
- }
-}
-
-void
-ganv_canvas_world_to_window(GanvCanvas* canvas, double worldx, double worldy,
- double* winx, double* winy)
-{
- g_return_if_fail(GANV_IS_CANVAS(canvas));
-
- if (winx) {
- *winx = (canvas->impl->pixels_per_unit) * (worldx - canvas->impl->scroll_x1) + canvas->impl->zoom_xofs;
- }
-
- if (winy) {
- *winy = (canvas->impl->pixels_per_unit) * (worldy - canvas->impl->scroll_y1) + canvas->impl->zoom_yofs;
- }
-}
-
-void
-ganv_canvas_set_port_order(GanvCanvas* canvas,
- GanvPortOrderFunc port_cmp,
- void* data)
-{
- g_return_if_fail(GANV_IS_CANVAS(canvas));
-
- canvas->impl->_port_order.port_cmp = port_cmp;
- canvas->impl->_port_order.data = data;
-}
-
-PortOrderCtx
-ganv_canvas_get_port_order(GanvCanvas* canvas)
-{
- return canvas->impl->_port_order;
-}
-
-gboolean
-ganv_canvas_exporting(GanvCanvas* canvas)
-{
- return canvas->impl->exporting;
-}
-
-} // extern "C"