Logo Search packages:      
Sourcecode: patchage version File versions  Download package

PatchBayArea.cpp

/* This file is part of Om.  Copyright (C) 2005 Dave Robillard.
 * 
 * Om 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.
 * 
 * Om 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 "PatchBayArea.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 PatchBay {
      

PatchBayArea::PatchBayArea(double width, double height)
: m_active_port(NULL),
  m_clicked_port(NULL),
  m_base_rect(*root(), 0, 0, width, height),
  m_connection_dragging(false),
  m_scroll_dragging(false),
  m_drag_connection(NULL),
  m_current_zoom(1.0),
  m_zoom(1.0),
  m_width(width),
  m_height(height)
{
      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, &PatchBayArea::canvas_event));
      //m_base_rect.signal_event().connect(sigc::mem_fun(this, &PatchBayArea::scroll_drag_handler));
      m_base_rect.signal_event().connect(sigc::mem_fun(this, &PatchBayArea::connection_drag_handler));
      
      set_dither(Gdk::RGB_DITHER_NORMAL); // NONE or NORMAL or MAX
      
      // This hide cursor crap is way, way, way more annoying than it needs to be
      
      /*GdkColor fg = {0xFFFFFF00, 0xFF, 0xFF, 0xFF};
      GdkColor bg = {0x00000000, 0x00, 0x00, 0x00};
      
      GdkPixmap* cursor_pm = gdk_pixmap_create_from_data(
            ((GdkDrawable*)get_parent_window().operator->()), "\0", 1, 1, 1, &fg, &bg);
      
      m_invisible_cursor = new Gdk::Cursor(gdk_cursor_new_from_pixmap(
            cursor_pm, cursor_pm, &fg, &bg, 0, 0), false);
      */
}


PatchBayArea::~PatchBayArea()
{
      destroy();
      //delete m_invisible_cursor;
}


bool
PatchBayArea::on_expose_event(GdkEventExpose* ev)
{
      if (m_current_zoom != m_zoom) {
            do_zoom();
            update_now();
      }
      
      return Gtk::Widget::on_expose_event(ev);
}


void
PatchBayArea::zoom(float pix_per_unit)
{
      m_zoom = pix_per_unit;
      queue_draw();
}


void
PatchBayArea::do_zoom()
{
      set_pixels_per_unit(m_zoom);

      for (ModuleMap::iterator m = m_modules.begin(); m != m_modules.end(); ++m)
            (*m).second->zoom(m_zoom);
      
      //request_redraw(0, 0, m_width, m_height);

      m_current_zoom = m_zoom;
}



/** Removes all ports and connections and modules.
 */
void
00113 PatchBayArea::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_active_port = NULL;
      m_clicked_port = NULL;
}


void
PatchBayArea::active_port(Port* p)
{
      if (m_active_port != NULL)
            m_active_port->rect()->property_fill_color_rgba() = m_active_port->color(); // "turn off" the old one
      
      m_active_port = p;
      
      if (p != NULL)
            m_active_port->rect()->property_fill_color() = "red";
}


Module*
PatchBayArea::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
00156 PatchBayArea::set_default_placement(Module* m)
{
      // 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
PatchBayArea::add_module(Module* m)
{
      std::pair<string, Module*> p(m->name(), m);
      m_modules.insert(p);
}


void
PatchBayArea::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 {
            std::cerr << "[PatchBayArea::remove_module] Unable to find module!" << std::endl;
      }
}


Port*
PatchBayArea::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 << "[PatchBayArea::find_port] Failed to find port " <<
            node_name << ":" << port_name << endl;

      return NULL;
}


/** Add a connection.
 */
void
00211 PatchBayArea::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
PatchBayArea::remove_connection(Port* port1, Port* port2)
{
      Connection* c = get_connection(port1, port2);
      if (c == NULL) {
            std::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
00248 PatchBayArea::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) {
            std::cerr << "Couldn't find connection.\n";
            return false;
      } else {
            remove_connection(c);
            return true;
      }
}


bool
PatchBayArea::are_connected(const Port* port1, const Port* port2)
{
      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*
PatchBayArea::get_connection(const Port* port1, const Port* port2)
{
      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;
}


Connection*
PatchBayArea::add_connection(Port* port1, Port* port2)
{
      // Create (graphical) connection object
      if (get_connection(port1, port2) == NULL) {
            Connection* c = new Connection(root(), port1, port2);
            port1->add_connection(c);
            port2->add_connection(c);
            m_connections.push_back(c);
      
            return c;
      } else {
            return NULL;
      }
}


void
PatchBayArea::remove_connection(Connection* connection)
{
      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
00327 PatchBayArea::ports_joined(Port* port1, Port* port2)
{
      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);
}


bool
00355 PatchBayArea::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_active_port = port;
                  port->popup_menu(event->button.button, event->button.time);
            } else {
                  handled = false;
            }
            break;

      case GDK_BUTTON_RELEASE:
            if (port_dragging) {
                  if (m_clicked_port == NULL) {
                        active_port(port);
                        m_clicked_port = port;
                  } else {
                        ports_joined(port, m_clicked_port);
                        m_clicked_port = NULL;
                        active_port(NULL);
                  }
                  port_dragging = false;
            } else {
                  handled = false;
            }
            break;

      case GDK_ENTER_NOTIFY:
            if (port != m_active_port)
                  port->hilite(true);
            break;

      case GDK_LEAVE_NOTIFY:
            if (port_dragging) {
                  m_connection_dragging = true;
                  m_clicked_port = port;
                  
                  assert(m_drag_connection == NULL);
                  
                  m_base_rect.grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
                        /**m_invisible_cursor*/Gdk::Cursor(Gdk::TOP_TEE), event->button.time);

                  port_dragging = false;
            } else {
                  if (port != m_active_port)
                        port->hilite(false);
            }
            break;

      default:
            handled = false;
      }
      
      return handled;
}


