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