|  | /*  Canvas Widget
 *  Copyleft (C) 2002 David Griffiths <dave@pawfal.org>
 *
 *  This program 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.
 *
 *  This program 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 more 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 "Fl/fl_draw.H"
#include "Fl_Canvas.h"
#include "Fl_DeviceGUI.h"
#include <iostream>
#include "../../SpiralSynthModularInfo.h"
// no of calls to handle when dragged, before the widget is redrawn
// to allow the wire (connection currently being made) to be redrawn
static const int UPDATE_TICKS = 5;
////////////////////////////////////////////////////////////////////////
Fl_Canvas::Fl_Canvas(int x, int y, int w, int h, char *name) :
Fl_Group(x,y,w,h,name),
cb_Connection(NULL),
cb_Unconnect(NULL),
cb_AddDevice(NULL),
m_ToolMenu(false),
m_UpdateTimer(0)
{
	m_IncompleteWire.OutputChild=-1;
	m_IncompleteWire.OutputPort=-1;
	m_IncompleteWire.OutputTerminal=false;
	m_IncompleteWire.InputChild=-1;
	m_IncompleteWire.InputPort=-1;
	m_IncompleteWire.InputTerminal=false;
	
	m_BG=NULL;
	m_BGData=NULL;
}
////////////////////////////////////////////////////////////////////////
Fl_Canvas::~Fl_Canvas()
{
}
////////////////////////////////////////////////////////////////////////
void Fl_Canvas::draw()
{			
	Fl_Widget*const* a = array();
	
  	if (damage() & ~FL_DAMAGE_CHILD) // redraw the entire thing:
	{ 	    
		if (m_BG)
		{
			int X=0,Y=0;
			while (Y<w())
			{	
				while (X<h())
				{
					m_BG->draw(parent()->x()+X,parent()->y()+Y);
#if FL_MAJOR_VERSION == 1 && FL_MINOR_VERSION == 0
					X+=m_BG->w;
#else
					X+=m_BG->w();
#endif	
				}		
#if FL_MAJOR_VERSION == 1 && FL_MINOR_VERSION == 0
 				Y+=m_BG->h;
#else
				Y+=m_BG->h();
#endif
				X=0;
			}
		}
		else
		{
			draw_box();
		}
				
   	 	for (int i=children(); i--;) 
		{
   			Fl_Widget& o = **a++;
   	 		draw_child(o);
   			draw_outside_label(o);
		}
		
		DrawWires();
	}
	else // only redraw the children that need it:
	{	
		for (int i=children(); i--;) update_child(**a++);
  	}
	
	if (m_ToolMenu)
	{
		int Pos=0,X=0,Y=0,textw,texth;
		int DegreesPerItem=30;
		float conv=3.151/180.0f;
		bool selected=false;
		m_Selected=-1;
		
		fl_font(fl_font(),10);
		for (vector< pair<string,int> >::iterator i=m_PluginNameList.begin();
			 i!=m_PluginNameList.end(); ++i)
		{								
			textw=0;
			fl_font(fl_font(),10);
			fl_measure(i->first.c_str(), textw, texth);
			
			X=m_x-(textw/2);
			Y=m_y-(m_PluginNameList.size()*5)+Pos*10;
						
			if (Fl::event_y()>Y-10 && Fl::event_y()<Y && 
				Fl::event_x()>X && Fl::event_x()<X+textw) 
			{
				fl_font(fl_font(),15);
				fl_measure(i->first.c_str(), textw, texth);
				X=m_x-(textw/2);
				m_Selected=i->second;
				selected=true;
			}
			else selected=false;
						
			fl_color(FL_GRAY);
						
			fl_color(FL_WHITE);
			fl_draw(i->first.c_str(),X-1,Y+1);
			if (selected) fl_color(FL_BLUE);	
			else fl_color(FL_BLACK);			
			fl_draw(i->first.c_str(),X,Y);			
			
			Pos+=1;	
		}
	}
}
////////////////////////////////////////////////////////////////////////
void Fl_Canvas::Poll()
{
	// bit of a workaround...	
	if (UserMakingConnection()) m_UpdateTimer++;
		
	if (m_UpdateTimer>UPDATE_TICKS) 
	{
		m_UpdateTimer=0;
		redraw();
	}
}
////////////////////////////////////////////////////////////////////////
void Fl_Canvas::DrawWires()
{
	for(vector<CanvasWire>::iterator i=m_WireVec.begin();
		i!=m_WireVec.end(); i++)
	{
		if (i->OutputChild>children() || i->InputChild>children())
		{
			cerr<<"wire output child = "<<i->OutputChild<<endl;
			cerr<<"wire input child = "<<i->InputChild<<endl;
			SpiralInfo::Alert("Wire drawing mismatch!");
			return;
		}
			   
		Fl_DeviceGUI* SourceDevice = (Fl_DeviceGUI*)(child(i->OutputChild));
		Fl_DeviceGUI* DestDevice   = (Fl_DeviceGUI*)(child(i->InputChild));
			 
		if (!SourceDevice || !DestDevice) 
		{
			SpiralInfo::Alert("Cant find source or dest device while drawing wires");
			return;
		}
         Fl_Color col = (Fl_Color) WIRE_COL0;
         switch (SourceDevice->GetPortType(i->OutputPort+SourceDevice->GetInfo()->NumInputs)) {
             case 0:     col = (Fl_Color) WIRE_COL0; break;
             case 1:     col = (Fl_Color) WIRE_COL1; break;
             case 2:     col = (Fl_Color) WIRE_COL2; break;
             case 3:     col = (Fl_Color) WIRE_COL3; break;
             case 4:     col = (Fl_Color) WIRE_COL4; break;
             default:    col = (Fl_Color) WIRE_COL0;
         }
 		fl_color(col);
		fl_line(SourceDevice->GetPortX(i->OutputPort+SourceDevice->GetInfo()->NumInputs),
				SourceDevice->GetPortY(i->OutputPort+SourceDevice->GetInfo()->NumInputs),
				DestDevice->GetPortX(i->InputPort),
				DestDevice->GetPortY(i->InputPort));	
	}
	
	DrawIncompleteWire();
}
////////////////////////////////////////////////////////////////////////
bool Fl_Canvas::UserMakingConnection()
{
	return 	(m_IncompleteWire.InputChild!=-1 || m_IncompleteWire.OutputChild!=-1);
}
////////////////////////////////////////////////////////////////////////
void Fl_Canvas::DrawIncompleteWire()
{	
	// draw the wire we are currently connecting
	if(m_IncompleteWire.InputChild!=-1)
	{
		Fl_DeviceGUI* Device = (Fl_DeviceGUI*)(child(m_IncompleteWire.InputChild));
		
		if (!Device) 
		{
			SpiralInfo::Alert("Cant find source or dest device while drawing wires");
			return;
		}
		
		
        Fl_Color col = (Fl_Color) WIRE_COL0;
         switch (Device->GetPortType(m_IncompleteWire.InputPort)) {
             case 0:     col = (Fl_Color) WIRE_COL0; break;
             case 1:     col = (Fl_Color) WIRE_COL1; break;
             case 2:     col = (Fl_Color) WIRE_COL2; break;
             case 3:     col = (Fl_Color) WIRE_COL3; break;
             case 4:     col = (Fl_Color) WIRE_COL4; break;
             default:    col = (Fl_Color) WIRE_COL0;
         }
 		fl_color(col);
		fl_line(Device->GetPortX(m_IncompleteWire.InputPort),
				Device->GetPortY(m_IncompleteWire.InputPort),
				Fl::event_x(),
				Fl::event_y());	
	}
	
	if(m_IncompleteWire.OutputChild!=-1)
	{
		Fl_DeviceGUI* Device = (Fl_DeviceGUI*)(child(m_IncompleteWire.OutputChild));
		
		if (!Device) 
		{
			SpiralInfo::Alert("Cant find source or dest device while drawing wires");
			return;
		}
		
		
        Fl_Color col = (Fl_Color) WIRE_COL0;
         switch (Device->GetPortType(m_IncompleteWire.OutputPort+Device->GetInfo()->NumInputs)) {
             case 0:     col = (Fl_Color) WIRE_COL0; break;
             case 1:     col = (Fl_Color) WIRE_COL1; break;
             case 2:     col = (Fl_Color) WIRE_COL2; break;
             case 3:     col = (Fl_Color) WIRE_COL3; break;
             case 4:     col = (Fl_Color) WIRE_COL4; break;
             default:    col = (Fl_Color) WIRE_COL0;
         }
 		fl_color(col);
		fl_line(Device->GetPortX(m_IncompleteWire.OutputPort+Device->GetInfo()->NumInputs),
				Device->GetPortY(m_IncompleteWire.OutputPort+Device->GetInfo()->NumInputs),
				Fl::event_x(),
				Fl::event_y());	
	}
}
////////////////////////////////////////////////////////////////////////
void Fl_Canvas::ClearIncompleteWire()
{
	// Turn off both ports
	if (m_IncompleteWire.OutputChild!=-1)
	{
		((Fl_DeviceGUI*)(child(m_IncompleteWire.OutputChild)))->RemoveConnection(m_IncompleteWire.OutputPort+
			((Fl_DeviceGUI*)(child(m_IncompleteWire.OutputChild)))->GetInfo()->NumInputs);
	}
	
	if (m_IncompleteWire.InputChild!=-1)
	{
		((Fl_DeviceGUI*)(child(m_IncompleteWire.InputChild)))->RemoveConnection(m_IncompleteWire.InputPort);
	}
	m_IncompleteWire.Clear();
}
////////////////////////////////////////////////////////////////////////
int Fl_Canvas::handle(int event)
{
	if (Fl_Group::handle(event)) return 1;
	
	if (event==FL_PUSH) 
	{
		ClearIncompleteWire();
		redraw();
	}
	
	if (Fl::event_button()==3)
	{
		if (event==FL_PUSH) 
		{	
			m_ToolMenu=true;
			m_x=Fl::event_x();
			m_y=Fl::event_y();
			redraw();
		}
		
		if (event==FL_DRAG)	redraw();
		
		if (event==FL_RELEASE && Fl::event_button()==3) 
		{
			m_ToolMenu=false;
			if (m_Selected!=-1 && cb_AddDevice) 
			{
				int args[3];
				args[0]=m_Selected;
				args[1]=m_x;
				args[2]=m_y;
				cb_AddDevice(this,args);
			}
			redraw();
		}	
	}
	return 1;
}
	
////////////////////////////////////////////////////////////////////////
void Fl_Canvas::PortClicked(Fl_DeviceGUI* Device, int Type, int Port, bool Value)
{
	// find out which child this comes from.
	int ChildNum=-1;	
	for(int n=0; n<children(); n++)
	{
		if(child(n)==Device)
		{
			ChildNum=n;
		}
	}
	
	if (ChildNum==-1) 
	{
		SpiralInfo::Alert("Port clicked callback can't find source child.");
		return;
	}
	
	if(Value) // Turned on the port
	{
		if(m_IncompleteWire.InputChild==-1 || m_IncompleteWire.OutputChild==-1)
		{
			if (Type==Fl_DeviceGUI::OUTPUT)
			{
				// make sure we don't make a output->output connection
				if (m_IncompleteWire.OutputChild==-1)
				{	
					m_IncompleteWire.OutputChild=ChildNum;
					m_IncompleteWire.OutputPort=Port;
					m_IncompleteWire.OutputID=Device->GetID();
					m_IncompleteWire.OutputTerminal=Device->IsTerminal();
				}
				else
				{
					ClearIncompleteWire();
				}
			}
			else
			{
				// make sure we don't make a input->input connection
				if (m_IncompleteWire.InputChild==-1)				
				{					
					m_IncompleteWire.InputChild=ChildNum;
					m_IncompleteWire.InputPort=Port;
					m_IncompleteWire.InputID=Device->GetID();
					m_IncompleteWire.InputTerminal=Device->IsTerminal();
				}
				else
				{
					ClearIncompleteWire();
				}
			}
			// if both have now been set...
			if (m_IncompleteWire.InputChild!=-1 && m_IncompleteWire.OutputChild!=-1)
			{
				m_WireVec.push_back(m_IncompleteWire);	
	
				// send the connect callback
				cb_Connection(this,(void*)&m_IncompleteWire);
				m_Graph.AddConnection(m_IncompleteWire.OutputID,m_IncompleteWire.OutputTerminal,
									  m_IncompleteWire.InputID,m_IncompleteWire.InputTerminal);
				
				// Turn on both ports
				Fl_DeviceGUI* ODGUI = (Fl_DeviceGUI*)(child(m_IncompleteWire.OutputChild));
				ODGUI->AddConnection(m_IncompleteWire.OutputPort+ODGUI->GetInfo()->NumInputs);
					
				Fl_DeviceGUI* IDGUI = (Fl_DeviceGUI*)(child(m_IncompleteWire.InputChild));
				IDGUI->AddConnection(m_IncompleteWire.InputPort);
				
				
				m_IncompleteWire.Clear();
				
				redraw();
			}
		}
	}
	else // Turned off the port
	{	
		// Find connections using this port
		bool Found=true;
		
		while (Found)
		{
			Found=false;
			
			for(vector<CanvasWire>::iterator i=m_WireVec.begin();
				i!=m_WireVec.end(); i++)
			{
				if ((Type==Fl_DeviceGUI::OUTPUT && i->OutputChild==ChildNum && i->OutputPort==Port) ||
					(Type==Fl_DeviceGUI::INPUT && i->InputChild==ChildNum && i->InputPort==Port))
				{
					// Turn off both ports
					Fl_DeviceGUI* ODGUI = (Fl_DeviceGUI*)(child(i->OutputChild));
					ODGUI->RemoveConnection(i->OutputPort+ODGUI->GetInfo()->NumInputs);
					
					Fl_DeviceGUI* IDGUI = (Fl_DeviceGUI*)(child(i->InputChild));
					IDGUI->RemoveConnection(i->InputPort);
									
					// send the unconnect callback	
					cb_Unconnect(this,(void*)&(*i));
					m_Graph.RemoveConnection(i->OutputID,i->InputID);		
					// Remove the wire
					m_WireVec.erase(i);
					
					Found=true;
					break;
				}
			}
		}
		
		redraw();
		
		// Clear the current selection
		m_IncompleteWire.Clear();
	}
}
////////////////////////////////////////////////////////////////////////
void Fl_Canvas::ClearConnections(Fl_DeviceGUI* Device)
{
	// find out which child this comes from.
	int ChildNum=-1;	
	for(int n=0; n<children(); n++)
	{
		if(child(n)==Device)
		{
			ChildNum=n;
		}
	}
	
	if (ChildNum==-1) 
	{
		SpiralInfo::Alert("Clear connections callback can't find source child.");
		return;
	}
	
	bool removedall=false;
		
	while (!removedall)
	{
		removedall=true;
		
		for (vector<CanvasWire>::iterator i=m_WireVec.begin();
			 i!=m_WireVec.end(); i++)
		{	
			if (i->OutputChild==ChildNum ||
			    i->InputChild==ChildNum)
			{
				// Turn off both ports
				((Fl_DeviceGUI*)(child(i->OutputChild)))->RemoveConnection(i->OutputPort+
					((Fl_DeviceGUI*)(child(i->OutputChild)))->GetInfo()->NumInputs);
				((Fl_DeviceGUI*)(child(i->InputChild)))->RemoveConnection(i->InputPort);
				// send the unconnect callback
				cb_Unconnect(this,(void*)&(*i));
				m_Graph.RemoveConnection(i->OutputID,i->InputID);
				
				m_WireVec.erase(i);
				removedall=false;
				break;
			}
		}
	}
}
////////////////////////////////////////////////////////////////////////
	
void Fl_Canvas::RemoveDevice(Fl_DeviceGUI* Device)
{
	// find out which child this comes from.
	int ChildNum=-1;	
	for(int n=0; n<children(); n++)
	{
		if(child(n)==Device)
		{
			ChildNum=n;
		}
	}
	
	if (ChildNum==-1) 
	{
		SpiralInfo::Alert("Remove device callback can't find source child.");
		return;
	}
	ClearConnections(Device);
	for (vector<CanvasWire>::iterator i=m_WireVec.begin();
		 i!=m_WireVec.end(); i++)
	{	
		if (i->OutputChild>ChildNum) i->OutputChild--;		
		if (i->InputChild>ChildNum)	 i->InputChild--;
	}
	
	remove(child(ChildNum));
	redraw(); 
}
////////////////////////////////////////////////////////////////////////
	
void Fl_Canvas::Clear() 
{ 
	m_Graph.Clear();
	int kids=children();
	for(int n=0; n<kids; n++)
	{
		remove(child(0));		
	}	
	
	m_WireVec.clear(); 
	redraw(); 
}
////////////////////////////////////////////////////////////////////////
	
	
istream &operator>>(istream &s, Fl_Canvas &o)
{
	int NumWires;
	s>>NumWires;
	
	// my bad, didn't version this stream - remove one day...
	if (NumWires==-1)
	{
		int version;
		s>>version;
		s>>NumWires;
		
		for(int n=0; n<NumWires; n++)
		{
			CanvasWire NewWire;
			
			s>>NewWire.OutputID;
			s>>NewWire.OutputChild;
			s>>NewWire.OutputPort;
			s>>NewWire.OutputTerminal;
			s>>NewWire.InputID;
			s>>NewWire.InputChild;
			s>>NewWire.InputPort;	
			s>>NewWire.InputTerminal;
			
			// if we can turn on both ports
			if (((Fl_DeviceGUI*)(o.child(NewWire.OutputChild)))->AddConnection(NewWire.OutputPort+
					((Fl_DeviceGUI*)(o.child(NewWire.OutputChild)))->GetInfo()->NumInputs) &&
				((Fl_DeviceGUI*)(o.child(NewWire.InputChild)))->AddConnection(NewWire.InputPort))
			{
				o.m_WireVec.push_back(NewWire);	
				// Notify connection by callback
				o.cb_Connection(&o,(void*)&NewWire);
				o.m_Graph.AddConnection(NewWire.OutputID,NewWire.OutputTerminal,NewWire.InputID,NewWire.InputTerminal);
			}				
		}
		
	}
	else
	{		
		for(int n=0; n<NumWires; n++)
		{
			CanvasWire NewWire;
			
			s>>NewWire.OutputID;
			s>>NewWire.OutputChild;
			s>>NewWire.OutputPort;
			s>>NewWire.InputID;
			s>>NewWire.InputChild;
			s>>NewWire.InputPort;	
	
			// if we can turn on both ports
			if (((Fl_DeviceGUI*)(o.child(NewWire.OutputChild)))->AddConnection(NewWire.OutputPort+
					((Fl_DeviceGUI*)(o.child(NewWire.OutputChild)))->GetInfo()->NumInputs) &&
				((Fl_DeviceGUI*)(o.child(NewWire.InputChild)))->AddConnection(NewWire.InputPort))
			{
				o.m_WireVec.push_back(NewWire);	
				// Notify connection by callback
				o.cb_Connection(&o,(void*)&NewWire);
				o.m_Graph.AddConnection(NewWire.OutputID,false,NewWire.InputID,false);
			}					
		}
	}	
	return s;
}
////////////////////////////////////////////////////////////////////////
ostream &operator<<(ostream &s, Fl_Canvas &o)
{
	int version=0;
	s<<-1<<" "<<version<<" ";
	
	s<<o.m_WireVec.size()<<endl;
	for(vector<CanvasWire>::iterator i=o.m_WireVec.begin();
		i!=o.m_WireVec.end(); i++)
	{
		s<<i->OutputID<<" ";
		s<<i->OutputChild<<" ";
		s<<i->OutputPort<<" ";
		s<<i->OutputTerminal<<" ";
		s<<i->InputID<<" ";
		s<<i->InputChild<<" ";
		s<<i->InputPort<<" ";	
		s<<i->InputTerminal<<endl;
	}
	return s;
}
 |