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