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