Added flowcanvas 0.0.1 (unreleased).

git-svn-id: svn://localhost/trunk/ardour2@62 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Taybin Rutkin 2005-10-21 19:05:27 +00:00
parent 2634aaadc5
commit dfbf777cea
15 changed files with 3886 additions and 0 deletions

View file

@ -0,0 +1,679 @@
/* This file is part of FlowCanvas. Copyright (C) 2005 Dave Robillard.
*
* FlowCanvas 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 2 of the License, or (at your option) any later
* version.
*
* FlowCanvas 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 this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "flowcanvas/Connection.h"
#include <cassert>
#include <math.h>
#include <libgnomecanvasmm/libgnomecanvasmm.h>
#include "flowcanvas/FlowCanvas.h"
// FIXME: remove
#include <iostream>
using std::cerr; using std::endl;
namespace LibFlowCanvas {
Connection::Connection(FlowCanvas* patch_bay, Port* source_port, Port* dest_port)
: Gnome::Canvas::Bpath(*patch_bay->root()),
m_patch_bay(patch_bay),
m_source_port(source_port),
m_dest_port(dest_port),
m_selected(false),
// m_path(Gnome::Canvas::PathDef::create())
m_path(gnome_canvas_path_def_new())
{
assert(m_source_port->is_output());
assert(m_dest_port->is_input());
m_colour = m_source_port->colour() + 0x44444400;
property_width_units() = 1.0;
property_outline_color_rgba() = m_colour;
property_cap_style() = (Gdk::CapStyle)GDK_CAP_ROUND;
update_location();
}
Connection::~Connection()
{
if (m_selected) {
for (list<Connection*>::iterator c = m_patch_bay->selected_connections().begin();
c != m_patch_bay->selected_connections().end(); ++c)
{
if ((*c) == this) {
m_patch_bay->selected_connections().erase(c);
break;
}
}
}
}
#undef MIN
#define MIN(x,y) (((x) < (y)) ? (x) : (y))
#undef MAX
#define MAX(x,y) (((x) > (y)) ? (x) : (y))
#if 0
/** Updates the connection's location to match it's source/dest ports.
*
* This is used when modules are dragged, to keep the connections attached
* to their ports.
*
* You are not expected to understand this.
*/
void
Connection::update_location()
{
const double src_x = m_source_port->connection_coords().get_x();
const double src_y = m_source_port->connection_coords().get_y();
const double dst_x = m_dest_port->connection_coords().get_x();
const double dst_y = m_dest_port->connection_coords().get_y();
const double src_mod_x = m_source_port->module()->property_x();
const double src_mod_y = m_source_port->module()->property_y();
const double src_mod_w = m_source_port->module()->width();
const double src_mod_h = m_source_port->module()->height();
const double dst_mod_x = m_dest_port->module()->property_x();
const double dst_mod_y = m_dest_port->module()->property_y();
const double dst_mod_w = m_dest_port->module()->width();
const double dst_mod_h = m_dest_port->module()->height();
// Y Modifier (-1 if src module is below dst module)
double y_mod = (src_y < dst_y) ? 1.0 : -1.0;
// Added in various places to keep things parallel
/*double src_port_offset = src_y - src_mod_y - src_title_h;
double dst_port_offset = dst_y - dst_mod_y - dst_title_h;
if (y_mod < 0.0) {
src_port_offset = src_mod_y + src_mod_h - src_y;
dst_port_offset = dst_mod_y + dst_mod_h - dst_y;
}*/
double src_port_offset = m_source_port->module()->port_connection_point_offset(m_source_port);
double src_offset_range = m_source_port->module()->port_connection_points_range();
double dst_port_offset = m_dest_port->module()->port_connection_point_offset(m_dest_port);
double dst_offset_range = m_dest_port->module()->port_connection_points_range();
/*
double smallest_offset = (src_port_offset < dst_port_offset)
? src_port_offset : dst_port_offset;
double smallest_offset_range = (src_port_offset < dst_port_offset)
? m_source_port->module()->port_connection_points_range()
: m_dest_port->module()->port_connection_points_range();
*/
double smallest_offset = (src_offset_range < dst_offset_range)
? src_port_offset : dst_port_offset;
double smallest_offset_range = (src_offset_range < dst_offset_range)
? m_source_port->module()->port_connection_points_range()
: m_dest_port->module()->port_connection_points_range();
//double largest_offset_range = (src_offset_range > dst_offset_range)
// ? m_source_port->module()->port_connection_points_range()
// : m_dest_port->module()->port_connection_points_range();
double x_dist = fabs(dst_x - src_x);
double y_dist = fabs(dst_y - src_y);
// Vertical distance between modules
double y_mod_dist = dst_mod_y - src_mod_y - src_mod_h;
if (dst_y < src_y)
y_mod_dist = src_mod_y - dst_mod_y - dst_mod_h;
if (y_mod_dist < 1.0)
y_mod_dist = 1.0;
// Horizontal distance between modules
double x_mod_dist = dst_mod_x - src_mod_x - src_mod_w;
if (src_x > dst_x + src_mod_w)
x_mod_dist = src_mod_x - dst_mod_x - dst_mod_w;
if (x_mod_dist < 1.0)
x_mod_dist = 1.0;
double tallest_mod_h = m_source_port->module()->height();
if (m_dest_port->module()->height() > tallest_mod_h)
tallest_mod_h = m_dest_port->module()->height();
double src_x1, src_y1, src_x2, src_y2, join_x, join_y; // Path 1
double dst_x2, dst_y2, dst_x1, dst_y1; // Path 2
src_x1 = src_y1 = src_x2 = src_y2 = join_x = join_y = dst_x2 = dst_y2 = dst_x1 = dst_y1 = 0.0;
static const double join_range = 15.0;
double src_offset = (src_y < dst_y)
? src_port_offset : src_offset_range - src_port_offset;
double dst_offset = (src_y < dst_y)
? dst_port_offset : dst_offset_range - dst_port_offset;
// Wrap around connections
if (dst_x < src_x && y_mod_dist < join_range*3.0) {
static const double module_padding = 20.0;
// FIXME: completely different meanings in this case than the normal case
// (this one is better though)
smallest_offset = (src_offset_range < dst_offset_range)
? src_offset : dst_offset;
// Limit straight out distance
if (x_dist > 60.0)
x_dist = 60.0;
if (x_dist < 80.0 && y_dist > 40.0)
x_dist = 80.0;
// Calculate join point
join_x = dst_mod_x + dst_mod_w + x_mod_dist/2.0;
join_y = (src_y < dst_y)
? MIN(src_mod_y, dst_mod_y)
: MAX(src_mod_y + src_mod_h, dst_mod_y + dst_mod_h);
join_y -= (smallest_offset/smallest_offset_range*join_range + module_padding) * y_mod;
if (join_x > src_mod_x)
join_x = src_mod_x;
// Path 1 (src_x, src_y) -> (join_x, join_y)
src_x1 = src_x + x_dist/5.0 + src_offset/src_offset_range*join_range;
src_y1 = src_y - (x_dist/3.0 + src_offset) * y_mod;
src_x2 = src_x + x_dist/3.0 + src_offset/src_offset_range*join_range;
src_y2 = join_y;
// Path 2, (join_x, join_y) -> (dst_x, dst_y)
dst_x1 = MIN(dst_x, src_mod_x) - x_dist/5.0 - dst_offset/dst_offset_range*join_range;
dst_y1 = MIN(dst_y, src_mod_y + src_mod_h) - (x_dist/3.0 + dst_offset) * y_mod;
dst_x2 = MIN(dst_x, src_mod_x) - x_dist/3.0 - dst_offset/dst_offset_range*join_range;
dst_y2 = join_y;
// Curve through connections and normal (left->right) connections
// (These two cases are continuous)
} else {
/* The trick with this one is to define each curve's points exclusively in terms
* of the join point (ie nothing about the other module), and choose the join point
* cleverly */
// Calculate join point
double ratio = (x_dist - y_dist) / (y_dist + x_dist);
join_x = (src_x + dst_x)/2.0;
join_y = (src_y + dst_y)/2.0;
// Vertical centre point between the modules
join_y = (src_y < dst_y)
? (dst_mod_y - (dst_mod_y - (src_mod_y + src_mod_h)) / 2.0)
: (src_mod_y - (src_mod_y - (dst_mod_y + dst_mod_h)) / 2.0);
join_y -= (smallest_offset / smallest_offset_range * join_range) - join_range/2.0;
// Interpolate between (src_x < dst_x) case and (src_y == dst_y) case
if (src_x < dst_x && x_dist > y_dist) {
join_y *= (1.0-ratio);
join_y += (src_y + dst_y)/2.0 * ratio;
}
if (src_x < dst_x) {
join_y += ((smallest_offset/smallest_offset_range)) * join_range * (1.0-fabs(ratio));
join_x -= ((smallest_offset/smallest_offset_range)) * join_range * (1.0-fabs(ratio)) * y_mod;
}
//cerr << "ratio: " << ratio << endl;
// Curve through connections
if (dst_x < src_x) {
double src_x_offset = fabs(src_x - join_x)/8.0 + src_offset_range - src_offset/src_offset_range*join_range;
double dst_x_offset = fabs(dst_x - join_x)/8.0 + dst_offset_range + dst_offset/dst_offset_range*join_range;
double src_y_offset = fabs(src_y - join_y)/4.0 + src_offset/src_offset_range*(src_offset_range+join_range)/2.0;
double dst_y_offset = fabs(dst_y - join_y)/2.0 + (dst_offset_range-dst_offset)/dst_offset_range*(dst_offset_range+join_range)/2.0;
// Path 1 (src_x, src_y) -> (join_x, join_y)
src_x1 = src_x + src_x_offset;
src_y1 = join_y - src_y_offset * y_mod;
src_x2 = src_x + src_x_offset;
src_y2 = join_y;
// Path 2, (join_x, join_y) -> (dst_x, dst_y)
dst_x1 = dst_x - dst_x_offset;
dst_y1 = join_y + dst_y_offset * y_mod;
dst_x2 = dst_x - dst_x_offset;
dst_y2 = join_y;
// Normal connections
} else {
double src_x_offset = fabs(src_x - join_x)/8.0 + src_offset_range - src_offset/src_offset_range*join_range;
double dst_x_offset = fabs(dst_x - join_x)/8.0 + dst_offset_range + dst_offset/dst_offset_range*join_range;
double src_y_offset = fabs(src_y - join_y)/4.0 + src_offset/src_offset_range*(src_offset_range+join_range)/2.0;
double dst_y_offset = fabs(dst_y - join_y)/2.0 + (dst_offset_range-dst_offset)/dst_offset_range*(dst_offset_range+join_range)/2.0;
// Path 1 (src_x, src_y) -> (join_x, join_y)
src_x1 = src_x + src_x_offset;
// Interpolate from curve through case
if (x_dist < y_dist) {
// Smooth transition from (src_y) to (join_y - src_y_offset * y_mod)
src_y1 = (src_y * (1.0-fabs(ratio))) + ((join_y - src_y_offset * y_mod) * -ratio);
// Smooth transition from (join_x + src_offset_range/4.0 * -ratio) to (src_x + src_x_offset)
src_x2 = (join_x + src_offset_range/4.0 * -ratio)*(1.0-fabs(ratio)) + (src_x + src_x_offset)*-ratio;
} else {
src_y1 = src_y;
src_x2 = join_x + src_offset_range/4.0 * -ratio;
}
src_y2 = join_y - src_y_offset*(1.0-fabs(ratio)) * y_mod;
// Path 2, (join_x, join_y) -> (dst_x, dst_y)
dst_x1 = dst_x - dst_x_offset;
dst_y1 = dst_y;
// Interpolate from curve through case
if (x_dist < y_dist) {
// Smooth transition from (dst_y) to (join_y - dst_y_offset * y_mod)
dst_y1 = (dst_y * (1.0-fabs(ratio))) + ((join_y + dst_y_offset * y_mod) * -ratio);
// Smooth transition from (join_x + dst_offset_range/4.0 * -ratio) to (dst_x + dst_x_offset)
dst_x2 = (join_x - dst_offset_range/4.0 * -ratio)*(1.0-fabs(ratio)) + (dst_x - dst_x_offset)*-ratio;
} else {
dst_y1 = dst_y;
dst_x2 = join_x - dst_offset_range/4.0 * -ratio;
}
dst_y2 = join_y + dst_y_offset*(1.0-fabs(ratio)) * y_mod;
}
/*
} else {
double src_x_offset = fabs(src_x - join_x)/4.0 + src_offset_range*2.0 - src_offset;
double dst_x_offset = fabs(dst_x - join_x)/4.0 + dst_offset + dst_offset_range;
double y_offset = fabs(join_y - src_y)/2.0;
// Path 1 (src_x, src_y) -> (join_x, join_y)
src_x1 = src_x + src_x_offset/2.0;
src_y1 = src_y;
if (x_dist < y_dist)
src_x2 = join_x + src_x_offset * -ratio;
else
src_x2 = join_x + src_offset_range/4.0 * -ratio;
src_y2 = join_y - y_offset*(1.0-fabs(ratio)) * y_mod;
// Path 2, (join_x, join_y) -> (dst_x, dst_y)
dst_x1 = dst_x - dst_x_offset/2.0;
dst_y1 = dst_y;
if (x_dist < y_dist)
dst_x2 = join_x - dst_x_offset * -ratio;
else
dst_x2 = join_x - dst_x_offset/4.0 * -ratio;
dst_y2 = join_y + y_offset*(1.0-fabs(ratio)) * y_mod;
}*/
}
/*
cerr << "src_x1: " << src_x1 << endl;
cerr << "src_y1: " << src_y1 << endl;
cerr << "src_x2: " << src_x2 << endl;
cerr << "src_y2: " << src_x2 << endl;
cerr << "join_x: " << join_x << endl;
cerr << "join_y: " << join_y << endl;
cerr << "dst_x1: " << dst_x1 << endl;
cerr << "dst_y1: " << dst_y1 << endl;
cerr << "dst_x2: " << dst_x2 << endl;
cerr << "dst_y2: " << dst_x2 << endl << endl;
*/
m_path->reset();
//m_path->moveto(0,0);
m_path->moveto(src_x, src_y);
//m_path->lineto(join_x, join_y);
m_path->curveto(src_x1, src_y1, src_x2, src_y2, join_x, join_y);
m_path->curveto(dst_x2, dst_y2, dst_x1, dst_y1, dst_x, dst_y);
set_bpath(m_path);
}
#endif
/** Updates the path of the connection to match it's ports if they've moved.
*/
void
Connection::update_location()
{
const double src_x = m_source_port->connection_coords().get_x();
const double src_y = m_source_port->connection_coords().get_y();
const double dst_x = m_dest_port->connection_coords().get_x();
const double dst_y = m_dest_port->connection_coords().get_y();
const double src_mod_x = m_source_port->module()->property_x();
const double src_mod_y = m_source_port->module()->property_y();
const double src_mod_w = m_source_port->module()->width();
const double src_mod_h = m_source_port->module()->height();
const double dst_mod_x = m_dest_port->module()->property_x();
const double dst_mod_y = m_dest_port->module()->property_y();
const double dst_mod_w = m_dest_port->module()->width();
const double dst_mod_h = m_dest_port->module()->height();
// Vertical distance between modules
double y_mod_dist = dst_mod_y - src_mod_y - src_mod_h;
if (dst_y < src_y)
y_mod_dist = src_mod_y - dst_mod_y - dst_mod_h;
if (y_mod_dist < 1.0)
y_mod_dist = 1.0;
// Horizontal distance between modules
double x_mod_dist = dst_mod_x - src_mod_x - src_mod_w;
if (src_x > dst_x + src_mod_w)
x_mod_dist = src_mod_x - dst_mod_x - dst_mod_w;
if (x_mod_dist < 1.0)
x_mod_dist = 1.0;
// Y Modifier (-1 if src module is below dst module)
double y_mod = (src_y < dst_y) ? 1.0 : -1.0;
double x_dist = fabsl(src_x - dst_x);
double y_dist = fabsl(src_y - dst_y);
double src_port_offset = m_source_port->module()->port_connection_point_offset(m_source_port);
double src_offset_range = m_source_port->module()->port_connection_points_range();
double dst_port_offset = m_dest_port->module()->port_connection_point_offset(m_dest_port);
double dst_offset_range = m_dest_port->module()->port_connection_points_range();
double smallest_offset = (src_offset_range < dst_offset_range)
? src_port_offset : dst_port_offset;
double smallest_offset_range = (src_offset_range < dst_offset_range)
? m_source_port->module()->port_connection_points_range()
: m_dest_port->module()->port_connection_points_range();
double tallest_module_height = m_source_port->module()->height();
if (m_dest_port->module()->height() > tallest_module_height)
tallest_module_height = m_dest_port->module()->height();
double src_x1, src_y1, src_x2, src_y2, join_x, join_y; // Path 1
double dst_x2, dst_y2, dst_x1, dst_y1; // Path 2
src_x1 = src_y1 = src_x2 = src_y2 = join_x = join_y = dst_x2 = dst_y2 = dst_x1 = dst_y1 = 0.0;
double join_range = 20.0;
double src_offset = (src_y < dst_y)
? src_port_offset : src_offset_range - src_port_offset;
double dst_offset = (src_y < dst_y)
? dst_port_offset : dst_offset_range - dst_port_offset;
// Wrap around connections
if ((src_x > dst_x)
&& y_mod_dist < join_range*3.0
&& ((dst_x > src_mod_x+src_mod_w+join_range*2.0) || src_x > dst_x)
&& (! ((src_mod_y + src_mod_h < dst_mod_y - join_range*3.0) || (dst_mod_y + dst_mod_h < src_mod_h - join_range*3.0)))) {
//|| (dst_x < src_x)) {
static const double module_padding = 20.0;
// FIXME: completely different meanings in this case than the normal case
// (this one is better though)
smallest_offset = (src_offset_range < dst_offset_range)
? src_offset : dst_offset;
// Limit straight out distance
if (x_dist > 60.0)
x_dist = 60.0;
if (x_dist < 80.0 && y_dist > 40.0)
x_dist = 80.0;
// Calculate join point
join_x = dst_mod_x + dst_mod_w + x_mod_dist/2.0;
join_y = (src_y < dst_y)
? MIN(src_mod_y, dst_mod_y)
: MAX(src_mod_y + src_mod_h, dst_mod_y + dst_mod_h);
join_y -= (smallest_offset/smallest_offset_range*join_range + module_padding) * y_mod;
if (join_x > src_mod_x)
join_x = src_mod_x;
// Path 1 (src_x, src_y) -> (join_x, join_y)
src_x1 = src_x + x_dist/5.0 + src_offset/src_offset_range*join_range;
src_y1 = src_y - (x_dist/3.0 + src_offset) * y_mod;
src_x2 = src_x + x_dist/3.0 + src_offset/src_offset_range*join_range;
src_y2 = join_y;
// Path 2, (join_x, join_y) -> (dst_x, dst_y)
dst_x1 = MIN(dst_x, src_mod_x) - x_dist/5.0 - dst_offset/dst_offset_range*join_range;
dst_y1 = MIN(dst_y, src_mod_y + src_mod_h) - (x_dist/3.0 + dst_offset) * y_mod;
dst_x2 = MIN(dst_x, src_mod_x) - x_dist/3.0 - dst_offset/dst_offset_range*join_range;
dst_y2 = join_y;
// Curve through connections
} else if (dst_x < src_x) {
join_range = MIN(join_range, smallest_offset_range);
// Calculate join point
double ratio = (x_dist - y_dist) / (y_dist + x_dist);
join_x = (src_x + dst_x)/2.0;
join_y = (src_y + dst_y)/2.0;
// Vertical centre point between the modules
join_y = (src_y < dst_y)
? (dst_mod_y - (dst_mod_y - (src_mod_y + src_mod_h)) / 2.0)
: (src_mod_y - (src_mod_y - (dst_mod_y + dst_mod_h)) / 2.0);
join_y -= (smallest_offset / smallest_offset_range * join_range) - join_range/2.0;
// Interpolate between (src_x < dst_x) case and (src_y == dst_y) case
if (src_x < dst_x && x_dist > y_dist) {
join_y *= (1.0-ratio);
join_y += (src_y + dst_y)/2.0 * ratio;
}
if (src_x < dst_x) {
join_y += ((smallest_offset/smallest_offset_range)) * join_range * (1.0-fabs(ratio));
join_x -= ((smallest_offset/smallest_offset_range)) * join_range * (1.0-fabs(ratio)) * y_mod;
}
//cerr << "ratio: " << ratio << endl;
double src_x_offset = fabs(src_x - join_x)/8.0 + MAX(src_offset_range,join_range) - src_offset/src_offset_range*join_range;
double dst_x_offset = fabs(dst_x - join_x)/8.0 + MAX(dst_offset_range,join_range) + dst_offset/dst_offset_range*join_range;
double src_y_offset = fabs(src_y - join_y)/4.0 + src_offset/src_offset_range*(src_offset_range+join_range)/2.0;
double dst_y_offset = fabs(dst_y - join_y)/4.0 + (dst_offset_range-dst_offset)/dst_offset_range*(dst_offset_range+join_range)/2.0;
// Path 1 (src_x, src_y) -> (join_x, join_y)
src_x1 = src_x + src_x_offset;
src_y1 = join_y - src_y_offset * y_mod;
src_x2 = src_x + src_x_offset;
src_y2 = join_y;
// Path 2, (join_x, join_y) -> (dst_x, dst_y)
dst_x1 = dst_x - dst_x_offset;
dst_y1 = join_y + dst_y_offset * y_mod;
dst_x2 = dst_x - dst_x_offset;
dst_y2 = join_y;
// In between curve through and normal connections
/* } else if (x_dist < y_dist && src_mod_y + src_mod_h < dst_mod_y) {
// Calculate join point
double ratio = (x_dist - y_dist) / (y_dist + x_dist);
join_x = (src_x + dst_x)/2.0;
join_y = (src_y + dst_y)/2.0;
// Vertical centre point between the modules
join_y = (src_y < dst_y)
? (dst_mod_y - (dst_mod_y - (src_mod_y + src_mod_h)) / 2.0)
: (src_mod_y - (src_mod_y - (dst_mod_y + dst_mod_h)) / 2.0);
join_y -= (smallest_offset / smallest_offset_range * join_range) - join_range/2.0;
// Interpolate between (src_x < dst_x) case and (src_y == dst_y) case
if (src_x < dst_x && x_dist > y_dist) {
join_y *= (1.0-ratio);
join_y += (src_y + dst_y)/2.0 * ratio;
}
if (src_x < dst_x) {
join_y += ((smallest_offset/smallest_offset_range)) * join_range * (1.0-fabs(ratio));
join_x -= ((smallest_offset/smallest_offset_range)) * join_range * (1.0-fabs(ratio)) * y_mod;
}
double src_x_offset = fabs(src_x - join_x)/8.0 + src_offset_range - src_offset/src_offset_range*join_range;
double dst_x_offset = fabs(dst_x - join_x)/8.0 + dst_offset_range + dst_offset/dst_offset_range*join_range;
double src_y_offset = fabs(src_y - join_y)/4.0 + src_offset/src_offset_range*(src_offset_range+join_range)/2.0;
double dst_y_offset = fabs(dst_y - join_y)/2.0 + (dst_offset_range-dst_offset)/dst_offset_range*(dst_offset_range+join_range)/2.0;
// Path 1 (src_x, src_y) -> (join_x, join_y)
src_x1 = src_x + src_x_offset;
// Interpolate from curve through case
if (x_dist < y_dist) {
// Smooth transition from (src_y) to (join_y - src_y_offset * y_mod)
src_y1 = (src_y * (1.0-fabs(ratio))) + ((join_y - src_y_offset * y_mod) * -ratio);
// Smooth transition from (join_x + src_offset_range/4.0 * -ratio) to (src_x + src_x_offset)
src_x2 = (join_x + src_offset_range/4.0 * -ratio)*(1.0-fabs(ratio)) + (src_x + src_x_offset)*-ratio;
} else {
src_y1 = src_y;
src_x2 = join_x + src_offset_range/4.0 * -ratio;
}
src_y2 = join_y - src_y_offset*(1.0-fabs(ratio)) * y_mod;
// Path 2, (join_x, join_y) -> (dst_x, dst_y)
dst_x1 = dst_x - dst_x_offset;
dst_y1 = dst_y;
// Interpolate from curve through case
if (x_dist < y_dist) {
// Smooth transition from (dst_y) to (join_y - dst_y_offset * y_mod)
dst_y1 = (dst_y * (1.0-fabs(ratio))) + ((join_y + dst_y_offset * y_mod) * -ratio);
// Smooth transition from (join_x + dst_offset_range/4.0 * -ratio) to (dst_x + dst_x_offset)
dst_x2 = (join_x - dst_offset_range/4.0 * -ratio)*(1.0-fabs(ratio)) + (dst_x - dst_x_offset)*-ratio;
} else {
dst_y1 = dst_y;
dst_x2 = join_x - dst_offset_range/4.0 * -ratio;
}
dst_y2 = join_y + dst_y_offset*(1.0-fabs(ratio)) * y_mod;
*/
// "Normal" connections
} else {
join_x = (src_x + dst_x)/2.0;
join_y = (src_y + dst_y)/2.0;
#if 0
join_range = MIN(join_range, x_dist/2.0);
/************ Find join point **************/
//const double join_range = 15.0;
//const double join_range = MIN(smallest_offset_range, x_dist/2.0);
//const double join_range = MIN(30.0, x_dist/2.0);
// Calculate join point
double ratio = (x_dist - y_dist) / (y_dist + x_dist);
cerr << "ratio: " << ratio << endl;
/* if (MAX(x_dist, y_dist) > smallest_offset_range * 2.0) {
// Vertical centre point between the modules
join_y = (src_y < dst_y)
? (dst_mod_y - (dst_mod_y - (src_mod_y + src_mod_h)) / 2.0)
: (src_mod_y - (src_mod_y - (dst_mod_y + dst_mod_h)) / 2.0);
join_y -= (smallest_offset / smallest_offset_range * join_range) - join_range/2.0;
// Interpolate between (src_x < dst_x) case and (src_y == dst_y) case
if (src_x < dst_x && x_dist > y_dist) {
join_y *= (1.0-ratio);
join_y += (src_y + dst_y)/2.0 * ratio;
}
}*/
if (src_x < dst_x) {
// join_y += ((smallest_offset/smallest_offset_range)) * join_range * (1.0-fabs(ratio));
join_x -= ((smallest_offset/smallest_offset_range)) * join_range * (1.0-fabs(ratio)) * y_mod;
join_x += join_range/2.0 * (1.0-fabs(ratio)); // center
}
#endif
/*************************************************/
// Path 1 (src_x, src_y) -> (join_x, join_y)
// Control point 1
src_x1 = src_x + fabs(join_x - src_x) / 2.0;
src_y1 = src_y;
// Control point 2
src_x2 = join_x - fabs(join_x - src_x) / 4.0;
src_y2 = join_y - fabs(join_y - src_y) / 2.0 * y_mod;
// Path 2, (join_x, join_y) -> (dst_x, dst_y)
// Control point 1
dst_x1 = dst_x - fabs(join_x - dst_x) / 2.0;
dst_y1 = dst_y;
// Control point 2
dst_x2 = join_x + fabs(join_x - dst_x) / 4.0;
dst_y2 = join_y + fabs(join_y - dst_y) / 2.0 * y_mod;
}
// This was broken in libgnomecanvasmm with GTK 2.8. Nice work, guys.
/*
m_path->reset();
m_path->moveto(src_x, src_y);
m_path->curveto(src_x1, src_y1, src_x2, src_y2, join_x, join_y);
m_path->curveto(dst_x2, dst_y2, dst_x1, dst_y1, dst_x, dst_y);
set_bpath(m_path);
*/
// Work around it w/ the C API
gnome_canvas_path_def_reset(m_path);
gnome_canvas_path_def_moveto(m_path, src_x, src_y);
gnome_canvas_path_def_curveto(m_path, src_x1, src_y1, src_x2, src_y2, join_x, join_y);
gnome_canvas_path_def_curveto(m_path, dst_x2, dst_y2, dst_x1, dst_y1, dst_x, dst_y);
GnomeCanvasBpath* c_obj = gobj();
gnome_canvas_item_set(GNOME_CANVAS_ITEM(c_obj), "bpath", m_path, NULL);
}
/** Removes the reference to this connection contained in the ports.
*
* Must be called before destroying a connection.
*/
void
Connection::disconnect()
{
m_source_port->remove_connection(this);
m_dest_port->remove_connection(this);
m_source_port = NULL;
m_dest_port = NULL;
}
void
Connection::hilite(bool b)
{
if (b)
property_outline_color_rgba() = 0xFF0000FF;
else
property_outline_color_rgba() = m_colour;
}
void
Connection::selected(bool selected)
{
m_selected = selected;
if (selected)
property_dash() = m_patch_bay->select_dash();
else
property_dash() = NULL;
}
} // namespace LibFlowCanvas

