ladish/flowcanvas/src/FlowCanvas.cpp

891 lines
22 KiB
C++

/* 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.h"
#include <cassert>
#include <map>
#include <iostream>
#include <cmath>
#include "Port.h"
#include "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::canvas_event));
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));
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), 300);
}
FlowCanvas::~FlowCanvas()
{
destroy();
}
void
FlowCanvas::on_map()
{
Gnome::Canvas::Canvas::on_map();
assert(get_window());
get_window()->set_cursor(Gdk::Cursor(Gdk::HAND2));
}
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()
{
clear_selection();
for (ConnectionList::iterator c = m_connections.begin(); c != m_connections.end(); ++c)
delete (*c);
for (ModuleMap::iterator m = m_modules.begin(); m != m_modules.end(); ++m)
delete (*m).second;
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 " << name << 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;
}
m_base_rect.ungrab(event->button.time);
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)
{
// Hack around bug I can't nail down :)
if (event->type == GDK_BUTTON_RELEASE) {
m_base_rect.ungrab(event->button.time);
}
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);
double x = event->button.x, y = event->button.y;
if (event->motion.is_hint) {
gint t_x;
gint t_y;
GdkModifierType state;
gdk_window_get_pointer(event->motion.window, &t_x, &t_y, &state);
x = t_x;
y = t_y;
}
m_select_rect->property_x2() = x;
m_select_rect->property_y2() = 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);
}
}
m_base_rect.ungrab(event->button.time);
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 = 0;
i = (i+1) % 10;
m_select_dash->offset = i;
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;
if (event->motion.is_hint) {
gint t_x;
gint t_y;
GdkModifierType state;
gdk_window_get_pointer(event->motion.window, &t_x, &t_y, &state);
x = t_x;
y = t_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();
}
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
m_base_rect.ungrab(event->button.time);
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;
selected_port(NULL);
m_connect_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;
}
} // namespace LibFlowCanvas