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