/*
bool
PatchBayArea::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
PatchBayArea::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
PatchBayArea::connection_drag_handler(GdkEvent* event)
{
      bool handled = true;
      
      // These are invisible, just used for making connections (while dragging)
      static Port*   drag_port = NULL;
      static Module* drag_module = NULL;
      static Port*   snapped_port = NULL;

      static bool snapped = false;
      
      if (event->type == GDK_BUTTON_PRESS && event->button.button == 2) {
            m_scroll_dragging = true;
      } else if (event->type == GDK_MOTION_NOTIFY && m_connection_dragging) {
            double x = event->button.x, y = event->button.y;
            root()->w2i(x, y);

            if (m_drag_connection == NULL) { // Havn't created the connection yet
                  assert(drag_port == NULL);
                  assert(m_clicked_port != NULL);
                  
                  drag_module = new Module(this, "");
                  drag_port = new Port(drag_module, "", true, 0);
                  drag_port->hide();
                  drag_module->hide();

                  drag_port->property_x() = x;
                  drag_port->property_y() = y;
                  m_drag_connection = new Connection(root(), m_clicked_port, drag_port);
                  m_drag_connection->property_line_style() = Gdk::LINE_DOUBLE_DASH;
                  m_drag_connection->property_last_arrowhead() = true;
            }

            if (snapped) {
                  if (m_drag_connection != NULL) m_drag_connection->hide();
                  Port* p = get_port_at(x, y);
                  if (m_drag_connection != NULL) m_drag_connection->show();
                  if (p != NULL) {
                        if (p != m_active_port) {
                              if (snapped_port != NULL)
                                    snapped_port->hilite(false);
                              p->hilite(true);
                              snapped_port = p;
                        }
                        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() = 0;
                        drag_module->property_y() = 0;
                        drag_port->property_x() = x;
                        drag_port->property_y() = y;
                  }
                  m_drag_connection->update_location();
            } else { // nor snapped to a port
                  assert(drag_module != NULL);
                  assert(drag_port != NULL);
                  assert(m_clicked_port != NULL);

                  // "Snap" to port, if we're on a port
                  if (m_drag_connection != NULL) m_drag_connection->hide();
                  Port* p = get_port_at(x, y);
                  if (m_drag_connection != NULL) m_drag_connection->show();
                  if (p != NULL && p != m_active_port) {
                        p->hilite(true);
                        snapped_port = p;
                        snapped = true;
                        drag_module->move_to(p->module()->property_x().get_value(), p->module()->property_y().get_value());
                        drag_port->property_x() = p->property_x().get_value();
                        drag_port->property_y() = p->property_y().get_value();
                  } else {
                        drag_port->property_x() = x;
                        drag_port->property_y() = y;
                  }
                  m_drag_connection->update_location();
            }
      } else if (event->type == GDK_BUTTON_RELEASE && m_connection_dragging) {
            m_base_rect.ungrab(event->button.time);
            
            double x = event->button.x;
            double y = event->button.y;
            m_base_rect.i2w(x, y);

            if (m_drag_connection != NULL) m_drag_connection->hide();
            Port* p = get_port_at(x, y);
            if (m_drag_connection != NULL) m_drag_connection->show();
      
            if (p != NULL) {
                  if (p == m_clicked_port) {   // drag ended on same port it started on
                        if (m_active_port == NULL) {  // no active port, just activate (hilite) it
                              active_port(m_clicked_port);
                        } else {  // there is already an active port, connect it with this one
                              if (m_active_port != m_clicked_port)
                                    ports_joined(m_active_port, m_clicked_port);
                              active_port(NULL);
                              m_clicked_port = NULL;
                              snapped_port = NULL;
                        }
                  } else {  // drag ended on different port
                        //p->hilite(false);
                        ports_joined(m_clicked_port, p);
                        active_port(NULL);
                        m_clicked_port = NULL;
                        snapped_port = NULL;
                  }
            }
            
            // Clean up dragging stuff
            if (m_clicked_port != NULL)
                  m_clicked_port->hilite(false);

            m_connection_dragging = false;
            delete m_drag_connection;
            m_drag_connection = NULL;
            delete drag_port;
            drag_port = NULL;
            delete drag_module;
            drag_module = NULL;
            snapped_port = NULL;
      } else {
            handled = false;
      }

      return handled;
}


Port*
PatchBayArea::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
PatchBayArea::port_menu_disconnect_all()
{
      Connection* c = NULL;
      list<Connection*> temp_list = m_active_port->connections();
      for (list<Connection*>::iterator i = temp_list.begin(); i != temp_list.end(); ++i) {
            c = *i;
            disconnect(c->source_port(), c->dest_port());
      }
      
      active_port(NULL);
}

} // namespace PatchBay

Generated by  Doxygen 1.6.0   Back to index