View file

@ -0,0 +1,884 @@
/* This file is part of FlowCanvas. Copyright (C) 2005 Dave Robillard.
*
* FlowCanvas 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 2 of the License, or (at your option) any later
* version.
*
* FlowCanvas 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 this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "flowcanvas/FlowCanvas.h"
#include <cassert>
#include <map>
#include <iostream>
#include <cmath>
#include "flowcanvas/Port.h"
#include "flowcanvas/Module.h"
using std::cerr; using std::cout; using std::endl;
namespace LibFlowCanvas {
FlowCanvas::FlowCanvas(double width, double height)
: m_selected_port(NULL),
m_connect_port(NULL),
m_zoom(1.0),
m_width(width),
m_height(height),
m_drag_state(NOT_DRAGGING),
m_base_rect(*root(), 0, 0, width, height),
m_select_rect(NULL),
m_select_dash(NULL)
{
set_scroll_region(0.0, 0.0, width, height);
set_center_scroll_region(true);
m_base_rect.property_fill_color_rgba() = 0x000000FF;
m_base_rect.show();
//m_base_rect.signal_event().connect(sigc::mem_fun(this, &FlowCanvas::scroll_drag_handler));
m_base_rect.signal_event().connect(sigc::mem_fun(this, &FlowCanvas::select_drag_handler));
m_base_rect.signal_event().connect(sigc::mem_fun(this, &FlowCanvas::connection_drag_handler));
m_base_rect.signal_event().connect(sigc::mem_fun(this, &FlowCanvas::canvas_event));
set_dither(Gdk::RGB_DITHER_NORMAL); // NONE or NORMAL or MAX
// Dash style for selected modules and selection box
m_select_dash = new ArtVpathDash();
m_select_dash->n_dash = 2;
m_select_dash->dash = art_new(double, 2);
m_select_dash->dash[0] = 5;
m_select_dash->dash[1] = 5;
Glib::signal_timeout().connect(
sigc::mem_fun(this, &FlowCanvas::animate_selected), 150);
}
FlowCanvas::~FlowCanvas()
{
destroy();
}
void
FlowCanvas::zoom(float pix_per_unit)
{
// Round to .25
m_zoom = static_cast<int>(pix_per_unit*4) / 4.0;
if (m_zoom < 0.25)
m_zoom = 0.25;
set_pixels_per_unit(m_zoom);
for (ModuleMap::iterator m = m_modules.begin(); m != m_modules.end(); ++m)
(*m).second->zoom(m_zoom);
}
void
FlowCanvas::clear_selection()
{
for (list<Module*>::iterator m = m_selected_modules.begin(); m != m_selected_modules.end(); ++m)
(*m)->selected(false);
for (list<Connection*>::iterator c = m_selected_connections.begin(); c != m_selected_connections.end(); ++c)
(*c)->selected(false);
m_selected_modules.clear();
m_selected_connections.clear();
}
/** Add a module to the current selection, and automagically select any connections
* between selected modules */
void
FlowCanvas::select_module(Module* m)
{
assert(! m->selected());
m_selected_modules.push_back(m);
Connection* c;
for (ConnectionList::iterator i = m_connections.begin(); i != m_connections.end(); ++i) {
c = (*i);
if ( !c->selected()) {
if (c->source_port()->module() == m && c->dest_port()->module()->selected()) {
c->selected(true);
m_selected_connections.push_back(c);
} else if (c->dest_port()->module() == m && c->source_port()->module()->selected()) {
c->selected(true);
m_selected_connections.push_back(c);
}
}
}
m->selected(true);
}
void
FlowCanvas::unselect_module(Module* m)
{
assert(m->selected());
// Remove any connections that aren't selected anymore because this module isn't
Connection* c;
for (ConnectionList::iterator i = m_selected_connections.begin(); i != m_selected_connections.end();) {
c = (*i);
if (c->selected()
&& ((c->source_port()->module() == m && c->dest_port()->module()->selected())
|| c->dest_port()->module() == m && c->source_port()->module()->selected()))
{
c->selected(false);
i = m_selected_connections.erase(i);
} else {
++i;
}
}
// Remove the module
for (list<Module*>::iterator i = m_selected_modules.begin(); i != m_selected_modules.end(); ++i) {
if ((*i) == m) {
m_selected_modules.erase(i);
break;
}
}
m->selected(false);
}
/** Removes all ports and connections and modules.
*/
void
FlowCanvas::destroy()
{
for (ModuleMap::iterator m = m_modules.begin(); m != m_modules.end(); ++m)
delete (*m).second;
for (ConnectionList::iterator c = m_connections.begin(); c != m_connections.end(); ++c)
delete (*c);
m_modules.clear();
m_connections.clear();
m_selected_port = NULL;
m_connect_port = NULL;
}
void
FlowCanvas::selected_port(Port* p)
{
if (m_selected_port != NULL)
m_selected_port->rect()->property_fill_color_rgba() = m_selected_port->colour(); // "turn off" the old one
m_selected_port = p;
if (p != NULL)
m_selected_port->rect()->property_fill_color() = "red";
}
Module*
FlowCanvas::find_module(const string& name)
{
ModuleMap::iterator m = m_modules.find(name);
if (m != m_modules.end())
return (*m).second;
else
return NULL;
}
/** Sets the passed module's location to a reasonable default.
*/
void
FlowCanvas::set_default_placement(Module* m)
{
assert(m != NULL);
// Simple cascade. This will get more clever in the future.
double x = ((m_width / 2.0) + (m_modules.size() * 25));
double y = ((m_height / 2.0) + (m_modules.size() * 25));
m->move_to(x, y);
}
void
FlowCanvas::add_module(Module* m)
{
assert(m != NULL);
std::pair<string, Module*> p(m->name(), m);
m_modules.insert(p);
}
void
FlowCanvas::remove_module(const string& name)
{
ModuleMap::iterator m = m_modules.find(name);
if (m != m_modules.end()) {
delete (*m).second;
m_modules.erase(m);
} else {
cerr << "[FlowCanvas::remove_module] Unable to find module!" << endl;
}
}
Port*
FlowCanvas::find_port(const string& node_name, const string& port_name)
{
Module* module = NULL;
Port* port = NULL;
for (ModuleMap::iterator i = m_modules.begin(); i != m_modules.end(); ++i) {
module = (*i).second;
port = module->port(port_name);
if (module->name() == node_name && port != NULL)
return port;
}
cerr << "[FlowCanvas::find_port] Failed to find port " <<
node_name << ":" << port_name << endl;
return NULL;
}
void
FlowCanvas::rename_module(const string& old_name, const string& new_name)
{
Module* module = NULL;
for (ModuleMap::iterator i = m_modules.begin(); i != m_modules.end(); ++i) {
module = (*i).second;
assert(module != NULL);
if (module->name() == old_name) {
m_modules.erase(i);
module->name(new_name);
add_module(module);
return;
}
}
cerr << "[FlowCanvas::rename_module] Failed to find module " <<
old_name << endl;
}
/** Add a connection.
*/
void
FlowCanvas::add_connection(const string& node1_name, const string& port1_name,
const string& node2_name, const string& port2_name)
{
Port* port1 = find_port(node1_name, port1_name);
Port* port2 = find_port(node2_name, port2_name);
if (port1 == NULL) {
cerr << "Unable to find port " << node1_name << ":" << port1_name
<< " to make connection." << endl;
} else if (port2 == NULL) {
cerr << "Unable to find port " << node2_name << ":" << port2_name
<< " to make connection." << endl;
} else {
add_connection(port1, port2);
}
}
bool
FlowCanvas::remove_connection(Port* port1, Port* port2)
{
assert(port1 != NULL);
assert(port2 != NULL);
Connection* c = get_connection(port1, port2);
if (c == NULL) {
cerr << "Couldn't find connection.\n";
return false;
} else {
remove_connection(c);
return true;
}
}
/** Remove a connection.
*
* Returns whether or not the connection was found (and removed).
*/
bool
FlowCanvas::remove_connection(const string& mod1_name, const string& port1_name, const string& mod2_name, const string& port2_name)
{
Connection* c = get_connection(find_port(mod1_name, port1_name),
find_port(mod2_name, port2_name));
if (c == NULL) {
cerr << "Couldn't find connection.\n";
return false;
} else {
remove_connection(c);
return true;
}
}
bool
FlowCanvas::are_connected(const Port* port1, const Port* port2)
{
assert(port1 != NULL);
assert(port2 != NULL);
ConnectionList::const_iterator c;
const Connection* connection;
for (c = m_connections.begin(); c != m_connections.end(); ++c) {
connection = *c;
if (connection->source_port() == port1 && connection->dest_port() == port2)
return true;
if (connection->source_port() == port2 && connection->dest_port() == port1)
return true;
}
return false;
}
Connection*
FlowCanvas::get_connection(const Port* port1, const Port* port2)
{
assert(port1 != NULL);
assert(port2 != NULL);
for (ConnectionList::iterator i = m_connections.begin(); i != m_connections.end(); ++i) {
if ( (*i)->source_port() == port1 && (*i)->dest_port() == port2 )
return *i;
else if ( (*i)->dest_port() == port1 && (*i)->source_port() == port2 )
return *i;
}
return NULL;
}
void
FlowCanvas::add_connection(Port* port1, Port* port2)
{
assert(port1->is_input() != port2->is_input());
assert(port1->is_output() != port2->is_output());
Port* src_port = NULL;
Port* dst_port = NULL;
if (port1->is_output() && port2->is_input()) {
src_port = port1;
dst_port = port2;
} else {
src_port = port2;
dst_port = port1;
}
// Create (graphical) connection object
if (get_connection(port1, port2) == NULL) {
Connection* c = new Connection(this, src_port, dst_port);
port1->add_connection(c);
port2->add_connection(c);
m_connections.push_back(c);
}
}
void
FlowCanvas::remove_connection(Connection* connection)
{
assert(connection != NULL);
ConnectionList::iterator i = find(m_connections.begin(), m_connections.end(), connection);
Connection* c = *i;
c->disconnect();
m_connections.erase(i);
delete c;
}
/** Called when two ports are 'toggled' (connected or disconnected)
*/
void
FlowCanvas::ports_joined(Port* port1, Port* port2)
{
assert(port1 != NULL);
assert(port2 != NULL);
port1->hilite(false);
port2->hilite(false);
string src_mod_name, dst_mod_name, src_port_name, dst_port_name;
Port* src_port = NULL;
Port* dst_port = NULL;
if (port2->is_input() && ! port1->is_input()) {
src_port = port1;
dst_port = port2;
} else if ( ! port2->is_input() && port1->is_input()) {
src_port = port2;
dst_port = port1;
} else {
return;
}
if (are_connected(src_port, dst_port))
disconnect(src_port, dst_port);
else
connect(src_port, dst_port);
}
/** Event handler for ports.
*
* These events can't be handled in the Port class because they have to do with
* connections etc. which deal with multiple ports (ie m_selected_port). Ports
* pass their events on to this function to get around this.
*/
bool
FlowCanvas::port_event(GdkEvent* event, Port* port)
{
static bool port_dragging = false;
bool handled = true;
switch (event->type) {
case GDK_BUTTON_PRESS:
if (event->button.button == 1) {
port_dragging = true;
} else if (event->button.button == 3) {
m_selected_port = port;
port->popup_menu(event->button.button, event->button.time);
} else {
handled = false;
}
break;
case GDK_BUTTON_RELEASE:
if (port_dragging) {
if (m_connect_port == NULL) {
selected_port(port);
m_connect_port = port;
} else {
ports_joined(port, m_connect_port);
m_connect_port = NULL;
selected_port(NULL);
}
port_dragging = false;
} else {
handled = false;
}
break;
case GDK_ENTER_NOTIFY:
if (port != m_selected_port)
port->hilite(true);
break;
case GDK_LEAVE_NOTIFY:
if (port_dragging) {
m_drag_state = CONNECTION;
m_connect_port = port;
m_base_rect.grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
Gdk::Cursor(Gdk::CROSSHAIR), event->button.time);
port_dragging = false;
} else {
if (port != m_selected_port)
port->hilite(false);
}
break;
default:
handled = false;
}
return handled;
}
/*
bool
FlowCanvas::canvas_event(GdkEvent* event)
{
if (m_connection_dragging) {
return connection_drag_handler(event);
} else if (m_scroll_dragging) {
return scroll_drag_handler(event);
} else if (event->type == GDK_BUTTON_PRESS && event->button.button == 2) {
get_scroll_offsets(m_scroll_offset_x, m_scroll_offset_y);
//double x, y;
//window_to_world(event->button.x, event->button.y, x, y);
//w2c(x, y, m_scroll_origin_x, m_scroll_origin_y);
m_scroll_origin_x = event->button.x;
m_scroll_origin_y = event->button.y;
//root()->w2i(m_scroll_origin_x, m_scroll_origin_y);
//window_to_world(event->button.x, event->button.y, x, y);
//w2c(x, y, m_scroll_origin_x, m_scroll_origin_y);
m_scroll_dragging = true;
}
return false;
}
*/
/* I can not get this to work for the life of me.
* Man I hate gnomecanvas.
bool
FlowCanvas::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 x_offset = 0;
static double y_offset = 0;
static double scroll_offset_x = 0;
static double scroll_offset_y = 0;
static double last_x = 0;
static double last_y = 0;
bool first_motion = true;
if (event->type == GDK_BUTTON_PRESS && event->button.button == 2) {
m_base_rect.grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK,
Gdk::Cursor(Gdk::FLEUR), event->button.time);
get_scroll_offsets(original_scroll_x, original_scroll_y);
scroll_offset_x = original_scroll_x;
scroll_offset_y = original_scroll_y;
origin_x = event->button.x;
origin_y = event->button.y;
last_x = origin_x;
last_y = origin_y;
first_motion = true;
m_scroll_dragging = true;
} else if (event->type == GDK_MOTION_NOTIFY && m_scroll_dragging) {
// These are world-relative coordinates
double x = event->motion.x_root;
double y = event->motion.y_root;
//c2w(x, y, x, y);
//world_to_window(x, y, x, y);
//window_to_world(event->button.x, event->button.y, x, y);
//w2c(x, y, x, y);
x_offset += last_x - x;//x + original_scroll_x;
y_offset += last_y - y;// + original_scroll_y;
//cerr << "Coord: (" << x << "," << y << ")\n";
//cerr << "Offset: (" << x_offset << "," << y_offset << ")\n";
int temp_x;
int temp_y;
w2c(lrint(x_offset), lrint(y_offset),
temp_x, temp_y);
scroll_offset_x += temp_x;
scroll_offset_y += temp_y;
scroll_to(scroll_offset_x,
scroll_offset_y);
last_x = x;
last_y = y;
} else if (event->type == GDK_BUTTON_RELEASE && m_scroll_dragging) {
m_base_rect.ungrab(event->button.time);
m_scroll_dragging = false;
} else {
handled = false;
}
return handled;
return false;
}
*/
bool
FlowCanvas::select_drag_handler(GdkEvent* event)
{
Module* module = NULL;
if (event->type == GDK_BUTTON_PRESS && event->button.button == 1) {
assert(m_select_rect == NULL);
m_drag_state = SELECT;
if ( !(event->button.state & GDK_CONTROL_MASK))
clear_selection();
m_select_rect = new Gnome::Canvas::Rect(*root(),
event->button.x, event->button.y, event->button.x, event->button.y);
m_select_rect->property_fill_color_rgba() = 0x273344FF;
m_select_rect->property_outline_color_rgba() = 0xEEEEFFFF;
m_select_rect->lower_to_bottom();
m_base_rect.lower_to_bottom();
return true;
} else if (event->type == GDK_MOTION_NOTIFY && m_drag_state == SELECT) {
assert(m_select_rect != NULL);
m_select_rect->property_x2() = event->button.x;
m_select_rect->property_y2() = event->button.y;
return true;
} else if (event->type == GDK_BUTTON_RELEASE && m_drag_state == SELECT) {
// Select all modules within rect
for (ModuleMap::iterator i = m_modules.begin(); i != m_modules.end(); ++i) {
module = (*i).second;
if (module->is_within(m_select_rect)) {
if (module->selected())
unselect_module(module);
else
select_module(module);
}
}
delete m_select_rect;
m_select_rect = NULL;
m_drag_state = NOT_DRAGGING;
return true;
}
return false;
}
/** Updates m_select_dash for rotation effect, and updates any
* selected item's borders (and the selection rectangle).
*/
bool
FlowCanvas::animate_selected()
{
static int i = 10;
if (--i == 0)
i = 10;
m_select_dash->offset = i;
if (m_select_rect != NULL)
m_select_rect->property_dash() = m_select_dash;
for (list<Module*>::iterator m = m_selected_modules.begin(); m != m_selected_modules.end(); ++m)
(*m)->rect()->property_dash() = m_select_dash;
for (list<Connection*>::iterator c = m_selected_connections.begin(); c != m_selected_connections.end(); ++c)
(*c)->property_dash() = m_select_dash;
return true;
}
bool
FlowCanvas::connection_drag_handler(GdkEvent* event)
{
bool handled = true;
// These are invisible, just used for making connections (while dragging)
static Module* drag_module = NULL;
static Port* drag_port = NULL;
static Connection* drag_connection = NULL;
static Port* snapped_port = NULL;
static bool snapped = false;
if (event->type == GDK_BUTTON_PRESS && event->button.button == 2) {
m_drag_state = SCROLL;
} else if (event->type == GDK_MOTION_NOTIFY && m_drag_state == CONNECTION) {
double x = event->button.x, y = event->button.y;
root()->w2i(x, y);
if (drag_connection == NULL) { // Havn't created the connection yet
assert(drag_port == NULL);
assert(m_connect_port != NULL);
drag_module = new Module(this, "");
bool drag_port_is_input = true;
if (m_connect_port->is_input())
drag_port_is_input = false;
drag_port = new Port(drag_module, "", drag_port_is_input, m_connect_port->colour());
drag_module->add_port(drag_port);
//drag_port->hide();
drag_module->hide();
drag_module->move_to(x, y);
drag_port->property_x() = 0;
drag_port->property_y() = 0;
drag_port->rect()->property_x2() = 1;
drag_port->rect()->property_y2() = 1;
if (drag_port_is_input)
drag_connection = new Connection(this, m_connect_port, drag_port);
else
drag_connection = new Connection(this, drag_port, m_connect_port);
drag_connection->update_location();
//drag_connection->property_line_style() = Gdk::LINE_DOUBLE_DASH;
//drag_connection->property_last_arrowhead() = true;
}
if (snapped) {
if (drag_connection != NULL) drag_connection->hide();
Port* p = get_port_at(x, y);
if (drag_connection != NULL) drag_connection->show();
if (p != NULL) {
if (p != m_selected_port) {
if (snapped_port != NULL)
snapped_port->hilite(false);
p->hilite(true);
snapped_port = p;
}
drag_module->property_x() = p->module()->property_x().get_value();
drag_module->rect()->property_x2() = p->module()->rect()->property_x2().get_value();
drag_module->property_y() = p->module()->property_y().get_value();
drag_module->rect()->property_y2() = p->module()->rect()->property_y2().get_value();
drag_port->property_x() = p->property_x().get_value();
drag_port->property_y() = p->property_y().get_value();
} else { // off the port now, unsnap
if (snapped_port != NULL)
snapped_port->hilite(false);
snapped_port = NULL;
snapped = false;
drag_module->property_x() = x;
drag_module->property_y() = y;
drag_port->property_x() = 0;
drag_port->property_y() = 0;
drag_port->rect()->property_x2() = 1;
drag_port->rect()->property_y2() = 1;
}
drag_connection->update_location();
} else { // not snapped to a port
assert(drag_module != NULL);
assert(drag_port != NULL);
assert(m_connect_port != NULL);
// "Snap" to port, if we're on a port and it's the right direction
if (drag_connection != NULL) drag_connection->hide();
Port* p = get_port_at(x, y);
if (drag_connection != NULL) drag_connection->show();
if (p != NULL && p->is_input() != m_connect_port->is_input()) {
p->hilite(true);
snapped_port = p;
snapped = true;
// Make drag module and port exactly the same size/loc as the snapped
drag_module->move_to(p->module()->property_x().get_value(), p->module()->property_y().get_value());
drag_module->width(p->module()->width());
drag_module->height(p->module()->height());
drag_port->property_x() = p->property_x().get_value();
drag_port->property_y() = p->property_y().get_value();
// Make the drag port as wide as the snapped port so the connection coords are the same
drag_port->rect()->property_x2() = p->rect()->property_x2().get_value();
drag_port->rect()->property_y2() = p->rect()->property_y2().get_value();
} else {
drag_module->property_x() = x;
drag_module->property_y() = y;
}
drag_connection->update_location();
}
} else if (event->type == GDK_BUTTON_RELEASE && m_drag_state == CONNECTION) {
m_base_rect.ungrab(event->button.time);
double x = event->button.x;
double y = event->button.y;
m_base_rect.i2w(x, y);
if (drag_connection != NULL) drag_connection->hide();
Port* p = get_port_at(x, y);
if (drag_connection != NULL) drag_connection->show();
if (p != NULL) {
if (p == m_connect_port) { // drag ended on same port it started on
if (m_selected_port == NULL) { // no active port, just activate (hilite) it
selected_port(m_connect_port);
} else { // there is already an active port, connect it with this one
if (m_selected_port != m_connect_port)
ports_joined(m_selected_port, m_connect_port);
selected_port(NULL);
m_connect_port = NULL;
snapped_port = NULL;
}
} else { // drag ended on different port
//p->hilite(false);
ports_joined(m_connect_port, p);
selected_port(NULL);
m_connect_port = NULL;
snapped_port = NULL;
}
}
// Clean up dragging stuff
if (m_connect_port != NULL)
m_connect_port->hilite(false);
m_drag_state = NOT_DRAGGING;
delete drag_connection;
drag_connection = NULL;
//delete drag_port;
drag_port = NULL;
delete drag_module; // deletes drag_port
drag_module = NULL;
snapped_port = NULL;
} else {
handled = false;
}
return handled;
}
Port*
FlowCanvas::get_port_at(double x, double y)
{
Gnome::Canvas::Item* item = get_item_at(x, y);
if (item == NULL) return NULL;
Port* p = NULL;
// Loop through every port and see if the item at these coordinates is that port
// yes, this is disgusting ;)
for (ModuleMap::iterator i = m_modules.begin(); i != m_modules.end(); ++i) {
for (PortList::iterator j = (*i).second->ports().begin(); j != (*i).second->ports().end(); ++j) {
p = (*j);
if ((Gnome::Canvas::Item*)p == item
|| (Gnome::Canvas::Item*)(p->rect()) == item
|| (Gnome::Canvas::Item*)(p->label()) == item) {
return p;
}
}
}
return NULL;
}
/*
void
FlowCanvas::port_menu_disconnect_all()
{
Connection* c = NULL;
list<Connection*> temp_list = m_selected_port->connections();
for (list<Connection*>::iterator i = temp_list.begin(); i != temp_list.end(); ++i) {
c = *i;
disconnect(c->source_port(), c->dest_port());
}
selected_port(NULL);
}
*/
} // namespace LibFlowCanvas

