⛏️ index : haiku.git

/* PatchView.cpp
 * -------------
 * Implements the main PatchBay view class.
 *
 * 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 "PatchView.h"

#include <Application.h>
#include <Bitmap.h>
#include <Catalog.h>
#include <Debug.h>
#include <IconUtils.h>
#include <InterfaceDefs.h>
#include <Message.h>
#include <Messenger.h>
#include <MidiRoster.h>
#include <Window.h>

#include "EndpointInfo.h"
#include "PatchRow.h"
#include "UnknownDeviceIcons.h"


#define B_TRANSLATION_CONTEXT "Patch Bay"


PatchView::PatchView(BRect rect)
	:
	BView(rect, "PatchView", B_FOLLOW_ALL, B_WILL_DRAW),
	fUnknownDeviceIcon(NULL)
{
	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);

	BRect iconRect(0, 0, LARGE_ICON_SIZE - 1, LARGE_ICON_SIZE - 1);
	fUnknownDeviceIcon = new BBitmap(iconRect, B_RGBA32);
	if (BIconUtils::GetVectorIcon(
			UnknownDevice::kVectorIcon,
			sizeof(UnknownDevice::kVectorIcon),
			fUnknownDeviceIcon)	== B_OK)
		return;
	delete fUnknownDeviceIcon;
}


PatchView::~PatchView()
{
	delete fUnknownDeviceIcon;
}


void
PatchView::AttachedToWindow()
{
	BMidiRoster* roster = BMidiRoster::MidiRoster();
	if (roster == NULL) {
		PRINT(("Couldn't get MIDI roster\n"));
		be_app->PostMessage(B_QUIT_REQUESTED);
		return;
	}
	
	BMessenger msgr(this);
	roster->StartWatching(&msgr);
	SetHighUIColor(B_PANEL_TEXT_COLOR);
}


void
PatchView::MessageReceived(BMessage* msg)
{
	switch (msg->what) {
	case B_MIDI_EVENT:
		HandleMidiEvent(msg);
		break;
	default:
		BView::MessageReceived(msg);
		break;
	}
}


bool
PatchView::GetToolTipAt(BPoint point, BToolTip** tip)
{
	bool found = false;
	int32 index = 0;
	endpoint_itor begin, end;
	int32 size = fConsumers.size();
	for (int32 i = 0; !found && i < size; i++) {
		BRect r = ColumnIconFrameAt(i);
		if (r.Contains(point)) {
			begin = fConsumers.begin();
			end = fConsumers.end();
			found = true;
			index = i;
		}
	}
	size = fProducers.size();
	for (int32 i = 0; !found && i < size; i++) {
		BRect r = RowIconFrameAt(i);
		if (r.Contains(point)) {
			begin = fProducers.begin();
			end = fProducers.end();
			found = true;
			index = i;
		}
	}
	
	if (!found)
		return false;

	endpoint_itor itor;
	for (itor = begin; itor != end; itor++, index--)
		if (index <= 0)
			break;
	
	if (itor == end)
		return false;

	BMidiRoster* roster = BMidiRoster::MidiRoster();
	if (roster == NULL)
		return false;
	BMidiEndpoint* obj = roster->FindEndpoint(itor->ID());
	if (obj == NULL)
		return false;

	BString str;
	str << "<" << obj->ID() << ">: " << obj->Name();
	obj->Release();

	SetToolTip(str.String());

	*tip = ToolTip();

	return true;
}


void
PatchView::Draw(BRect /* updateRect */)
{
	// draw producer icons
	SetDrawingMode(B_OP_OVER);
	int32 index = 0;
	for (list<EndpointInfo>::const_iterator i = fProducers.begin();
		i != fProducers.end(); i++) {
			const BBitmap* bitmap = (i->Icon()) ? i->Icon() : fUnknownDeviceIcon;
			DrawBitmapAsync(bitmap, RowIconFrameAt(index++).LeftTop());
	}

	// draw consumer icons
	int32 index2 = 0;
	for (list<EndpointInfo>::const_iterator i = fConsumers.begin();
		i != fConsumers.end(); i++) {
			const BBitmap* bitmap = (i->Icon()) ? i->Icon() : fUnknownDeviceIcon;
			DrawBitmapAsync(bitmap, ColumnIconFrameAt(index2++).LeftTop());
	}

	if (index == 0 && index2 == 0) {
		const char* message = B_TRANSLATE("No MIDI devices found!");
		float width = StringWidth(message);
		BRect rect = Bounds();

		rect.top = rect.top + rect.bottom / 2;
		rect.left = rect.left + rect.right / 2;
		rect.left -= width / 2;

		DrawString(message, rect.LeftTop());

		// Since the message is centered, we need to redraw the whole view in
		// this case.
		SetFlags(Flags() | B_FULL_UPDATE_ON_RESIZE);
	} else
		SetFlags(Flags() & ~B_FULL_UPDATE_ON_RESIZE);
}


