mirror of
https://github.com/Ardour/ardour.git
synced 2025-12-23 23:17:46 +01:00
Added flowcanvas 0.0.1 (unreleased).
git-svn-id: svn://localhost/trunk/ardour2@62 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
parent
2634aaadc5
commit
dfbf777cea
15 changed files with 3886 additions and 0 deletions
679
libs/flowcanvas/src/Connection.cpp
Normal file
679
libs/flowcanvas/src/Connection.cpp
Normal 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
|
||||
|
||||
884
libs/flowcanvas/src/FlowCanvas.cpp
Normal file
884
libs/flowcanvas/src/FlowCanvas.cpp
Normal 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
|
||||
472
libs/flowcanvas/src/Module.cpp
Normal file
472
libs/flowcanvas/src/Module.cpp
Normal 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
|
||||
189
libs/flowcanvas/src/Port.cpp
Normal file
189
libs/flowcanvas/src/Port.cpp
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue