1/* PatchView.cpp
2 * -------------
3 * Implements the main PatchBay view class.
4 *
5 * Copyright 2013, Haiku, Inc. All rights reserved.
6 * Distributed under the terms of the MIT License.
7 *
8 * Revisions by Pete Goodeve
9 *
10 * Copyright 1999, Be Incorporated.   All Rights Reserved.
11 * This file may be used under the terms of the Be Sample Code License.
12 */
13
14#include "PatchView.h"
15
16#include <Application.h>
17#include <Bitmap.h>
18#include <Catalog.h>
19#include <Debug.h>
20#include <IconUtils.h>
21#include <InterfaceDefs.h>
22#include <Message.h>
23#include <Messenger.h>
24#include <MidiRoster.h>
25#include <Window.h>
26
27#include "EndpointInfo.h"
28#include "PatchRow.h"
29#include "UnknownDeviceIcons.h"
30
31
32#define B_TRANSLATION_CONTEXT "Patch Bay"
33
34
35PatchView::PatchView(BRect rect)
36	:
37	BView(rect, "PatchView", B_FOLLOW_ALL, B_WILL_DRAW),
38	fUnknownDeviceIcon(NULL)
39{
40	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
41
42	BRect iconRect(0, 0, LARGE_ICON_SIZE - 1, LARGE_ICON_SIZE - 1);
43	fUnknownDeviceIcon = new BBitmap(iconRect, B_RGBA32);
44	if (BIconUtils::GetVectorIcon(
45			UnknownDevice::kVectorIcon,
46			sizeof(UnknownDevice::kVectorIcon),
47			fUnknownDeviceIcon)	== B_OK)
48		return;
49	delete fUnknownDeviceIcon;
50}
51
52
53PatchView::~PatchView()
54{
55	delete fUnknownDeviceIcon;
56}
57
58
59void
60PatchView::AttachedToWindow()
61{
62	BMidiRoster* roster = BMidiRoster::MidiRoster();
63	if (roster == NULL) {
64		PRINT(("Couldn't get MIDI roster\n"));
65		be_app->PostMessage(B_QUIT_REQUESTED);
66		return;
67	}
68
69	BMessenger msgr(this);
70	roster->StartWatching(&msgr);
71	SetHighUIColor(B_PANEL_TEXT_COLOR);
72}
73
74
75void
76PatchView::MessageReceived(BMessage* msg)
77{
78	switch (msg->what) {
79	case B_MIDI_EVENT:
80		HandleMidiEvent(msg);
81		break;
82	default:
83		BView::MessageReceived(msg);
84		break;
85	}
86}
87
88
89bool
90PatchView::GetToolTipAt(BPoint point, BToolTip** tip)
91{
92	bool found = false;
93	int32 index = 0;
94	endpoint_itor begin, end;
95	int32 size = fConsumers.size();
96	for (int32 i = 0; !found && i < size; i++) {
97		BRect r = ColumnIconFrameAt(i);
98		if (r.Contains(point)) {
99			begin = fConsumers.begin();
100			end = fConsumers.end();
101			found = true;
102			index = i;
103		}
104	}
105	size = fProducers.size();
106	for (int32 i = 0; !found && i < size; i++) {
107		BRect r = RowIconFrameAt(i);
108		if (r.Contains(point)) {
109			begin = fProducers.begin();
110			end = fProducers.end();
111			found = true;
112			index = i;
113		}
114	}
115
116	if (!found)
117		return false;
118
119	endpoint_itor itor;
120	for (itor = begin; itor != end; itor++, index--)
121		if (index <= 0)
122			break;
123
124	if (itor == end)
125		return false;
126
127	BMidiRoster* roster = BMidiRoster::MidiRoster();
128	if (roster == NULL)
129		return false;
130	BMidiEndpoint* obj = roster->FindEndpoint(itor->ID());
131	if (obj == NULL)
132		return false;
133
134	BString str;
135	str << "<" << obj->ID() << ">: " << obj->Name();
136	obj->Release();
137
138	SetToolTip(str.String());
139
140	*tip = ToolTip();
141
142	return true;
143}
144
145
146void
147PatchView::Draw(BRect /* updateRect */)
148{
149	// draw producer icons
150	SetDrawingMode(B_OP_OVER);
151	int32 index = 0;
152	for (list<EndpointInfo>::const_iterator i = fProducers.begin();
153		i != fProducers.end(); i++) {
154			const BBitmap* bitmap = (i->Icon()) ? i->Icon() : fUnknownDeviceIcon;
155			DrawBitmapAsync(bitmap, RowIconFrameAt(index++).LeftTop());
156	}
157
158	// draw consumer icons
159	int32 index2 = 0;
160	for (list<EndpointInfo>::const_iterator i = fConsumers.begin();
161		i != fConsumers.end(); i++) {
162			const BBitmap* bitmap = (i->Icon()) ? i->Icon() : fUnknownDeviceIcon;
163			DrawBitmapAsync(bitmap, ColumnIconFrameAt(index2++).LeftTop());
164	}
165
166	if (index == 0 && index2 == 0) {
167		const char* message = B_TRANSLATE("No MIDI devices found!");
168		float width = StringWidth(message);
169		BRect rect = Bounds();
170
171		rect.top = rect.top + rect.bottom / 2;
172		rect.left = rect.left + rect.right / 2;
173		rect.left -= width / 2;
174
175		DrawString(message, rect.LeftTop());
176
177		// Since the message is centered, we need to redraw the whole view in
178		// this case.
179		SetFlags(Flags() | B_FULL_UPDATE_ON_RESIZE);
180	} else
181		SetFlags(Flags() & ~B_FULL_UPDATE_ON_RESIZE);
182}
183
184
185BRect
186PatchView::ColumnIconFrameAt(int32 index) const
187{
188	BRect rect;
189	rect.left = ROW_LEFT + METER_PADDING + index * COLUMN_WIDTH;
190	rect.top = 10;
191	rect.right = rect.left + 31;
192	rect.bottom = rect.top + 31;
193	return rect;
194}
195
196
197BRect
198PatchView::RowIconFrameAt(int32 index) const
199{
200	BRect rect;
201	rect.left = 10;
202	rect.top = ROW_TOP + index * ROW_HEIGHT;
203	rect.right = rect.left + 31;
204	rect.bottom = rect.top + 31;
205	return rect;
206}
207
208
209void
210PatchView::HandleMidiEvent(BMessage* msg)
211{
212	SET_DEBUG_ENABLED(true);
213
214	int32 op;
215	if (msg->FindInt32("be:op", &op) != B_OK) {
216		PRINT(("PatchView::HandleMidiEvent: \"op\" field not found\n"));
217		return;
218	}
219
220	switch (op) {
221	case B_MIDI_REGISTERED:
222		{
223			int32 id;
224			if (msg->FindInt32("be:id", &id) != B_OK) {
225				PRINT(("PatchView::HandleMidiEvent: \"be:id\""
226					" field not found in B_MIDI_REGISTERED event\n"));
227				break;
228			}
229
230			const char* type;
231			if (msg->FindString("be:type", &type) != B_OK) {
232				PRINT(("PatchView::HandleMidiEvent: \"be:type\""
233					" field not found in B_MIDI_REGISTERED event\n"));
234				break;
235			}
236
237			PRINT(("MIDI Roster Event B_MIDI_REGISTERED: id=%" B_PRId32
238					", type=%s\n", id, type));
239			if (strcmp(type, "producer") == 0)
240				AddProducer(id);
241			else if (strcmp(type, "consumer") == 0)
242				AddConsumer(id);
243		}
244		break;
245	case B_MIDI_UNREGISTERED:
246		{
247			int32 id;
248			if (msg->FindInt32("be:id", &id) != B_OK) {
249				PRINT(("PatchView::HandleMidiEvent: \"be:id\""
250					" field not found in B_MIDI_UNREGISTERED\n"));
251				break;
252			}
253
254			const char* type;
255			if (msg->FindString("be:type", &type) != B_OK) {
256				PRINT(("PatchView::HandleMidiEvent: \"be:type\""
257					" field not found in B_MIDI_UNREGISTERED\n"));
258				break;
259			}
260
261			PRINT(("MIDI Roster Event B_MIDI_UNREGISTERED: id=%" B_PRId32
262					", type=%s\n", id, type));
263			if (strcmp(type, "producer") == 0)
264				RemoveProducer(id);
265			else if (strcmp(type, "consumer") == 0)
266				RemoveConsumer(id);
267		}
268		break;
269	case B_MIDI_CHANGED_PROPERTIES:
270		{
271			int32 id;
272			if (msg->FindInt32("be:id", &id) != B_OK) {
273				PRINT(("PatchView::HandleMidiEvent: \"be:id\""
274					" field not found in B_MIDI_CHANGED_PROPERTIES\n"));
275				break;
276			}
277
278			const char* type;
279			if (msg->FindString("be:type", &type) != B_OK) {
280				PRINT(("PatchView::HandleMidiEvent: \"be:type\""
281					" field not found in B_MIDI_CHANGED_PROPERTIES\n"));
282				break;
283			}
284
285			BMessage props;
286			if (msg->FindMessage("be:properties", &props) != B_OK) {
287				PRINT(("PatchView::HandleMidiEvent: \"be:properties\""
288					" field not found in B_MIDI_CHANGED_PROPERTIES\n"));
289				break;
290			}
291
292			PRINT(("MIDI Roster Event B_MIDI_CHANGED_PROPERTIES: id=%" B_PRId32
293					", type=%s\n", id, type));
294			if (strcmp(type, "producer") == 0)
295				UpdateProducerProps(id, &props);
296			else if (strcmp(type, "consumer") == 0)
297				UpdateConsumerProps(id, &props);
298
299		}
300		break;
301	case B_MIDI_CHANGED_NAME:
302	case B_MIDI_CHANGED_LATENCY:
303		// we don't care about these
304		break;
305	case B_MIDI_CONNECTED:
306		{
307			int32 prod;
308			if (msg->FindInt32("be:producer", &prod) != B_OK) {
309				PRINT(("PatchView::HandleMidiEvent: \"be:producer\""
310					" field not found in B_MIDI_CONNECTED\n"));
311				break;
312			}
313
314			int32 cons;
315			if (msg->FindInt32("be:consumer", &cons) != B_OK) {
316				PRINT(("PatchView::HandleMidiEvent: \"be:consumer\""
317					" field not found in B_MIDI_CONNECTED\n"));
318				break;
319			}
320			PRINT(("MIDI Roster Event B_MIDI_CONNECTED: producer=%" B_PRId32
321					", consumer=%" B_PRId32 "\n", prod, cons));
322			Connect(prod, cons);
323		}
324		break;
325	case B_MIDI_DISCONNECTED:
326		{
327			int32 prod;
328			if (msg->FindInt32("be:producer", &prod) != B_OK) {
329				PRINT(("PatchView::HandleMidiEvent: \"be:producer\""
330					" field not found in B_MIDI_DISCONNECTED\n"));
331				break;
332			}
333
334			int32 cons;
335			if (msg->FindInt32("be:consumer", &cons) != B_OK) {
336				PRINT(("PatchView::HandleMidiEvent: \"be:consumer\""
337					" field not found in B_MIDI_DISCONNECTED\n"));
338				break;
339			}
340			PRINT(("MIDI Roster Event B_MIDI_DISCONNECTED: producer=%" B_PRId32
341					", consumer=%" B_PRId32 "\n", prod, cons));
342			Disconnect(prod, cons);
343		}
344		break;
345	default:
346		PRINT(("PatchView::HandleMidiEvent: unknown opcode %" B_PRId32 "\n",
347			op));
348		break;
349	}
350}
351
352
353void
354PatchView::AddProducer(int32 id)
355{
356	EndpointInfo info(id);
357	fProducers.push_back(info);
358
359	Window()->BeginViewTransaction();
360	PatchRow* row = new PatchRow(id);
361	fPatchRows.push_back(row);
362	BPoint p1 = CalcRowOrigin(fPatchRows.size() - 1);
363	BPoint p2 = CalcRowSize();
364	row->MoveTo(p1);
365	row->ResizeTo(p2.x, p2.y);
366	for (list<EndpointInfo>::const_iterator i = fConsumers.begin();
367		i != fConsumers.end(); i++)
368			row->AddColumn(i->ID());
369	AddChild(row);
370	Invalidate();
371	Window()->EndViewTransaction();
372}
373
374
375void
376PatchView::AddConsumer(int32 id)
377{
378	EndpointInfo info(id);
379	fConsumers.push_back(info);
380
381	Window()->BeginViewTransaction();
382	BPoint newSize = CalcRowSize();
383	for (row_itor i = fPatchRows.begin(); i != fPatchRows.end(); i++) {
384		(*i)->AddColumn(id);
385		(*i)->ResizeTo(newSize.x, newSize.y - 1);
386	}
387	Invalidate();
388	Window()->EndViewTransaction();
389}
390
391
392void
393PatchView::RemoveProducer(int32 id)
394{
395	for (endpoint_itor i = fProducers.begin(); i != fProducers.end(); i++) {
396		if (i->ID() == id) {
397			fProducers.erase(i);
398			break;
399		}
400	}
401
402	Window()->BeginViewTransaction();
403	for (row_itor i = fPatchRows.begin(); i != fPatchRows.end(); i++) {
404		if ((*i)->ID() == id) {
405			PatchRow* row = *i;
406			i = fPatchRows.erase(i);
407			RemoveChild(row);
408			delete row;
409			float moveBy = -1 * CalcRowSize().y;
410			while (i != fPatchRows.end()) {
411				(*i++)->MoveBy(0, moveBy);
412			}
413			break;
414		}
415	}
416	Invalidate();
417	Window()->EndViewTransaction();
418}
419
420
421void
422PatchView::RemoveConsumer(int32 id)
423{
424	Window()->BeginViewTransaction();
425	for (endpoint_itor i = fConsumers.begin(); i != fConsumers.end(); i++) {
426		if (i->ID() == id) {
427			fConsumers.erase(i);
428			break;
429		}
430	}
431
432	BPoint newSize = CalcRowSize();
433	for (row_itor i = fPatchRows.begin(); i != fPatchRows.end(); i++) {
434		(*i)->RemoveColumn(id);
435		(*i)->ResizeTo(newSize.x, newSize.y - 1);
436	}
437	Invalidate();
438	Window()->EndViewTransaction();
439}
440
441
442void
443PatchView::UpdateProducerProps(int32 id, const BMessage* props)
444{
445	for (endpoint_itor i = fProducers.begin(); i != fProducers.end(); i++) {
446		if (i->ID() == id) {
447			i->UpdateProperties(props);
448			Invalidate();
449			break;
450		}
451	}
452}
453
454
455void
456PatchView::UpdateConsumerProps(int32 id, const BMessage* props)
457{
458	for (endpoint_itor i = fConsumers.begin(); i != fConsumers.end(); i++) {
459		if (i->ID() == id) {
460			i->UpdateProperties(props);
461			Invalidate();
462			break;
463		}
464	}
465}
466
467
468void
469PatchView::Connect(int32 prod, int32 cons)
470{
471	for (row_itor i = fPatchRows.begin(); i != fPatchRows.end(); i++) {
472		if ((*i)->ID() == prod) {
473			(*i)->Connect(cons);
474			break;
475		}
476	}
477}
478
479
480void
481PatchView::Disconnect(int32 prod, int32 cons)
482{
483	for (row_itor i = fPatchRows.begin(); i != fPatchRows.end(); i++) {
484		if ((*i)->ID() == prod) {
485			(*i)->Disconnect(cons);
486			break;
487		}
488	}
489}
490
491
492BPoint
493PatchView::CalcRowOrigin(int32 rowIndex) const
494{
495	BPoint point;
496	point.x = ROW_LEFT;
497	point.y = ROW_TOP + rowIndex * ROW_HEIGHT;
498	return point;
499}
500
501
502BPoint
503PatchView::CalcRowSize() const
504{
505	BPoint point;
506	point.x = METER_PADDING + fConsumers.size()*COLUMN_WIDTH;
507	point.y = ROW_HEIGHT - 1;
508	return point;
509}
510