BRect
PatchView::ColumnIconFrameAt(int32 index) const
{
	BRect rect;
	rect.left = ROW_LEFT + METER_PADDING + index * COLUMN_WIDTH;
	rect.top = 10;
	rect.right = rect.left + 31;
	rect.bottom = rect.top + 31;
	return rect;
}


BRect
PatchView::RowIconFrameAt(int32 index) const
{
	BRect rect;
	rect.left = 10;
	rect.top = ROW_TOP + index * ROW_HEIGHT;
	rect.right = rect.left + 31;
	rect.bottom = rect.top + 31;
	return rect;
}


void
PatchView::HandleMidiEvent(BMessage* msg)
{
	SET_DEBUG_ENABLED(true);
	
	int32 op;
	if (msg->FindInt32("be:op", &op) != B_OK) {
		PRINT(("PatchView::HandleMidiEvent: \"op\" field not found\n"));
		return;
	}

	switch (op) {
	case B_MIDI_REGISTERED:
		{
			int32 id;
			if (msg->FindInt32("be:id", &id) != B_OK) {
				PRINT(("PatchView::HandleMidiEvent: \"be:id\""
					" field not found in B_MIDI_REGISTERED event\n"));
				break;
			}
			
			const char* type;
			if (msg->FindString("be:type", &type) != B_OK) {
				PRINT(("PatchView::HandleMidiEvent: \"be:type\""
					" field not found in B_MIDI_REGISTERED event\n"));
				break;
			}
			
			PRINT(("MIDI Roster Event B_MIDI_REGISTERED: id=%" B_PRId32
					", type=%s\n", id, type));
			if (strcmp(type, "producer") == 0)
				AddProducer(id);
			else if (strcmp(type, "consumer") == 0)
				AddConsumer(id);
		}
		break;
	case B_MIDI_UNREGISTERED:
		{
			int32 id;
			if (msg->FindInt32("be:id", &id) != B_OK) {
				PRINT(("PatchView::HandleMidiEvent: \"be:id\""
					" field not found in B_MIDI_UNREGISTERED\n"));
				break;
			}
			
			const char* type;
			if (msg->FindString("be:type", &type) != B_OK) {
				PRINT(("PatchView::HandleMidiEvent: \"be:type\""
					" field not found in B_MIDI_UNREGISTERED\n"));
				break;
			}
			
			PRINT(("MIDI Roster Event B_MIDI_UNREGISTERED: id=%" B_PRId32
					", type=%s\n", id, type));
			if (strcmp(type, "producer") == 0)
				RemoveProducer(id);
			else if (strcmp(type, "consumer") == 0)
				RemoveConsumer(id);
		}
		break;
	case B_MIDI_CHANGED_PROPERTIES:
		{
			int32 id;
			if (msg->FindInt32("be:id", &id) != B_OK) {
				PRINT(("PatchView::HandleMidiEvent: \"be:id\""
					" field not found in B_MIDI_CHANGED_PROPERTIES\n"));
				break;
			}
			
			const char* type;
			if (msg->FindString("be:type", &type) != B_OK) {
				PRINT(("PatchView::HandleMidiEvent: \"be:type\""
					" field not found in B_MIDI_CHANGED_PROPERTIES\n"));
				break;
			}
			
			BMessage props;
			if (msg->FindMessage("be:properties", &props) != B_OK) {
				PRINT(("PatchView::HandleMidiEvent: \"be:properties\""
					" field not found in B_MIDI_CHANGED_PROPERTIES\n"));
				break;
			}
			
			PRINT(("MIDI Roster Event B_MIDI_CHANGED_PROPERTIES: id=%" B_PRId32
					", type=%s\n", id, type));
			if (strcmp(type, "producer") == 0)
				UpdateProducerProps(id, &props);
			else if (strcmp(type, "consumer") == 0)
				UpdateConsumerProps(id, &props);
			
		}
		break;
	case B_MIDI_CHANGED_NAME:
	case B_MIDI_CHANGED_LATENCY:
		// we don't care about these
		break;
	case B_MIDI_CONNECTED:
		{
			int32 prod;
			if (msg->FindInt32("be:producer", &prod) != B_OK) {
				PRINT(("PatchView::HandleMidiEvent: \"be:producer\""
					" field not found in B_MIDI_CONNECTED\n"));
				break;
			}
			
			int32 cons;
			if (msg->FindInt32("be:consumer", &cons) != B_OK) {
				PRINT(("PatchView::HandleMidiEvent: \"be:consumer\""
					" field not found in B_MIDI_CONNECTED\n"));
				break;
			}
			PRINT(("MIDI Roster Event B_MIDI_CONNECTED: producer=%" B_PRId32
					", consumer=%" B_PRId32 "\n", prod, cons));
			Connect(prod, cons);
		}
		break;
	case B_MIDI_DISCONNECTED:
		{
			int32 prod;
			if (msg->FindInt32("be:producer", &prod) != B_OK) {
				PRINT(("PatchView::HandleMidiEvent: \"be:producer\""
					" field not found in B_MIDI_DISCONNECTED\n"));
				break;
			}
			
			int32 cons;
			if (msg->FindInt32("be:consumer", &cons) != B_OK) {
				PRINT(("PatchView::HandleMidiEvent: \"be:consumer\""
					" field not found in B_MIDI_DISCONNECTED\n"));
				break;
			}
			PRINT(("MIDI Roster Event B_MIDI_DISCONNECTED: producer=%" B_PRId32
					", consumer=%" B_PRId32 "\n", prod, cons));
			Disconnect(prod, cons);
		}
		break;
	default:
		PRINT(("PatchView::HandleMidiEvent: unknown opcode %" B_PRId32 "\n",
			op));
		break;
	}
}