View file

@ -0,0 +1,472 @@
/* This file is part of FlowCanvas. Copyright (C) 2005 Dave Robillard.
*
* FlowCanvas 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 2 of the License, or (at your option) any later
* version.
*
* FlowCanvas 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 this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "flowcanvas/Module.h"
#include "flowcanvas/FlowCanvas.h"
#include <functional>
#include <list>
#include <cstdlib>
#include <cassert>
#include <cmath>
using std::string;
namespace LibFlowCanvas {
static const int MODULE_FILL_COLOUR = 0x122A3CFF;
static const int MODULE_OUTLINE_COLOUR = 0x8899AAFF;
static const int MODULE_TITLE_COLOUR = 0xDDEEFFFF;
Module::Module(FlowCanvas* patch_bay, const string& name, double x, double y)
: Gnome::Canvas::Group(*patch_bay->root(), x, y),
m_name(name),
m_selected(false),
m_patch_bay(patch_bay),
m_module_box(*this, 0, 0, 0, 0), // w, h set later
m_canvas_title(*this, 0, 6, name) // x set later
{
assert(m_patch_bay != NULL);
m_module_box.property_fill_color_rgba() = MODULE_FILL_COLOUR;
m_module_box.property_outline_color_rgba() = MODULE_OUTLINE_COLOUR;
m_module_box.property_join_style() = Gdk::JOIN_ROUND;
border_width(1.0);
m_canvas_title.property_size_set() = true;
m_canvas_title.property_size() = 10000;
m_canvas_title.property_weight_set() = true;
m_canvas_title.property_weight() = 400;
m_canvas_title.property_fill_color_rgba() = MODULE_TITLE_COLOUR;
width(m_canvas_title.property_text_width() + 6.0);
height(m_canvas_title.property_text_height() + 2.0);
m_canvas_title.property_x() = m_width/2.0;
signal_event().connect(sigc::mem_fun(this, &Module::module_event));
}
Module::~Module()
{
if (m_selected) {
for (list<Module*>::iterator i = m_patch_bay->selected_modules().begin();
i != m_patch_bay->selected_modules().end(); ++i)
{
if ((*i) == this) {
m_patch_bay->selected_modules().erase(i);
break;
}
}
}
for (PortList::iterator p = m_ports.begin(); p != m_ports.end(); ++p)
delete (*p);
}
/** Set the border width of the module.
*
* Do NOT directly set the width_units property on the rect, use this function.
*/
void
Module::border_width(double w)
{
m_border_width = w;
m_module_box.property_width_units() = w;
}
bool
Module::module_event(GdkEvent* event)
{
assert(event != NULL);
static double x, y;
static double drag_start_x, drag_start_y;
double module_x, module_y; // FIXME: bad name, actually mouse click loc
static bool dragging = false;
bool handled = true;
module_x = event->button.x;
module_y = event->button.y;
property_parent().get_value()->w2i(module_x, module_y);
switch (event->type) {
case GDK_2BUTTON_PRESS:
on_double_click();
handled = true;
break;
case GDK_BUTTON_PRESS:
if (event->button.button == 1) {
x = module_x;
y = module_y;
// Set these so we can tell on a button release if a drag actually
// happened (if not, it's just a click)
drag_start_x = x;
drag_start_y = y;
grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK,
Gdk::Cursor(Gdk::FLEUR),
event->button.time);
dragging = true;
} else if (event->button.button == 2) {
on_double_click();
handled = true;
} else if (event->button.button == 3) {
show_menu(&event->button);
} else {
handled = false;
}
break;
case GDK_MOTION_NOTIFY:
if ((dragging && (event->motion.state & GDK_BUTTON1_MASK))) {
double new_x = module_x;
double new_y = module_y;
// Move any other selected modules if we're selected
if (m_selected) {
for (list<Module*>::iterator i = m_patch_bay->selected_modules().begin();
i != m_patch_bay->selected_modules().end(); ++i)
{
(*i)->move(new_x - x, new_y - y);
}
} else {
move(new_x - x, new_y - y);
}
x = new_x;
y = new_y;
} else {
handled = false;
}
break;
case GDK_BUTTON_RELEASE:
if (dragging) {
ungrab(event->button.time);
dragging = false;
if (module_x != drag_start_x || module_y != drag_start_y) {// dragged
store_location();
} else { // just a click
if (m_selected) {
m_patch_bay->unselect_module(this);
assert(!m_selected);
} else {
if ( !(event->button.state & GDK_CONTROL_MASK))
m_patch_bay->clear_selection();
m_patch_bay->select_module(this);
assert(m_selected);
}
}
} else {
handled = false;
}
break;
case GDK_ENTER_NOTIFY:
hilite(true);
raise_to_top();
for (PortList::iterator p = m_ports.begin(); p != m_ports.end(); ++p)
(*p)->raise_connections();
break;
case GDK_LEAVE_NOTIFY:
hilite(false);
break;
default:
handled = false;
}
return handled;
}
void
Module::zoom(float z)
{
m_canvas_title.property_size() = static_cast<int>(roundf(10000.0f * z));
for (PortList::iterator p = m_ports.begin(); p != m_ports.end(); ++p)
(*p)->zoom(z);
}
void
Module::hilite(bool b)
{
if (b) {
m_module_box.property_fill_color_rgba() = 0x223553FF;
} else {
m_module_box.property_fill_color_rgba() = MODULE_FILL_COLOUR;
}
}
void
Module::selected(bool selected)
{
m_selected = selected;
if (selected) {
m_module_box.property_fill_color_rgba() = 0x223553FF;
m_module_box.property_outline_color_rgba() = 0xEEEEFFFF;
m_module_box.property_dash() = m_patch_bay->select_dash();
m_canvas_title.property_fill_color_rgba() = 0xEEEEFFFF;
//for (PortList::iterator p = m_ports.begin(); p != m_ports.end(); ++p)
// (*p)->rect()->property_outline_color_rgba() = 0xEEEEFFFF;
} else {
m_module_box.property_fill_color_rgba() = MODULE_FILL_COLOUR;
m_module_box.property_outline_color_rgba() = MODULE_OUTLINE_COLOUR;
m_module_box.property_dash() = NULL;
m_canvas_title.property_fill_color_rgba() = MODULE_TITLE_COLOUR;
//for (PortList::iterator p = m_ports.begin(); p != m_ports.end(); ++p)
// (*p)->rect()->property_outline_color_rgba() = 0x8899AAFF;
}
}
bool
Module::is_within(const Gnome::Canvas::Rect* const rect)
{
const double x1 = rect->property_x1();
const double y1 = rect->property_y1();
const double x2 = rect->property_x2();
const double y2 = rect->property_y2();
if (x1 < x2 && y1 < y2) {
return (property_x() > x1
&& property_y() > y1
&& property_x() + width() < x2
&& property_y() + height() < y2);
} else if (x2 < x1 && y2 < y1) {
return (property_x() > x2
&& property_y() > y2
&& property_x() + width() < x1
&& property_y() + height() < y1);
} else if (x1 < x2 && y2 < y1) {
return (property_x() > x1
&& property_y() > y2
&& property_x() + width() < x2
&& property_y() + height() < y1);
} else if (x2 < x1 && y1 < y2) {
return (property_x() > x2
&& property_y() > y1
&& property_x() + width() < x1
&& property_y() + height() < y2);
} else {
return false;
}
}
void
Module::remove_port(const string& port_name, bool resize_to_fit)
{
for (PortList::iterator i = m_ports.begin(); i != m_ports.end(); ++i) {
if ((*i)->name() == port_name) {
delete (*i);
i = m_ports.erase(i);
}
}
if (resize_to_fit)
resize();
}
void
Module::width(double w)
{
m_width = w;
m_module_box.property_x2() = m_module_box.property_x1() + w;
}
void
Module::height(double h)
{
m_height = h;
m_module_box.property_y2() = m_module_box.property_y1() + h;
}
/** Overloaded Group::move to update connection paths and keep module on the canvas */
void
Module::move(double dx, double dy)
{
double new_x = property_x() + dx;
double new_y = property_y() + dy;
if (new_x < 0) dx = property_x() * -1;
else if (new_x + m_width > m_patch_bay->width()) dx = m_patch_bay->width() - property_x() - m_width;
if (new_y < 0) dy = property_y() * -1;
else if (new_y + m_height > m_patch_bay->height()) dy = m_patch_bay->height() - property_y() - m_height;
Gnome::Canvas::Group::move(dx, dy);
// Deal with moving the connection lines
for (PortList::iterator p = ports().begin(); p != ports().end(); ++p)
(*p)->move_connections();
}
/** Move to the specified absolute coordinate on the canvas */
void
Module::move_to(double x, double y)
{
if (x < 0) x = 0;
if (y < 0) y = 0;
if (x + m_width > m_patch_bay->width()) x = m_patch_bay->width() - m_width;
if (y + m_height > m_patch_bay->height()) y = m_patch_bay->height() - m_height;
// Man, not many things left to try to get the damn things to move! :)
property_x() = x;
property_y() = y;
move(0, 0);
// Deal with moving the connection lines
for (PortList::iterator p = ports().begin(); p != ports().end(); ++p)
(*p)->move_connections();
}
void
Module::name(const string& n)
{
m_name = n;
m_canvas_title.property_text() = m_name;
resize();
}
/*
void
Module::add_port(const string& port_name, bool is_input, int colour, bool resize_to_fit)
{
Port* port = new Port(this, port_name, is_input, colour);
port->signal_event().connect(
sigc::bind<Port*>(sigc::mem_fun(m_patch_bay, &FlowCanvas::port_event), port));
add_port(port, resize_to_fit);
}
*/
void
Module::add_port(Port* port, bool resize_to_fit)
{
m_ports.push_back(port);
if (resize_to_fit)
resize();
}
/** Resize the module to fit its contents best.
*/
void
Module::resize()
{
double widest_in = 0.0;
double widest_out = 0.0;
Port* p = NULL;
// Find widest in/out ports
for (PortList::iterator i = m_ports.begin(); i != m_ports.end(); ++i) {
p = (*i);
if (p->is_input() && p->width() > widest_in)
widest_in = p->width();
else if (p->is_output() && p->width() > widest_out)
widest_out = p->width();
}
// Make sure module is wide enough for ports
if (widest_in > widest_out)
width(widest_in + 5.0 + border_width()*2.0);
else
width(widest_out + 5.0 + border_width()*2.0);
// Make sure module is wide enough for title
if (m_canvas_title.property_text_width() + 6.0 > m_width)
width(m_canvas_title.property_text_width() + 6.0);
// Set height to contain ports and title
double height_base = m_canvas_title.property_text_height() + 2;
double h = height_base;
if (m_ports.size() > 0)
h += m_ports.size() * ((*m_ports.begin())->height()+2.0);
height(h);
// Move ports to appropriate locations
double y;
int i = 0;
for (PortList::iterator pi = m_ports.begin(); pi != m_ports.end(); ++pi, ++i) {
Port* p = (*pi);
y = height_base + (i * (p->height() + 2.0));
if (p->is_input()) {
p->width(widest_in);
p->property_x() = 1.0;//border_width();
p->property_y() = y;
} else {
p->width(widest_out);
p->property_x() = m_width - p->width() - 1.0;//p->border_width();
p->property_y() = y;
}
}
// Center title
m_canvas_title.property_x() = m_width/2.0;
// Update connection locations if we've moved/resized
for (PortList::iterator pi = m_ports.begin(); pi != m_ports.end(); ++pi, ++i) {
(*pi)->move_connections();
}
// Make things actually move to their new locations (?!)
move(0, 0);
}
/** Port offset, for connection drawing. See doc/port_offsets.dia */
double
Module::port_connection_point_offset(Port* port)
{
assert(port->module() == this);
assert(ports().size() > 0);
return (port->connection_coords().get_y()
- m_ports.front()->connection_coords().get_y());
}
/** Range of port offsets, for connection drawing. See doc/port_offsets.dia */
double
Module::port_connection_points_range()
{
assert(m_ports.size() > 0);
double ret = fabs(m_ports.back()->connection_coords().get_y()
- m_ports.front()->connection_coords().get_y());
return (ret < 1.0) ? 1.0 : ret;
}
} // namespace LibFlowCanvas

