1 // qjackctlPatchbay.cpp 2 // 3 /**************************************************************************** 4 Copyright (C) 2003-2021, rncbc aka Rui Nuno Capela. All rights reserved. 5 6 This program is free software; you can redistribute it and/or 7 modify it under the terms of the GNU General Public License 8 as published by the Free Software Foundation; either version 2 9 of the License, or (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License along 17 with this program; if not, write to the Free Software Foundation, Inc., 18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 20 *****************************************************************************/ 21 22 #include "qjackctlPatchbay.h" 23 24 #include "qjackctlMainForm.h" 25 26 #include <QMessageBox> 27 #include <QHeaderView> 28 #include <QPainterPath> 29 #include <QPainter> 30 #include <QPolygon> 31 #include <QPixmap> 32 #include <QBitmap> 33 #include <QTimer> 34 #include <QMenu> 35 #include <QToolTip> 36 #include <QScrollBar> 37 #include <QRegularExpression> 38 39 #include <QDragEnterEvent> 40 #include <QDragMoveEvent> 41 42 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) 43 #include <QMimeData> 44 #include <QDrag> 45 #endif 46 47 // Interactivity socket form. 48 #include "qjackctlSocketForm.h" 49 50 // External patchbay loader. 51 #include "qjackctlPatchbayFile.h" 52 53 54 //---------------------------------------------------------------------- 55 // class qjackctlPlugItem -- Socket plug list item. 56 // 57 58 // Constructor. 59 qjackctlPlugItem::qjackctlPlugItem ( qjackctlSocketItem *pSocket, 60 const QString& sPlugName, qjackctlPlugItem *pPlugAfter ) 61 : QTreeWidgetItem(pSocket, pPlugAfter, QJACKCTL_PLUGITEM) 62 { 63 QTreeWidgetItem::setText(0, sPlugName); 64 65 m_pSocket = pSocket; 66 m_sPlugName = sPlugName; 67 68 m_pSocket->plugs().append(this); 69 70 int iPixmap; 71 if (pSocket->socketType() == QJACKCTL_SOCKETTYPE_JACK_AUDIO) 72 iPixmap = QJACKCTL_XPM_AUDIO_PLUG; 73 else 74 iPixmap = QJACKCTL_XPM_MIDI_PLUG; 75 QTreeWidgetItem::setIcon(0, QIcon(pSocket->pixmap(iPixmap))); 76 77 QTreeWidgetItem::setFlags(QTreeWidgetItem::flags() 78 & ~Qt::ItemIsSelectable); 79 } 80 81 // Default destructor. 82 qjackctlPlugItem::~qjackctlPlugItem (void) 83 { 84 const int iPlug = m_pSocket->plugs().indexOf(this); 85 if (iPlug >= 0) 86 m_pSocket->plugs().removeAt(iPlug); 87 } 88 89 90 // Instance accessors. 91 const QString& qjackctlPlugItem::socketName (void) const 92 { 93 return m_pSocket->socketName(); 94 } 95 96 const QString& qjackctlPlugItem::plugName (void) const 97 { 98 return m_sPlugName; 99 } 100 101 102 // Patchbay socket item accessor. 103 qjackctlSocketItem *qjackctlPlugItem::socket (void) const 104 { 105 return m_pSocket; 106 } 107 108 109 //---------------------------------------------------------------------- 110 // class qjackctlSocketItem -- Jack client list item. 111 // 112 113 // Constructor. 114 qjackctlSocketItem::qjackctlSocketItem ( qjackctlSocketList *pSocketList, 115 const QString& sSocketName, const QString& sClientName, 116 int iSocketType, qjackctlSocketItem *pSocketAfter ) 117 : QTreeWidgetItem(pSocketList->listView(), pSocketAfter, QJACKCTL_SOCKETITEM) 118 { 119 QTreeWidgetItem::setText(0, sSocketName); 120 121 m_pSocketList = pSocketList; 122 m_sSocketName = sSocketName; 123 m_sClientName = sClientName; 124 m_iSocketType = iSocketType; 125 m_bExclusive = false; 126 m_sSocketForward.clear(); 127 128 m_pSocketList->sockets().append(this); 129 130 updatePixmap(); 131 } 132 133 // Default destructor. 134 qjackctlSocketItem::~qjackctlSocketItem (void) 135 { 136 QListIterator<qjackctlSocketItem *> iter(m_connects); 137 while (iter.hasNext()) 138 (iter.next())->removeConnect(this); 139 140 m_connects.clear(); 141 m_plugs.clear(); 142 143 const int iSocket = m_pSocketList->sockets().indexOf(this); 144 if (iSocket >= 0) 145 m_pSocketList->sockets().removeAt(iSocket); 146 } 147 148 149 // Instance accessors. 150 const QString& qjackctlSocketItem::socketName (void) const 151 { 152 return m_sSocketName; 153 } 154 155 const QString& qjackctlSocketItem::clientName (void) const 156 { 157 return m_sClientName; 158 } 159 160 int qjackctlSocketItem::socketType (void) const 161 { 162 return m_iSocketType; 163 } 164 165 bool qjackctlSocketItem::isExclusive (void) const 166 { 167 return m_bExclusive; 168 } 169 170 const QString& qjackctlSocketItem::forward (void) const 171 { 172 return m_sSocketForward; 173 } 174 175 176 void qjackctlSocketItem::setSocketName ( const QString& sSocketName ) 177 { 178 m_sSocketName = sSocketName; 179 } 180 181 void qjackctlSocketItem::setClientName ( const QString& sClientName ) 182 { 183 m_sClientName = sClientName; 184 } 185 186 void qjackctlSocketItem::setSocketType ( int iSocketType ) 187 { 188 m_iSocketType = iSocketType; 189 } 190 191 void qjackctlSocketItem::setExclusive ( bool bExclusive ) 192 { 193 m_bExclusive = bExclusive; 194 } 195 196 void qjackctlSocketItem::setForward ( const QString& sSocketForward ) 197 { 198 m_sSocketForward = sSocketForward; 199 } 200 201 202 // Socket flags accessor. 203 bool qjackctlSocketItem::isReadable (void) const 204 { 205 return m_pSocketList->isReadable(); 206 } 207 208 209 // Plug finder. 210 qjackctlPlugItem *qjackctlSocketItem::findPlug ( const QString& sPlugName ) 211 { 212 QListIterator<qjackctlPlugItem *> iter(m_plugs); 213 while (iter.hasNext()) { 214 qjackctlPlugItem *pPlug = iter.next(); 215 if (sPlugName == pPlug->plugName()) 216 return pPlug; 217 } 218 219 return nullptr; 220 } 221 222 223 // Plug list accessor. 224 QList<qjackctlPlugItem *>& qjackctlSocketItem::plugs (void) 225 { 226 return m_plugs; 227 } 228 229 230 // Connected socket list primitives. 231 void qjackctlSocketItem::addConnect( qjackctlSocketItem *pSocket ) 232 { 233 m_connects.append(pSocket); 234 } 235 236 void qjackctlSocketItem::removeConnect( qjackctlSocketItem *pSocket ) 237 { 238 int iSocket = m_connects.indexOf(pSocket); 239 if (iSocket >= 0) 240 m_connects.removeAt(iSocket); 241 } 242 243 244 245 // Connected socket finder. 246 qjackctlSocketItem *qjackctlSocketItem::findConnectPtr ( 247 qjackctlSocketItem *pSocketPtr ) 248 { 249 QListIterator<qjackctlSocketItem *> iter(m_connects); 250 while (iter.hasNext()) { 251 qjackctlSocketItem *pSocket = iter.next(); 252 if (pSocketPtr == pSocket) 253 return pSocket; 254 } 255 256 return nullptr; 257 } 258 259 260 // Connection cache list accessor. 261 const QList<qjackctlSocketItem *>& qjackctlSocketItem::connects (void) const 262 { 263 return m_connects; 264 } 265 266 267 // Plug list cleaner. 268 void qjackctlSocketItem::clear (void) 269 { 270 qDeleteAll(m_plugs); 271 m_plugs.clear(); 272 } 273 274 275 // Socket item pixmap peeker. 276 const QPixmap& qjackctlSocketItem::pixmap ( int iPixmap ) const 277 { 278 return m_pSocketList->pixmap(iPixmap); 279 } 280 281 282 // Update pixmap to its proper context. 283 void qjackctlSocketItem::updatePixmap (void) 284 { 285 int iPixmap; 286 if (m_iSocketType == QJACKCTL_SOCKETTYPE_JACK_AUDIO) { 287 iPixmap = (m_bExclusive 288 ? QJACKCTL_XPM_AUDIO_SOCKET_X 289 : QJACKCTL_XPM_AUDIO_SOCKET); 290 } else { 291 iPixmap = (m_bExclusive 292 ? QJACKCTL_XPM_MIDI_SOCKET_X 293 : QJACKCTL_XPM_MIDI_SOCKET); 294 } 295 QTreeWidgetItem::setIcon(0, QIcon(pixmap(iPixmap))); 296 } 297 298 299 // Socket item openness status. 300 void qjackctlSocketItem::setOpen ( bool bOpen ) 301 { 302 QTreeWidgetItem::setExpanded(bOpen); 303 } 304 305 306 bool qjackctlSocketItem::isOpen (void) const 307 { 308 return QTreeWidgetItem::isExpanded(); 309 } 310 311 312 //---------------------------------------------------------------------- 313 // qjackctlSocketList -- Jack client list. 314 // 315 316 // Constructor. 317 qjackctlSocketList::qjackctlSocketList ( 318 qjackctlSocketListView *pListView, bool bReadable ) 319 { 320 QPixmap pmXSocket1(":/images/xsocket1.png"); 321 322 m_pListView = pListView; 323 m_bReadable = bReadable; 324 325 if (bReadable) { 326 m_sSocketCaption = tr("Output"); 327 m_apPixmaps[QJACKCTL_XPM_AUDIO_SOCKET] = new QPixmap(":/images/asocketo.png"); 328 m_apPixmaps[QJACKCTL_XPM_AUDIO_SOCKET_X] = createPixmapMerge(*m_apPixmaps[QJACKCTL_XPM_AUDIO_SOCKET], pmXSocket1); 329 m_apPixmaps[QJACKCTL_XPM_AUDIO_CLIENT] = new QPixmap(":/images/acliento.png"); 330 m_apPixmaps[QJACKCTL_XPM_AUDIO_PLUG] = new QPixmap(":/images/aportlno.png"); 331 m_apPixmaps[QJACKCTL_XPM_MIDI_SOCKET] = new QPixmap(":/images/msocketo.png"); 332 m_apPixmaps[QJACKCTL_XPM_MIDI_SOCKET_X] = createPixmapMerge(*m_apPixmaps[QJACKCTL_XPM_MIDI_SOCKET], pmXSocket1); 333 m_apPixmaps[QJACKCTL_XPM_MIDI_CLIENT] = new QPixmap(":/images/mcliento.png"); 334 m_apPixmaps[QJACKCTL_XPM_MIDI_PLUG] = new QPixmap(":/images/mporto.png"); 335 } else { 336 m_sSocketCaption = tr("Input"); 337 m_apPixmaps[QJACKCTL_XPM_AUDIO_SOCKET] = new QPixmap(":/images/asocketi.png"); 338 m_apPixmaps[QJACKCTL_XPM_AUDIO_SOCKET_X] = createPixmapMerge(*m_apPixmaps[QJACKCTL_XPM_AUDIO_SOCKET], pmXSocket1); 339 m_apPixmaps[QJACKCTL_XPM_AUDIO_CLIENT] = new QPixmap(":/images/aclienti.png"); 340 m_apPixmaps[QJACKCTL_XPM_AUDIO_PLUG] = new QPixmap(":/images/aportlni.png"); 341 m_apPixmaps[QJACKCTL_XPM_MIDI_SOCKET] = new QPixmap(":/images/msocketi.png"); 342 m_apPixmaps[QJACKCTL_XPM_MIDI_SOCKET_X] = createPixmapMerge(*m_apPixmaps[QJACKCTL_XPM_MIDI_SOCKET], pmXSocket1); 343 m_apPixmaps[QJACKCTL_XPM_MIDI_CLIENT] = new QPixmap(":/images/mclienti.png"); 344 m_apPixmaps[QJACKCTL_XPM_MIDI_PLUG] = new QPixmap(":/images/mporti.png"); 345 } 346 347 if (!m_sSocketCaption.isEmpty()) 348 m_sSocketCaption += ' '; 349 m_sSocketCaption += tr("Socket"); 350 } 351 352 // Default destructor. 353 qjackctlSocketList::~qjackctlSocketList (void) 354 { 355 clear(); 356 357 for (int iPixmap = 0; iPixmap < QJACKCTL_XPM_PIXMAPS; iPixmap++) 358 delete m_apPixmaps[iPixmap]; 359 } 360 361 362 // Client finder. 363 qjackctlSocketItem *qjackctlSocketList::findSocket ( 364 const QString& sSocketName, int iSocketType ) 365 { 366 QListIterator<qjackctlSocketItem *> iter(m_sockets); 367 while (iter.hasNext()) { 368 qjackctlSocketItem *pSocket = iter.next(); 369 if (sSocketName == pSocket->socketName() && 370 iSocketType == pSocket->socketType()) 371 return pSocket; 372 } 373 374 return nullptr; 375 } 376 377 378 // Socket list accessor. 379 QList<qjackctlSocketItem *>& qjackctlSocketList::sockets (void) 380 { 381 return m_sockets; 382 } 383 384 385 // List view accessor. 386 qjackctlSocketListView *qjackctlSocketList::listView (void) const 387 { 388 return m_pListView; 389 } 390 391 392 // Socket flags accessor. 393 bool qjackctlSocketList::isReadable (void) const 394 { 395 return m_bReadable; 396 } 397 398 399 // Socket caption titler. 400 const QString& qjackctlSocketList::socketCaption (void) const 401 { 402 return m_sSocketCaption; 403 } 404 405 406 // Socket list cleaner. 407 void qjackctlSocketList::clear (void) 408 { 409 qDeleteAll(m_sockets); 410 m_sockets.clear(); 411 } 412 413 414 // Socket list pixmap peeker. 415 const QPixmap& qjackctlSocketList::pixmap ( int iPixmap ) const 416 { 417 return *m_apPixmaps[iPixmap]; 418 } 419 420 421 // Merge two pixmaps with union of respective masks. 422 QPixmap *qjackctlSocketList::createPixmapMerge ( 423 const QPixmap& xpmDst, const QPixmap& xpmSrc ) 424 { 425 QPixmap *pXpmMerge = new QPixmap(xpmDst); 426 if (pXpmMerge) { 427 QBitmap bmMask = xpmDst.mask(); 428 QPainter(&bmMask).drawPixmap(0, 0, xpmSrc.mask()); 429 pXpmMerge->setMask(bmMask); 430 QPainter(pXpmMerge).drawPixmap(0, 0, xpmSrc); 431 } 432 return pXpmMerge; 433 } 434 435 436 // Return currently selected socket item. 437 qjackctlSocketItem *qjackctlSocketList::selectedSocketItem (void) const 438 { 439 qjackctlSocketItem *pSocketItem = nullptr; 440 441 QTreeWidgetItem *pItem = m_pListView->currentItem(); 442 if (pItem) { 443 if (pItem->type() == QJACKCTL_PLUGITEM) { 444 pSocketItem = static_cast<qjackctlSocketItem *> (pItem->parent()); 445 } else { 446 pSocketItem = static_cast<qjackctlSocketItem *> (pItem); 447 } 448 } 449 450 return pSocketItem; 451 } 452 453 454 // Add a new socket item, using interactive form. 455 bool qjackctlSocketList::addSocketItem (void) 456 { 457 bool bResult = false; 458 459 qjackctlSocketForm socketForm(m_pListView); 460 socketForm.setWindowTitle(tr("<New> - %1").arg(m_sSocketCaption)); 461 socketForm.setSocketCaption(m_sSocketCaption); 462 socketForm.setPixmaps(m_apPixmaps); 463 socketForm.setSocketList(this); 464 socketForm.setSocketNew(true); 465 qjackctlPatchbaySocket socket(m_sSocketCaption 466 + ' ' + QString::number(m_sockets.count() + 1), 467 QString(), QJACKCTL_SOCKETTYPE_JACK_AUDIO); 468 socketForm.load(&socket); 469 if (socketForm.exec()) { 470 socketForm.save(&socket); 471 qjackctlSocketItem *pSocketItem = selectedSocketItem(); 472 // m_pListView->setUpdatesEnabled(false); 473 if (pSocketItem) 474 pSocketItem->setSelected(false); 475 pSocketItem = new qjackctlSocketItem(this, socket.name(), 476 socket.clientName(), socket.type(), pSocketItem); 477 if (pSocketItem) { 478 pSocketItem->setExclusive(socket.isExclusive()); 479 pSocketItem->setForward(socket.forward()); 480 qjackctlPlugItem *pPlugItem = nullptr; 481 QStringListIterator iter(socket.pluglist()); 482 while (iter.hasNext()) { 483 pPlugItem = new qjackctlPlugItem( 484 pSocketItem, iter.next(), pPlugItem); 485 } 486 bResult = true; 487 } 488 pSocketItem->setSelected(true); 489 m_pListView->setCurrentItem(pSocketItem); 490 // m_pListView->setUpdatesEnabled(true); 491 // m_pListView->update(); 492 m_pListView->setDirty(true); 493 } 494 495 return bResult; 496 } 497 498 499 // Remove (delete) currently selected socket item. 500 bool qjackctlSocketList::removeSocketItem (void) 501 { 502 bool bResult = false; 503 504 qjackctlSocketItem *pSocketItem = selectedSocketItem(); 505 if (pSocketItem) { 506 if (QMessageBox::warning(m_pListView, 507 tr("Warning") + " - " QJACKCTL_SUBTITLE1, 508 tr("%1 about to be removed:\n\n" 509 "\"%2\"\n\nAre you sure?") 510 .arg(m_sSocketCaption) 511 .arg(pSocketItem->socketName()), 512 QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { 513 delete pSocketItem; 514 bResult = true; 515 m_pListView->setDirty(true); 516 } 517 } 518 519 return bResult; 520 } 521 522 523 // View or change the properties of currently selected socket item. 524 bool qjackctlSocketList::editSocketItem (void) 525 { 526 bool bResult = false; 527 528 qjackctlSocketItem *pSocketItem = selectedSocketItem(); 529 if (pSocketItem) { 530 qjackctlSocketForm socketForm(m_pListView); 531 socketForm.setWindowTitle(pSocketItem->socketName() 532 + " - " + m_sSocketCaption); 533 socketForm.setSocketCaption(m_sSocketCaption); 534 socketForm.setPixmaps(m_apPixmaps); 535 socketForm.setSocketList(this); 536 socketForm.setSocketNew(false); 537 qjackctlPatchbaySocket socket(pSocketItem->socketName(), 538 pSocketItem->clientName(), pSocketItem->socketType()); 539 socket.setExclusive(pSocketItem->isExclusive()); 540 socket.setForward(pSocketItem->forward()); 541 QListIterator<qjackctlPlugItem *> iter(pSocketItem->plugs()); 542 while (iter.hasNext()) 543 socket.pluglist().append((iter.next())->plugName()); 544 socketForm.load(&socket); 545 socketForm.setConnectCount(pSocketItem->connects().count()); 546 if (socketForm.exec()) { 547 socketForm.save(&socket); 548 // m_pListView->setUpdatesEnabled(false); 549 pSocketItem->clear(); 550 pSocketItem->setText(0, socket.name()); 551 pSocketItem->setSocketName(socket.name()); 552 pSocketItem->setClientName(socket.clientName()); 553 pSocketItem->setSocketType(socket.type()); 554 pSocketItem->setExclusive(socket.isExclusive()); 555 pSocketItem->setForward(socket.forward()); 556 pSocketItem->updatePixmap(); 557 qjackctlPlugItem *pPlugItem = nullptr; 558 QStringListIterator iter(socket.pluglist()); 559 while (iter.hasNext()) { 560 pPlugItem = new qjackctlPlugItem( 561 pSocketItem, iter.next(), pPlugItem); 562 } 563 pSocketItem->setSelected(true); 564 m_pListView->setCurrentItem(pSocketItem); 565 // m_pListView->setUpdatesEnabled(true); 566 // m_pListView->triggerUpdate(); 567 m_pListView->setDirty(true); 568 bResult = true; 569 } 570 } 571 572 return bResult; 573 } 574 575 576 // Duplicate and change the properties of currently selected socket item. 577 bool qjackctlSocketList::copySocketItem (void) 578 { 579 bool bResult = false; 580 581 qjackctlSocketItem *pSocketItem = selectedSocketItem(); 582 if (pSocketItem) { 583 qjackctlSocketForm socketForm(m_pListView); 584 // Find a new distinguishable socket name, please. 585 int iSocketNo = 1; 586 QString sSocketName = pSocketItem->socketName();; 587 QString sSocketMask = sSocketName; 588 sSocketMask.remove(QRegularExpression("[ |0-9]+$")).append(" %1"); 589 const int iSocketType = pSocketItem->socketType(); 590 do { sSocketName = sSocketMask.arg(++iSocketNo); } 591 while (findSocket(sSocketName, iSocketType)); 592 // Show up as a new socket... 593 socketForm.setWindowTitle(tr("%1 <Copy> - %2") 594 .arg(pSocketItem->socketName()).arg(m_sSocketCaption)); 595 socketForm.setSocketCaption(m_sSocketCaption); 596 socketForm.setPixmaps(m_apPixmaps); 597 socketForm.setSocketList(this); 598 socketForm.setSocketNew(true); 599 qjackctlPatchbaySocket socket(sSocketName, 600 pSocketItem->clientName(), iSocketType); 601 if (pSocketItem->isExclusive()) 602 socket.setExclusive(true); 603 QListIterator<qjackctlPlugItem *> iter(pSocketItem->plugs()); 604 while (iter.hasNext()) 605 socket.pluglist().append((iter.next())->plugName()); 606 socketForm.load(&socket); 607 if (socketForm.exec()) { 608 socketForm.save(&socket); 609 pSocketItem = new qjackctlSocketItem(this, socket.name(), 610 socket.clientName(), socket.type(), pSocketItem); 611 if (pSocketItem) { 612 pSocketItem->setExclusive(socket.isExclusive()); 613 pSocketItem->setForward(socket.forward()); 614 qjackctlPlugItem *pPlugItem = nullptr; 615 QStringListIterator iter(socket.pluglist()); 616 while (iter.hasNext()) { 617 pPlugItem = new qjackctlPlugItem( 618 pSocketItem, iter.next(), pPlugItem); 619 } 620 bResult = true; 621 } 622 pSocketItem->setSelected(true); 623 m_pListView->setCurrentItem(pSocketItem); 624 // m_pListView->setUpdatesEnabled(true); 625 // m_pListView->triggerUpdate(); 626 m_pListView->setDirty(true); 627 } 628 } 629 630 return bResult; 631 } 632 633 634 // Toggle exclusive currently selected socket item. 635 bool qjackctlSocketList::exclusiveSocketItem (void) 636 { 637 bool bResult = false; 638 639 qjackctlSocketItem *pSocketItem = selectedSocketItem(); 640 if (pSocketItem) { 641 pSocketItem->setExclusive(!pSocketItem->isExclusive()); 642 pSocketItem->updatePixmap(); 643 bResult = true; 644 m_pListView->setDirty(true); 645 } 646 647 return bResult; 648 } 649 650 651 // Move current selected socket item up one position. 652 bool qjackctlSocketList::moveUpSocketItem (void) 653 { 654 bool bResult = false; 655 656 qjackctlSocketItem *pSocketItem = selectedSocketItem(); 657 if (pSocketItem) { 658 const int iItem = m_pListView->indexOfTopLevelItem(pSocketItem); 659 if (iItem > 0) { 660 QTreeWidgetItem *pItem = m_pListView->takeTopLevelItem(iItem); 661 if (pItem) { 662 m_pListView->insertTopLevelItem(iItem - 1, pItem); 663 pSocketItem->setSelected(true); 664 m_pListView->setCurrentItem(pSocketItem); 665 // m_pListView->setUpdatesEnabled(true); 666 // m_pListView->update(); 667 m_pListView->setDirty(true); 668 bResult = true; 669 } 670 } 671 } 672 673 return bResult; 674 } 675 676 677 // Move current selected socket item down one position. 678 bool qjackctlSocketList::moveDownSocketItem (void) 679 { 680 bool bResult = false; 681 682 qjackctlSocketItem *pSocketItem = selectedSocketItem(); 683 if (pSocketItem) { 684 const int iItem = m_pListView->indexOfTopLevelItem(pSocketItem); 685 const int iItemCount = m_pListView->topLevelItemCount(); 686 if (iItem < iItemCount - 1) { 687 QTreeWidgetItem *pItem = m_pListView->takeTopLevelItem(iItem); 688 if (pItem) { 689 m_pListView->insertTopLevelItem(iItem + 1, pItem); 690 pSocketItem->setSelected(true); 691 m_pListView->setCurrentItem(pSocketItem); 692 // m_pListView->setUpdatesEnabled(true); 693 // m_pListView->update(); 694 m_pListView->setDirty(true); 695 bResult = true; 696 } 697 } 698 } 699 700 return bResult; 701 } 702 703 704 //---------------------------------------------------------------------------- 705 // qjackctlSocketListView -- Socket list view, supporting drag-n-drop. 706 707 // Constructor. 708 qjackctlSocketListView::qjackctlSocketListView ( 709 qjackctlPatchbayView *pPatchbayView, bool bReadable ) 710 : QTreeWidget(pPatchbayView) 711 { 712 m_pPatchbayView = pPatchbayView; 713 m_bReadable = bReadable; 714 715 m_pAutoOpenTimer = 0; 716 m_iAutoOpenTimeout = 0; 717 718 m_pDragItem = nullptr; 719 m_pDropItem = nullptr; 720 721 QHeaderView *pHeader = QTreeWidget::header(); 722 // pHeader->setDefaultAlignment(Qt::AlignLeft); 723 // pHeader->setDefaultSectionSize(120); 724 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) 725 // pHeader->setSectionResizeMode(QHeaderView::Custom); 726 pHeader->setSectionsMovable(false); 727 pHeader->setSectionsClickable(true); 728 #else 729 // pHeader->setResizeMode(QHeaderView::Custom); 730 pHeader->setMovable(false); 731 pHeader->setClickable(true); 732 #endif 733 // pHeader->setSortIndicatorShown(true); 734 pHeader->setStretchLastSection(true); 735 736 QTreeWidget::setRootIsDecorated(true); 737 QTreeWidget::setUniformRowHeights(true); 738 // QTreeWidget::setDragEnabled(true); 739 QTreeWidget::setAcceptDrops(true); 740 QTreeWidget::setDropIndicatorShown(true); 741 QTreeWidget::setAutoScroll(true); 742 QTreeWidget::setSelectionMode(QAbstractItemView::SingleSelection); 743 QTreeWidget::setSizePolicy( 744 QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); 745 QTreeWidget::setSortingEnabled(false); 746 QTreeWidget::setMinimumWidth(120); 747 QTreeWidget::setColumnCount(1); 748 749 // Trap for help/tool-tips events. 750 QTreeWidget::viewport()->installEventFilter(this); 751 752 QString sText; 753 if (m_bReadable) 754 sText = tr("Output Sockets / Plugs"); 755 else 756 sText = tr("Input Sockets / Plugs"); 757 QTreeWidget::headerItem()->setText(0, sText); 758 QTreeWidget::setToolTip(sText); 759 760 setAutoOpenTimeout(800); 761 } 762 763 // Default destructor. 764 qjackctlSocketListView::~qjackctlSocketListView (void) 765 { 766 setAutoOpenTimeout(0); 767 } 768 769 770 // Patchbay view dirty flag accessors. 771 void qjackctlSocketListView::setDirty ( bool bDirty ) 772 { 773 m_pPatchbayView->setDirty(bDirty); 774 } 775 776 bool qjackctlSocketListView::dirty (void) const 777 { 778 return m_pPatchbayView->dirty(); 779 } 780 781 782 // Auto-open timeout method. 783 void qjackctlSocketListView::setAutoOpenTimeout ( int iAutoOpenTimeout ) 784 { 785 m_iAutoOpenTimeout = iAutoOpenTimeout; 786 787 if (m_pAutoOpenTimer) 788 delete m_pAutoOpenTimer; 789 m_pAutoOpenTimer = nullptr; 790 791 if (m_iAutoOpenTimeout > 0) { 792 m_pAutoOpenTimer = new QTimer(this); 793 QObject::connect(m_pAutoOpenTimer, 794 SIGNAL(timeout()), 795 SLOT(timeoutSlot())); 796 } 797 } 798 799 800 // Auto-open timeout accessor. 801 int qjackctlSocketListView::autoOpenTimeout (void) const 802 { 803 return m_iAutoOpenTimeout; 804 } 805 806 807 // Auto-open timer slot. 808 void qjackctlSocketListView::timeoutSlot (void) 809 { 810 if (m_pAutoOpenTimer) { 811 m_pAutoOpenTimer->stop(); 812 if (m_pDropItem && m_pDropItem->type() == QJACKCTL_SOCKETITEM) { 813 qjackctlSocketItem *pSocketItem 814 = static_cast<qjackctlSocketItem *> (m_pDropItem); 815 if (pSocketItem && !pSocketItem->isOpen()) 816 pSocketItem->setOpen(true); 817 } 818 } 819 } 820 821 822 // Trap for help/tool-tip events. 823 bool qjackctlSocketListView::eventFilter ( QObject *pObject, QEvent *pEvent ) 824 { 825 QWidget *pViewport = QTreeWidget::viewport(); 826 if (static_cast<QWidget *> (pObject) == pViewport 827 && pEvent->type() == QEvent::ToolTip) { 828 QHelpEvent *pHelpEvent = static_cast<QHelpEvent *> (pEvent); 829 if (pHelpEvent) { 830 QTreeWidgetItem *pItem = QTreeWidget::itemAt(pHelpEvent->pos()); 831 if (pItem && pItem->type() == QJACKCTL_SOCKETITEM) { 832 qjackctlSocketItem *pSocketItem 833 = static_cast<qjackctlSocketItem *> (pItem); 834 if (pSocketItem) { 835 QToolTip::showText(pHelpEvent->globalPos(), 836 pSocketItem->clientName(), pViewport); 837 return true; 838 } 839 } 840 else 841 if (pItem && pItem->type() == QJACKCTL_PLUGITEM) { 842 qjackctlPlugItem *pPlugItem 843 = static_cast<qjackctlPlugItem *> (pItem); 844 if (pPlugItem) { 845 QToolTip::showText(pHelpEvent->globalPos(), 846 pPlugItem->plugName(), pViewport); 847 return true; 848 } 849 } 850 } 851 } 852 853 // Not handled here. 854 return QTreeWidget::eventFilter(pObject, pEvent); 855 } 856 857 858 // Drag-n-drop stuff. 859 QTreeWidgetItem *qjackctlSocketListView::dragDropItem ( const QPoint& pos ) 860 { 861 QTreeWidgetItem *pItem = QTreeWidget::itemAt(pos); 862 if (pItem) { 863 if (m_pDropItem != pItem) { 864 QTreeWidget::setCurrentItem(pItem); 865 m_pDropItem = pItem; 866 if (m_pAutoOpenTimer) 867 m_pAutoOpenTimer->start(m_iAutoOpenTimeout); 868 qjackctlPatchbay *pPatchbay = m_pPatchbayView->binding(); 869 if ((pItem->flags() & Qt::ItemIsDropEnabled) == 0 870 || pPatchbay == nullptr || !pPatchbay->canConnectSelected()) 871 pItem = nullptr; 872 } 873 } else { 874 m_pDropItem = nullptr; 875 if (m_pAutoOpenTimer) 876 m_pAutoOpenTimer->stop(); 877 } 878 879 return pItem; 880 } 881 882 void qjackctlSocketListView::dragEnterEvent ( QDragEnterEvent *pDragEnterEvent ) 883 { 884 if (pDragEnterEvent->source() != this && 885 pDragEnterEvent->mimeData()->hasText() && 886 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 887 dragDropItem(pDragEnterEvent->position().toPoint())) { 888 #else 889 dragDropItem(pDragEnterEvent->pos())) { 890 #endif 891 pDragEnterEvent->accept(); 892 } else { 893 pDragEnterEvent->ignore(); 894 } 895 } 896 897 898 void qjackctlSocketListView::dragMoveEvent ( QDragMoveEvent *pDragMoveEvent ) 899 { 900 if (pDragMoveEvent->source() != this && 901 pDragMoveEvent->mimeData()->hasText() && 902 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 903 dragDropItem(pDragMoveEvent->position().toPoint())) { 904 #else 905 dragDropItem(pDragMoveEvent->pos())) { 906 #endif 907 pDragMoveEvent->accept(); 908 } else { 909 pDragMoveEvent->ignore(); 910 } 911 } 912 913 914 void qjackctlSocketListView::dragLeaveEvent ( QDragLeaveEvent * ) 915 { 916 m_pDropItem = 0; 917 if (m_pAutoOpenTimer) 918 m_pAutoOpenTimer->stop(); 919 } 920 921 922 void qjackctlSocketListView::dropEvent ( QDropEvent *pDropEvent ) 923 { 924 if (pDropEvent->source() != this && 925 pDropEvent->mimeData()->hasText() && 926 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 927 dragDropItem(pDropEvent->position().toPoint())) { 928 #else 929 dragDropItem(pDropEvent->pos())) { 930 #endif 931 const QString sText = pDropEvent->mimeData()->text(); 932 qjackctlPatchbay *pPatchbay = m_pPatchbayView->binding(); 933 if (!sText.isEmpty() && pPatchbay) 934 pPatchbay->connectSelected(); 935 } 936 937 dragLeaveEvent(nullptr); 938 } 939 940 941 // Handle mouse events for drag-and-drop stuff. 942 void qjackctlSocketListView::mousePressEvent ( QMouseEvent *pMouseEvent ) 943 { 944 QTreeWidget::mousePressEvent(pMouseEvent); 945 946 if (pMouseEvent->button() == Qt::LeftButton) { 947 m_posDrag = pMouseEvent->pos(); 948 m_pDragItem = QTreeWidget::itemAt(m_posDrag); 949 } 950 } 951 952 953 void qjackctlSocketListView::mouseMoveEvent ( QMouseEvent *pMouseEvent ) 954 { 955 QTreeWidget::mouseMoveEvent(pMouseEvent); 956 957 if ((pMouseEvent->buttons() & Qt::LeftButton) && m_pDragItem 958 && ((pMouseEvent->pos() - m_posDrag).manhattanLength() 959 >= QApplication::startDragDistance())) { 960 // We'll start dragging something alright... 961 QMimeData *pMimeData = new QMimeData(); 962 pMimeData->setText(m_pDragItem->text(0)); 963 QDrag *pDrag = new QDrag(this); 964 pDrag->setMimeData(pMimeData); 965 pDrag->setPixmap(m_pDragItem->icon(0).pixmap(16)); 966 pDrag->setHotSpot(QPoint(-4, -12)); 967 pDrag->exec(Qt::LinkAction); 968 // We've dragged and maybe dropped it by now... 969 m_pDragItem = nullptr; 970 } 971 } 972 973 974 // Context menu request event handler. 975 void qjackctlSocketListView::contextMenuEvent ( 976 QContextMenuEvent *pContextMenuEvent ) 977 { 978 m_pPatchbayView->contextMenu( 979 pContextMenuEvent->globalPos(), 980 (m_bReadable 981 ? m_pPatchbayView->OSocketList() 982 : m_pPatchbayView->ISocketList()) 983 ); 984 } 985 986 987 //---------------------------------------------------------------------- 988 // qjackctlPatchworkView -- Socket connector widget. 989 // 990 991 // Constructor. 992 qjackctlPatchworkView::qjackctlPatchworkView ( 993 qjackctlPatchbayView *pPatchbayView ) 994 : QWidget(pPatchbayView) 995 { 996 m_pPatchbayView = pPatchbayView; 997 998 QWidget::setMinimumWidth(20); 999 // QWidget::setMaximumWidth(120); 1000 QWidget::setSizePolicy( 1001 QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); 1002 } 1003 1004 // Default destructor. 1005 qjackctlPatchworkView::~qjackctlPatchworkView (void) 1006 { 1007 } 1008 1009 1010 // Legal socket item position helper. 1011 int qjackctlPatchworkView::itemY ( QTreeWidgetItem *pItem ) const 1012 { 1013 QRect rect; 1014 QTreeWidget *pList = pItem->treeWidget(); 1015 QTreeWidgetItem *pParent = pItem->parent(); 1016 qjackctlSocketItem *pSocketItem = nullptr; 1017 if (pParent && pParent->type() == QJACKCTL_SOCKETITEM) 1018 pSocketItem = static_cast<qjackctlSocketItem *> (pParent); 1019 if (pSocketItem && !pSocketItem->isOpen()) { 1020 rect = pList->visualItemRect(pParent); 1021 } else { 1022 rect = pList->visualItemRect(pItem); 1023 } 1024 return rect.top() + rect.height() / 2; 1025 } 1026 1027 1028 // Draw visible socket connection relation lines 1029 void qjackctlPatchworkView::drawConnectionLine ( QPainter *pPainter, 1030 int x1, int y1, int x2, int y2, int h1, int h2 ) 1031 { 1032 // Account for list view headers. 1033 y1 += h1; 1034 y2 += h2; 1035 1036 // Invisible output plugs don't get a connecting dot. 1037 if (y1 > h1) 1038 pPainter->drawLine(x1, y1, x1 + 4, y1); 1039 1040 // Setup control points 1041 QPolygon spline(4); 1042 const int cp = int(float(x2 - x1 - 8) * 0.4f); 1043 spline.putPoints(0, 4, 1044 x1 + 4, y1, x1 + 4 + cp, y1, 1045 x2 - 4 - cp, y2, x2 - 4, y2); 1046 // The connection line, it self. 1047 QPainterPath path; 1048 path.moveTo(spline.at(0)); 1049 path.cubicTo(spline.at(1), spline.at(2), spline.at(3)); 1050 pPainter->strokePath(path, pPainter->pen()); 1051 1052 // Invisible input plugs don't get a connecting dot. 1053 if (y2 > h2) 1054 pPainter->drawLine(x2 - 4, y2, x2, y2); 1055 } 1056 1057 1058 // Draw socket forwrading line (for input sockets / right pane only) 1059 void qjackctlPatchworkView::drawForwardLine ( QPainter *pPainter, 1060 int x, int dx, int y1, int y2, int h ) 1061 { 1062 // Account for list view headers. 1063 y1 += h; 1064 y2 += h; 1065 dx += 4; 1066 1067 // Draw it... 1068 if (y1 < y2) { 1069 pPainter->drawLine(x - dx, y1 + 4, x, y1); 1070 pPainter->drawLine(x - dx, y1 + 4, x - dx, y2 - 4); 1071 pPainter->drawLine(x - dx, y2 - 4, x, y2); 1072 // Down arrow... 1073 pPainter->drawLine(x - dx, y2 - 8, x - dx - 2, y2 - 12); 1074 pPainter->drawLine(x - dx, y2 - 8, x - dx + 2, y2 - 12); 1075 } else { 1076 pPainter->drawLine(x - dx, y1 - 4, x, y1); 1077 pPainter->drawLine(x - dx, y1 - 4, x - dx, y2 + 4); 1078 pPainter->drawLine(x - dx, y2 + 4, x, y2); 1079 // Up arrow... 1080 pPainter->drawLine(x - dx, y2 + 8, x - dx - 2, y2 + 12); 1081 pPainter->drawLine(x - dx, y2 + 8, x - dx + 2, y2 + 12); 1082 } 1083 } 1084 1085 1086 // Draw visible socket connection relation arrows. 1087 void qjackctlPatchworkView::paintEvent ( QPaintEvent * ) 1088 { 1089 if (m_pPatchbayView->OSocketList() == nullptr || 1090 m_pPatchbayView->ISocketList() == nullptr) 1091 return; 1092 1093 QPainter painter(this); 1094 int x1, y1, h1; 1095 int x2, y2, h2; 1096 int i, rgb[3] = { 0x99, 0x66, 0x33 }; 1097 1098 // Draw all lines anti-aliased... 1099 painter.setRenderHint(QPainter::Antialiasing); 1100 1101 // Inline adaptive to darker background themes... 1102 if (QWidget::palette().window().color().value() < 0x7f) 1103 for (i = 0; i < 3; ++i) rgb[i] += 0x33; 1104 1105 // Initialize color changer. 1106 i = 0; 1107 // Almost constants. 1108 x1 = 0; 1109 x2 = width(); 1110 h1 = ((m_pPatchbayView->OListView())->header())->sizeHint().height(); 1111 h2 = ((m_pPatchbayView->IListView())->header())->sizeHint().height(); 1112 // For each client item... 1113 qjackctlSocketItem *pOSocket, *pISocket; 1114 QListIterator<qjackctlSocketItem *> osocket( 1115 (m_pPatchbayView->OSocketList())->sockets()); 1116 while (osocket.hasNext()) { 1117 pOSocket = osocket.next(); 1118 // Set new connector color. 1119 ++i; 1120 painter.setPen(QColor(rgb[i % 3], rgb[(i / 3) % 3], rgb[(i / 9) % 3])); 1121 // Get starting connector arrow coordinates. 1122 y1 = itemY(pOSocket); 1123 // Get input socket connections... 1124 QListIterator<qjackctlSocketItem *> isocket(pOSocket->connects()); 1125 while (isocket.hasNext()) { 1126 pISocket = isocket.next(); 1127 // Obviously, there is a connection from pOPlug to pIPlug items: 1128 y2 = itemY(pISocket); 1129 drawConnectionLine(&painter, x1, y1, x2, y2, h1, h2); 1130 } 1131 } 1132 1133 // Look for forwarded inputs... 1134 QList<qjackctlSocketItem *> iforwards; 1135 // Make a local copy of just the forwarding socket list, if any... 1136 QListIterator<qjackctlSocketItem *> isocket( 1137 (m_pPatchbayView->ISocketList())->sockets()); 1138 while (isocket.hasNext()) { 1139 pISocket = isocket.next(); 1140 // Check if its forwarded... 1141 if (pISocket->forward().isEmpty()) 1142 continue; 1143 iforwards.append(pISocket); 1144 } 1145 // (Re)initialize color changer. 1146 i = 0; 1147 // Now traverse those for proper connection drawing... 1148 int dx = 0; 1149 QListIterator<qjackctlSocketItem *> iter(iforwards); 1150 while (iter.hasNext()) { 1151 pISocket = iter.next(); 1152 qjackctlSocketItem *pISocketForward 1153 = m_pPatchbayView->ISocketList()->findSocket( 1154 pISocket->forward(), pISocket->socketType()); 1155 if (pISocketForward == nullptr) 1156 continue; 1157 // Set new connector color. 1158 ++i; 1159 painter.setPen(QColor(rgb[i % 3], rgb[(i / 3) % 3], rgb[(i / 9) % 3])); 1160 // Get starting connector arrow coordinates. 1161 y1 = itemY(pISocketForward); 1162 y2 = itemY(pISocket); 1163 drawForwardLine(&painter, x2, dx, y1, y2, h2); 1164 dx += 2; 1165 } 1166 } 1167 1168 1169 // Context menu request event handler. 1170 void qjackctlPatchworkView::contextMenuEvent ( 1171 QContextMenuEvent *pContextMenuEvent ) 1172 { 1173 m_pPatchbayView->contextMenu(pContextMenuEvent->globalPos(), nullptr); 1174 } 1175 1176 1177 // Widget event slots... 1178 1179 void qjackctlPatchworkView::contentsChanged (void) 1180 { 1181 QWidget::update(); 1182 } 1183 1184 1185 //---------------------------------------------------------------------------- 1186 // qjackctlPatchbayView -- Integrated patchbay widget. 1187 1188 // Constructor. 1189 qjackctlPatchbayView::qjackctlPatchbayView ( QWidget *pParent ) 1190 : QSplitter(Qt::Horizontal, pParent) 1191 { 1192 m_pOListView = new qjackctlSocketListView(this, true); 1193 m_pPatchworkView = new qjackctlPatchworkView(this); 1194 m_pIListView = new qjackctlSocketListView(this, false); 1195 1196 m_pPatchbay = nullptr; 1197 1198 QSplitter::setHandleWidth(2); 1199 1200 QObject::connect(m_pOListView, SIGNAL(itemExpanded(QTreeWidgetItem *)), 1201 m_pPatchworkView, SLOT(contentsChanged())); 1202 QObject::connect(m_pOListView, SIGNAL(itemCollapsed(QTreeWidgetItem *)), 1203 m_pPatchworkView, SLOT(contentsChanged())); 1204 QObject::connect(m_pOListView->verticalScrollBar(), SIGNAL(valueChanged(int)), 1205 m_pPatchworkView, SLOT(contentsChanged())); 1206 // QObject::connect(m_pOListView->header(), SIGNAL(sectionClicked(int)), 1207 // m_pPatchworkView, SLOT(contentsChanged())); 1208 1209 QObject::connect(m_pIListView, SIGNAL(itemExpanded(QTreeWidgetItem *)), 1210 m_pPatchworkView, SLOT(contentsChanged())); 1211 QObject::connect(m_pIListView, SIGNAL(itemCollapsed(QTreeWidgetItem *)), 1212 m_pPatchworkView, SLOT(contentsChanged())); 1213 QObject::connect(m_pIListView->verticalScrollBar(), SIGNAL(valueChanged(int)), 1214 m_pPatchworkView, SLOT(contentsChanged())); 1215 // QObject::connect(m_pIListView->header(), SIGNAL(sectionClicked(int)), 1216 // m_pPatchworkView, SLOT(contentsChanged())); 1217 1218 m_bDirty = false; 1219 } 1220 1221 1222 // Default destructor. 1223 qjackctlPatchbayView::~qjackctlPatchbayView (void) 1224 { 1225 } 1226 1227 1228 // Common context menu slot. 1229 void qjackctlPatchbayView::contextMenu ( const QPoint& pos, 1230 qjackctlSocketList *pSocketList ) 1231 { 1232 qjackctlPatchbay *pPatchbay = binding(); 1233 if (pPatchbay == nullptr) 1234 return; 1235 1236 QMenu menu(this); 1237 QAction *pAction; 1238 1239 if (pSocketList) { 1240 qjackctlSocketItem *pSocketItem = pSocketList->selectedSocketItem(); 1241 bool bEnabled = (pSocketItem != nullptr); 1242 pAction = menu.addAction(QIcon(":/images/add1.png"), 1243 tr("Add..."), pSocketList, SLOT(addSocketItem())); 1244 pAction = menu.addAction(QIcon(":/images/edit1.png"), 1245 tr("Edit..."), pSocketList, SLOT(editSocketItem())); 1246 pAction->setEnabled(bEnabled); 1247 pAction = menu.addAction(QIcon(":/images/copy1.png"), 1248 tr("Copy..."), pSocketList, SLOT(copySocketItem())); 1249 pAction->setEnabled(bEnabled); 1250 pAction = menu.addAction(QIcon(":/images/remove1.png"), 1251 tr("Remove"), pSocketList, SLOT(removeSocketItem())); 1252 pAction->setEnabled(bEnabled); 1253 menu.addSeparator(); 1254 pAction = menu.addAction( 1255 tr("Exclusive"), pSocketList, SLOT(exclusiveSocketItem())); 1256 pAction->setCheckable(true); 1257 pAction->setChecked(bEnabled && pSocketItem->isExclusive()); 1258 pAction->setEnabled(bEnabled && (pSocketItem->connects().count() < 2)); 1259 // Construct the forwarding menu, 1260 // overriding the last one, if any... 1261 QMenu *pForwardMenu = menu.addMenu(tr("Forward")); 1262 // Assume sockets iteration follows item index order (0,1,2...) 1263 // and remember that we only do this for input sockets... 1264 int iIndex = 0; 1265 if (pSocketItem && pSocketList == ISocketList()) { 1266 QListIterator<qjackctlSocketItem *> isocket(ISocketList()->sockets()); 1267 while (isocket.hasNext()) { 1268 qjackctlSocketItem *pISocket = isocket.next(); 1269 // Must be of same type of target one... 1270 int iSocketType = pISocket->socketType(); 1271 if (iSocketType != pSocketItem->socketType()) 1272 continue; 1273 const QString& sSocketName = pISocket->socketName(); 1274 if (pSocketItem->socketName() == sSocketName) 1275 continue; 1276 int iPixmap = 0; 1277 switch (iSocketType) { 1278 case QJACKCTL_SOCKETTYPE_JACK_AUDIO: 1279 iPixmap = (pISocket->isExclusive() 1280 ? QJACKCTL_XPM_AUDIO_SOCKET_X 1281 : QJACKCTL_XPM_AUDIO_SOCKET); 1282 break; 1283 case QJACKCTL_SOCKETTYPE_JACK_MIDI: 1284 case QJACKCTL_SOCKETTYPE_ALSA_MIDI: 1285 iPixmap = (pISocket->isExclusive() 1286 ? QJACKCTL_XPM_MIDI_SOCKET_X 1287 : QJACKCTL_XPM_MIDI_SOCKET); 1288 break; 1289 } 1290 pAction = pForwardMenu->addAction( 1291 QIcon(ISocketList()->pixmap(iPixmap)), sSocketName); 1292 pAction->setChecked(pSocketItem->forward() == sSocketName); 1293 pAction->setData(iIndex); 1294 ++iIndex; 1295 } 1296 // nullptr forward always present, 1297 // and has invalid index parameter (-1)... 1298 if (iIndex > 0) 1299 pForwardMenu->addSeparator(); 1300 pAction = pForwardMenu->addAction(tr("(None)")); 1301 pAction->setCheckable(true); 1302 pAction->setChecked(pSocketItem->forward().isEmpty()); 1303 pAction->setData(-1); 1304 // We have something here... 1305 QObject::connect(pForwardMenu, 1306 SIGNAL(triggered(QAction*)), 1307 SLOT(activateForwardMenu(QAction*))); 1308 } 1309 pForwardMenu->setEnabled(iIndex > 0); 1310 menu.addSeparator(); 1311 const int iItem = (pSocketList->listView())->indexOfTopLevelItem(pSocketItem); 1312 const int iItemCount = (pSocketList->listView())->topLevelItemCount(); 1313 pAction = menu.addAction(QIcon(":/images/up1.png"), 1314 tr("Move Up"), pSocketList, SLOT(moveUpSocketItem())); 1315 pAction->setEnabled(bEnabled && iItem > 0); 1316 pAction = menu.addAction(QIcon(":/images/down1.png"), 1317 tr("Move Down"), pSocketList, SLOT(moveDownSocketItem())); 1318 pAction->setEnabled(bEnabled && iItem < iItemCount - 1); 1319 menu.addSeparator(); 1320 } 1321 1322 pAction = menu.addAction(QIcon(":/images/connect1.png"), 1323 tr("&Connect"), pPatchbay, SLOT(connectSelected()), 1324 tr("Alt+C", "Connect")); 1325 pAction->setEnabled(pPatchbay->canConnectSelected()); 1326 pAction = menu.addAction(QIcon(":/images/disconnect1.png"), 1327 tr("&Disconnect"), pPatchbay, SLOT(disconnectSelected()), 1328 tr("Alt+D", "Disconnect")); 1329 pAction->setEnabled(pPatchbay->canDisconnectSelected()); 1330 pAction = menu.addAction(QIcon(":/images/disconnectall1.png"), 1331 tr("Disconnect &All"), pPatchbay, SLOT(disconnectAll()), 1332 tr("Alt+A", "Disconnect All")); 1333 pAction->setEnabled(pPatchbay->canDisconnectAll()); 1334 1335 menu.addSeparator(); 1336 pAction = menu.addAction(QIcon(":/images/refresh1.png"), 1337 tr("&Refresh"), pPatchbay, SLOT(refresh()), 1338 tr("Alt+R", "Refresh")); 1339 1340 menu.exec(pos); 1341 } 1342 1343 1344 // Select the forwarding socket name from context menu. 1345 void qjackctlPatchbayView::activateForwardMenu ( QAction *pAction ) 1346 { 1347 int iIndex = pAction->data().toInt(); 1348 1349 // Get currently input socket (assume its nicely selected) 1350 qjackctlSocketItem *pSocketItem = ISocketList()->selectedSocketItem(); 1351 if (pSocketItem) { 1352 // Check first for forward from nil... 1353 if (iIndex < 0) { 1354 pSocketItem->setForward(QString()); 1355 setDirty(true); 1356 return; 1357 } 1358 // Hopefully, its a real socket about to be forwraded... 1359 QListIterator<qjackctlSocketItem *> isocket(ISocketList()->sockets()); 1360 while (isocket.hasNext()) { 1361 qjackctlSocketItem *pISocket = isocket.next(); 1362 // Must be of same type of target one... 1363 if (pISocket->socketType() != pSocketItem->socketType()) 1364 continue; 1365 const QString& sSocketName = pISocket->socketName(); 1366 if (pSocketItem->socketName() == sSocketName) 1367 continue; 1368 if (iIndex == 0) { 1369 pSocketItem->setForward(sSocketName); 1370 setDirty(true); 1371 break; 1372 } 1373 --iIndex; 1374 } 1375 } 1376 } 1377 1378 1379 // Patchbay binding methods. 1380 void qjackctlPatchbayView::setBinding ( qjackctlPatchbay *pPatchbay ) 1381 { 1382 m_pPatchbay = pPatchbay; 1383 } 1384 1385 qjackctlPatchbay *qjackctlPatchbayView::binding (void) const 1386 { 1387 return m_pPatchbay; 1388 } 1389 1390 1391 // Patchbay client list accessors. 1392 qjackctlSocketList *qjackctlPatchbayView::OSocketList (void) const 1393 { 1394 if (m_pPatchbay) 1395 return m_pPatchbay->OSocketList(); 1396 else 1397 return nullptr; 1398 } 1399 1400 qjackctlSocketList *qjackctlPatchbayView::ISocketList (void) const 1401 { 1402 if (m_pPatchbay) 1403 return m_pPatchbay->ISocketList(); 1404 else 1405 return nullptr; 1406 } 1407 1408 1409 // Dirty flag methods. 1410 void qjackctlPatchbayView::setDirty ( bool bDirty ) 1411 { 1412 m_bDirty = bDirty; 1413 if (bDirty) 1414 emit contentsChanged(); 1415 } 1416 1417 bool qjackctlPatchbayView::dirty (void) const 1418 { 1419 return m_bDirty; 1420 } 1421 1422 1423 //---------------------------------------------------------------------- 1424 // qjackctlPatchbay -- Output-to-Input client/plugs connection object. 1425 // 1426 1427 // Constructor. 1428 qjackctlPatchbay::qjackctlPatchbay ( qjackctlPatchbayView *pPatchbayView ) 1429 { 1430 m_pPatchbayView = pPatchbayView; 1431 1432 m_pOSocketList = new qjackctlSocketList(m_pPatchbayView->OListView(), true); 1433 m_pISocketList = new qjackctlSocketList(m_pPatchbayView->IListView(), false); 1434 1435 m_pPatchbayView->setBinding(this); 1436 } 1437 1438 // Default destructor. 1439 qjackctlPatchbay::~qjackctlPatchbay (void) 1440 { 1441 m_pPatchbayView->setBinding(nullptr); 1442 1443 delete m_pOSocketList; 1444 m_pOSocketList = nullptr; 1445 1446 delete m_pISocketList; 1447 m_pISocketList = nullptr; 1448 1449 (m_pPatchbayView->PatchworkView())->update(); 1450 } 1451 1452 1453 // Connection primitive. 1454 void qjackctlPatchbay::connectSockets ( qjackctlSocketItem *pOSocket, 1455 qjackctlSocketItem *pISocket ) 1456 { 1457 if (pOSocket->findConnectPtr(pISocket) == nullptr) { 1458 pOSocket->addConnect(pISocket); 1459 pISocket->addConnect(pOSocket); 1460 } 1461 } 1462 1463 // Disconnection primitive. 1464 void qjackctlPatchbay::disconnectSockets ( qjackctlSocketItem *pOSocket, 1465 qjackctlSocketItem *pISocket ) 1466 { 1467 if (pOSocket->findConnectPtr(pISocket) != nullptr) { 1468 pOSocket->removeConnect(pISocket); 1469 pISocket->removeConnect(pOSocket); 1470 } 1471 } 1472 1473 1474 // Test if selected plugs are connectable. 1475 bool qjackctlPatchbay::canConnectSelected (void) 1476 { 1477 QTreeWidgetItem *pOItem = (m_pOSocketList->listView())->currentItem(); 1478 if (pOItem == nullptr) 1479 return false; 1480 1481 QTreeWidgetItem *pIItem = (m_pISocketList->listView())->currentItem(); 1482 if (pIItem == nullptr) 1483 return false; 1484 1485 qjackctlSocketItem *pOSocket = nullptr; 1486 switch (pOItem->type()) { 1487 case QJACKCTL_SOCKETITEM: 1488 pOSocket = static_cast<qjackctlSocketItem *> (pOItem); 1489 break; 1490 case QJACKCTL_PLUGITEM: 1491 pOSocket = (static_cast<qjackctlPlugItem *> (pOItem))->socket(); 1492 break; 1493 default: 1494 return false; 1495 } 1496 1497 qjackctlSocketItem *pISocket = nullptr; 1498 switch (pIItem->type()) { 1499 case QJACKCTL_SOCKETITEM: 1500 pISocket = static_cast<qjackctlSocketItem *> (pIItem); 1501 break; 1502 case QJACKCTL_PLUGITEM: 1503 pISocket = (static_cast<qjackctlPlugItem *> (pIItem))->socket(); 1504 break; 1505 default: 1506 return false; 1507 } 1508 1509 // Sockets must be of the same type... 1510 if (pOSocket->socketType() != pISocket->socketType()) 1511 return false; 1512 1513 // Exclusive sockets may not accept more than one cable. 1514 if (pOSocket->isExclusive() && pOSocket->connects().count() > 0) 1515 return false; 1516 if (pISocket->isExclusive() && pISocket->connects().count() > 0) 1517 return false; 1518 1519 // One-to-one connection... 1520 return (pOSocket->findConnectPtr(pISocket) == nullptr); 1521 } 1522 1523 1524 // Connect current selected plugs. 1525 bool qjackctlPatchbay::connectSelected (void) 1526 { 1527 QTreeWidgetItem *pOItem = (m_pOSocketList->listView())->currentItem(); 1528 if (pOItem == nullptr) 1529 return false; 1530 1531 QTreeWidgetItem *pIItem = (m_pISocketList->listView())->currentItem(); 1532 if (pIItem == nullptr) 1533 return false; 1534 1535 qjackctlSocketItem *pOSocket = nullptr; 1536 switch (pOItem->type()) { 1537 case QJACKCTL_SOCKETITEM: 1538 pOSocket = static_cast<qjackctlSocketItem *> (pOItem); 1539 break; 1540 case QJACKCTL_PLUGITEM: 1541 pOSocket = (static_cast<qjackctlPlugItem *> (pOItem))->socket(); 1542 break; 1543 default: 1544 return false; 1545 } 1546 1547 qjackctlSocketItem *pISocket = nullptr; 1548 switch (pIItem->type()) { 1549 case QJACKCTL_SOCKETITEM: 1550 pISocket = static_cast<qjackctlSocketItem *> (pIItem); 1551 break; 1552 case QJACKCTL_PLUGITEM: 1553 pISocket = (static_cast<qjackctlPlugItem *> (pIItem))->socket(); 1554 break; 1555 default: 1556 return false; 1557 } 1558 1559 // Sockets must be of the same type... 1560 if (pOSocket->socketType() != pISocket->socketType()) 1561 return false; 1562 1563 // Exclusive sockets may not accept more than one cable. 1564 if (pOSocket->isExclusive() && pOSocket->connects().count() > 0) 1565 return false; 1566 if (pISocket->isExclusive() && pISocket->connects().count() > 0) 1567 return false; 1568 1569 // One-to-one connection... 1570 connectSockets(pOSocket, pISocket); 1571 1572 // Making one list dirty will take care of the rest... 1573 m_pPatchbayView->setDirty(true); 1574 1575 return true; 1576 } 1577 1578 1579 // Test if selected plugs are disconnectable. 1580 bool qjackctlPatchbay::canDisconnectSelected (void) 1581 { 1582 QTreeWidgetItem *pOItem = (m_pOSocketList->listView())->currentItem(); 1583 if (pOItem == nullptr) 1584 return false; 1585 1586 QTreeWidgetItem *pIItem = (m_pISocketList->listView())->currentItem(); 1587 if (pIItem == nullptr) 1588 return false; 1589 1590 qjackctlSocketItem *pOSocket = nullptr; 1591 switch (pOItem->type()) { 1592 case QJACKCTL_SOCKETITEM: 1593 pOSocket = static_cast<qjackctlSocketItem *> (pOItem); 1594 break; 1595 case QJACKCTL_PLUGITEM: 1596 pOSocket = (static_cast<qjackctlPlugItem *> (pOItem))->socket(); 1597 break; 1598 default: 1599 return false; 1600 } 1601 1602 qjackctlSocketItem *pISocket = nullptr; 1603 switch (pIItem->type()) { 1604 case QJACKCTL_SOCKETITEM: 1605 pISocket = static_cast<qjackctlSocketItem *> (pIItem); 1606 break; 1607 case QJACKCTL_PLUGITEM: 1608 pISocket = (static_cast<qjackctlPlugItem *> (pIItem))->socket(); 1609 break; 1610 default: 1611 return false; 1612 } 1613 1614 // Sockets must be of the same type... 1615 if (pOSocket->socketType() != pISocket->socketType()) 1616 return false; 1617 1618 return (pOSocket->findConnectPtr(pISocket) != 0); 1619 } 1620 1621 1622 // Disconnect current selected plugs. 1623 bool qjackctlPatchbay::disconnectSelected (void) 1624 { 1625 QTreeWidgetItem *pOItem = (m_pOSocketList->listView())->currentItem(); 1626 if (!pOItem) 1627 return false; 1628 1629 QTreeWidgetItem *pIItem = (m_pISocketList->listView())->currentItem(); 1630 if (!pIItem) 1631 return false; 1632 1633 qjackctlSocketItem *pOSocket = nullptr; 1634 switch (pOItem->type()) { 1635 case QJACKCTL_SOCKETITEM: 1636 pOSocket = static_cast<qjackctlSocketItem *> (pOItem); 1637 break; 1638 case QJACKCTL_PLUGITEM: 1639 pOSocket = (static_cast<qjackctlPlugItem *> (pOItem))->socket(); 1640 break; 1641 default: 1642 return false; 1643 } 1644 1645 qjackctlSocketItem *pISocket = nullptr; 1646 switch (pIItem->type()) { 1647 case QJACKCTL_SOCKETITEM: 1648 pISocket = static_cast<qjackctlSocketItem *> (pIItem); 1649 break; 1650 case QJACKCTL_PLUGITEM: 1651 pISocket = (static_cast<qjackctlPlugItem *> (pIItem))->socket(); 1652 break; 1653 default: 1654 return false; 1655 } 1656 1657 // Sockets must be of the same type... 1658 if (pOSocket->socketType() != pISocket->socketType()) 1659 return false; 1660 1661 // One-to-one disconnection... 1662 disconnectSockets(pOSocket, pISocket); 1663 1664 // Making one list dirty will take care of the rest... 1665 m_pPatchbayView->setDirty(true); 1666 1667 return true; 1668 } 1669 1670 1671 // Test if any plug is disconnectable. 1672 bool qjackctlPatchbay::canDisconnectAll (void) 1673 { 1674 QListIterator<qjackctlSocketItem *> osocket(m_pOSocketList->sockets()); 1675 while (osocket.hasNext()) { 1676 qjackctlSocketItem *pOSocket = osocket.next(); 1677 if (pOSocket->connects().count() > 0) 1678 return true; 1679 } 1680 1681 return false; 1682 } 1683 1684 1685 // Disconnect all plugs. 1686 bool qjackctlPatchbay::disconnectAll (void) 1687 { 1688 if (QMessageBox::warning(m_pPatchbayView, 1689 tr("Warning") + " - " QJACKCTL_SUBTITLE1, 1690 tr("This will disconnect all sockets.\n\n" 1691 "Are you sure?"), 1692 QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { 1693 return false; 1694 } 1695 1696 QListIterator<qjackctlSocketItem *> osocket(m_pOSocketList->sockets()); 1697 while (osocket.hasNext()) { 1698 qjackctlSocketItem *pOSocket = osocket.next(); 1699 QListIterator<qjackctlSocketItem *> isocket(pOSocket->connects()); 1700 while (isocket.hasNext()) 1701 disconnectSockets(pOSocket, isocket.next()); 1702 } 1703 1704 // Making one list dirty will take care of the rest... 1705 m_pPatchbayView->setDirty(true); 1706 1707 return true; 1708 } 1709 1710 1711 // Expand all socket items. 1712 void qjackctlPatchbay::expandAll (void) 1713 { 1714 (m_pOSocketList->listView())->expandAll(); 1715 (m_pISocketList->listView())->expandAll(); 1716 (m_pPatchbayView->PatchworkView())->update(); 1717 } 1718 1719 1720 // Complete contents rebuilder. 1721 void qjackctlPatchbay::refresh (void) 1722 { 1723 (m_pOSocketList->listView())->update(); 1724 (m_pISocketList->listView())->update(); 1725 (m_pPatchbayView->PatchworkView())->update(); 1726 } 1727 1728 1729 // Complete contents clearer. 1730 void qjackctlPatchbay::clear (void) 1731 { 1732 // Clear socket lists. 1733 m_pOSocketList->clear(); 1734 m_pISocketList->clear(); 1735 1736 // Reset dirty flag. 1737 m_pPatchbayView->setDirty(false); 1738 1739 // May refresh everything. 1740 refresh(); 1741 } 1742 1743 1744 // Patchbay client list accessors. 1745 qjackctlSocketList *qjackctlPatchbay::OSocketList (void) const 1746 { 1747 return m_pOSocketList; 1748 } 1749 1750 qjackctlSocketList *qjackctlPatchbay::ISocketList (void) const 1751 { 1752 return m_pISocketList; 1753 } 1754 1755 1756 // External rack transfer method: copy patchbay structure from master rack model. 1757 void qjackctlPatchbay::loadRackSockets ( qjackctlSocketList *pSocketList, 1758 QList<qjackctlPatchbaySocket *>& socketlist ) 1759 { 1760 pSocketList->clear(); 1761 qjackctlSocketItem *pSocketItem = nullptr; 1762 1763 QListIterator<qjackctlPatchbaySocket *> sockit(socketlist); 1764 while (sockit.hasNext()) { 1765 qjackctlPatchbaySocket *pSocket = sockit.next(); 1766 pSocketItem = new qjackctlSocketItem(pSocketList, pSocket->name(), 1767 pSocket->clientName(), pSocket->type(), pSocketItem); 1768 if (pSocketItem) { 1769 pSocketItem->setExclusive(pSocket->isExclusive()); 1770 pSocketItem->setForward(pSocket->forward()); 1771 pSocketItem->updatePixmap(); 1772 qjackctlPlugItem *pPlugItem = nullptr; 1773 QStringListIterator iter(pSocket->pluglist()); 1774 while (iter.hasNext()) { 1775 pPlugItem = new qjackctlPlugItem( 1776 pSocketItem, iter.next(), pPlugItem); 1777 } 1778 } 1779 } 1780 } 1781 1782 void qjackctlPatchbay::loadRack ( qjackctlPatchbayRack *pPatchbayRack ) 1783 { 1784 (m_pOSocketList->listView())->setUpdatesEnabled(false); 1785 (m_pISocketList->listView())->setUpdatesEnabled(false); 1786 1787 // Load ouput sockets. 1788 loadRackSockets(m_pOSocketList, pPatchbayRack->osocketlist()); 1789 1790 // Load input sockets. 1791 loadRackSockets(m_pISocketList, pPatchbayRack->isocketlist()); 1792 1793 // Now ready to load from cable model. 1794 QListIterator<qjackctlPatchbayCable *> iter(pPatchbayRack->cablelist()); 1795 while (iter.hasNext()) { 1796 qjackctlPatchbayCable *pCable = iter.next(); 1797 // Get proper sockets... 1798 qjackctlPatchbaySocket *pOSocket = pCable->outputSocket(); 1799 qjackctlPatchbaySocket *pISocket = pCable->inputSocket(); 1800 if (pOSocket && pISocket) { 1801 qjackctlSocketItem *pOSocketItem 1802 = m_pOSocketList->findSocket(pOSocket->name(), pOSocket->type()); 1803 qjackctlSocketItem *pISocketItem 1804 = m_pISocketList->findSocket(pISocket->name(), pISocket->type()); 1805 if (pOSocketItem && pISocketItem) 1806 connectSockets(pOSocketItem, pISocketItem); 1807 } 1808 } 1809 1810 (m_pOSocketList->listView())->setUpdatesEnabled(true); 1811 (m_pISocketList->listView())->setUpdatesEnabled(true); 1812 1813 (m_pOSocketList->listView())->update(); 1814 (m_pISocketList->listView())->update(); 1815 (m_pPatchbayView->PatchworkView())->update(); 1816 1817 // Reset dirty flag. 1818 m_pPatchbayView->setDirty(false); 1819 } 1820 1821 // External rack transfer method: copy patchbay structure into master rack model. 1822 void qjackctlPatchbay::saveRackSockets ( qjackctlSocketList *pSocketList, 1823 QList<qjackctlPatchbaySocket *>& socketlist ) 1824 { 1825 // Have QTreeWidget item order into account: 1826 qjackctlSocketListView *pListView = pSocketList->listView(); 1827 if (pListView == nullptr) 1828 return; 1829 1830 socketlist.clear(); 1831 1832 const int iItemCount = pListView->topLevelItemCount(); 1833 for (int iItem = 0; iItem < iItemCount; ++iItem) { 1834 QTreeWidgetItem *pItem = pListView->topLevelItem(iItem); 1835 if (pItem->type() != QJACKCTL_SOCKETITEM) 1836 continue; 1837 qjackctlSocketItem *pSocketItem 1838 = static_cast<qjackctlSocketItem *> (pItem); 1839 if (pSocketItem == nullptr) 1840 continue; 1841 qjackctlPatchbaySocket *pSocket 1842 = new qjackctlPatchbaySocket(pSocketItem->socketName(), 1843 pSocketItem->clientName(), pSocketItem->socketType()); 1844 if (pSocket) { 1845 pSocket->setExclusive(pSocketItem->isExclusive()); 1846 pSocket->setForward(pSocketItem->forward()); 1847 QListIterator<qjackctlPlugItem *> iter(pSocketItem->plugs()); 1848 while (iter.hasNext()) 1849 pSocket->pluglist().append((iter.next())->plugName()); 1850 socketlist.append(pSocket); 1851 } 1852 } 1853 } 1854 1855 void qjackctlPatchbay::saveRack ( qjackctlPatchbayRack *pPatchbayRack ) 1856 { 1857 // Save ouput sockets. 1858 saveRackSockets(m_pOSocketList, pPatchbayRack->osocketlist()); 1859 1860 // Save input sockets. 1861 saveRackSockets(m_pISocketList, pPatchbayRack->isocketlist()); 1862 1863 // Now ready to save into cable model. 1864 pPatchbayRack->cablelist().clear(); 1865 1866 // Start from output sockets... 1867 QListIterator<qjackctlSocketItem *> osocket(m_pOSocketList->sockets()); 1868 while (osocket.hasNext()) { 1869 qjackctlSocketItem *pOSocketItem = osocket.next(); 1870 // Then to input sockets... 1871 QListIterator<qjackctlSocketItem *> isocket(pOSocketItem->connects()); 1872 while (isocket.hasNext()) { 1873 qjackctlSocketItem *pISocketItem = isocket.next(); 1874 // Now find proper racked sockets... 1875 qjackctlPatchbaySocket *pOSocket = pPatchbayRack->findSocket( 1876 pPatchbayRack->osocketlist(), pOSocketItem->socketName()); 1877 qjackctlPatchbaySocket *pISocket = pPatchbayRack->findSocket( 1878 pPatchbayRack->isocketlist(), pISocketItem->socketName()); 1879 if (pOSocket && pISocket) { 1880 pPatchbayRack->addCable( 1881 new qjackctlPatchbayCable(pOSocket, pISocket)); 1882 } 1883 } 1884 } 1885 1886 // Reset dirty flag. 1887 m_pPatchbayView->setDirty(false); 1888 } 1889 1890 1891 // Connections snapshot. 1892 void qjackctlPatchbay::connectionsSnapshot (void) 1893 { 1894 qjackctlMainForm *pMainForm = qjackctlMainForm::getInstance(); 1895 if (pMainForm == nullptr) 1896 return; 1897 1898 qjackctlPatchbayRack rack; 1899 rack.connectJackSnapshot(pMainForm->jackClient()); 1900 rack.connectAlsaSnapshot(pMainForm->alsaSeq()); 1901 loadRack(&rack); 1902 1903 // Set dirty flag. 1904 m_pPatchbayView->setDirty(true); 1905 } 1906 1907 1908 // end of qjackctlPatchbay.cpp 1909