510 lines
11 KiB
C++
510 lines
11 KiB
C++
/* 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;
|
|
}
|