bool GraphComponent::closeSelectedPlugins ()
{
    DBG ("GraphComponent::closeSelectedPlugins");

    jassert (host != 0);

    if (selectedNodes.getNumSelected () > 0)
    {
        for (int i = 0; i < selectedNodes.getNumSelected (); i++)
        {
            GraphNodeComponent* selected = selectedNodes.getSelectedItem (i);

            BasePlugin* plugin = (BasePlugin*) selected->getUserData ();
            if (plugin)
            {
                if (owner->isPluginEditorWindowOpen (plugin))
                    owner->closePluginEditorWindow (plugin);

                host->closePlugin (plugin);

                selectedNodes.deselect (selected);
                deletePluginNode (selected);
            }
        }

        selectedNodes.deselectAll ();

        graphChanged ();
        
        return true;
    }
    
    return false;
}
GraphNodeComponent* GraphComponent::findNodeByUserData (void* data)
{
    for (int i = nodes.size(); --i >= 0;)
    {
        GraphNodeComponent* node = nodes.getUnchecked (i);
        if (node->getUserData () == data)
            return node;
    }
    return 0;
}
//==============================================================================
void GraphLinkComponent::mouseUp (const MouseEvent& e)
{
    if (e.mods.isRightButtonDown())
    {
        GraphNodeComponent* node = (to ? to->getParentGraphComponent ()
                                       : (from ? from->getParentGraphComponent ()
                                               : 0));
        if (node)
            node->notifyLinkPopupMenuSelected (this);
    }
}
void GraphComponent::nodeSelected (GraphNodeComponent* node)
{
    DBG ("GraphComponent::nodeSelected");

    jassert (node != 0)

    switch (selectedNodes.getNumSelected ())
    {
    case 0: // no items selected
        {
            BasePlugin* plugin = (BasePlugin*) node->getUserData ();
            if (plugin)
                plugin->setValue (PROP_GRAPHSELECTED, 1);

            selectedNodes.addToSelection (node);

            node->repaint ();
        }
        break;
    default: // one or more items selected
        {
            if (! selectedNodes.isSelected (node))
            {
                for (int i = 0; i < selectedNodes.getNumSelected (); i++)
                {
                    GraphNodeComponent* selected = selectedNodes.getSelectedItem (i);

                    BasePlugin* plugin = (BasePlugin*) selected->getUserData ();
                    if (plugin)
                        plugin->setValue (PROP_GRAPHSELECTED, 0);

                    selected->repaint ();
                }

                selectedNodes.deselectAll ();

                BasePlugin* plugin = (BasePlugin*) node->getUserData ();
                if (plugin)
                    plugin->setValue (PROP_GRAPHSELECTED, 1);

                selectedNodes.addToSelection (node);

                node->repaint ();
            }
        }
        break;
    }
}
//==============================================================================
void GraphComponent::findLassoItemsInArea (Array<GraphNodeComponent*> &itemsFound, int x, int y, int width, int height)
{
    for (int i = 0; i < nodes.size (); i++)
    {
        GraphNodeComponent* node = nodes.getUnchecked (i);
        if ((node->getX() >= x && node->getX() < x + width)
             && (node->getY() >= y && node->getY() < y + height))
        {       
            itemsFound.addIfNotAlreadyThere (node);
            selectedNodes.addToSelection (node);
        }
        else
        {
            selectedNodes.deselect (node);
        }
    }
}
//==============================================================================
void GraphLinkComponent::paint (Graphics& g)
{
    Colour wireColour = Colours::black;
    if ((to != 0 && from != 0))
    {
        GraphNodeComponent* node = to->getParentGraphComponent ();
        wireColour = node->getConnectorColour (to, isMouseOverOrDragging());
    }
    else
    {
        if (to)
        {
            GraphNodeComponent* node = to->getParentGraphComponent ();
            wireColour = node->getConnectorColour (to, true);
        }
        else if (from)
        {
            GraphNodeComponent* node = from->getParentGraphComponent ();
            wireColour = node->getConnectorColour (from, true);
        }
    }

    g.setColour (wireColour);
    g.strokePath (path, PathStrokeType (lineThickness));
void GraphComponent::nodeMoved (GraphNodeComponent* node, const int deltaX, const int deltaY)
{
//    DBG ("GraphComponent::nodeMoved " + String (deltaX) + " " + String (deltaY));

    jassert (node != 0);

    for (int i = 0; i < selectedNodes.getNumSelected (); i++)
    {
        GraphNodeComponent* selected = selectedNodes.getSelectedItem (i);
        if (selected
            && selected != node
            && ! selected->isLocked ())
        {
            BasePlugin* plugin = (BasePlugin*) selected->getUserData ();
            if (plugin)
            {
                int x = selected->getX() + deltaX;
                int y = selected->getY() + deltaY;

                x = jmax (0, jmin (getWidth () - selected->getWidth(), x));
                y = jmax (0, jmin (getHeight () - selected->getHeight(), y));
            
                plugin->setValue (PROP_GRAPHXPOS, x);
                plugin->setValue (PROP_GRAPHYPOS, y);
                
                selected->setTopLeftPosition (x, y);
            }
        }
    }

    BasePlugin* plugin = (BasePlugin*) node->getUserData ();
    if (plugin)
    {
        plugin->setValue (PROP_GRAPHXPOS, node->getX());
        plugin->setValue (PROP_GRAPHYPOS, node->getY());
    }

    Viewport* parent = findParentComponentOfClass ((Viewport*) 0);
    if (parent)
    {
        parent->notifyComponentChanged ();
    }
}
//==============================================================================
bool GraphComponent::createPluginNode (BasePlugin* plugin)
{
    if (plugin)
    {
        DBG ("GraphComponent::createPluginNode");
 
        GraphNodeComponent* component;
        addAndMakeVisible (component = new GraphNodeComponent());
        component->setNodeListener (this);

        // set common data
        component->setUserData (plugin);
        component->setUniqueID (plugin->getUniqueHash());
        component->setText (plugin->getName ());
        component->setLeftToRight (leftToRight);
        component->setNodeColour (Colour::fromString (plugin->getValue (PROP_GRAPHCOLOUR, T("0xff808080"))));

        // read properties
        // int maxPortCount = jmax (plugin->getNumInputs () + plugin->getNumMidiInputs (),
        //                          plugin->getNumOutputs () + plugin->getNumMidiOutputs ());
                                 
        // TODO - if (maxPortCount * portWidth > defaultNodeWidth) ...
        int pluginType = plugin->getType();

        int w = defaultNodeWidth, h = defaultNodeHeight;
        int numIns = plugin->getNumInputs () + plugin->getNumMidiInputs ();
        int numOuts = plugin->getNumOutputs () + plugin->getNumMidiOutputs ();

        if (leftToRight)
        {
            h = jmax (h, (jmax (numIns, numOuts) + 1) * 16);

            const int textHeight = currentFont.getStringWidth (plugin->getName());
            h = jmax (h, 16 + jmin (textHeight, 300));
        }
        else
        {
            w = jmax (w, (jmax (numIns, numOuts) + 1) * 16);

            const int textWidth = currentFont.getStringWidth (plugin->getName());
            w = jmax (w, 16 + jmin (textWidth, 300));
        }
        
        int wasLocked = plugin->getIntValue (PROP_GRAPHLOCKED, -1);
        if (wasLocked < 0)  wasLocked = 0;
        int wasSelected = plugin->getIntValue (PROP_GRAPHSELECTED, 0);
        int xPos = plugin->getIntValue (PROP_GRAPHXPOS, -1);
        int yPos = plugin->getIntValue (PROP_GRAPHYPOS, -1);
        int wSize = plugin->getIntValue (PROP_GRAPHWSIZE, w);
        if (wSize < 0) wSize = defaultNodeWidth;
        int hSize = plugin->getIntValue (PROP_GRAPHHSIZE, h);
        if (hSize < 0) hSize = defaultNodeHeight;
        String pluginTooltip = plugin->getName ();

        // input plugin
        if (pluginType == JOST_PLUGINTYPE_INPUT)
        {
            inputs = component;
            if (xPos < 0)       xPos = leftToRight ? 2 : (getWidth () - w) / 2;
            if (yPos < 0)       yPos = leftToRight ? (getHeight() - h) / 2 : 2;
        }
        // output plugin
        else if (pluginType == JOST_PLUGINTYPE_OUTPUT)
        {
            outputs = component;
            if (xPos < 0)       xPos = leftToRight ? getWidth () - w - 2 : (getWidth () - w) / 2;
            if (yPos < 0)       yPos = leftToRight ? (getHeight() - h) / 2 : getHeight() - h - 2;
        }
        // generic plugin
        else
        {
            if (xPos < 0)       xPos = getBounds().getCentreX();
            if (yPos < 0)       yPos = getBounds().getCentreY();
            pluginTooltip = plugin->getFile ().getFullPathName ();
        }

        component->setBounds (xPos, yPos, wSize, hSize);
        component->setLocked (wasLocked);
        component->setTooltip (pluginTooltip);
        component->setVisible (true);
        if (wasSelected) selectedNodes.addToSelection (component);

        plugin->setValue (PROP_GRAPHXPOS, xPos);
        plugin->setValue (PROP_GRAPHYPOS, yPos);
        plugin->setValue (PROP_GRAPHWSIZE, wSize);
        plugin->setValue (PROP_GRAPHHSIZE, hSize);

        // restore audio input / output ports
        if (pluginType != JOST_PLUGINTYPE_INPUT)
            for (int j = 0; j < plugin->getNumInputs (); j++)
                component->addInputConnector (JOST_LINKTYPE_AUDIO); // audio cable

        if (pluginType != JOST_PLUGINTYPE_OUTPUT)
            for (int j = 0; j < plugin->getNumOutputs (); j++)
                component->addOutputConnector (JOST_LINKTYPE_AUDIO); // audio cable

        // restore midi input / output ports
        if (pluginType != JOST_PLUGINTYPE_INPUT)
            for (int j = 0; j < plugin->getNumMidiInputs (); j++)
                component->addInputConnector (JOST_LINKTYPE_MIDI); // midi cable

        if (pluginType != JOST_PLUGINTYPE_OUTPUT)
            for (int j = 0; j < plugin->getNumMidiOutputs (); j++)
                component->addOutputConnector (JOST_LINKTYPE_MIDI); // midi cable

        nodes.add (component);

        Viewport* parent = findParentComponentOfClass ((Viewport*) 0);
        if (parent)
        {
            parent->notifyComponentChanged ();
        }

        return true;
    }

    return false;
}
void GraphComponent::updateDisplayPlugins ()
{
    DBG ("GraphComponent::updateDisplayPlugins");

    jassert (host != 0);

    cleanInternalGraph ();

    // rebuild nodes !
    for (int i = 0; i < host->getPluginsCount (); i++)
    {
        createPluginNode (host->getPluginByIndex (i));
    }

    // update audio / midi connection links !
    ProcessingGraph* audioGraph = host->getAudioGraph ();

    for (int i = 0; i < audioGraph->getNodeCount (); i++)
    {
        ProcessingNode* source = audioGraph->getNode (i);
        BasePlugin* sourceData = (BasePlugin*) source->getData ();
        GraphNodeComponent* sourceNode = findNodeByUserData (sourceData);

        if (sourceNode != 0)
        {
            for (int j = source->getLinksCount (JOST_LINKTYPE_AUDIO); --j >= 0;)
            {
                ProcessingLink* link = source->getLink (JOST_LINKTYPE_AUDIO, j);
                BasePlugin* destData = (BasePlugin*) link->destination->getData ();
                GraphNodeComponent* destNode = findNodeByUserData (destData);

                if (destNode != 0)
                {
                    sourceNode->connectToNode (link->sourcePort,
                                               destNode,
                                               link->destinationPort,
                                               false);
                }
            }
        }
    }

    for (int i = 0; i < audioGraph->getNodeCount (); i++)
    {
        ProcessingNode* source = audioGraph->getNode (i);
        BasePlugin* sourceData = (BasePlugin*) source->getData ();
        GraphNodeComponent* sourceNode = findNodeByUserData (sourceData);

        if (sourceNode != 0)
        {
            for (int j = source->getLinksCount (JOST_LINKTYPE_MIDI); --j >= 0;)
            {
                ProcessingLink* link = source->getLink (JOST_LINKTYPE_MIDI, j);
                BasePlugin* destData = (BasePlugin*) link->destination->getData ();
                GraphNodeComponent* destNode = findNodeByUserData (destData);

                if (destNode != 0)
                {
                    sourceNode->connectToNode (link->sourcePort + sourceNode->getFirstOutputOfType (JOST_LINKTYPE_MIDI),
                                               destNode,
                                               link->destinationPort + destNode->getFirstInputOfType (JOST_LINKTYPE_MIDI),
                                               false);
                }
            }
        }
    }

    // update connectors
    graphChanged ();

    // update fixed node size and position
    resized ();
}
bool GraphComponent::keyPressed (const KeyPress& key)
{
    if (key.isKeyCode (KeyPress::leftKey))
    {
        for (int i = 0; i < selectedNodes.getNumSelected (); i++)
        {
            GraphNodeComponent* selected = selectedNodes.getSelectedItem (i);
            if (selected != inputs && selected != outputs)
                selected->setTopLeftPosition (selected->getX() - 1, selected->getY());
        }
    }
    else if (key.isKeyCode (KeyPress::rightKey))
    {
        for (int i = 0; i < selectedNodes.getNumSelected (); i++)
        {
            GraphNodeComponent* selected = selectedNodes.getSelectedItem (i);
            if (selected != inputs && selected != outputs)
                selected->setTopLeftPosition (selected->getX() + 1, selected->getY());
        }
    }
    else if (key.isKeyCode (KeyPress::upKey))
    {
        for (int i = 0; i < selectedNodes.getNumSelected (); i++)
        {
            GraphNodeComponent* selected = selectedNodes.getSelectedItem (i);
            if (selected != inputs && selected != outputs)
                selected->setTopLeftPosition (selected->getX(), selected->getY() - 1);
        }
    }
    else if (key.isKeyCode (KeyPress::downKey))
    {
        for (int i = 0; i < selectedNodes.getNumSelected (); i++)
        {
            GraphNodeComponent* selected = selectedNodes.getSelectedItem (i);
            if (selected != inputs && selected != outputs)
                selected->setTopLeftPosition (selected->getX(), selected->getY() + 1);
        }
    }
    else if (key.isKeyCode (KeyPress::deleteKey))
    {
        closeSelectedPlugins ();
    }
    else
    {
        return Component::keyPressed (key);
    }

    return true;
}
//==============================================================================
void GraphComponent::recalculateConnectionsRecursive (GraphNodeComponent* node,
                                                      ProcessingGraph* graph,
                                                      const int insertIndex,
//                                                      const int connectorType,
                                                      const bool createConnection)
{
    BasePlugin* source = (BasePlugin*) node->getUserData ();

    if (source != 0 && ! graph->contains (source))
    {
        graph->insertNode (insertIndex, source);
        int currentIndex = insertIndex + 1;

        for (int i = 0; i < node->getOutputConnectorCount (); i++)
        {
            int sourceOutputMidiOffset = node->getFirstOutputOfType (JOST_LINKTYPE_MIDI);
            int destOutputMidiOffset = 0;

            GraphConnectorComponent* connector = node->getOutputConnector (i);
//            if (connector->getType () == connectorType)
            {
                // we are the same type, get linked connectors
                Array <GraphConnectorComponent*> linked;
                connector->getLinkedConnectors (linked);

                for (int j = 0; j < linked.size (); j++)
                {
                    GraphConnectorComponent* other = linked.getUnchecked (j);
                    GraphNodeComponent* otherNode = other->getParentGraphComponent();

                    // breath first
                    recalculateConnectionsRecursive (otherNode,
                                                     graph,
                                                     currentIndex,
//                                                     connectorType,
                                                     createConnection);

                    // create link here !
                    if (createConnection)
                    {
                        BasePlugin* destination = (BasePlugin*) otherNode->getUserData ();
                        if (destination != 0)
                        {
                            if (connector->getType () == JOST_LINKTYPE_AUDIO)
                            {
                                graph->connectTo (source,
                                                  connector->getConnectorID(),
                                                  destination,
                                                  other->getConnectorID(),
                                                  JOST_LINKTYPE_AUDIO);
                            }
                            else if (connector->getType () == JOST_LINKTYPE_MIDI)
                            {
                                destOutputMidiOffset = otherNode->getFirstInputOfType (JOST_LINKTYPE_MIDI);

                                graph->connectTo (source,
                                                  connector->getConnectorID() - sourceOutputMidiOffset,
                                                  destination,
                                                  other->getConnectorID() - destOutputMidiOffset,
                                                  JOST_LINKTYPE_MIDI);
                            }
                        }
                    }
                    // end create link !
                }
          }
        }
    }

}