xref: /haiku/src/apps/patchbay/PatchView.cpp (revision f648efd3d411e5d5590b586091e5cb75da896cfd)
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=%" B_PRId32
216 					", type=%s\n", 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=%" B_PRId32
240 					", type=%s\n", 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=%" B_PRId32
271 					", type=%s\n", 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=%" B_PRId32
299 					", consumer=%" B_PRId32 "\n", 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=%" B_PRId32
319 					", consumer=%" B_PRId32 "\n", prod, cons));
320 			Disconnect(prod, cons);
321 		}
322 		break;
323 	default:
324 		PRINT(("PatchView::HandleMidiEvent: unknown opcode %" B_PRId32 "\n",
325 			op));
326 		break;
327 	}
328 }
329 
330 
331 void
332 PatchView::AddProducer(int32 id)
333 {
334 	EndpointInfo info(id);
335 	fProducers.push_back(info);
336 
337 	Window()->BeginViewTransaction();
338 	PatchRow* row = new PatchRow(id);
339 	fPatchRows.push_back(row);
340 	BPoint p1 = CalcRowOrigin(fPatchRows.size() - 1);
341 	BPoint p2 = CalcRowSize();
342 	row->MoveTo(p1);
343 	row->ResizeTo(p2.x, p2.y);
344 	for (list<EndpointInfo>::const_iterator i = fConsumers.begin();
345 		i != fConsumers.end(); i++)
346 			row->AddColumn(i->ID());
347 	AddChild(row);
348 	Invalidate();
349 	Window()->EndViewTransaction();
350 }
351 
352 
353 void
354 PatchView::AddConsumer(int32 id)
355 {
356 	EndpointInfo info(id);
357 	fConsumers.push_back(info);
358 
359 	Window()->BeginViewTransaction();
360 	BPoint newSize = CalcRowSize();
361 	for (row_itor i = fPatchRows.begin(); i != fPatchRows.end(); i++) {
362 		(*i)->AddColumn(id);
363 		(*i)->ResizeTo(newSize.x, newSize.y - 1);
364 	}
365 	Invalidate();
366 	Window()->EndViewTransaction();
367 }
368 
369 
370 void
371 PatchView::RemoveProducer(int32 id)
372 {
373 	for (endpoint_itor i = fProducers.begin(); i != fProducers.end(); i++) {
374 		if (i->ID() == id) {
375 			fProducers.erase(i);
376 			break;
377 		}
378 	}
379 
380 	Window()->BeginViewTransaction();
381 	for (row_itor i = fPatchRows.begin(); i != fPatchRows.end(); i++) {
382 		if ((*i)->ID() == id) {
383 			PatchRow* row = *i;
384 			i = fPatchRows.erase(i);
385 			RemoveChild(row);
386 			delete row;
387 			float moveBy = -1 * CalcRowSize().y;
388 			while (i != fPatchRows.end()) {
389 				(*i++)->MoveBy(0, moveBy);
390 			}
391 			break;
392 		}
393 	}
394 	Invalidate();
395 	Window()->EndViewTransaction();
396 }
397 
398 
399 void
400 PatchView::RemoveConsumer(int32 id)
401 {
402 	Window()->BeginViewTransaction();
403 	for (endpoint_itor i = fConsumers.begin(); i != fConsumers.end(); i++) {
404 		if (i->ID() == id) {
405 			fConsumers.erase(i);
406 			break;
407 		}
408 	}
409 
410 	BPoint newSize = CalcRowSize();
411 	for (row_itor i = fPatchRows.begin(); i != fPatchRows.end(); i++) {
412 		(*i)->RemoveColumn(id);
413 		(*i)->ResizeTo(newSize.x, newSize.y - 1);
414 	}
415 	Invalidate();
416 	Window()->EndViewTransaction();
417 }
418 
419 
420 void
421 PatchView::UpdateProducerProps(int32 id, const BMessage* props)
422 {
423 	for (endpoint_itor i = fProducers.begin(); i != fProducers.end(); i++) {
424 		if (i->ID() == id) {
425 			i->UpdateProperties(props);
426 			Invalidate();
427 			break;
428 		}
429 	}
430 }
431 
432 
433 void
434 PatchView::UpdateConsumerProps(int32 id, const BMessage* props)
435 {
436 	for (endpoint_itor i = fConsumers.begin(); i != fConsumers.end(); i++) {
437 		if (i->ID() == id) {
438 			i->UpdateProperties(props);
439 			Invalidate();
440 			break;
441 		}
442 	}
443 }
444 
445 
446 void
447 PatchView::Connect(int32 prod, int32 cons)
448 {
449 	for (row_itor i = fPatchRows.begin(); i != fPatchRows.end(); i++) {
450 		if ((*i)->ID() == prod) {
451 			(*i)->Connect(cons);
452 			break;
453 		}
454 	}
455 }
456 
457 
458 void
459 PatchView::Disconnect(int32 prod, int32 cons)
460 {
461 	for (row_itor i = fPatchRows.begin(); i != fPatchRows.end(); i++) {
462 		if ((*i)->ID() == prod) {
463 			(*i)->Disconnect(cons);
464 			break;
465 		}
466 	}
467 }
468 
469 
470 BPoint
471 PatchView::CalcRowOrigin(int32 rowIndex) const
472 {
473 	BPoint point;
474 	point.x = ROW_LEFT;
475 	point.y = ROW_TOP + rowIndex * ROW_HEIGHT;
476 	return point;
477 }
478 
479 
480 BPoint
481 PatchView::CalcRowSize() const
482 {
483 	BPoint point;
484 	point.x = METER_PADDING + fConsumers.size()*COLUMN_WIDTH;
485 	point.y = ROW_HEIGHT - 1;
486 	return point;
487 }
488