View file

@ -0,0 +1,189 @@
/* This file is part of FlowCanvas. Copyright (C) 2005 Dave Robillard.
*
* FlowCanvas 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 2 of the License, or (at your option) any later
* version.
*
* FlowCanvas 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 this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <libgnomecanvasmm/libgnomecanvasmm.h>
#include "flowcanvas/Port.h"
#include "flowcanvas/Module.h"
#include "flowcanvas/FlowCanvas.h"
namespace LibFlowCanvas {
Port::Port(Module* module, const string& name, bool is_input, int color)
: Gnome::Canvas::Group(*module, 0, 0),
m_module(module),
m_name(name),
m_is_input(is_input),
m_colour(color),
m_label(*this, 1, 1, m_name),
m_rect(*this, 0, 0, 0, 0)
{
m_menu.items().push_back(Gtk::Menu_Helpers::MenuElem(
"Disconnect All", sigc::mem_fun(this, &Port::disconnect_all)));
m_rect.property_fill_color_rgba() = color;
// Make rectangle pretty
//m_rect.property_outline_color_rgba() = 0x8899AAFF;
m_rect.property_outline_color_rgba() = color;
m_rect.property_join_style() = Gdk::JOIN_MITER;
border_width(1.0);
// Make label pretty
m_label.property_size() = PORT_LABEL_SIZE;
m_label.property_fill_color_rgba() = 0xFFFFFFFF;
m_label.property_weight() = 200;
m_width = m_label.property_text_width() + 4.0;
m_height = m_label.property_text_height();
// Place everything
m_rect.property_x1() = 0;
m_rect.property_y1() = 0;
m_rect.property_x2() = m_width;
m_rect.property_y2() = m_height;
m_label.property_x() = m_label.property_text_width() / 2 + 1;
m_label.property_y() = m_height / 2;
m_label.raise_to_top();
}
/** Set the border width of the port's rectangle.
*
* Do NOT directly set the width_units property on the rect, use this function.
*/
void
Port::border_width(double w)
{
m_border_width = w;
m_rect.property_width_units() = w;
}
void
Port::name(const string& n)
{
m_name = n;
// Reposition label
m_label.property_text() = m_name;
m_width = m_label.property_text_width() + 4.0;
m_height = m_label.property_text_height();
m_rect.property_x2() = m_width;
m_rect.property_y2() = m_height;
m_label.property_x() = m_label.property_text_width() / 2 + 1;
m_label.property_y() = m_height / 2;
m_module->resize();
}
void
Port::zoom(float z)
{
m_label.property_size() = static_cast<int>(8000 * z);
}
/** Update the location of all connections to/from this port if this port has moved */
void
Port::move_connections()
{
for (list<Connection*>::iterator i = m_connections.begin(); i != m_connections.end(); i++) {
((Connection*)(*i))->update_location();
}
}
void
Port::remove_connection(Connection* c)
{
m_connections.erase(
find(m_connections.begin(), m_connections.end(), c)
);
}
void
Port::disconnect_all()
{
Connection* c = NULL;
list<Connection*> temp_list = m_connections;
for (list<Connection*>::iterator i = temp_list.begin(); i != temp_list.end(); ++i) {
c = *i;
m_module->patch_bay()->disconnect(c->source_port(), c->dest_port());
}
}
void
Port::hilite(bool b)
{
m_module->hilite(b);
for (list<Connection*>::iterator i = m_connections.begin(); i != m_connections.end(); ++i) {
(*i)->hilite(b);
if (b)
(*i)->raise_to_top();
}
if (b) {
raise_to_top();
m_rect.raise_to_top();
m_label.raise_to_top();
m_rect.property_fill_color_rgba() = m_colour + 0x33333300;
} else {
m_rect.property_fill_color_rgba() = m_colour;
}
}
void
Port::raise_connections()
{
for (list<Connection*>::iterator i = m_connections.begin(); i != m_connections.end(); ++i) {
(*i)->raise_to_top();
}
}
// Returns the world-relative coordinates of where a connection line
// should attach
Gnome::Art::Point
Port::connection_coords()
{
double x = (is_input()) ? m_rect.property_x1()-1.0 : m_rect.property_x2()+1.0;
double y = m_rect.property_y1() + m_height / 2;
i2w(x, y); // convert to world-relative coords
return Gnome::Art::Point(x, y);
}
void
Port::width(double w)
{
double diff = w - m_width;
m_rect.property_x2() = m_rect.property_x2() + diff;
m_width = w;
}
} // namespace LibFlowCanvas