root/src/apps/patchbay/PatchRow.cpp
/* PatchRow.cpp
 * ------------
 *
 * Copyright 2013, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Revisions by Pete Goodeve
 *
 * Copyright 1999, Be Incorporated.   All Rights Reserved.
 * This file may be used under the terms of the Be Sample Code License.
 */
 
#include "PatchRow.h"

#include <stdio.h>
#include <CheckBox.h>
#include <Debug.h>
#include <MidiRoster.h>
#include <MidiConsumer.h>
#include <MidiProducer.h>
#include <Window.h>

#include "MidiEventMeter.h"

extern const float ROW_LEFT = 50.0f;
extern const float ROW_TOP = 50.0f;
extern const float ROW_HEIGHT = 40.0f;
extern const float COLUMN_WIDTH = 40.0f;
extern const float METER_PADDING = 15.0f;
extern const uint32 MSG_CONNECT_REQUEST = 'mCRQ';

static const BPoint kBoxOffset(8, 7);

// PatchCheckBox is the check box that describes a connection
// between a producer and a consumer.
class PatchCheckBox : public BCheckBox
{
public:
        PatchCheckBox(BRect r, int32 producerID, int32 consumerID)
                :
                BCheckBox(r, "", "", new BMessage(MSG_CONNECT_REQUEST)),
                fProducerID(producerID),
                fConsumerID(consumerID)
        {}

        int32 ProducerID() const
        {
                return fProducerID;
        }
        int32 ConsumerID() const
        {
                return fConsumerID;
        }

        void DoConnect();
        
private:
        int32 fProducerID;
        int32 fConsumerID;
};


PatchRow::PatchRow(int32 producerID)
        :
        BView(BRect(0, 0, 0, 0), "PatchRow", B_FOLLOW_NONE,
                B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_PULSE_NEEDED),
        fProducerID(producerID),
        fEventMeter(NULL)
{
        fEventMeter = new MidiEventMeter(fProducerID);
}


PatchRow::~PatchRow()
{
        delete fEventMeter;
}


int32
PatchRow::ID() const
{
        return fProducerID;
}


void
PatchRow::Pulse()
{
        if (fEventMeter != NULL)
                fEventMeter->Pulse(this);
}


void
PatchRow::Draw(BRect)
{
        if (fEventMeter != NULL)
                fEventMeter->Draw(this);
}


void
PatchRow::AddColumn(int32 consumerID)
{
        BRect rect;
        int32 numColumns = CountChildren();
        rect.left = numColumns * COLUMN_WIDTH + METER_PADDING + kBoxOffset.x;
        rect.top = kBoxOffset.y;
        rect.right = rect.left + 20;
        rect.bottom = rect.top + 20;
        
        PatchCheckBox* box = new PatchCheckBox(rect, fProducerID, consumerID);
        AddChild(box);
        box->SetTarget(this);
}


void
PatchRow::RemoveColumn(int32 consumerID)
{
        int32 numChildren = CountChildren();
        for (int32 i = 0; i < numChildren; i++) {
                PatchCheckBox* box = dynamic_cast<PatchCheckBox*>(ChildAt(i));
                if (box != NULL && box->ConsumerID() == consumerID) {
                        RemoveChild(box);
                        delete box;
                        while (i < numChildren) {
                                box = dynamic_cast<PatchCheckBox*>(ChildAt(i++));
                                if (box != NULL)
                                        box->MoveBy(-COLUMN_WIDTH, 0);
                        }
                        break;
                }
        }
}


void
PatchRow::Connect(int32 consumerID)
{
        int32 numChildren = CountChildren();
        for (int32 i = 0; i < numChildren; i++) {
                PatchCheckBox* box = dynamic_cast<PatchCheckBox*>(ChildAt(i));
                if (box != NULL && box->ConsumerID() == consumerID)
                        box->SetValue(1);
        }
}


void
PatchRow::Disconnect(int32 consumerID)
{
        int32 numChildren = CountChildren();
        for (int32 i = 0; i < numChildren; i++) {
                PatchCheckBox* box = dynamic_cast<PatchCheckBox*>(ChildAt(i));
                if (box != NULL && box->ConsumerID() == consumerID)
                        box->SetValue(0);
        }
}


void
PatchRow::AttachedToWindow()
{
        Window()->SetPulseRate(200000);
        AdoptParentColors();
        int32 numChildren = CountChildren();
        for (int32 i = 0; i < numChildren; i++) {
                PatchCheckBox* box = dynamic_cast<PatchCheckBox*>(ChildAt(i));
                if (box != NULL)
                        box->SetTarget(this);
        }
}


void
PatchRow::MessageReceived(BMessage* msg)
{
        switch (msg->what) {
        case MSG_CONNECT_REQUEST:
                {
                        BControl* ctrl;
                        if (msg->FindPointer("source", (void**) &ctrl) == B_OK) {
                                PatchCheckBox* box = dynamic_cast<PatchCheckBox*>(ctrl);
                                if (box != NULL)
                                        box->DoConnect();
                        }
                }
                break;
        default:
                BView::MessageReceived(msg);
                break;
        }
}


void
PatchCheckBox::DoConnect()
{
        int32 value = Value();
        int32 inverseValue = (value + 1) % 2;

        BMidiRoster* roster = BMidiRoster::MidiRoster();
        if (roster == NULL) {
                SetValue(inverseValue);
                return;
        }
        
        BMidiProducer* producer = roster->FindProducer(fProducerID);
        BMidiConsumer* consumer = roster->FindConsumer(fConsumerID);    
        if (producer != NULL && consumer != NULL) {
                status_t err;
                if (value != 0)
                        err = producer->Connect(consumer);
                else
                        err = producer->Disconnect(consumer);
                
                if (err != B_OK) {
                        SetValue(inverseValue);
                }
        } else
                SetValue(inverseValue);

        if (producer != NULL)
                producer->Release();
        if (consumer != NULL)
                consumer->Release();
}