haiku/src/apps/patchbay/PatchView.cpp

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;
}