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 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 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 SetHighUIColor(B_PANEL_TEXT_COLOR); 72 } 73 74 75 void 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 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 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 186 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 198 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 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 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 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 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 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 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 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 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 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 493 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 503 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