void
PatchView::AddProducer(int32 id)
{
	EndpointInfo info(id);
	fProducers.push_back(info);
	
	Window()->BeginViewTransaction();
	PatchRow* row = new PatchRow(id);
	fPatchRows.push_back(row);	
	BPoint p1 = CalcRowOrigin(fPatchRows.size() - 1);
	BPoint p2 = CalcRowSize();
	row->MoveTo(p1);
	row->ResizeTo(p2.x, p2.y);
	for (list<EndpointInfo>::const_iterator i = fConsumers.begin();
		i != fConsumers.end(); i++)
			row->AddColumn(i->ID());
	AddChild(row);
	Invalidate();
	Window()->EndViewTransaction();
}


void
PatchView::AddConsumer(int32 id)
{
	EndpointInfo info(id);
	fConsumers.push_back(info);
	
	Window()->BeginViewTransaction();
	BPoint newSize = CalcRowSize();
	for (row_itor i = fPatchRows.begin(); i != fPatchRows.end(); i++) {
		(*i)->AddColumn(id);
		(*i)->ResizeTo(newSize.x, newSize.y - 1);
	}
	Invalidate();
	Window()->EndViewTransaction();
}


void
PatchView::RemoveProducer(int32 id)
{
	for (endpoint_itor i = fProducers.begin(); i != fProducers.end(); i++) {
		if (i->ID() == id) {
			fProducers.erase(i);
			break;
		}
	}

	Window()->BeginViewTransaction();
	for (row_itor i = fPatchRows.begin(); i != fPatchRows.end(); i++) {
		if ((*i)->ID() == id) {
			PatchRow* row = *i;
			i = fPatchRows.erase(i);
			RemoveChild(row);
			delete row;
			float moveBy = -1 * CalcRowSize().y;
			while (i != fPatchRows.end()) {
				(*i++)->MoveBy(0, moveBy);
			}
			break;
		}
	}
	Invalidate();
	Window()->EndViewTransaction();
}


void
PatchView::RemoveConsumer(int32 id)
{
	Window()->BeginViewTransaction();
	for (endpoint_itor i = fConsumers.begin(); i != fConsumers.end(); i++) {
		if (i->ID() == id) {
			fConsumers.erase(i);
			break;
		}
	}

	BPoint newSize = CalcRowSize();
	for (row_itor i = fPatchRows.begin(); i != fPatchRows.end(); i++) {
		(*i)->RemoveColumn(id);
		(*i)->ResizeTo(newSize.x, newSize.y - 1);
	}
	Invalidate();
	Window()->EndViewTransaction();
}


void
PatchView::UpdateProducerProps(int32 id, const BMessage* props)
{
	for (endpoint_itor i = fProducers.begin(); i != fProducers.end(); i++) {
		if (i->ID() == id) {
			i->UpdateProperties(props);
			Invalidate();
			break;
		}
	}
}


void
PatchView::UpdateConsumerProps(int32 id, const BMessage* props)
{
	for (endpoint_itor i = fConsumers.begin(); i != fConsumers.end(); i++) {
		if (i->ID() == id) {
			i->UpdateProperties(props);
			Invalidate();
			break;
		}
	}
}


void
PatchView::Connect(int32 prod, int32 cons)
{
	for (row_itor i = fPatchRows.begin(); i != fPatchRows.end(); i++) {
		if ((*i)->ID() == prod) {
			(*i)->Connect(cons);
			break;
		}
	}
}


void
PatchView::Disconnect(int32 prod, int32 cons)
{
	for (row_itor i = fPatchRows.begin(); i != fPatchRows.end(); i++) {
		if ((*i)->ID() == prod) {
			(*i)->Disconnect(cons);
			break;
		}
	}
}


BPoint
PatchView::CalcRowOrigin(int32 rowIndex) const
{
	BPoint point;
	point.x = ROW_LEFT;
	point.y = ROW_TOP + rowIndex * ROW_HEIGHT;
	return point;
}


BPoint
PatchView::CalcRowSize() const
{
	BPoint point;
	point.x = METER_PADDING + fConsumers.size()*COLUMN_WIDTH;
	point.y = ROW_HEIGHT - 1;
	return point;
}