xref: /haiku/src/apps/patchbay/PatchView.cpp (revision bab64f65bb775dc23060e276f1f1c4498ab7af6c)
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 
PatchView(BRect rect)35 PatchView::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 
~PatchView()53 PatchView::~PatchView()
54 {
55 	delete fUnknownDeviceIcon;
56 }
57 
58 
59 void
AttachedToWindow()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 	SetHighUIColor(B_PANEL_TEXT_COLOR);
72 }
73 
74 
75 void
MessageReceived(BMessage * msg)76 PatchView::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 
89 bool
GetToolTipAt(BPoint point,BToolTip ** tip)90 PatchView::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 
146 void
Draw(BRect)147 PatchView::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 
185 BRect
ColumnIconFrameAt(int32 index) const186 PatchView::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 
197 BRect
RowIconFrameAt(int32 index) const198 PatchView::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 
209 void
HandleMidiEvent(BMessage * msg)210 PatchView::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 
353 void
AddProducer(int32 id)354 PatchView::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 
375 void
AddConsumer(int32 id)376 PatchView::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 
392 void
RemoveProducer(int32 id)393 PatchView::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 
421 void
RemoveConsumer(int32 id)422 PatchView::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 
442 void
UpdateProducerProps(int32 id,const BMessage * props)443 PatchView::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 
455 void
UpdateConsumerProps(int32 id,const BMessage * props)456 PatchView::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 
468 void
Connect(int32 prod,int32 cons)469 PatchView::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 
480 void
Disconnect(int32 prod,int32 cons)481 PatchView::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 
492 BPoint
CalcRowOrigin(int32 rowIndex) const493 PatchView::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 
502 BPoint
CalcRowSize() const503 PatchView::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