1 //=========================================================
2 // MusE
3 // Linux Music Editor
4 //
5 // RoutePopupMenu.cpp
6 // (C) Copyright 2011-2015 Tim E. Real (terminator356 A T sourceforge D O T net)
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License
10 // as published by the Free Software Foundation; version 2 of
11 // the License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 //=============================================================================
22
23 #include <QBitArray>
24 #include <QByteArray>
25 #include <qtextcodec.h>
26 #include <QActionGroup>
27 #include <QLayout>
28 #include <QApplication>
29 #include <QStyle>
30 #include <list>
31
32 #include "app.h"
33 #include "routepopup.h"
34 #include "gconfig.h"
35 #include "midiport.h"
36 #include "mididev.h"
37 #include "audio.h"
38 #include "driver/audiodev.h"
39 #include "song.h"
40 #include "synth.h"
41 #include "icons.h"
42 #include "menutitleitem.h"
43
44 #include "globaldefs.h"
45 #include "gconfig.h"
46 #include "globals.h"
47
48 // Forwards from header:
49 #include <QWidget>
50 #include <QAction>
51 #include <QPoint>
52 #include <QResizeEvent>
53 #include "track.h"
54 #include "operations.h"
55
56 // For debugging output: Uncomment the fprintf section.
57 #define DEBUG_PRST_ROUTES(dev, format, args...) // fprintf(dev, format, ##args);
58 #define DEBUG_PRST_ROUTES_2(dev, format, args...) // fprintf(dev, format, ##args);
59
60 #define _USE_CUSTOM_WIDGET_ACTIONS_
61
62 // REMOVE Tim. Persistent routes. Added. Make this permanent later if it works OK and makes good sense.
63 #define _USE_SIMPLIFIED_SOLO_CHAIN_
64
65 // REMOVE Tim. Persistent routes. Added. Make this permanent later if it works OK and makes good sense.
66 #define _USE_MIDI_ROUTE_PER_CHANNEL_
67
68 // Undefine if and when multiple output routes are added to midi tracks.
69 #define _USE_MIDI_TRACK_SINGLE_OUT_PORT_CHAN_
70
71 #define _SHOW_CANONICAL_NAMES_ 0x1000
72 #define _SHOW_FIRST_ALIASES_ 0x1001
73 #define _SHOW_SECOND_ALIASES_ 0x1002
74
75 #define _ALIASES_WIDGET_ACTION_ 0x2000
76 #define _OPEN_MIDI_CONFIG_ 0x2001
77 #define _OPEN_ROUTING_DIALOG_ 0x2002
78 #define _GROUPING_CHANNELS_WIDGET_ACTION_ 0x2003
79
80 namespace MusEGui {
81
82
addGroupingChannelsAction(PopupMenu * lb)83 void RoutePopupMenu::addGroupingChannelsAction(PopupMenu* lb)
84 {
85 RoutingMatrixWidgetAction* name_wa = new RoutingMatrixWidgetAction(2, 0, 0, this, tr("Channel grouping:"));
86 name_wa->setArrayStayOpen(true);
87 name_wa->setData(_GROUPING_CHANNELS_WIDGET_ACTION_);
88 name_wa->array()->setColumnsExclusive(true);
89 name_wa->array()->setExclusiveToggle(false);
90 name_wa->array()->headerSetVisible(false);
91 name_wa->array()->setText(0, tr("Mono "));
92 name_wa->array()->setText(1, tr("Stereo "));
93 switch(MusEGlobal::config.routerGroupingChannels)
94 {
95 case 1:
96 name_wa->array()->setValue(0, true);
97 break;
98 case 2:
99 name_wa->array()->setValue(1, true);
100 break;
101 default:
102 break;
103 }
104 // Must rebuild array after text changes.
105 name_wa->updateChannelArray();
106 lb->addAction(name_wa);
107 lb->addSeparator();
108 }
109
110 //---------------------------------------------------------
111 // addMenuItem
112 //---------------------------------------------------------
113
addMenuItem(MusECore::AudioTrack * track,MusECore::Track * route_track,PopupMenu * lb,int id,int channel,int,bool isOutput)114 int RoutePopupMenu::addMenuItem(MusECore::AudioTrack* track, MusECore::Track* route_track, PopupMenu* lb,
115 int id, int channel, int /*channels*/, bool isOutput)
116 {
117 if(route_track->isMidiTrack())
118 return ++id;
119
120 MusECore::RouteList* rl = isOutput ? track->outRoutes() : track->inRoutes();
121
122 const bool circ_route = (isOutput ? track : route_track)->isCircularRoute(isOutput ? route_track : track);
123
124 MusECore::RouteCapabilitiesStruct t_caps = track->routeCapabilities();
125 MusECore::RouteCapabilitiesStruct rt_caps = route_track->routeCapabilities();
126 const int t_chans = isOutput ? t_caps._trackChannels._outChannels : t_caps._trackChannels._inChannels;
127 const int rt_chans = isOutput ? rt_caps._trackChannels._inChannels : rt_caps._trackChannels._outChannels;
128
129 // Support Audio Output Track to Audio Input Track 'Omni' routes.
130 if(isOutput && track->type() == MusECore::Track::AUDIO_OUTPUT && route_track->type() == MusECore::Track::AUDIO_INPUT)
131 {
132 if(channel != -1 || !t_caps._trackChannels._outRoutable || !rt_caps._trackChannels._inRoutable)
133 return ++id;
134 }
135 else
136 if(!isOutput && track->type() == MusECore::Track::AUDIO_INPUT && route_track->type() == MusECore::Track::AUDIO_OUTPUT)
137 {
138 if(channel != -1 || !t_caps._trackChannels._inRoutable || !rt_caps._trackChannels._outRoutable)
139 return ++id;
140 }
141 else
142 {
143 if(t_chans <= 0)
144 return ++id;
145 if(rt_chans <= 0)
146 return ++id;
147 }
148
149 #ifndef _USE_CUSTOM_WIDGET_ACTIONS_
150
151 // Is it an omnibus route?
152 if(channel == -1)
153 {
154 QAction* act = lb->addAction(route_track->displayName());
155 act->setCheckable(true);
156 MusECore::Route r(route_track, -1);
157 act->setData(QVariant::fromValue(r));
158 for(MusECore::ciRoute ir = rl->begin(); ir != rl->end(); ++ir)
159 {
160 if(ir->type == MusECore::Route::TRACK_ROUTE && ir->track == route_track &&
161 ir->remoteChannel == r.channel &&
162 ir->channel == r.remoteChannel &&
163 ir->channels == r.channels)
164 {
165 act->setChecked(true);
166 break;
167 }
168 }
169 if(!act->isChecked() && circ_route) // If circular route exists, allow user to break it, otherwise forbidden.
170 act->setEnabled(false);
171 }
172 else
173 #endif
174
175 {
176
177 #ifdef _USE_CUSTOM_WIDGET_ACTIONS_
178
179 QAction* act = lb->addAction(route_track->displayName());
180 act->setCheckable(true);
181 MusECore::Route r(route_track, -1);
182 act->setData(QVariant::fromValue(r));
183 if(rl->contains(r))
184 act->setChecked(true);
185
186 if(rt_chans != 0 && t_chans != 0)
187 {
188 RoutePopupMenu* subp = new RoutePopupMenu(_route, this, isOutput, _broadcastChanges);
189 subp->addAction(new MenuTitleItem(tr("Channels"), this));
190 act->setMenu(subp);
191 QActionGroup* act_group = new QActionGroup(this);
192 act_group->setExclusive(false);
193 for(int row = 0; row < rt_chans; ++row)
194 {
195 //RoutingMatrixWidgetAction* wa = new RoutingMatrixWidgetAction(t_chans, redLedIcon, darkRedLedIcon, this, QString::number(row + 1));
196 RoutingMatrixWidgetAction* wa = new RoutingMatrixWidgetAction(t_chans, nullptr, nullptr, this, QString::number(row + 1));
197 wa->setFont(wa->smallFont());
198 wa->array()->headerSetVisible(row == 0);
199 r.channel = row;
200 wa->setData(QVariant::fromValue(r)); // Ignore the routing channel and channels - our action holds the channels.
201 for(int col = 0; col < t_chans; ++col)
202 {
203 for(MusECore::ciRoute ir = rl->begin(); ir != rl->end(); ++ir)
204 {
205 if(ir->type == MusECore::Route::TRACK_ROUTE && ir->track == route_track &&
206 ir->remoteChannel == row &&
207 ir->channel == col &&
208 ir->channels == 1)
209 {
210 wa->array()->setValue(col, true);
211 break;
212 }
213 }
214 }
215 // Must rebuild array after text changes.
216 wa->updateChannelArray();
217 // subp->addAction(wa);
218 act_group->addAction(wa);
219 }
220 subp->addActions(act_group->actions());
221 }
222
223 if(!act->isChecked() && circ_route) // If circular route exists, allow user to break it, otherwise forbidden.
224 act->setEnabled(false);
225
226 #else
227
228 // It's not an omnibus route. Add the individual channels...
229 PopupMenu* subp = new PopupMenu(this, true);
230 subp->setTitle(route_track->displayName());
231 subp->addAction(new MenuTitleItem(tr("Channels"), this));
232 QActionGroup* act_group = new QActionGroup(this);
233 act_group->setExclusive(false);
234 for(int i = 0; i < rt_chans; ++i)
235 {
236 // QAction* act = subp->addAction(QString::number(i + 1));
237 QAction* act = act_group->addAction(QString::number(i + 1));
238 act->setCheckable(true);
239 MusECore::Route r(route_track, i, 1);
240 r.remoteChannel = channel;
241 act->setData(QVariant::fromValue(r));
242
243 for(MusECore::ciRoute ir = rl->begin(); ir != rl->end(); ++ir)
244 {
245 if(ir->type == MusECore::Route::TRACK_ROUTE && ir->track == route_track &&
246 ir->remoteChannel == r.channel &&
247 ir->channel == r.remoteChannel &&
248 ir->channels == r.channels)
249 {
250 act->setChecked(true);
251 break;
252 }
253 }
254 if(!act->isChecked() && circ_route) // If circular route exists, allow user to break it, otherwise forbidden.
255 act->setEnabled(false);
256 }
257 subp->addActions(act_group->actions());
258 lb->addMenu(subp);
259 #endif
260
261 }
262
263 return ++id;
264 }
265
266 //---------------------------------------------------------
267 // addAuxPorts
268 //---------------------------------------------------------
269
addAuxPorts(MusECore::AudioTrack * t,PopupMenu * lb,int id,int channel,int channels,bool isOutput)270 int RoutePopupMenu::addAuxPorts(MusECore::AudioTrack* t, PopupMenu* lb, int id, int channel, int channels, bool isOutput)
271 {
272 MusECore::AuxList* al = MusEGlobal::song->auxs();
273 for (MusECore::iAudioAux i = al->begin(); i != al->end(); ++i) {
274 MusECore::Track* track = *i;
275 if (t == track)
276 continue;
277 id = addMenuItem(t, track, lb, id, channel, channels, isOutput);
278 }
279 return id;
280 }
281
282 //---------------------------------------------------------
283 // addInPorts
284 //---------------------------------------------------------
285
addInPorts(MusECore::AudioTrack * t,PopupMenu * lb,int id,int channel,int channels,bool isOutput)286 int RoutePopupMenu::addInPorts(MusECore::AudioTrack* t, PopupMenu* lb, int id, int channel, int channels, bool isOutput)
287 {
288 MusECore::InputList* al = MusEGlobal::song->inputs();
289 for (MusECore::iAudioInput i = al->begin(); i != al->end(); ++i) {
290 MusECore::Track* track = *i;
291 if (t == track)
292 continue;
293 id = addMenuItem(t, track, lb, id, channel, channels, isOutput);
294 }
295 return id;
296 }
297
298 //---------------------------------------------------------
299 // addOutPorts
300 //---------------------------------------------------------
301
addOutPorts(MusECore::AudioTrack * t,PopupMenu * lb,int id,int channel,int channels,bool isOutput)302 int RoutePopupMenu::addOutPorts(MusECore::AudioTrack* t, PopupMenu* lb, int id, int channel, int channels, bool isOutput)
303 {
304 MusECore::OutputList* al = MusEGlobal::song->outputs();
305 for (MusECore::iAudioOutput i = al->begin(); i != al->end(); ++i) {
306 MusECore::Track* track = *i;
307 if (t == track)
308 continue;
309 id = addMenuItem(t, track, lb, id, channel, channels, isOutput);
310 }
311 return id;
312 }
313
314 //---------------------------------------------------------
315 // addGroupPorts
316 //---------------------------------------------------------
317
addGroupPorts(MusECore::AudioTrack * t,PopupMenu * lb,int id,int channel,int channels,bool isOutput)318 int RoutePopupMenu::addGroupPorts(MusECore::AudioTrack* t, PopupMenu* lb, int id, int channel, int channels, bool isOutput)
319 {
320 MusECore::GroupList* al = MusEGlobal::song->groups();
321 for (MusECore::iAudioGroup i = al->begin(); i != al->end(); ++i) {
322 MusECore::Track* track = *i;
323 if (t == track)
324 continue;
325 id = addMenuItem(t, track, lb, id, channel, channels, isOutput);
326 }
327 return id;
328 }
329
330 //---------------------------------------------------------
331 // addWavePorts
332 //---------------------------------------------------------
333
addWavePorts(MusECore::AudioTrack * t,PopupMenu * lb,int id,int channel,int channels,bool isOutput)334 int RoutePopupMenu::addWavePorts(MusECore::AudioTrack* t, PopupMenu* lb, int id, int channel, int channels, bool isOutput)
335 {
336 MusECore::WaveTrackList* al = MusEGlobal::song->waves();
337 for (MusECore::iWaveTrack i = al->begin(); i != al->end(); ++i) {
338 MusECore::Track* track = *i;
339 if (t == track)
340 continue;
341 id = addMenuItem(t, track, lb, id, channel, channels, isOutput);
342 }
343 return id;
344 }
345
addMidiTracks(MusECore::Track * t,PopupMenu * pup,bool isOutput)346 void RoutePopupMenu::addMidiTracks(MusECore::Track* t, PopupMenu* pup, bool isOutput)
347 {
348 const MusECore::RouteList* const rl = isOutput ? t->outRoutes() : t->inRoutes();
349 const MusECore::MidiTrackList* const mtracks = MusEGlobal::song->midis();
350 for(MusECore::ciMidiTrack imt = mtracks->begin(); imt != mtracks->end(); ++imt)
351 {
352 MusECore::MidiTrack* const mt = *imt;
353 QAction* act = pup->addAction(mt->displayName());
354 act->setCheckable(true);
355 const MusECore::Route r(mt, -1);
356 act->setData(QVariant::fromValue(r));
357 if(rl->contains(r))
358 act->setChecked(true);
359 }
360 }
361
addMidiPorts(MusECore::Track * t,PopupMenu * pup,bool isOutput,bool show_synths,bool want_writable)362 void RoutePopupMenu::addMidiPorts(MusECore::Track* t, PopupMenu* pup, bool isOutput, bool show_synths, bool want_writable)
363 {
364
365 #ifdef _USE_CUSTOM_WIDGET_ACTIONS_
366
367 const MusECore::RouteList* const rl = isOutput ? t->outRoutes() : t->inRoutes();
368 MusECore::MidiDevice* md;
369
370 bool is_first_pass = true;
371 QActionGroup* act_group = nullptr;
372 // Order the entire listing by device type.
373 for(int dtype = 0; dtype <= MusECore::MidiDevice::SYNTH_MIDI; ++dtype)
374 {
375 // Currently only midi port output to midi track input supports 'Omni' routes.
376 #ifdef _USE_MIDI_TRACK_SINGLE_OUT_PORT_CHAN_
377 if(isOutput)
378 {
379 // Count the number of required rows.
380 int rows = 0;
381 for(int i = 0; i < MusECore::MIDI_PORTS; ++i)
382 {
383 md = MusEGlobal::midiPorts[i].device();
384 // This is desirable, but could lead to 'hidden' routes unless we add more support such as removing the existing routes when user changes flags.
385 // So for now, just list all valid ports whether read or write.
386 //if(!md)
387 // continue;
388 if(!md || !(md->rwFlags() & (want_writable ? 1 : 2))) // If this is an output click we are looking for midi writeable here.
389 continue;
390 // Do not list synth devices!
391 if(!show_synths && md->isSynti())
392 continue;
393 // We only want the sorted device type.
394 if(md->deviceType() != dtype)
395 continue;
396 ++rows;
397 }
398 if(rows == 0)
399 continue;
400
401 if(is_first_pass)
402 {
403 is_first_pass = false;
404 act_group = new QActionGroup(this);
405 act_group->setExclusive(true);
406 }
407
408 int row = 0;
409 MusECore::Route r(-1); // Midi port route.
410 for(int i = 0; i < MusECore::MIDI_PORTS; ++i)
411 {
412 md = MusEGlobal::midiPorts[i].device();
413 // This is desirable, but could lead to 'hidden' routes unless we add more support such as removing the existing routes when user changes flags.
414 // So for now, just list all valid ports whether read or write.
415 //if(!md)
416 // continue;
417 if(!md || !(md->rwFlags() & (want_writable ? 1 : 2))) // If this is an output click we are looking for midi writeable here.
418 continue;
419 // Do not list synth devices!
420 if(!show_synths && md->isSynti())
421 continue;
422 // We only want the sorted device type.
423 if(md->deviceType() != dtype)
424 continue;
425
426 //RoutingMatrixWidgetAction* wa = new RoutingMatrixWidgetAction(
427 // MusECore::MUSE_MIDI_CHANNELS, redLedIcon, darkRedLedIcon, this, QString("%1:%2").arg(i + 1).arg(md->name()));
428 RoutingMatrixWidgetAction* wa = new RoutingMatrixWidgetAction(
429 MusECore::MUSE_MIDI_CHANNELS, nullptr, nullptr, this, QString("%1:%2").arg(i + 1).arg(md->name()));
430 if(row == 0)
431 {
432 switch(dtype)
433 {
434 case MusECore::MidiDevice::ALSA_MIDI:
435 wa->array()->headerSetTitle(tr("ALSA devices"));
436 break;
437
438 case MusECore::MidiDevice::JACK_MIDI:
439 wa->array()->headerSetTitle(tr("JACK devices"));
440 break;
441
442 case MusECore::MidiDevice::SYNTH_MIDI:
443 wa->array()->headerSetTitle(tr("Synth devices"));
444 break;
445 }
446 wa->array()->setArrayTitle(tr("Channels"));
447 wa->array()->headerSetVisible(true);
448 }
449 else
450 wa->array()->headerSetVisible(false);
451
452 r.midiPort = i;
453 wa->setData(QVariant::fromValue(r));
454
455 wa->array()->setColumnsExclusive(true);
456 MusECore::MidiTrack* mt = static_cast<MusECore::MidiTrack*>(t);
457 if(i == mt->outPort())
458 wa->array()->setValue(mt->outChannel(), true);
459 // Must rebuild array after text changes.
460 wa->updateChannelArray();
461
462 // Make it easy for the user: Show the device's jack ports as well.
463 // This is reasonable for midi devices since they are hidden away.
464 // (Midi devices were made tracks, and midi ports eliminated, in the old MusE-2 muse_evolution branch!)
465 switch(md->deviceType())
466 {
467 case MusECore::MidiDevice::JACK_MIDI:
468 {
469 const MusECore::Route md_r(md, -1);
470 RoutePopupMenu* subp = new RoutePopupMenu(md_r, this, isOutput, _broadcastChanges);
471 addJackPorts(md_r, subp);
472 wa->setMenu(subp);
473 }
474 break;
475
476 default:
477 break;
478 }
479
480 act_group->addAction(wa);
481 ++row;
482 }
483 pup->addActions(act_group->actions());
484 }
485 else
486
487 #endif // _USE_MIDI_TRACK_SINGLE_OUT_PORT_CHAN_
488
489 {
490 // Count the number of required rows.
491 int rows = 0;
492 for(int i = 0; i < MusECore::MIDI_PORTS; ++i)
493 {
494 md = MusEGlobal::midiPorts[i].device();
495 // This is desirable, but could lead to 'hidden' routes unless we add more support such as removing the existing routes when user changes flags.
496 // So for now, just list all valid ports whether read or write.
497 //if(!md)
498 // continue;
499 if(!md || !(md->rwFlags() & (want_writable ? 1 : 2))) // If this is an input click we are looking for midi readable here.
500 continue;
501 // Do not list synth devices!
502 if(!show_synths && md->isSynti())
503 continue;
504 // We only want the sorted device type.
505 if(md->deviceType() != dtype)
506 continue;
507 ++rows;
508 }
509 if(rows == 0)
510 continue;
511
512 // It's an input. Allow 'Omni' routes'...
513 int row = 0;
514 for(int i = 0; i < MusECore::MIDI_PORTS; ++i)
515 {
516 md = MusEGlobal::midiPorts[i].device();
517 // This is desirable, but could lead to 'hidden' routes unless we add more support such as removing the existing routes when user changes flags.
518 // So for now, just list all valid ports whether read or write.
519 //if(!md)
520 // continue;
521 if(!md || !(md->rwFlags() & (want_writable ? 1 : 2))) // If this is an input click we are looking for midi readable here.
522 continue;
523 // Do not list synth devices!
524 if(!show_synths && md->isSynti())
525 continue;
526 // We only want the sorted device type.
527 if(md->deviceType() != dtype)
528 continue;
529
530 MusECore::Route r(i, -1);
531 //RoutingMatrixWidgetAction* wa = new RoutingMatrixWidgetAction(
532 // MusECore::MUSE_MIDI_CHANNELS, redLedIcon, darkRedLedIcon, this, QString("%1:%2").arg(i + 1).arg(md->name()));
533 RoutingMatrixWidgetAction* wa = new RoutingMatrixWidgetAction(
534 MusECore::MUSE_MIDI_CHANNELS, nullptr, nullptr, this, QString("%1:%2").arg(i + 1).arg(md->name()));
535 if(row == 0)
536 {
537 wa->array()->setCheckBoxTitle(tr("Omni"));
538 switch(dtype)
539 {
540 case MusECore::MidiDevice::ALSA_MIDI:
541 wa->array()->headerSetTitle(tr("ALSA devices"));
542 break;
543
544 case MusECore::MidiDevice::JACK_MIDI:
545 wa->array()->headerSetTitle(tr("JACK devices"));
546 break;
547
548 case MusECore::MidiDevice::SYNTH_MIDI:
549 wa->array()->headerSetTitle(tr("Synth devices"));
550 break;
551 }
552 wa->array()->setArrayTitle(tr("Channels"));
553 wa->array()->headerSetVisible(true);
554 }
555 else
556 wa->array()->headerSetVisible(false);
557
558 wa->setHasCheckBox(true);
559 if(rl->contains(r))
560 wa->setCheckBoxChecked(true);
561 wa->setData(QVariant::fromValue(r)); // Ignore the routing channel and channels - our action holds the channels.
562
563 #ifdef _USE_MIDI_ROUTE_PER_CHANNEL_
564 for(int col = 0; col < MusECore::MUSE_MIDI_CHANNELS; ++col)
565 {
566 r.channel = col;
567 if(rl->contains(r))
568 wa->array()->setValue(col, true);
569 }
570 #else // _USE_MIDI_ROUTE_PER_CHANNEL_
571 int chans = 0;
572 // Is there already a route?
573 for(MusECore::ciRoute ir = rl->begin(); ir != rl->end(); ++ir)
574 {
575 switch(ir->type)
576 {
577 case MusECore::Route::MIDI_PORT_ROUTE:
578 if(ir->midiPort == i)
579 chans = ir->channel; // Grab the channels.
580 break;
581 case MusECore::Route::TRACK_ROUTE:
582 case MusECore::Route::JACK_ROUTE:
583 case MusECore::Route::MIDI_DEVICE_ROUTE:
584 break;
585 }
586 if(chans != 0)
587 break;
588 }
589 if(chans != 0 && chans != -1)
590 {
591 for(int col = 0; col < MusECore::MUSE_MIDI_CHANNELS; ++col)
592 {
593 if(chans & (1 << col))
594 wa->array()->setValue(col, true);
595 }
596 }
597 #endif // _USE_MIDI_ROUTE_PER_CHANNEL_
598
599 // Must rebuild array after text changes.
600 wa->updateChannelArray();
601
602 // Make it easy for the user: Show the device's jack ports as well.
603 // This is reasonable for midi devices since they are hidden away.
604 // (Midi devices were made tracks, and midi ports eliminated, in the old MusE-2 muse_evolution branch!)
605 switch(md->deviceType())
606 {
607 case MusECore::MidiDevice::JACK_MIDI:
608 {
609 //PopupMenu* subp = new PopupMenu(this, true);
610 const MusECore::Route md_r(md, -1);
611 RoutePopupMenu* subp = new RoutePopupMenu(md_r, this, isOutput, _broadcastChanges);
612 addJackPorts(md_r, subp);
613 wa->setMenu(subp);
614 }
615 break;
616
617 default:
618 break;
619 }
620
621 pup->addAction(wa);
622 ++row;
623 }
624 }
625
626 #else // _USE_CUSTOM_WIDGET_ACTIONS_
627
628 pup->addAction(new MenuTitleItem(qApp->translate("@default", QT_TRANSLATE_NOOP("@default", "Output port/device")), pup));
629 for(int i = 0; i < MIDI_PORTS; ++i)
630 {
631 MusECore::MidiPort* mp = &MusEGlobal::midiPorts[i];
632 MusECore::MidiDevice* md = mp->device();
633
634 // This is desirable, but could lead to 'hidden' routes unless we add more support
635 // such as removing the existing routes when user changes flags.
636 // So for now, just list all valid ports whether read or write.
637 //if(!md)
638 // continue;
639 if(!md || !(md->rwFlags() & (want_writable ? 1 : 2))) // If this is an input click we are looking for midi outputs here.
640 continue;
641
642 // Do not list synth devices!
643 if(!show_synths && md->isSynti())
644 continue;
645 // We only want the sorted device type.
646 if(md->deviceType() != dtype)
647 continue;
648
649 MusECore::RouteList* rl = isOutput ? t->outRoutes() : t->inRoutes();
650
651 int chanmask = 0;
652 // To reduce number of routes required, from one per channel to just one containing a channel mask.
653 // Look for the first route to this midi port. There should always be only a single route for each midi port, now.
654 for(MusECore::ciRoute ir = rl->begin(); ir != rl->end(); ++ir)
655 {
656 if(ir->type == MusECore::Route::MIDI_PORT_ROUTE && ir->midiPort == i)
657 {
658 // We have a route to the midi port. Grab the channel mask.
659 chanmask = ir->channel;
660 break;
661 }
662 }
663
664 PopupMenu* subp = new PopupMenu(pup, true);
665 subp->setTitle(md->name());
666 QAction* act;
667
668 for(int ch = 0; ch < MusECore::MUSE_MIDI_CHANNELS; ++ch)
669 {
670 act = subp->addAction(QString("Channel %1").arg(ch+1));
671 act->setCheckable(true);
672
673 int chbit = 1 << ch;
674 MusECore::Route srcRoute(i, chbit); // In accordance with channel mask, use the bit position.
675
676 act->setData(QVariant::fromValue(srcRoute));
677
678 if(chanmask & chbit) // Is the channel already set? Show item check mark.
679 act->setChecked(true);
680 }
681 act = subp->addAction(QString("Toggle all"));
682 //act->setCheckable(true);
683 MusECore::Route togRoute(i, (1 << MusECore::MUSE_MIDI_CHANNELS) - 1); // Set all channel bits.
684 act->setData(QVariant::fromValue(togRoute));
685 pup->addMenu(subp);
686 }
687
688 #endif // _USE_CUSTOM_WIDGET_ACTIONS_
689
690 }
691
692 return;
693 }
694
695 //---------------------------------------------------------
696 // addSynthPorts
697 //---------------------------------------------------------
698
addSynthPorts(MusECore::AudioTrack * t,PopupMenu * lb,int id,int channel,int channels,bool isOutput)699 int RoutePopupMenu::addSynthPorts(MusECore::AudioTrack* t, PopupMenu* lb, int id, int channel, int channels, bool isOutput)
700 {
701 MusECore::SynthIList* al = MusEGlobal::song->syntis();
702 for (MusECore::iSynthI i = al->begin(); i != al->end(); ++i) {
703 MusECore::Track* track = *i;
704 if (t == track)
705 continue;
706 id = addMenuItem(t, track, lb, id, channel, channels, isOutput);
707 }
708 return id;
709 }
710
711 //---------------------------------------------------------
712 // addJackPorts
713 //---------------------------------------------------------
714
addJackPorts(const MusECore::Route & route,PopupMenu * lb)715 void RoutePopupMenu::addJackPorts(const MusECore::Route& route, PopupMenu* lb)
716 {
717 if(!MusEGlobal::checkAudioDevice())
718 return;
719
720 MusECore::RouteList* rl = nullptr;
721 int channels = -1;
722 std::list<QString> ol;
723 MusECore::RouteCapabilitiesStruct rcaps;
724 switch(route.type)
725 {
726 case MusECore::Route::TRACK_ROUTE:
727 ol = _isOutMenu ? MusEGlobal::audioDevice->inputPorts() : MusEGlobal::audioDevice->outputPorts();
728 rl = _isOutMenu ? route.track->outRoutes() : route.track->inRoutes();
729 rcaps = route.track->routeCapabilities();
730 channels = _isOutMenu ? rcaps._jackChannels._outChannels : rcaps._jackChannels._inChannels;
731 break;
732
733 case MusECore::Route::MIDI_DEVICE_ROUTE:
734 ol = _isOutMenu ? MusEGlobal::audioDevice->inputPorts(true) : MusEGlobal::audioDevice->outputPorts(true);
735 rl = _isOutMenu ? route.device->outRoutes() : route.device->inRoutes();
736 break;
737
738 case MusECore::Route::JACK_ROUTE:
739 case MusECore::Route::MIDI_PORT_ROUTE:
740 return;
741 break;
742 }
743
744 const int sz = ol.size();
745 if(sz != 0)
746 {
747
748 #ifdef _USE_CUSTOM_WIDGET_ACTIONS_
749
750 //RoutingMatrixWidgetAction* name_wa = new RoutingMatrixWidgetAction(2, redLedIcon, darkRedLedIcon, this, tr("Show aliases:"));
751 RoutingMatrixWidgetAction* name_wa = new RoutingMatrixWidgetAction(2, nullptr, nullptr, this, tr("Show aliases:"));
752 name_wa->setArrayStayOpen(true);
753 name_wa->setData(_ALIASES_WIDGET_ACTION_);
754 name_wa->array()->setColumnsExclusive(true);
755 name_wa->array()->setExclusiveToggle(true);
756 name_wa->array()->headerSetVisible(false);
757 name_wa->array()->setText(0, tr("First "));
758 name_wa->array()->setText(1, tr("Second "));
759 switch(MusEGlobal::config.preferredRouteNameOrAlias)
760 {
761 case MusEGlobal::RoutePreferFirstAlias:
762 name_wa->array()->setValue(0, true);
763 break;
764 case MusEGlobal::RoutePreferSecondAlias:
765 name_wa->array()->setValue(1, true);
766 break;
767 case MusEGlobal::RoutePreferCanonicalName:
768 break;
769 }
770 // Must rebuild array after text changes.
771 name_wa->updateChannelArray();
772 lb->addAction(name_wa);
773 lb->addSeparator();
774 #else
775 QActionGroup* act_grp = new QActionGroup(this);
776 act_grp->setExclusive(true);
777 act = act_grp->addAction(tr("Show names"));
778 act->setCheckable(true);
779 act->setData(_SHOW_CANONICAL_NAMES_);
780 if(MusEGlobal::config.preferredRouteNameOrAlias == MusEGlobal::RoutePreferCanonicalName)
781 act->setChecked(true);
782 act = act_grp->addAction(tr("Show first aliases"));
783 act->setCheckable(true);
784 act->setData(_SHOW_FIRST_ALIASES_);
785 if(MusEGlobal::config.preferredRouteNameOrAlias == MusEGlobal::RoutePreferFirstAlias)
786 act->setChecked(true);
787 act = act_grp->addAction(tr("Show second aliases"));
788 act->setCheckable(true);
789 act->setData(_SHOW_SECOND_ALIASES_);
790 if(MusEGlobal::config.preferredRouteNameOrAlias == MusEGlobal::RoutePreferSecondAlias)
791 act->setChecked(true);
792 lb->addActions(act_grp->actions());
793 lb->addSeparator();
794 #endif
795
796 #ifdef _USE_CUSTOM_WIDGET_ACTIONS_
797
798 QActionGroup* act_group = new QActionGroup(this);
799 act_group->setExclusive(false);
800 int row = 0;
801 for(std::list<QString>::iterator ip = ol.begin(); ip != ol.end(); ++ip)
802 {
803 QByteArray ba = (*ip).toLatin1();
804 const char* port_name = ba.constData();
805 void* const port = MusEGlobal::audioDevice->findPort(port_name);
806 if(port)
807 {
808 //RoutingMatrixWidgetAction* wa = new RoutingMatrixWidgetAction(channels == -1 ? 1 : channels, redLedIcon, darkRedLedIcon, this);
809 RoutingMatrixWidgetAction* wa = new RoutingMatrixWidgetAction(channels == -1 ? 1 : channels, nullptr, nullptr, this);
810 if(row == 0)
811 {
812 wa->array()->headerSetTitle(tr("Jack ports"));
813 if(channels == -1)
814 {
815 wa->array()->setArrayTitle(tr("Connect"));
816 wa->array()->headerSetVisible(false);
817 }
818 else
819 {
820 wa->array()->setArrayTitle(tr("Channels"));
821 wa->array()->headerSetVisible(true);
822 }
823 }
824 else
825 wa->array()->headerSetVisible(false);
826
827 char good_name[ROUTE_PERSISTENT_NAME_SIZE];
828
829 // Get the preferred display name.
830 MusEGlobal::audioDevice->portName(port, good_name, ROUTE_PERSISTENT_NAME_SIZE, MusEGlobal::config.preferredRouteNameOrAlias);
831 wa->setActionText(good_name);
832
833 // Get a good routing name.
834 MusEGlobal::audioDevice->portName(port, good_name, ROUTE_PERSISTENT_NAME_SIZE);
835 MusECore::Route r(MusECore::Route::JACK_ROUTE, -1, port, -1, -1, -1, good_name);
836
837 wa->setData(QVariant::fromValue(r));
838 if(channels == -1)
839 {
840 if(rl->contains(r))
841 wa->array()->setValue(0, true);
842 }
843 else
844 {
845 for(int i = 0; i < channels; ++i)
846 {
847 r.channel = i;
848 if(rl->contains(r))
849 wa->array()->setValue(i, true);
850 }
851 }
852 // Must rebuild array after text changes.
853 wa->updateChannelArray();
854 // lb->addAction(wa);
855 act_group->addAction(wa);
856 ++row;
857 }
858 }
859 lb->addActions(act_group->actions());
860
861 #else
862
863 QAction* act = 0;
864 if(channels == -1)
865 {
866 if(!MusEGlobal::checkAudioDevice())
867 {
868 clear();
869 return;
870 }
871 for(std::list<QString>::iterator ip = ol.begin(); ip != ol.end(); ++ip)
872 {
873 act = lb->addAction(*ip);
874 act->setCheckable(true);
875
876 QByteArray ba = (*ip).toLatin1();
877 const char* port_name = ba.constData();
878 char good_name[ROUTE_PERSISTENT_NAME_SIZE];
879 void* const port = MusEGlobal::audioDevice->findPort(port_name);
880 if(port)
881 {
882 MusEGlobal::audioDevice->portName(port, good_name, ROUTE_PERSISTENT_NAME_SIZE);
883 port_name = good_name;
884 }
885 MusECore::Route dst(MusECore::Route::JACK_ROUTE, -1, NULL, -1, -1, -1, port_name);
886
887 act->setData(QVariant::fromValue(dst));
888 if(rl->exists(r))
889 act->setChecked(true);
890 }
891 }
892 else
893 {
894 for(int i = 0; i < channels; ++i)
895 {
896 QString chBuffer = tr("Channel") + QString(" ") + QString::number(i + 1);
897 MenuTitleItem* titel = new MenuTitleItem(chBuffer, this);
898 lb->addAction(titel);
899
900 if(!MusEGlobal::checkAudioDevice())
901 {
902 clear();
903 return;
904 }
905 for(std::list<QString>::iterator ip = ol.begin(); ip != ol.end(); ++ip)
906 {
907 act = lb->addAction(*ip);
908 act->setCheckable(true);
909
910 QByteArray ba = (*ip).toLatin1();
911 const char* port_name = ba.constData();
912 char good_name[ROUTE_PERSISTENT_NAME_SIZE];
913 void* const port = MusEGlobal::audioDevice->findPort(port_name);
914 if(port)
915 {
916 MusEGlobal::audioDevice->portName(port, good_name, ROUTE_PERSISTENT_NAME_SIZE);
917 port_name = good_name;
918 }
919 MusECore::Route dst(MusECore::Route::JACK_ROUTE, -1, NULL, i, -1, -1, port_name);
920
921 act->setData(QVariant::fromValue(dst));
922 for(MusECore::ciRoute ir = rl->begin(); ir != rl->end(); ++ir)
923 {
924 if(*ir == dst)
925 {
926 act->setChecked(true);
927 break;
928 }
929 }
930 }
931 if(i+1 != channels)
932 lb->addSeparator();
933 }
934 }
935 #endif
936
937 }
938
939 QList<QAction*> act_list;
940 int row = 0;
941 for(MusECore::iRoute ir = rl->begin(); ir != rl->end(); ++ir)
942 {
943 switch(ir->type)
944 {
945 case MusECore::Route::JACK_ROUTE:
946 if(ir->jackPort == nullptr && MusEGlobal::audioDevice->findPort(ir->persistentJackPortName) == nullptr)
947 {
948 //RoutingMatrixWidgetAction* wa = new RoutingMatrixWidgetAction(channels == -1 ? 1 : channels, redLedIcon, darkRedLedIcon,
949 RoutingMatrixWidgetAction* wa = new RoutingMatrixWidgetAction(channels == -1 ? 1 : channels, nullptr, nullptr,
950 this, ir->persistentJackPortName);
951 wa->setEnabled(false);
952 if(row == 0)
953 {
954 wa->array()->headerSetTitle(tr("Jack ports"));
955 if(channels == -1)
956 {
957 wa->array()->setArrayTitle(tr("Connect"));
958 wa->array()->headerSetVisible(false);
959 }
960 else
961 {
962 wa->array()->setArrayTitle(tr("Channels"));
963 wa->array()->headerSetVisible(true);
964 }
965 }
966 else
967 wa->array()->headerSetVisible(false);
968
969 MusECore::Route r(MusECore::Route::JACK_ROUTE, -1, nullptr, -1, -1, -1, ir->persistentJackPortName);
970 wa->setData(QVariant::fromValue(r));
971
972 if(channels == -1)
973 wa->array()->setValue(0, true);
974 else
975 {
976 for(int i = 0; i < channels; ++i)
977 {
978 if(i == ir->channel)
979 wa->array()->setValue(i, true);
980 }
981 }
982 // Must rebuild array after text changes.
983 wa->updateChannelArray();
984 act_list.append(wa);
985 ++row;
986 }
987 break;
988
989 case MusECore::Route::TRACK_ROUTE:
990 case MusECore::Route::MIDI_DEVICE_ROUTE:
991 case MusECore::Route::MIDI_PORT_ROUTE:
992 break;
993 }
994 }
995
996 if(!act_list.isEmpty())
997 {
998 RoutePopupMenu* subp = new RoutePopupMenu(route, this, _isOutMenu, _broadcastChanges);
999 subp->setTitle(tr("Unavailable"));
1000 const int sz = act_list.size();
1001 for(int i = 0; i < sz; ++i)
1002 subp->addAction(act_list.at(i));
1003 lb->addMenu(subp);
1004 }
1005 }
1006
1007 //======================
1008 // RoutePopupMenu
1009 //======================
1010
RoutePopupMenu(QWidget * parent,bool isOutput,bool broadcastChanges)1011 RoutePopupMenu::RoutePopupMenu(QWidget* parent, bool isOutput, bool broadcastChanges)
1012 : PopupMenu(parent, true), _isOutMenu(isOutput), _broadcastChanges(broadcastChanges)
1013 {
1014 init();
1015 }
1016
RoutePopupMenu(const MusECore::Route & route,QWidget * parent,bool isOutput,bool broadcastChanges)1017 RoutePopupMenu::RoutePopupMenu(const MusECore::Route& route, QWidget* parent, bool isOutput, bool broadcastChanges)
1018 : PopupMenu(parent, true), _route(route), _isOutMenu(isOutput), _broadcastChanges(broadcastChanges)
1019 {
1020 init();
1021 }
1022
RoutePopupMenu(const MusECore::Route & route,const QString & title,QWidget * parent,bool isOutput,bool broadcastChanges)1023 RoutePopupMenu::RoutePopupMenu(const MusECore::Route& route, const QString& title, QWidget* parent, bool isOutput, bool broadcastChanges)
1024 //: PopupMenu(title, parent, true), _track(track), _isOutMenu(isOutput)
1025 : PopupMenu(title, parent, true), _route(route), _isOutMenu(isOutput), _broadcastChanges(broadcastChanges)
1026 {
1027 init();
1028 }
1029
init()1030 void RoutePopupMenu::init()
1031 {
1032 _hoverIsFromMouse = false;
1033 connect(this, SIGNAL(hovered(QAction*)), SLOT(routePopupHovered(QAction*)));
1034 connect(MusEGlobal::song, SIGNAL(songChanged(MusECore::SongChangedStruct_t)), SLOT(songChanged(MusECore::SongChangedStruct_t)));
1035 }
1036
event(QEvent * event)1037 bool RoutePopupMenu::event(QEvent* event)
1038 {
1039 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::event:%p activePopupWidget:%p this:%p class:%s event type:%d\n",
1040 event, QApplication::activePopupWidget(), this, metaObject()->className(), event->type());
1041
1042 switch(event->type())
1043 {
1044 // Technical difficulties:
1045 // "mouseReleaseEvent() is called when a mouse button is released. A widget receives mouse release events
1046 // when it has received the corresponding mouse press event. This means that if the user presses the mouse
1047 // inside your widget, then drags the mouse somewhere else before releasing the mouse button, your widget
1048 // receives the release event. There is one exception: if a popup menu appears while the mouse button is held down,
1049 // this popup immediately steals the mouse events."
1050 // Unfortunately that's exactly what we don't want. The mouse release events are not being passed to the higher-up menu
1051 // if we hold the mouse down and move over another menu item which has a submenu - the (delayed) appearance of that
1052 // submenu steals the release. Oddly, if the mouse is moved further - even just once - within the new item,
1053 // the release event IS passed on. So to avoid dealing with that distinction, let's just pass on all release events.
1054 // Should be OK under normal usage, since it makes some sense that no mouse events should be reaching a submenu anyway -
1055 // they have no effect since the cursor position is outside of them !
1056 // NOTE: If a submenu OVERLAPS its higher-up menu, this could be a big problem. In general how to deal with overlapping popups
1057 // when we wish to be able to click on items in both a menu and its submenu. Overlapping will only happen with too-wide
1058 // menus which should be rare for routing, but we could also defer to the advanced router when the popup becomes too wide.
1059 case QEvent::MouseButtonRelease:
1060 case QEvent::MouseButtonPress:
1061 // Removed. Causes very high CPU usage spikes.
1062 // I think I remember adding MouseMove simply for 'good luck' rather than any real usefulness.
1063 // Tested OK /without/ this on KUBUNTU 15.10, we don't seem to need it. Retest on 14.04 LTS...
1064 // case QEvent::MouseMove:
1065 {
1066 QMouseEvent* mev = static_cast<QMouseEvent*>(event);
1067 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::event type:%d x:%d y:%d gx:%d gy:%d sx:%f sy:%f wx:%f wy:%f lx:%f ly:%f\n",
1068 mev->type(),
1069 mev->pos().x(), mev->pos().y(),
1070 mev->globalPos().x(), mev->globalPos().y(),
1071 mev->screenPos().x(), mev->screenPos().y(),
1072 mev->windowPos().x(), mev->windowPos().y(),
1073 mev->localPos().x(), mev->localPos().y());
1074
1075 QMenu* target_menu = nullptr;
1076 const int sz = QApplication::topLevelWidgets().size();
1077 for(int i = 0; i < sz; ++i)
1078 {
1079 QWidget* w = QApplication::topLevelWidgets().at(i);
1080 DEBUG_PRST_ROUTES(stderr, " checking top level widget:%p\n", w);
1081 if(QMenu* menu = qobject_cast<QMenu*>(w))
1082 {
1083 if(menu->windowType() != Qt::Popup)
1084 continue;
1085 DEBUG_PRST_ROUTES(stderr, " checking hit in menu:%p visible:%d modal:%d\n", menu, menu->isVisible(), menu->isModal());
1086 if(!menu->isVisible() || !menu->geometry().contains(mev->globalPos()))
1087 continue;
1088 DEBUG_PRST_ROUTES(stderr, " hit\n");
1089 // If we hit the submenu it means the submenu is partially or wholly obscuring the other menu.
1090 // We must honour the submenu in this case even if it is only slightly obscuring the other menu.
1091 if(menu == this)
1092 {
1093 DEBUG_PRST_ROUTES(stderr, " menu is this\n");
1094 return PopupMenu::event(mev);
1095 }
1096 // The menu is a good target - the mouse is within it and it is not obscured.
1097 // Regardless, afterward continue watching for THIS menu...
1098 if(!target_menu)
1099 target_menu = menu;
1100 }
1101 }
1102
1103 if(target_menu)
1104 {
1105 DEBUG_PRST_ROUTES(stderr, " target_menu:%p\n", target_menu);
1106 QMouseEvent new_mev(mev->type(),
1107 //mev->windowPos(), // Relative to the widget the mouse is actually over (menu variable).
1108 QPointF(target_menu->mapFromGlobal(mev->globalPos())),
1109 mev->screenPos(),
1110 mev->button(),
1111 mev->buttons(),
1112 mev->modifiers());
1113 new_mev.setAccepted(mev->isAccepted());
1114 new_mev.setTimestamp(mev->timestamp());
1115 QApplication::sendEvent(target_menu, &new_mev);
1116 return true;
1117 }
1118
1119 DEBUG_PRST_ROUTES(stderr, " no target popup found\n");
1120 }
1121 break;
1122
1123 case QEvent::KeyPress:
1124 {
1125 QKeyEvent* e = static_cast<QKeyEvent*>(event);
1126 switch(e->key())
1127 {
1128 case Qt::Key_Space:
1129 if (!style()->styleHint(QStyle::SH_Menu_SpaceActivatesItem, nullptr, this))
1130 break;
1131 // NOTE: Error suppressor for new gcc 7 'fallthrough' level 3 and 4:
1132 // FALLTHROUGH
1133 case Qt::Key_Select:
1134 case Qt::Key_Return:
1135 case Qt::Key_Enter:
1136 {
1137 if(activeAction() && (!contextMenu() || !contextMenu()->isVisible()))
1138 {
1139 if(RoutingMatrixWidgetAction* mwa = qobject_cast<RoutingMatrixWidgetAction*>(activeAction()))
1140 {
1141 bool accept = false;
1142 if(mwa->hasCheckBox() && mwa->isSelected())
1143 {
1144 mwa->setCheckBoxChecked(!mwa->checkBoxChecked());
1145 }
1146 else if(mwa->array()->columns() != 0 && mwa->array()->activeColumn() != -1)
1147 {
1148 mwa->array()->setValue(mwa->array()->activeColumn(), !mwa->array()->value(mwa->array()->activeColumn()));
1149 // Reset any other switch bars besides this one which are part of a QActionGroup.
1150 // Since they are all part of an action group, force them to be exclusive regardless of their exclusivity settings.
1151 QActionGroup* act_group = mwa->actionGroup();
1152 if(act_group && act_group->isExclusive())
1153 {
1154 const int sz = act_group->actions().size();
1155 for(int i = 0; i < sz; ++i)
1156 {
1157 if(RoutingMatrixWidgetAction* act = qobject_cast<RoutingMatrixWidgetAction*>(act_group->actions().at(i)))
1158 {
1159 if(act != mwa)
1160 {
1161 // Set any column to false, and exclusiveColumns and exclusiveToggle to true which will reset all columns.
1162 act->array()->setValues(0, false, true, true);
1163 //update(); // Redraw the indicators.
1164 act->updateCreatedWidgets(); // Redraw the indicators.
1165 }
1166 }
1167 }
1168 }
1169 if(mwa->arrayStayOpen())
1170 accept = true;
1171 }
1172 else
1173 {
1174 // Nothing selected. Do nothing. TODO: Select the first available item, like QMenu does...
1175 e->accept();
1176 return true; // We handled it.
1177 }
1178
1179 mwa->updateCreatedWidgets();
1180 e->accept();
1181 mwa->trigger(); // Trigger the action.
1182 // Check for Ctrl to stay open.
1183 if(!accept && (!stayOpen() || (!MusEGlobal::config.popupsDefaultStayOpen && (e->modifiers() & Qt::ControlModifier) == 0)))
1184 closeUp(); // Close all the popups.
1185 return true; // We handled it.
1186 }
1187 // Otherwise let ancestor PopupMenu handle it...
1188 }
1189 }
1190 break;
1191
1192 default:
1193 break;
1194 }
1195 }
1196 break;
1197
1198 default:
1199 break;
1200 }
1201
1202 return PopupMenu::event(event);
1203 }
1204
resizeEvent(QResizeEvent * e)1205 void RoutePopupMenu::resizeEvent(QResizeEvent* e)
1206 {
1207 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::resizeEvent\n");
1208 e->ignore();
1209 PopupMenu::resizeEvent(e);
1210 }
1211
mouseReleaseEvent(QMouseEvent * e)1212 void RoutePopupMenu::mouseReleaseEvent(QMouseEvent* e)
1213 {
1214 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::mouseReleaseEvent this:%p x:%d y:%d\n", this, e->pos().x(), e->pos().y());
1215 if(contextMenu() && contextMenu()->isVisible())
1216 return;
1217
1218 DEBUG_PRST_ROUTES_2(stderr, "RoutePopupMenu::mouseReleaseEvent begin: this:%p active action:%p\n", this, activeAction());
1219
1220 bool activate = false;
1221 bool accept = false;
1222
1223 QAction* action = actionAt(e->pos());
1224 RoutingMatrixWidgetAction* act_mwa = qobject_cast<RoutingMatrixWidgetAction*>(action);
1225
1226 // RoutingMatrixWidgetAction* mwa = 0;
1227 // QAction* action = actionAt(e->pos());
1228 // if(action)
1229 // {
1230 // mwa = qobject_cast<RoutingMatrixWidgetAction*>(action);
1231 // if(mwa)
1232 // {
1233 // RoutePopupHit hit = mwa->hitTest(e->pos(), RoutePopupHit::HitTestClick);
1234 // switch(hit._type)
1235 // {
1236 // case RoutePopupHit::HitChannel:
1237 // {
1238 // mwa->array()->setValue(hit._value, !mwa->array()->value(hit._value));
1239 //
1240 // // Reset any other switch bars besides this one which are part of a QActionGroup.
1241 // // Since they are all part of an action group, force them to be exclusive regardless of their exclusivity settings.
1242 // QActionGroup* act_group = mwa->actionGroup();
1243 // if(act_group && act_group->isExclusive())
1244 // {
1245 // const int sz = act_group->actions().size();
1246 // for(int i = 0; i < sz; ++i)
1247 // {
1248 // if(RoutingMatrixWidgetAction* act = qobject_cast<RoutingMatrixWidgetAction*>(act_group->actions().at(i)))
1249 // {
1250 // if(act != mwa)
1251 // {
1252 // // Set any column to false, and exclusiveColumns and exclusiveToggle to true which will reset all columns.
1253 // act->array()->setValues(0, false, true, true);
1254 // act->updateCreatedWidgets(); // Redraw the indicators.
1255 // }
1256 // }
1257 // }
1258 // }
1259 //
1260 // if(mwa->arrayStayOpen())
1261 // accept = true;
1262 // activate = true;
1263 // }
1264 // break;
1265 //
1266 // case RoutePopupHit::HitMenuItem:
1267 // mwa->setCheckBoxChecked(!mwa->checkBoxChecked());
1268 // activate = true;
1269 // break;
1270 //
1271 // case RoutePopupHit::HitChannelBar:
1272 // case RoutePopupHit::HitSpace:
1273 // accept = true;
1274 // break;
1275 //
1276 // case RoutePopupHit::HitNone:
1277 // break;
1278 // }
1279 // }
1280 // }
1281
1282 int ch_hit_clk_idx_min = -1;
1283 int ch_hit_clk_idx_max = -1;
1284 int ch_hit_clk_ch_start = -1;
1285 bool ch_hit_clk_val = false;
1286 QActionGroup* ch_hit_clk_act_group = nullptr;
1287
1288 const int sz = actions().size();
1289 for(int i = 0; i < sz; ++i)
1290 {
1291 if(RoutingMatrixWidgetAction* mwa = qobject_cast<RoutingMatrixWidgetAction*>(actions().at(i)))
1292 {
1293 bool do_upd = false;
1294 // Sanity check: Only activate the item(s) if the action truly is the active one.
1295 //if(mwa == activeAction())
1296 if(mwa == action)
1297 {
1298 RoutePopupHit hit = mwa->hitTest(e->pos(), RoutePopupHit::HitTestClick);
1299 switch(hit._type)
1300 {
1301 case RoutePopupHit::HitChannel:
1302 {
1303 // Support grouping together of channels.
1304 ch_hit_clk_idx_min = i;
1305 ch_hit_clk_idx_max = ch_hit_clk_idx_min + MusEGlobal::config.routerGroupingChannels;
1306 if(ch_hit_clk_idx_max > sz)
1307 ch_hit_clk_idx_min = sz - MusEGlobal::config.routerGroupingChannels;
1308 ch_hit_clk_ch_start = hit._value - (i - ch_hit_clk_idx_min);
1309 const int ch_diff = mwa->array()->columns() - (ch_hit_clk_ch_start + MusEGlobal::config.routerGroupingChannels);
1310 if(ch_diff < 0)
1311 {
1312 ch_hit_clk_idx_min += ch_diff;
1313 ch_hit_clk_idx_max += ch_diff;
1314 ch_hit_clk_ch_start += ch_diff;
1315 }
1316
1317 ch_hit_clk_act_group = mwa->actionGroup();
1318 ch_hit_clk_val = !mwa->array()->value(hit._value);
1319
1320 DEBUG_PRST_ROUTES_2(stderr, "RoutePopupMenu::mouseReleaseEvent i:%d hit._value:%d ch_hit_clk_idx_min:%d ch_hit_clk_idx_max:%d ch_hit_clk_ch_start:%d ch_hit_clk_val:%d\n",
1321 i, hit._value, ch_hit_clk_idx_min, ch_hit_clk_idx_max, ch_hit_clk_ch_start, ch_hit_clk_val);
1322
1323 if(mwa->array()->value(hit._value) != ch_hit_clk_val)
1324 {
1325 DEBUG_PRST_ROUTES_2(stderr, " calling mwa->array()->setValue\n");
1326 mwa->array()->setValue(hit._value, ch_hit_clk_val);
1327 do_upd = true;
1328 }
1329 if(mwa->setMenuItemPressed(false) || mwa->array()->setPressedColumn(-1))
1330 do_upd = true;
1331
1332 // // Reset any other switch bars besides this one which are part of a QActionGroup.
1333 // // Since they are all part of an action group, force them to be exclusive regardless of their exclusivity settings.
1334 // QActionGroup* act_group = mwa->actionGroup();
1335 // if(act_group && act_group->isExclusive())
1336 // {
1337 // const int sz = act_group->actions().size();
1338 // for(int i = 0; i < sz; ++i)
1339 // {
1340 // if(RoutingMatrixWidgetAction* act = qobject_cast<RoutingMatrixWidgetAction*>(act_group->actions().at(i)))
1341 // {
1342 // if(act != mwa)
1343 // {
1344 // // Set any column to false, and exclusiveColumns and exclusiveToggle to true which will reset all columns.
1345 // act->array()->setValues(0, false, true, true);
1346 // act->updateCreatedWidgets(); // Redraw the indicators.
1347 // }
1348 // }
1349 // }
1350 // }
1351
1352 if(mwa->arrayStayOpen())
1353 accept = true;
1354 activate = true;
1355
1356 // // Directly execute the trigger handler.
1357 // e->accept();
1358 // routePopupActivated(mwa);
1359 }
1360 break;
1361
1362 case RoutePopupHit::HitMenuItem:
1363 {
1364 const bool chk = !mwa->checkBoxChecked();
1365 if(mwa->checkBoxChecked() != chk)
1366 {
1367 mwa->setCheckBoxChecked(chk);
1368 do_upd = true;
1369 }
1370 activate = true;
1371
1372 // // Directly execute the trigger handler.
1373 // routePopupActivated(mwa);
1374 }
1375 break;
1376
1377 case RoutePopupHit::HitChannelBar:
1378 case RoutePopupHit::HitSpace:
1379 accept = true;
1380
1381 // e->accept();
1382 break;
1383
1384 case RoutePopupHit::HitNone:
1385 break;
1386 }
1387 }
1388 if(do_upd)
1389 mwa->updateCreatedWidgets();
1390 }
1391 }
1392
1393
1394
1395 for(int i = 0; i < sz; ++i)
1396 {
1397 if(RoutingMatrixWidgetAction* mwa = qobject_cast<RoutingMatrixWidgetAction*>(actions().at(i)))
1398 {
1399 bool do_upd = false;
1400 // Sanity check: Only activate the item(s) which are not the active one.
1401 //if(mwa != activeAction())
1402 if(mwa != action)
1403 {
1404 DEBUG_PRST_ROUTES_2(stderr, "RoutePopupMenu::mouseReleaseEvent i:%d this:%p inactive mwa:%p\n", i, this, mwa);
1405
1406 if(ch_hit_clk_act_group && ch_hit_clk_act_group == mwa->actionGroup())
1407 {
1408 DEBUG_PRST_ROUTES_2(stderr, " ch_hit_clk_act_group && ch_hit_clk_act_group == mwa->actionGroup()\n");
1409 if(ch_hit_clk_act_group->isExclusive())
1410 {
1411 // Reset any other switch bars besides this one which are part of a QActionGroup.
1412 // Since they are all part of an action group, force them to be exclusive regardless of their exclusivity settings.
1413 // Set any column to false, and exclusiveColumns and exclusiveToggle to true which will reset all columns.
1414 DEBUG_PRST_ROUTES_2(stderr, " calling mwa->array()->setValues (reset)\n");
1415 mwa->array()->setValues(0, false, true, true);
1416 do_upd = true;
1417 }
1418 else if(i >= ch_hit_clk_idx_min && i < ch_hit_clk_idx_max)
1419 {
1420 const int ch = ch_hit_clk_ch_start + (i - ch_hit_clk_idx_min);
1421 // mwa->array()->setValue(ch, !mwa->array()->value(ch));
1422 if(mwa->array()->value(ch) != ch_hit_clk_val)
1423 {
1424 DEBUG_PRST_ROUTES_2(stderr, " calling mwa->array()->setValue ch:%d\n", ch);
1425 mwa->array()->setValue(ch, ch_hit_clk_val);
1426 do_upd = true;
1427 }
1428
1429 // // Directly execute the trigger handler.
1430 // e->accept();
1431 // routePopupActivated(mwa);
1432
1433 DEBUG_PRST_ROUTES_2(stderr, " ch:%d active col:%d pressed col:%d\n",
1434 ch_hit_clk_ch_start + (i - ch_hit_clk_idx_min), mwa->array()->activeColumn(), mwa->array()->pressedColumn());
1435 }
1436 }
1437 // else
1438 if(mwa->setMenuItemPressed(false) || mwa->array()->setPressedColumn(-1))
1439 do_upd = true;
1440
1441 }
1442 if(do_upd)
1443 mwa->updateCreatedWidgets();
1444 }
1445 }
1446
1447 if(!action || !act_mwa)
1448 {
1449 e->ignore();
1450 // Defer to PopupMenu, where we handle regular actions with checkboxes.
1451 PopupMenu::mouseReleaseEvent(e);
1452 DEBUG_PRST_ROUTES_2(stderr, "RoutePopupMenu::mouseReleaseEvent defer end: this:%p active action:%p\n", this, activeAction());
1453 return;
1454 }
1455
1456 if(accept)
1457 {
1458 DEBUG_PRST_ROUTES_2(stderr, "RoutePopupMenu::mouseReleaseEvent accept\n");
1459 e->accept();
1460 if(activate)
1461 {
1462 DEBUG_PRST_ROUTES_2(stderr, "RoutePopupMenu::mouseReleaseEvent accept: directly executing trigger handler routePopupActivated(act_mwa)\n");
1463 // Directly execute the trigger handler.
1464 routePopupActivated(act_mwa);
1465 }
1466 DEBUG_PRST_ROUTES_2(stderr, "RoutePopupMenu::mouseReleaseEvent accept end: this:%p active action:%p\n", this, activeAction());
1467 return;
1468 }
1469
1470 // Check for Ctrl to stay open.
1471 if(!stayOpen() || (!MusEGlobal::config.popupsDefaultStayOpen && (e->modifiers() & Qt::ControlModifier) == 0))
1472 {
1473 DEBUG_PRST_ROUTES_2(stderr, "RoutePopupMenu::mouseReleaseEvent No stay-open\n");
1474 e->ignore();
1475 // If this is the active popup widget let the ancestor activate and close it, otherwise we must close this manually.
1476 // // QMenu* m = qobject_cast<QMenu*>(QApplication::activePopupWidget());
1477 // // if(m == this)
1478 // // PopupMenu::mouseReleaseEvent(e);
1479 // // else
1480 // // {
1481
1482 if(activate)
1483 {
1484 DEBUG_PRST_ROUTES_2(stderr, " activate true: directly executing trigger handler routePopupActivated(act_mwa)\n");
1485 // Directly execute the trigger handler.
1486 routePopupActivated(act_mwa);
1487 }
1488
1489 // Close all the popups.
1490 closeUp();
1491 // // }
1492 return;
1493 }
1494
1495 e->accept();
1496 if(activate)
1497 {
1498 DEBUG_PRST_ROUTES_2(stderr, " activate true: directly executing trigger handler routePopupActivated(act_mwa)\n");
1499 routePopupActivated(act_mwa);
1500 }
1501 DEBUG_PRST_ROUTES_2(stderr, "RoutePopupMenu::mouseReleaseEvent end: this:%p active action:%p\n", this, activeAction());
1502 }
1503
mousePressEvent(QMouseEvent * e)1504 void RoutePopupMenu::mousePressEvent(QMouseEvent* e)
1505 {
1506 DEBUG_PRST_ROUTES_2(stderr, "RoutePopupMenu::mousePressEvent begin: this:%p active action:%p\n", this, activeAction());
1507 // e->ignore();
1508 // PopupMenu::mousePressEvent(e);
1509 // DEBUG_PRST_ROUTES_2(stderr, "RoutePopupMenu::mousePressEvent after begin: this:%p active action:%p\n", this, activeAction());
1510
1511 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::mousePressEvent this:%p x:%d y:%d\n", this, e->pos().x(), e->pos().y());
1512
1513 RoutingMatrixWidgetAction* act_mwa = qobject_cast<RoutingMatrixWidgetAction*>(actionAt(e->pos()));
1514
1515 bool accept = false;
1516
1517 int ch_hit_clk_idx_min = -1;
1518 int ch_hit_clk_idx_max = -1;
1519 int ch_hit_clk_ch_start = -1;
1520 QActionGroup* ch_hit_clk_act_group = nullptr;
1521
1522 const int sz = actions().size();
1523 for(int i = 0; i < sz; ++i)
1524 {
1525 if(RoutingMatrixWidgetAction* mwa = qobject_cast<RoutingMatrixWidgetAction*>(actions().at(i)))
1526 {
1527 bool do_upd = false;
1528 // Sanity check: Only activate the item(s) if the action truly is the active one.
1529 DEBUG_PRST_ROUTES_2(stderr, "RoutePopupMenu::mousePressEvent this:%p mwa:%p activeAction:%p\n", this, mwa, activeAction());
1530 //if(mwa == activeAction())
1531 if(mwa == act_mwa)
1532 {
1533 DEBUG_PRST_ROUTES_2(stderr, " is active\n");
1534 RoutePopupHit hit = mwa->hitTest(e->pos(), RoutePopupHit::HitTestClick);
1535 switch(hit._type)
1536 {
1537 case RoutePopupHit::HitChannel:
1538 {
1539 // Support grouping together of channels.
1540 ch_hit_clk_idx_min = i;
1541 ch_hit_clk_idx_max = ch_hit_clk_idx_min + MusEGlobal::config.routerGroupingChannels;
1542 if(ch_hit_clk_idx_max > sz)
1543 ch_hit_clk_idx_min = sz - MusEGlobal::config.routerGroupingChannels;
1544 ch_hit_clk_ch_start = hit._value - (i - ch_hit_clk_idx_min);
1545 const int ch_diff = mwa->array()->columns() - (ch_hit_clk_ch_start + MusEGlobal::config.routerGroupingChannels);
1546 if(ch_diff < 0)
1547 {
1548 ch_hit_clk_idx_min += ch_diff;
1549 ch_hit_clk_idx_max += ch_diff;
1550 ch_hit_clk_ch_start += ch_diff;
1551 }
1552 // Set the pressed value. If the column was already active, update again
1553 // so that pressed colour overrides highlighted colour.
1554 if(mwa->array()->setPressedColumn(hit._value) || mwa->array()->activeColumn() == hit._value)
1555 do_upd = true;
1556 ch_hit_clk_act_group = mwa->actionGroup();
1557 DEBUG_PRST_ROUTES_2(stderr, " HitChannel ch:%d active col:%d pressed col:%d\n",
1558 hit._value, mwa->array()->activeColumn(), mwa->array()->pressedColumn());
1559 accept = true;
1560 }
1561 break;
1562
1563 case RoutePopupHit::HitMenuItem:
1564 if(mwa->setMenuItemPressed(true))
1565 do_upd = true;
1566 accept = true;
1567 break;
1568
1569 case RoutePopupHit::HitChannelBar:
1570 case RoutePopupHit::HitSpace:
1571 if(mwa->setMenuItemPressed(false) || mwa->array()->setPressedColumn(-1))
1572 do_upd = true;
1573 accept = true;
1574 break;
1575
1576 case RoutePopupHit::HitNone:
1577 if(mwa->setMenuItemPressed(false) || mwa->array()->setPressedColumn(-1))
1578 do_upd = true; // TODO Close the menu instead of letting QMenu do it (below)?
1579 break;
1580 }
1581 }
1582 if(do_upd)
1583 mwa->updateCreatedWidgets();
1584 }
1585 }
1586
1587 for(int i = 0; i < sz; ++i)
1588 {
1589 if(RoutingMatrixWidgetAction* mwa = qobject_cast<RoutingMatrixWidgetAction*>(actions().at(i)))
1590 {
1591 bool do_upd = false;
1592 // Sanity check: Only activate the item(s) which are not the active one.
1593 //if(mwa != activeAction())
1594 if(mwa != act_mwa)
1595 {
1596 DEBUG_PRST_ROUTES_2(stderr, "RoutePopupMenu::mousePressEvent this:%p inactive mwa:%p\n", this, mwa);
1597 if(ch_hit_clk_act_group &&
1598 !ch_hit_clk_act_group->isExclusive() &&
1599 ch_hit_clk_act_group == mwa->actionGroup() &&
1600 i >= ch_hit_clk_idx_min &&
1601 i < ch_hit_clk_idx_max)
1602 {
1603 if(mwa->array()->setPressedColumn(ch_hit_clk_ch_start + (i - ch_hit_clk_idx_min)))
1604 do_upd = true;
1605 DEBUG_PRST_ROUTES_2(stderr, " ch:%d active col:%d pressed col:%d\n",
1606 ch_hit_clk_ch_start + (i - ch_hit_clk_idx_min), mwa->array()->activeColumn(), mwa->array()->pressedColumn());
1607 }
1608 else if(mwa->array()->setPressedColumn(-1))
1609 do_upd = true;
1610 }
1611 if(do_upd)
1612 mwa->updateCreatedWidgets();
1613 }
1614 }
1615
1616
1617 if(accept)
1618 {
1619 // // e->accept();
1620 // // PopupMenu::mousePressEvent(e);
1621 // // return;
1622 }
1623
1624 DEBUG_PRST_ROUTES_2(stderr, "RoutePopupMenu::mousePressEvent before end: this:%p active action:%p\n", this, activeAction());
1625 e->ignore();
1626 PopupMenu::mousePressEvent(e);
1627 DEBUG_PRST_ROUTES_2(stderr, "RoutePopupMenu::mousePressEvent end: this:%p active action:%p\n", this, activeAction());
1628 }
1629
mouseMoveEvent(QMouseEvent * e)1630 void RoutePopupMenu::mouseMoveEvent(QMouseEvent* e)
1631 {
1632 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::mouseMoveEvent this:%p\n", this);
1633
1634 RoutingMatrixWidgetAction* act_mwa = qobject_cast<RoutingMatrixWidgetAction*>(actionAt(e->pos()));
1635
1636 // Inform the hover handler that it was a mouse hover.
1637 _hoverIsFromMouse = true;
1638 // Ignore the event and pass it on. Let any new active action and hover signal be generated before the code below is run.
1639 e->ignore();
1640 PopupMenu::mouseMoveEvent(e);
1641 // Clear the flag.
1642 _hoverIsFromMouse = false;
1643
1644 int ch_hit_hvr_idx_min = -1;
1645 int ch_hit_hvr_idx_max = -1;
1646 int ch_hit_hvr_ch_start = -1;
1647 QActionGroup* ch_hit_hvr_act_group = nullptr;
1648
1649 int ch_hit_clk_idx_min = -1;
1650 int ch_hit_clk_idx_max = -1;
1651 int ch_hit_clk_ch_start = -1;
1652 QActionGroup* ch_hit_clk_act_group = nullptr;
1653
1654 const int sz = actions().size();
1655 for(int i = 0; i < sz; ++i)
1656 {
1657 if(RoutingMatrixWidgetAction* mwa = qobject_cast<RoutingMatrixWidgetAction*>(actions().at(i)))
1658 {
1659 bool do_upd = false;
1660 // Sanity check: Only activate the item(s) if the action truly is the active one.
1661 //if(mwa == activeAction())
1662 if(mwa == act_mwa)
1663 {
1664 RoutePopupHit hit = mwa->hitTest(e->pos(), RoutePopupHit::HitTestHover);
1665 switch(hit._type)
1666 {
1667 case RoutePopupHit::HitChannel:
1668 {
1669 // Update the current 'last' hover info.
1670 _lastHoveredHit = hit;
1671 // Support grouping together of channels.
1672 ch_hit_hvr_idx_min = i;
1673 ch_hit_hvr_idx_max = ch_hit_hvr_idx_min + MusEGlobal::config.routerGroupingChannels;
1674 if(ch_hit_hvr_idx_max > sz)
1675 ch_hit_hvr_idx_min = sz - MusEGlobal::config.routerGroupingChannels;
1676 ch_hit_hvr_ch_start = hit._value - (i - ch_hit_hvr_idx_min);
1677 const int ch_diff = mwa->array()->columns() - (ch_hit_hvr_ch_start + MusEGlobal::config.routerGroupingChannels);
1678 if(ch_diff < 0)
1679 {
1680 ch_hit_hvr_idx_min += ch_diff;
1681 ch_hit_hvr_idx_max += ch_diff;
1682 ch_hit_hvr_ch_start += ch_diff;
1683 }
1684 // Set the values.
1685 if(mwa->isSelected())
1686 {
1687 mwa->setSelected(false);
1688 do_upd = true;
1689 }
1690 if(mwa->array()->activeColumn() != hit._value)
1691 {
1692 DEBUG_PRST_ROUTES(stderr, " Setting active column:%d\n", hit._value);
1693 mwa->array()->setActiveColumn(hit._value);
1694 do_upd = true;
1695 }
1696 ch_hit_hvr_act_group = mwa->actionGroup();
1697 }
1698 break;
1699
1700 case RoutePopupHit::HitMenuItem:
1701 // Update the current 'last' hover info.
1702 _lastHoveredHit = hit;
1703 if(!mwa->isSelected())
1704 {
1705 mwa->setSelected(true);
1706 do_upd = true;
1707 }
1708 if(mwa->array()->activeColumn() != -1)
1709 {
1710 mwa->array()->setActiveColumn(-1);
1711 do_upd = true;
1712 }
1713 break;
1714
1715 case RoutePopupHit::HitChannelBar:
1716 case RoutePopupHit::HitSpace:
1717 case RoutePopupHit::HitNone:
1718 if(mwa->isSelected())
1719 {
1720 mwa->setSelected(false);
1721 do_upd = true;
1722 }
1723 if(mwa->array()->activeColumn() != -1)
1724 {
1725 mwa->array()->setActiveColumn(-1);
1726 do_upd = true;
1727 }
1728 break;
1729 }
1730 // }
1731 // else
1732 // {
1733 // if(mwa->isSelected())
1734 // {
1735 // mwa->setSelected(false);
1736 // do_upd = true;
1737 // }
1738 // if(mwa->array()->activeColumn() != -1)
1739 // {
1740 // mwa->array()->setActiveColumn(-1);
1741 // do_upd = true;
1742 // }
1743 // }
1744
1745 if(e->buttons() != Qt::NoButton)
1746 {
1747 RoutePopupHit hit = mwa->hitTest(e->pos(), RoutePopupHit::HitTestClick);
1748 switch(hit._type)
1749 {
1750 case RoutePopupHit::HitChannel:
1751 {
1752 // Support grouping together of channels.
1753 ch_hit_clk_idx_min = i;
1754 ch_hit_clk_idx_max = ch_hit_clk_idx_min + MusEGlobal::config.routerGroupingChannels;
1755 if(ch_hit_clk_idx_max > sz)
1756 ch_hit_clk_idx_min = sz - MusEGlobal::config.routerGroupingChannels;
1757 ch_hit_clk_ch_start = hit._value - (i - ch_hit_clk_idx_min);
1758 const int ch_diff = mwa->array()->columns() - (ch_hit_clk_ch_start + MusEGlobal::config.routerGroupingChannels);
1759 if(ch_diff < 0)
1760 {
1761 ch_hit_clk_idx_min += ch_diff;
1762 ch_hit_clk_idx_max += ch_diff;
1763 ch_hit_clk_ch_start += ch_diff;
1764 }
1765 // Set the value.
1766 if(mwa->array()->setPressedColumn(hit._value))
1767 do_upd = true;
1768 ch_hit_clk_act_group = mwa->actionGroup();
1769 }
1770 break;
1771
1772 case RoutePopupHit::HitMenuItem:
1773 if(mwa->setMenuItemPressed(true))
1774 do_upd = true;
1775 break;
1776
1777 case RoutePopupHit::HitChannelBar:
1778 case RoutePopupHit::HitSpace:
1779 case RoutePopupHit::HitNone:
1780 if(mwa->setMenuItemPressed(false) || mwa->array()->setPressedColumn(-1))
1781 do_upd = true;
1782 break;
1783 }
1784 }
1785 }
1786 if(do_upd)
1787 mwa->updateCreatedWidgets();
1788 }
1789 }
1790
1791
1792 for(int i = 0; i < sz; ++i)
1793 {
1794 if(RoutingMatrixWidgetAction* mwa = qobject_cast<RoutingMatrixWidgetAction*>(actions().at(i)))
1795 {
1796 bool do_upd = false;
1797 // Sanity check: Only activate the item(s) which are not the active one.
1798 //if(mwa != activeAction())
1799 if(mwa != act_mwa)
1800 {
1801 if(mwa->isSelected())
1802 {
1803 mwa->setSelected(false);
1804 do_upd = true;
1805 }
1806
1807 if(ch_hit_hvr_act_group &&
1808 !ch_hit_hvr_act_group->isExclusive() &&
1809 ch_hit_hvr_act_group == mwa->actionGroup() &&
1810 i >= ch_hit_hvr_idx_min && i < ch_hit_hvr_idx_max)
1811 {
1812 const int ch = ch_hit_hvr_ch_start + (i - ch_hit_hvr_idx_min);
1813 if(mwa->array()->activeColumn() != ch)
1814 {
1815 DEBUG_PRST_ROUTES(stderr, " Setting inactive column:%d\n", ch);
1816 mwa->array()->setActiveColumn(ch);
1817 do_upd = true;
1818 }
1819 }
1820 else if(mwa->array()->activeColumn() != -1)
1821 {
1822 mwa->array()->setActiveColumn(-1);
1823 do_upd = true;
1824 }
1825 // }
1826
1827 if(e->buttons() != Qt::NoButton &&
1828 ch_hit_clk_act_group &&
1829 !ch_hit_clk_act_group->isExclusive() &&
1830 ch_hit_clk_act_group == mwa->actionGroup() &&
1831 i >= ch_hit_clk_idx_min &&
1832 i < ch_hit_clk_idx_max)
1833 {
1834 if(mwa->array()->setPressedColumn(ch_hit_clk_ch_start + (i - ch_hit_clk_idx_min)))
1835 do_upd = true;
1836 }
1837 else if(mwa->array()->setPressedColumn(-1))
1838 do_upd = true;
1839 }
1840
1841 if(do_upd)
1842 mwa->updateCreatedWidgets();
1843 }
1844 }
1845
1846
1847 }
1848
routePopupHovered(QAction * action)1849 void RoutePopupMenu::routePopupHovered(QAction* action)
1850 {
1851 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::popHovered this:%p action:%p _hoverIsFromMouse:%d text:%s\n", this, action, _hoverIsFromMouse, action->text().toLatin1().constData());
1852
1853 // Ignore if this hover was from mouse.
1854 // Also, we get this hovered signal even if the hovered action is from another popup, so ignore it.
1855 if(!_hoverIsFromMouse && actions().contains(action))
1856 {
1857 const int sz = actions().size();
1858 for(int i = 0; i < sz; ++i)
1859 {
1860 if(RoutingMatrixWidgetAction* mwa = qobject_cast<RoutingMatrixWidgetAction*>(actions().at(i)))
1861 {
1862 bool do_upd = false;
1863 if(mwa == action)
1864 {
1865 switch(_lastHoveredHit._type)
1866 {
1867 case RoutePopupHit::HitChannel:
1868 {
1869 if(mwa->isSelected())
1870 {
1871 mwa->setSelected(false);
1872 do_upd = true;
1873 }
1874 const int cols = mwa->array()->columns();
1875 if(cols != 0)
1876 {
1877 int col = _lastHoveredHit._value; // The column.
1878 if(col >= cols)
1879 {
1880 col = cols - 1; // Clip it.
1881 _lastHoveredHit._value = col; // Adjust the current 'last' column setting.
1882 }
1883 if(mwa->array()->activeColumn() != col)
1884 {
1885 mwa->array()->setActiveColumn(col);
1886 do_upd = true;
1887 }
1888 }
1889 }
1890 break;
1891
1892 case RoutePopupHit::HitMenuItem:
1893 if(mwa->hasCheckBox() && !mwa->isSelected())
1894 {
1895 mwa->setSelected(true);
1896 do_upd = true;
1897 }
1898 if(mwa->array()->activeColumn() != -1)
1899 {
1900 mwa->array()->setActiveColumn(-1);
1901 do_upd = true;
1902 }
1903 break;
1904
1905 case RoutePopupHit::HitChannelBar:
1906 case RoutePopupHit::HitSpace:
1907 case RoutePopupHit::HitNone:
1908 // If it has a checkbox (or there is no channel bar) select the checkbox/text area.
1909 if(mwa->hasCheckBox() || mwa->array()->columns() == 0)
1910 {
1911 // Update the current 'last' hover info.
1912 _lastHoveredHit._type = RoutePopupHit::HitMenuItem;
1913 _lastHoveredHit._action = mwa;
1914 _lastHoveredHit._value = 0;
1915 if(!mwa->isSelected())
1916 {
1917 mwa->setSelected(true);
1918 do_upd = true;
1919 }
1920 }
1921 // Otherwise select the first available channel bar column.
1922 else
1923 {
1924 // Update the current 'last' hover info.
1925 _lastHoveredHit._type = RoutePopupHit::HitChannel;
1926 _lastHoveredHit._action = mwa;
1927 _lastHoveredHit._value = 0;
1928 if(mwa->array()->activeColumn() != 0)
1929 {
1930 mwa->array()->setActiveColumn(0);
1931 do_upd = true;
1932 }
1933 }
1934 break;
1935 }
1936 }
1937 else
1938 {
1939 if(mwa->isSelected())
1940 {
1941 mwa->setSelected(false); // Reset the checkbox/text active area.
1942 do_upd = true;
1943 }
1944 if(mwa->array()->activeColumn() != -1)
1945 {
1946 mwa->array()->setActiveColumn(-1); // Reset any active column.
1947 do_upd = true;
1948 }
1949 }
1950
1951 if(do_upd)
1952 mwa->updateCreatedWidgets();
1953 }
1954 }
1955 }
1956
1957 // Clear the flag.
1958 //_hoverIsFromMouse = false;
1959 }
1960
keyPressEvent(QKeyEvent * e)1961 void RoutePopupMenu::keyPressEvent(QKeyEvent* e)
1962 {
1963 if(activeAction())
1964 {
1965 if(RoutingMatrixWidgetAction* mwa = qobject_cast<RoutingMatrixWidgetAction*>(activeAction()))
1966 {
1967 bool do_upd = false;
1968 bool key_accepted = false;
1969 const int key = e->key();
1970 switch(key)
1971 {
1972 case Qt::Key_Left:
1973 {
1974 switch(_lastHoveredHit._type)
1975 {
1976 case RoutePopupHit::HitMenuItem:
1977 // Allow the menu to close.
1978 break;
1979
1980 case RoutePopupHit::HitChannel:
1981 // If we're on the first available channel and there's no checkbox, allow the menu to close.
1982 if(_lastHoveredHit._value == 0 && !mwa->hasCheckBox())
1983 break;
1984 // Fall through.
1985 case RoutePopupHit::HitChannelBar:
1986 case RoutePopupHit::HitSpace:
1987 case RoutePopupHit::HitNone:
1988 {
1989 // Get the next available item to the left.
1990 RoutePopupHit hit = mwa->previousHit(_lastHoveredHit);
1991 switch(hit._type)
1992 {
1993 case RoutePopupHit::HitChannel:
1994 {
1995 if(mwa->isSelected())
1996 {
1997 mwa->setSelected(false);
1998 do_upd = true;
1999 }
2000 if(mwa->array()->activeColumn() != hit._value)
2001 {
2002 mwa->array()->setActiveColumn(hit._value);
2003 do_upd = true;
2004 }
2005 // Update the current 'last' hover info.
2006 _lastHoveredHit = hit;
2007 key_accepted = true;
2008 }
2009 break;
2010
2011 case RoutePopupHit::HitMenuItem:
2012 if(!mwa->isSelected())
2013 {
2014 mwa->setSelected(true);
2015 do_upd = true;
2016 }
2017 if(mwa->array()->activeColumn() != -1)
2018 {
2019 mwa->array()->setActiveColumn(-1);
2020 do_upd = true;
2021 }
2022 // Update the current 'last' hover info.
2023 _lastHoveredHit = hit;
2024 key_accepted = true;
2025 break;
2026
2027 case RoutePopupHit::HitChannelBar:
2028 case RoutePopupHit::HitSpace:
2029 case RoutePopupHit::HitNone:
2030 if(mwa->isSelected())
2031 {
2032 mwa->setSelected(false);
2033 do_upd = true;
2034 }
2035 if(mwa->array()->activeColumn() != -1)
2036 {
2037 mwa->array()->setActiveColumn(-1);
2038 do_upd = true;
2039 }
2040 // Update the current 'last' hover info.
2041 _lastHoveredHit = hit;
2042 key_accepted = true;
2043 break;
2044 }
2045 }
2046 break;
2047 }
2048 }
2049 break;
2050
2051 case Qt::Key_Right:
2052 {
2053 switch(_lastHoveredHit._type)
2054 {
2055 case RoutePopupHit::HitChannel:
2056 // If we're on the last available channel, allow any submenu to open.
2057 if(mwa->array()->columns() != 0 && _lastHoveredHit._value == mwa->array()->columns() - 1)
2058 break;
2059 // Fall through.
2060 case RoutePopupHit::HitMenuItem:
2061 case RoutePopupHit::HitChannelBar:
2062 case RoutePopupHit::HitSpace:
2063 case RoutePopupHit::HitNone:
2064 {
2065 // Get the next available item to the right.
2066 RoutePopupHit hit = mwa->nextHit(_lastHoveredHit);
2067 switch(hit._type)
2068 {
2069 case RoutePopupHit::HitChannel:
2070 {
2071 if(mwa->isSelected())
2072 {
2073 mwa->setSelected(false);
2074 do_upd = true;
2075 }
2076 if(mwa->array()->activeColumn() != hit._value)
2077 {
2078 mwa->array()->setActiveColumn(hit._value);
2079 do_upd = true;
2080 }
2081 // Update the current 'last' hover info.
2082 _lastHoveredHit = hit;
2083 key_accepted = true;
2084 }
2085 break;
2086
2087 case RoutePopupHit::HitMenuItem:
2088 if(!mwa->isSelected())
2089 {
2090 mwa->setSelected(true);
2091 do_upd = true;
2092 }
2093 if(mwa->array()->activeColumn() != -1)
2094 {
2095 mwa->array()->setActiveColumn(-1);
2096 do_upd = true;
2097 }
2098 // Update the current 'last' hover info.
2099 _lastHoveredHit = hit;
2100 key_accepted = true;
2101 break;
2102
2103 case RoutePopupHit::HitChannelBar:
2104 case RoutePopupHit::HitSpace:
2105 case RoutePopupHit::HitNone:
2106 if(mwa->isSelected())
2107 {
2108 mwa->setSelected(false);
2109 do_upd = true;
2110 }
2111 if(mwa->array()->activeColumn() != -1)
2112 {
2113 mwa->array()->setActiveColumn(-1);
2114 do_upd = true;
2115 }
2116 // Update the current 'last' hover info.
2117 _lastHoveredHit = hit;
2118 key_accepted = true;
2119 break;
2120 }
2121 }
2122 }
2123 }
2124 break;
2125
2126 default:
2127 break;
2128 }
2129
2130 if(do_upd)
2131 mwa->updateCreatedWidgets();
2132 if(key_accepted)
2133 {
2134 e->accept();
2135 return;
2136 }
2137 }
2138 }
2139
2140 e->ignore();
2141 PopupMenu::keyPressEvent(e);
2142 }
2143
songChanged(MusECore::SongChangedStruct_t val)2144 void RoutePopupMenu::songChanged(MusECore::SongChangedStruct_t val)
2145 {
2146 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::songChanged flags:%ld", (long int)val._flags);
2147 if(val & (SC_ROUTE | SC_CHANNELS | SC_CONFIG))
2148 updateRouteMenus();
2149 if(val & SC_PORT_ALIAS_PREFERENCE)
2150 preferredPortAliasChanged();
2151 if(val & SC_ROUTER_CHANNEL_GROUPING)
2152 routerChannelGroupingChanged();
2153 }
2154
updateItemTexts(PopupMenu * menu)2155 bool RoutePopupMenu::updateItemTexts(PopupMenu* menu)
2156 {
2157 if(!menu)
2158 menu = this;
2159 QList<QAction*> list = menu->actions();
2160 const int sz = list.size();
2161 bool changed = false;
2162 for(int i = 0; i < sz; ++i)
2163 {
2164 QAction* act = list.at(i);
2165 if(RoutingMatrixWidgetAction* wa = qobject_cast<RoutingMatrixWidgetAction*>(act))
2166 {
2167 // Take care of struct Route first. Insert other future custom structures here too !
2168 if(act->data().canConvert<MusECore::Route>())
2169 {
2170 const MusECore::Route r = act->data().value<MusECore::Route>();
2171 switch(r.type)
2172 {
2173 case MusECore::Route::JACK_ROUTE:
2174 {
2175 if(MusEGlobal::checkAudioDevice())
2176 {
2177 char good_name[ROUTE_PERSISTENT_NAME_SIZE];
2178 const char* port_name = r.persistentJackPortName;
2179 void* const port = MusEGlobal::audioDevice->findPort(port_name);
2180 if(port)
2181 {
2182 MusEGlobal::audioDevice->portName(port, good_name, ROUTE_PERSISTENT_NAME_SIZE, MusEGlobal::config.preferredRouteNameOrAlias);
2183 const QString str(good_name);
2184 if(wa->actionText() != str)
2185 {
2186 wa->setActionText(str);
2187 changed = true;
2188 }
2189 }
2190 if(changed)
2191 {
2192 // wa->updateChannelArray();
2193 }
2194 }
2195 }
2196 break;
2197
2198 case MusECore::Route::TRACK_ROUTE:
2199 case MusECore::Route::MIDI_DEVICE_ROUTE:
2200 case MusECore::Route::MIDI_PORT_ROUTE:
2201 break;
2202 }
2203 }
2204
2205
2206 }
2207 else
2208 // Take care of struct Route first. Insert other future custom structures here too !
2209 if(act->data().canConvert<MusECore::Route>())
2210 {
2211 const MusECore::Route r = act->data().value<MusECore::Route>();
2212 switch(r.type)
2213 {
2214 case MusECore::Route::JACK_ROUTE:
2215 act->setText(r.displayName(MusEGlobal::config.preferredRouteNameOrAlias));
2216 break;
2217
2218 case MusECore::Route::TRACK_ROUTE:
2219 case MusECore::Route::MIDI_DEVICE_ROUTE:
2220 case MusECore::Route::MIDI_PORT_ROUTE:
2221 break;
2222 }
2223 }
2224 }
2225
2226 return changed;
2227 }
2228
2229 // Updates item texts and the 'preferred alias action'. Returns true if any action was changed.
preferredPortAliasChanged()2230 bool RoutePopupMenu::preferredPortAliasChanged()
2231 {
2232 QList<QAction*> list = actions();
2233 const int sz = list.size();
2234 bool changed = false;
2235 for(int i = 0; i < sz; ++i)
2236 {
2237 QAction* act = list.at(i);
2238 // Check for custom widget action.
2239 if(RoutingMatrixWidgetAction* wa = qobject_cast<RoutingMatrixWidgetAction*>(act))
2240 {
2241 // Check for Route data type.
2242 // Take care of struct Route first. Add other future custom structures here too.
2243 if(act->data().canConvert<MusECore::Route>())
2244 {
2245 const MusECore::Route r = act->data().value<MusECore::Route>();
2246 switch(r.type)
2247 {
2248 case MusECore::Route::JACK_ROUTE:
2249 {
2250 if(MusEGlobal::checkAudioDevice())
2251 {
2252 char good_name[ROUTE_PERSISTENT_NAME_SIZE];
2253 const char* port_name = r.persistentJackPortName;
2254 void* const port = MusEGlobal::audioDevice->findPort(port_name);
2255 if(port)
2256 {
2257 MusEGlobal::audioDevice->portName(port, good_name, ROUTE_PERSISTENT_NAME_SIZE, MusEGlobal::config.preferredRouteNameOrAlias);
2258 const QString str(good_name);
2259 if(wa->actionText() != str)
2260 {
2261 wa->setActionText(str);
2262 changed = true;
2263 }
2264 }
2265 }
2266 }
2267 break;
2268
2269 case MusECore::Route::TRACK_ROUTE:
2270 case MusECore::Route::MIDI_DEVICE_ROUTE:
2271 case MusECore::Route::MIDI_PORT_ROUTE:
2272 break;
2273 }
2274 }
2275 // No Route data type. Check for int data IDs.
2276 // Handle future data types above, before this in case those types might be convertible to int.
2277 else
2278 {
2279 bool ok = false;
2280 const int n = act->data().toInt(&ok);
2281 if(ok)
2282 {
2283 switch(n)
2284 {
2285 #ifdef _USE_CUSTOM_WIDGET_ACTIONS_
2286 // Check for the 'preferred port alias' action.
2287 case _ALIASES_WIDGET_ACTION_:
2288 {
2289 int v;
2290 if(wa->array()->value(0))
2291 v = MusEGlobal::RoutePreferFirstAlias;
2292 else if(wa->array()->value(1))
2293 v = MusEGlobal::RoutePreferSecondAlias;
2294 else
2295 v = MusEGlobal::RoutePreferCanonicalName;
2296
2297 if(v != MusEGlobal::config.preferredRouteNameOrAlias)
2298 {
2299 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::preferredPortAliasChanged setting alias array preferredRouteNameOrAlias:%d\n",
2300 MusEGlobal::config.preferredRouteNameOrAlias);
2301 switch(MusEGlobal::config.preferredRouteNameOrAlias)
2302 {
2303 case MusEGlobal::RoutePreferFirstAlias:
2304 wa->array()->setValue(0, true);
2305 break;
2306 case MusEGlobal::RoutePreferSecondAlias:
2307 wa->array()->setValue(1, true);
2308 break;
2309 case MusEGlobal::RoutePreferCanonicalName:
2310 // Just set any column to false to clear this exclusive array.
2311 wa->array()->setValue(0, false);
2312 break;
2313 }
2314 changed = true;
2315 }
2316 }
2317 break;
2318 #endif
2319
2320 default:
2321 break;
2322 }
2323 }
2324 }
2325 }
2326 // Not a custom widget action. Check for Route data type.
2327 // Take care of struct Route first. Add other future custom structures here too.
2328 else if(act->data().canConvert<MusECore::Route>())
2329 {
2330 const MusECore::Route r = act->data().value<MusECore::Route>();
2331 switch(r.type)
2332 {
2333 case MusECore::Route::JACK_ROUTE:
2334 {
2335 const QString rname = r.displayName(MusEGlobal::config.preferredRouteNameOrAlias);
2336 if(act->text() != rname)
2337 {
2338 act->setText(rname);
2339 changed = true;
2340 }
2341 }
2342 break;
2343
2344 case MusECore::Route::TRACK_ROUTE:
2345 case MusECore::Route::MIDI_DEVICE_ROUTE:
2346 case MusECore::Route::MIDI_PORT_ROUTE:
2347 break;
2348 }
2349 }
2350 }
2351
2352 return changed;
2353 }
2354
routerChannelGroupingChanged()2355 bool RoutePopupMenu::routerChannelGroupingChanged()
2356 {
2357 QList<QAction*> list = actions();
2358 const int sz = list.size();
2359 bool changed = false;
2360 for(int i = 0; i < sz; ++i)
2361 {
2362 QAction* act = list.at(i);
2363 // Check for custom widget action.
2364 if(RoutingMatrixWidgetAction* wa = qobject_cast<RoutingMatrixWidgetAction*>(act))
2365 {
2366 // Check for Route data type.
2367 // Take care of struct Route first. Add other future custom structures here too.
2368 if(act->data().canConvert<MusECore::Route>())
2369 {
2370 // Nothing to do here yet.
2371 }
2372 // No Route data type. Check for int data IDs.
2373 // Handle future data types above, before this in case those types might be convertible to int.
2374 else
2375 {
2376 bool ok = false;
2377 const int n = act->data().toInt(&ok);
2378 if(ok)
2379 {
2380 switch(n)
2381 {
2382 #ifdef _USE_CUSTOM_WIDGET_ACTIONS_
2383 // Check for the 'grouping channels' action.
2384 case _GROUPING_CHANNELS_WIDGET_ACTION_:
2385 {
2386 int v;
2387 if(wa->array()->value(0))
2388 v = 1;
2389 else
2390 v = 2;
2391
2392 if(v != MusEGlobal::config.routerGroupingChannels)
2393 {
2394 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::routerChannelGroupingChanged setting array routerGroupingChannels:%d\n",
2395 MusEGlobal::config.routerGroupingChannels);
2396 switch(MusEGlobal::config.routerGroupingChannels)
2397 {
2398 case 1:
2399 wa->array()->setValue(0, true);
2400 changed = true;
2401 break;
2402 case 2:
2403 wa->array()->setValue(1, true);
2404 changed = true;
2405 break;
2406 default:
2407 break;
2408 }
2409 }
2410 }
2411 break;
2412 #endif
2413
2414 default:
2415 break;
2416 }
2417 }
2418 }
2419 }
2420 // Not a custom widget action. Check for Route data type.
2421 // Take care of struct Route first. Add other future custom structures here too.
2422 else if(act->data().canConvert<MusECore::Route>())
2423 {
2424 // Nothing to do here yet.
2425 }
2426 }
2427 return changed;
2428 }
2429
cloneMenu(const QString & title,QWidget * parent,bool,bool showTooltips)2430 PopupMenu* RoutePopupMenu::cloneMenu(const QString& title, QWidget* parent, bool /*stayOpen*/, bool showTooltips)
2431 {
2432 PopupMenu* m = new RoutePopupMenu(_route, title, parent, _isOutMenu, _broadcastChanges);
2433 m->setToolTipsVisible(showTooltips);
2434 return m;
2435 }
2436
updateRouteMenus()2437 void RoutePopupMenu::updateRouteMenus()
2438 {
2439 // NOTE: The purpose of this routine is to make sure the items actually reflect
2440 // the routing status.
2441 // In case for some reason a route could not be added (or removed).
2442 // Then the item will be properly un-checked (or checked) here.
2443
2444 // TODO Fix this up a bit. It doesn't quite respond to complete removal, and other situations are a bit odd.
2445 // Best to ignore it for now since it only half-works. p4.0.42
2446
2447 /*
2448 //printf("RoutePopupMenu::updateRouteMenus\n");
2449
2450 if(!_track || actions().isEmpty() || !isVisible())
2451 return;
2452
2453 MusECore::RouteList* rl = _isOutMenu ? _track->outRoutes() : _track->inRoutes();
2454
2455 // Clear all the action check marks.
2456 clearAllChecks();
2457
2458 // Take care of Midi Port to Audio Input routes first...
2459 if(_isOutMenu && _track->isMidiTrack())
2460 {
2461 int port = ((MusECore::MidiTrack*)_track)->outPort();
2462 if(port >= 0 && port < MIDI_PORTS)
2463 {
2464 MusECore::MidiPort* mp = &MusEGlobal::midiPorts[port];
2465 MusECore::RouteList* mprl = mp->outRoutes();
2466 for (MusECore::ciRoute ir = mprl->begin(); ir != mprl->end(); ++ir)
2467 {
2468 if(ir->type == MusECore::Route::TRACK_ROUTE && ir->track && ir->track->type() == MusECore::Track::AUDIO_INPUT)
2469 {
2470 for(int ch = 0; ch < MusECore::MUSE_MIDI_CHANNELS; ++ch)
2471 {
2472 int chbits = 1 << ch;
2473 if(ir->channel & chbits)
2474 {
2475 MusECore::Route r(ir->track, chbits);
2476 //printf("RoutePopupMenu::updateRouteMenus MusECore::MidiPort to AudioInput chbits:%d\n", chbits); //
2477 QAction* act = findActionFromData(QVariant::fromValue(r));
2478 if(act)
2479 {
2480 //printf(" ... Found\n"); //
2481 act->setChecked(true);
2482 }
2483 }
2484 }
2485 }
2486 }
2487 }
2488 }
2489
2490 // Now check the ones that are found in the route list.
2491 for(MusECore::ciRoute irl = rl->begin(); irl != rl->end(); ++irl)
2492 {
2493 // Do MidiTrack to MidiPort routes...
2494 if(irl->type == MusECore::Route::MIDI_PORT_ROUTE)
2495 {
2496
2497 #ifdef _USE_CUSTOM_WIDGET_ACTIONS_
2498
2499 // Widget action handles channels. Look for route with channels ignored and set to zero.
2500 MusECore::Route r(irl->midiPort, 0);
2501 QAction* act = findActionFromData(QVariant::fromValue(r));
2502 if(act)
2503 {
2504 //printf("RoutePopupMenu::updateRouteMenus found MidiTrack to MidiPort irl type:%d\n", irl->type); //
2505 // Check for custom widget actions first.
2506 PixmapButtonsWidgetAction* mc_wa = dynamic_cast<PixmapButtonsWidgetAction*>(act);
2507 if(mc_wa)
2508 {
2509 //printf(" ... Found custom, setting current state\n"); //
2510 mc_wa->setCurrentState(irl->channel); // Set all channels at once.
2511 }
2512 }
2513
2514 #else
2515 //printf("RoutePopupMenu::updateRouteMenus MIDI_PORT_ROUTE\n");
2516 for(int ch = 0; ch < MusECore::MUSE_MIDI_CHANNELS; ++ch)
2517 {
2518 int chbits = 1 << ch;
2519 if(irl->channel & chbits)
2520 {
2521 MusECore::Route r(irl->midiPort, chbits);
2522 //printf("RoutePopupMenu::updateRouteMenus MidiTrack to MidiPort irl type:%d\n", irl->type); //
2523 // If act is a PixmapButtonsWidgetAction, route channel is ignored and is zero.
2524 QAction* act = findActionFromData(QVariant::fromValue(r));
2525 if(act)
2526 {
2527 //printf(" ... Found\n"); //
2528 act->setChecked(true);
2529 }
2530 }
2531 }
2532 #endif // _USE_CUSTOM_WIDGET_ACTIONS_
2533
2534 }
2535 else
2536 // Do all other routes...
2537 {
2538
2539 #ifdef _USE_CUSTOM_WIDGET_ACTIONS_
2540
2541 // Do MidiPort to MidiTrack routes...
2542 if(irl->type == MusECore::Route::TRACK_ROUTE && irl->track && irl->track->type() == MusECore::Track::AUDIO_INPUT)
2543 {
2544 // Widget action handles channels. Look for route with channels ignored and set to zero.
2545 MusECore::Route r(irl->track, 0);
2546 QAction* act = findActionFromData(QVariant::fromValue(r));
2547 if(act)
2548 {
2549 // Check for custom widget actions first.
2550 PixmapButtonsWidgetAction* mc_wa = dynamic_cast<PixmapButtonsWidgetAction*>(act);
2551 if(mc_wa)
2552 {
2553 //printf("RoutePopupMenu::updateRouteMenus found custom irl type:%d\n", irl->type); //
2554 mc_wa->setCurrentState(irl->channel); // Set all channels at once.
2555 continue;
2556 }
2557 }
2558 }
2559
2560 #endif // _USE_CUSTOM_WIDGET_ACTIONS_
2561
2562 //printf("RoutePopupMenu::updateRouteMenus other irl type:%d\n", irl->type);
2563 if(act)
2564 {
2565 //printf("RoutePopupMenu::updateRouteMenus found other irl type:%d\n", irl->type); //
2566 act->setChecked(true);
2567 }
2568 }
2569 }
2570 */
2571 }
2572
trackRouteActivated(QAction * action,MusECore::Route & rem_route,MusECore::PendingOperationList & operations)2573 void RoutePopupMenu::trackRouteActivated(QAction* action, MusECore::Route& rem_route, MusECore::PendingOperationList& operations)
2574 {
2575 // Check for custom routing matrix action.
2576 RoutingMatrixWidgetAction* matrix_wa = qobject_cast<RoutingMatrixWidgetAction*>(action);
2577 if(!matrix_wa)
2578 return;
2579
2580 switch(rem_route.type)
2581 {
2582 case MusECore::Route::TRACK_ROUTE:
2583 {
2584 // Make sure the track still exists.
2585 if(MusEGlobal::song->tracks()->find(rem_route.track) == MusEGlobal::song->tracks()->end())
2586 return;
2587
2588 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::trackRouteActivated:\n");
2589
2590 MusECore::Track* track = _route.track;
2591
2592 MusECore::TrackList* tracks = MusEGlobal::song->tracks();
2593 for(MusECore::iTrack it = tracks->begin(); it != tracks->end(); ++it)
2594 {
2595 MusECore::Track* t = *it;
2596 // Track types must be same.
2597 if((track->isMidiTrack() && !t->isMidiTrack()) || (t->type() != track->type()))
2598 continue;
2599 // We are looking for the given track alone if unselected, or else all selected tracks.
2600 // Ignore other tracks if broadcasting changes is disabled.
2601 if(t != track && (!_broadcastChanges || !t->selected() || !track->selected()))
2602 continue;
2603
2604
2605 const int cols = matrix_wa->array()->columns();
2606 for(int col = 0; col < cols; ++col)
2607 {
2608 MusECore::Route this_route(t, col, 1);
2609 rem_route.channels = 1;
2610
2611 const MusECore::Route& src = _isOutMenu ? this_route : rem_route;
2612 const MusECore::Route& dst = _isOutMenu ? rem_route : this_route;
2613
2614 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::trackRouteActivated: checking operations\n");
2615 const bool val = matrix_wa->array()->value(col);
2616 // Connect if route does not exist. Allow it to reconnect a partial route.
2617 if(val && MusECore::routeCanConnect(src, dst))
2618 {
2619 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::trackRouteActivated: adding AddRoute operation\n");
2620 operations.add(MusECore::PendingOperationItem(src, dst, MusECore::PendingOperationItem::AddRoute));
2621 }
2622 // Disconnect if route exists. Allow it to reconnect a partial route.
2623 else if(!val && MusECore::routeCanDisconnect(src, dst))
2624 {
2625 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::trackRouteActivated: adding DeleteRoute operation\n");
2626 operations.add(MusECore::PendingOperationItem(src, dst, MusECore::PendingOperationItem::DeleteRoute));
2627 }
2628 }
2629 }
2630 }
2631 break;
2632
2633 case MusECore::Route::JACK_ROUTE:
2634 case MusECore::Route::MIDI_DEVICE_ROUTE:
2635 case MusECore::Route::MIDI_PORT_ROUTE:
2636 return;
2637 break;
2638 }
2639 }
2640
jackRouteActivated(QAction * action,const MusECore::Route & route,const MusECore::Route & rem_route,MusECore::PendingOperationList & operations)2641 void RoutePopupMenu::jackRouteActivated(QAction* action, const MusECore::Route& route, const MusECore::Route& rem_route, MusECore::PendingOperationList& operations)
2642 {
2643 // Check for custom routing matrix action.
2644 RoutingMatrixWidgetAction* matrix_wa = qobject_cast<RoutingMatrixWidgetAction*>(action);
2645 if(!matrix_wa)
2646 return;
2647
2648 if(!MusEGlobal::checkAudioDevice())
2649 return;
2650
2651 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::jackRouteActivated: Matrix\n");
2652
2653 const int cols = matrix_wa->array()->columns();
2654 const char* const port_name = rem_route.persistentJackPortName;
2655 void* const port = MusEGlobal::audioDevice->findPort(port_name);
2656 if(port)
2657 {
2658 for(int col = 0; col < cols; ++col)
2659 {
2660 MusECore::Route this_route(route);
2661 switch(route.type)
2662 {
2663 case MusECore::Route::MIDI_DEVICE_ROUTE:
2664 this_route.channel = -1;
2665 break;
2666
2667 case MusECore::Route::TRACK_ROUTE:
2668 {
2669 this_route.channel = col;
2670
2671 MusECore::Track* track = route.track;
2672 if(track)
2673 {
2674 MusECore::TrackList* tracks = MusEGlobal::song->tracks();
2675 for(MusECore::iTrack it = tracks->begin(); it != tracks->end(); ++it)
2676 {
2677 MusECore::Track* t = *it;
2678 // Track types must be same.
2679 if((track->isMidiTrack() && !t->isMidiTrack()) || (t->type() != track->type()))
2680 continue;
2681 // We are looking for the given track alone if unselected, or else all selected tracks.
2682 // Ignore other tracks if broadcasting changes is disabled.
2683 if(t != track && (!_broadcastChanges || !t->selected() || !track->selected()))
2684 continue;
2685
2686 this_route.track = t;
2687 // TODO Lazy identical copy of code below. Streamline somehow...
2688 const MusECore::Route r_route(port);
2689 const MusECore::Route& src = _isOutMenu ? this_route : r_route;
2690 const MusECore::Route& dst = _isOutMenu ? r_route : this_route;
2691 const bool val = matrix_wa->array()->value(col);
2692 DEBUG_PRST_ROUTES_2(stderr, "RoutePopupMenu::jackRouteActivated: checking operations col:%d val:%d\n", col, val);
2693 // Connect if route does not exist. Allow it to reconnect a partial route.
2694 if(val && MusECore::routeCanConnect(src, dst))
2695 {
2696 DEBUG_PRST_ROUTES_2(stderr, "RoutePopupMenu::jackRouteActivated: adding AddRoute operation\n");
2697 operations.add(MusECore::PendingOperationItem(src, dst, MusECore::PendingOperationItem::AddRoute));
2698 }
2699 // Disconnect if route exists. Allow it to reconnect a partial route.
2700 else if(!val && MusECore::routeCanDisconnect(src, dst))
2701 {
2702 DEBUG_PRST_ROUTES_2(stderr, "RoutePopupMenu::jackRouteActivated: adding DeleteRoute operation\n");
2703 operations.add(MusECore::PendingOperationItem(src, dst, MusECore::PendingOperationItem::DeleteRoute));
2704 }
2705
2706 }
2707 }
2708 // We took care of it. Continue with the next column.
2709 continue;
2710
2711 }
2712 break;
2713
2714 case MusECore::Route::MIDI_PORT_ROUTE:
2715 if(route.midiPort == -1)
2716 return;
2717 if(MusECore::MidiDevice* md = MusEGlobal::midiPorts[route.midiPort].device())
2718 {
2719 this_route.type = MusECore::Route::MIDI_DEVICE_ROUTE;
2720 this_route.device = md;
2721 this_route.channel = -1;
2722 }
2723 else
2724 return;
2725 break;
2726
2727 case MusECore::Route::JACK_ROUTE:
2728 break;
2729 }
2730
2731 const MusECore::Route r_route(port);
2732 const MusECore::Route& src = _isOutMenu ? this_route : r_route;
2733 const MusECore::Route& dst = _isOutMenu ? r_route : this_route;
2734
2735 const bool val = matrix_wa->array()->value(col);
2736 DEBUG_PRST_ROUTES_2(stderr, "RoutePopupMenu::jackRouteActivated: checking operations col:%d val:%d\n", col, val);
2737 // Connect if route does not exist. Allow it to reconnect a partial route.
2738 if(val && MusECore::routeCanConnect(src, dst))
2739 {
2740 DEBUG_PRST_ROUTES_2(stderr, "RoutePopupMenu::jackRouteActivated: adding AddRoute operation\n");
2741 operations.add(MusECore::PendingOperationItem(src, dst, MusECore::PendingOperationItem::AddRoute));
2742 }
2743 // Disconnect if route exists. Allow it to reconnect a partial route.
2744 else if(!val && MusECore::routeCanDisconnect(src, dst))
2745 {
2746 DEBUG_PRST_ROUTES_2(stderr, "RoutePopupMenu::jackRouteActivated: adding DeleteRoute operation\n");
2747 operations.add(MusECore::PendingOperationItem(src, dst, MusECore::PendingOperationItem::DeleteRoute));
2748 }
2749 }
2750 }
2751 }
2752
audioTrackPopupActivated(QAction * action,MusECore::Route & rem_route,MusECore::PendingOperationList & operations)2753 void RoutePopupMenu::audioTrackPopupActivated(QAction* action, MusECore::Route& rem_route, MusECore::PendingOperationList& operations)
2754 {
2755 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::audioTrackPopupActivated: action text:%s checked:%d name:%s\n",
2756 action->text().toLatin1().constData(), action->isChecked(),
2757 action->objectName().toLatin1().constData());
2758
2759 MusECore::Track* track = _route.track;
2760 // Check for custom routing matrix action.
2761 RoutingMatrixWidgetAction* matrix_wa = qobject_cast<RoutingMatrixWidgetAction*>(action);
2762 if(matrix_wa)
2763 {
2764 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::audioTrackPopupActivated: Matrix\n");
2765
2766 switch(rem_route.type)
2767 {
2768 case MusECore::Route::JACK_ROUTE:
2769 jackRouteActivated(action, _route, rem_route, operations);
2770 break;
2771
2772 case MusECore::Route::TRACK_ROUTE:
2773 trackRouteActivated(action, rem_route, operations);
2774 break;
2775
2776 case MusECore::Route::MIDI_DEVICE_ROUTE:
2777 break;
2778 case MusECore::Route::MIDI_PORT_ROUTE:
2779 break;
2780 }
2781 }
2782 #ifndef _USE_SIMPLIFIED_SOLO_CHAIN_
2783 // Support Midi Port to Audio Input routes.
2784 else if(!_isOutMenu && track->type() == MusECore::Track::AUDIO_INPUT && rem_route.type == MusECore::Route::MIDI_PORT_ROUTE)
2785 {
2786 MusECore::TrackList* tracks = MusEGlobal::song->tracks();
2787 for(MusECore::iTrack it = tracks->begin(); it != tracks->end(); ++it)
2788 {
2789 MusECore::Track* t = *it;
2790 // Track types must be same.
2791 if((track->isMidiTrack() && !t->isMidiTrack()) || (t->type() != track->type()))
2792 continue;
2793 // We are looking for the given track alone if unselected, or else all selected tracks.
2794 // Ignore other tracks if broadcasting changes is disabled.
2795 if(t != track && (!_broadcastChanges || !t->selected() || !track->selected()))
2796 continue;
2797
2798 MusECore::RouteList* rl = _isOutMenu ? t->outRoutes() : t->inRoutes();
2799 // Check for custom midi channel select action.
2800 PixmapButtonsWidgetAction* cs_wa = qobject_cast<PixmapButtonsWidgetAction*>(action);
2801 if(cs_wa)
2802 {
2803 const QBitArray ba = cs_wa->currentState();
2804 const int ba_sz = ba.size();
2805 int chbits = 0;
2806 for(int mch = 0; mch < MusECore::MUSE_MIDI_CHANNELS && mch < ba_sz; ++mch)
2807 {
2808 if(ba.at(mch))
2809 chbits |= (1 << mch);
2810 }
2811
2812 rem_route.channel = chbits; // Restore the desired route channels from the custom widget action state.
2813 int mdidx = rem_route.midiPort;
2814
2815 int chmask = 0;
2816 MusECore::ciRoute iir = rl->begin();
2817 for (; iir != rl->end(); ++iir)
2818 if(iir->type == MusECore::Route::MIDI_PORT_ROUTE && iir->midiPort == mdidx) { // Is there already a route to this port?
2819 chmask = iir->channel; // Grab the channel mask.
2820 break;
2821 }
2822
2823 // Only if something changed...
2824 if(chmask != chbits)
2825 {
2826 if(chmask != 0)
2827 {
2828 // Disconnect all existing channels.
2829 MusECore::Route dstRoute(t, chmask);
2830 operations.add(MusECore::PendingOperationItem(*iir, dstRoute, MusECore::PendingOperationItem::DeleteRoute));
2831 }
2832 if(chbits != 0)
2833 {
2834 // Connect desired channels.
2835 MusECore::Route dstRoute(t, chbits);
2836 operations.add(MusECore::PendingOperationItem(rem_route, dstRoute, MusECore::PendingOperationItem::AddRoute));
2837 }
2838 }
2839 }
2840 else
2841 {
2842 int chbit = rem_route.channel;
2843 MusECore::Route dstRoute(t, chbit);
2844 int mdidx = rem_route.midiPort;
2845 int chmask = 0;
2846 MusECore::ciRoute iir = rl->begin();
2847 for (; iir != rl->end(); ++iir)
2848 {
2849 if(iir->type == MusECore::Route::MIDI_PORT_ROUTE && iir->midiPort == mdidx) // Is there already a route to this port?
2850 {
2851 chmask = iir->channel; // Grab the channel mask.
2852 break;
2853 }
2854 }
2855
2856 if ((chmask & chbit) == chbit) // Is the channel's bit(s) set?
2857 operations.add(MusECore::PendingOperationItem(rem_route, dstRoute, MusECore::PendingOperationItem::DeleteRoute));
2858 else
2859 operations.add(MusECore::PendingOperationItem(rem_route, dstRoute, MusECore::PendingOperationItem::AddRoute));
2860 }
2861 }
2862 }
2863 #endif // _USE_SIMPLIFIED_SOLO_CHAIN_
2864 else
2865 {
2866 // Make sure the track still exists.
2867 if(MusEGlobal::song->tracks()->find(rem_route.track) == MusEGlobal::song->tracks()->end())
2868 return;
2869
2870 MusECore::TrackList* tracks = MusEGlobal::song->tracks();
2871 for(MusECore::iTrack it = tracks->begin(); it != tracks->end(); ++it)
2872 {
2873 MusECore::Track* t = *it;
2874 // Track types must be same.
2875 if((track->isMidiTrack() && !t->isMidiTrack()) || (t->type() != track->type()))
2876 continue;
2877 // We are looking for the given track alone if unselected, or else all selected tracks.
2878 // Ignore other tracks if broadcasting changes is disabled.
2879 if(t != track && (!_broadcastChanges || !t->selected() || !track->selected()))
2880 continue;
2881
2882
2883 MusECore::Route this_route(t, rem_route.channel, rem_route.channels);
2884 this_route.remoteChannel = rem_route.remoteChannel;
2885
2886 const MusECore::Route& src = _isOutMenu ? this_route : rem_route;
2887 const MusECore::Route& dst = _isOutMenu ? rem_route : this_route;
2888
2889 // Connect if route does not exist. Allow it to reconnect a partial route.
2890 if(action->isChecked() && MusECore::routeCanConnect(src, dst))
2891 {
2892 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::audioTrackPopupActivated: Route: adding AddRoute operation\n");
2893 operations.add(MusECore::PendingOperationItem(src, dst, MusECore::PendingOperationItem::AddRoute));
2894 }
2895 // Disconnect if route exists. Allow it to reconnect a partial route.
2896 else if(!action->isChecked() && MusECore::routeCanDisconnect(src, dst))
2897 {
2898 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::audioTrackPopupActivated: Route: adding DeleteRoute operation\n");
2899 operations.add(MusECore::PendingOperationItem(src, dst, MusECore::PendingOperationItem::DeleteRoute));
2900 }
2901 }
2902 }
2903 }
2904
midiTrackPopupActivated(QAction * action,MusECore::Route & rem_route,MusECore::PendingOperationList & operations)2905 void RoutePopupMenu::midiTrackPopupActivated(QAction* action, MusECore::Route& rem_route, MusECore::PendingOperationList& operations)
2906 {
2907 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::midiTrackPopupActivated: action text:%s checked:%d name:%s\n",
2908 action->text().toLatin1().constData(), action->isChecked(),
2909 action->objectName().toLatin1().constData());
2910
2911 MusECore::Track* track = _route.track;
2912 if(rem_route.type == MusECore::Route::TRACK_ROUTE && rem_route.track &&
2913 // Make sure the track still exists.
2914 MusEGlobal::song->tracks()->find(rem_route.track) != MusEGlobal::song->tracks()->end() &&
2915 rem_route.track->type() == MusECore::Track::AUDIO_INPUT)
2916 {
2917
2918 MusECore::MidiTrackList* tracks = MusEGlobal::song->midis();
2919 for(MusECore::iMidiTrack it = tracks->begin(); it != tracks->end(); ++it)
2920 {
2921 MusECore::MidiTrack* mt = *it;
2922 // We are looking for the given track alone if unselected, or else all selected tracks.
2923 // Ignore other tracks if broadcasting changes is disabled.
2924 if(mt != track && (!_broadcastChanges || !mt->selected() || !track->selected()))
2925 continue;
2926
2927 #ifdef _USE_SIMPLIFIED_SOLO_CHAIN_
2928 // Support Midi Track to Audio Input track soloing chain routes.
2929 // Support omni routes only, because if channels are supported, the graphical router becomes more complicated.
2930 if(_isOutMenu && rem_route.channel == -1)
2931 {
2932 const MusECore::Route this_route(mt, -1);
2933 operations.add(MusECore::PendingOperationItem(this_route, rem_route,
2934 action->isChecked() ?
2935 MusECore::PendingOperationItem::AddRoute :
2936 MusECore::PendingOperationItem::DeleteRoute));
2937 }
2938 #else
2939 // Support Midi Port to Audio Input track routes.
2940 int chbit = rem_route.channel;
2941 int port = mt->outPort();
2942 if(port < 0 || port >= MIDI_PORTS)
2943 return;
2944
2945 MusECore::MidiPort* mp = &MusEGlobal::midiPorts[port];
2946 MusECore::Route bRoute(port, chbit);
2947
2948 int chmask = 0;
2949 MusECore::RouteList* mprl = _isOutMenu ? mp->outRoutes() : mp->inRoutes();
2950 MusECore::ciRoute ir = mprl->begin();
2951 for (; ir != mprl->end(); ++ir)
2952 if(ir->type == MusECore::Route::TRACK_ROUTE && ir->track == rem_route.track) { // Is there already a route to this port?
2953 chmask = ir->channel; // Grab the channel mask.
2954 break;
2955 }
2956 if ((chmask & chbit) == chbit) // Is the channel's bit(s) set?
2957 {
2958 // disconnect
2959 if(_isOutMenu)
2960 operations.add(MusECore::PendingOperationItem(bRoute, rem_route, MusECore::PendingOperationItem::DeleteRoute));
2961 else
2962 operations.add(MusECore::PendingOperationItem(rem_route, bRoute, MusECore::PendingOperationItem::DeleteRoute));
2963 }
2964 else
2965 {
2966 // connect
2967 if(_isOutMenu)
2968 operations.add(MusECore::PendingOperationItem(bRoute, rem_route, MusECore::PendingOperationItem::AddRoute));
2969 else
2970 operations.add(MusECore::PendingOperationItem(rem_route, bRoute, MusECore::PendingOperationItem::AddRoute));
2971 }
2972 #endif
2973
2974 }
2975 }
2976 // Midi track to Midi Port routes.
2977 else if(rem_route.type == MusECore::Route::MIDI_PORT_ROUTE)
2978 {
2979 // Check for custom routing matrix action.
2980 RoutingMatrixWidgetAction* matrix_wa = qobject_cast<RoutingMatrixWidgetAction*>(action);
2981
2982 MusECore::MidiTrack::ChangedType_t changed = MusECore::MidiTrack::NothingChanged;
2983
2984 MusECore::MidiTrackList* tracks = MusEGlobal::song->midis();
2985 for(MusECore::iMidiTrack it = tracks->begin(); it != tracks->end(); ++it)
2986 {
2987 MusECore::MidiTrack* mt = *it;
2988 // We are looking for the given track alone if unselected, or else all selected tracks.
2989 // Ignore other tracks if broadcasting changes is disabled.
2990 if(mt != track && (!_broadcastChanges || !mt->selected() || !track->selected()))
2991 continue;
2992
2993 if(matrix_wa)
2994 {
2995 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::midiTrackPopupActivated midi port: matrix:\n");
2996
2997 #ifdef _USE_MIDI_ROUTE_PER_CHANNEL_
2998 const int cols = matrix_wa->array()->columns();
2999 switch(rem_route.type)
3000 {
3001 case MusECore::Route::MIDI_PORT_ROUTE:
3002 {
3003 if(rem_route.isValid() && rem_route.midiPort != -1)
3004 {
3005 // Do channel routes...
3006 for(int col = 0; col < cols && col < MusECore::MUSE_MIDI_CHANNELS; ++col)
3007 {
3008 const bool val = matrix_wa->array()->value(col);
3009
3010 #ifdef _USE_MIDI_TRACK_SINGLE_OUT_PORT_CHAN_
3011 // In this case the channel bar (and any other channel bars in a QActionGroup) will be in exclusive mode...
3012 if(_isOutMenu)
3013 {
3014 if(val)
3015 {
3016 const bool p_changed = rem_route.midiPort != mt->outPort();
3017 const bool c_changed = col != mt->outChannel();
3018 if(p_changed || c_changed)
3019 {
3020 // Avoid repeated idlings. And remember to un-idle outside of the loop!
3021 if(!MusEGlobal::audio->isIdle())
3022 MusEGlobal::audio->msgIdle(true);
3023
3024 if(p_changed && c_changed)
3025 changed |= mt->setOutPortAndChannelAndUpdate(rem_route.midiPort, col, false);
3026 else if(p_changed)
3027 changed |= mt->setOutPortAndUpdate(rem_route.midiPort, false);
3028 else if(c_changed)
3029 changed |= mt->setOutChanAndUpdate(col, false);
3030 }
3031 break;
3032 }
3033 continue;
3034 }
3035
3036 #endif
3037 MusECore::Route this_route(mt, col);
3038 rem_route.channel = col;
3039 const MusECore::Route& src = _isOutMenu ? this_route : rem_route;
3040 const MusECore::Route& dst = _isOutMenu ? rem_route : this_route;
3041 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::midiTrackPopupActivated: checking operations\n");
3042 // Connect if route does not exist. Allow it to reconnect a partial route.
3043 if(val && MusECore::routeCanConnect(src, dst))
3044 {
3045 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::midiTrackPopupActivated: adding AddRoute operation\n");
3046 operations.add(MusECore::PendingOperationItem(src, dst, MusECore::PendingOperationItem::AddRoute));
3047 }
3048 // Disconnect if route exists. Allow it to reconnect a partial route.
3049 else if(!val && MusECore::routeCanDisconnect(src, dst))
3050 {
3051 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::midiTrackPopupActivated: adding DeleteRoute operation\n");
3052 operations.add(MusECore::PendingOperationItem(src, dst, MusECore::PendingOperationItem::DeleteRoute));
3053 }
3054 }
3055 }
3056
3057 // Do Omni route...
3058 if(matrix_wa->hasCheckBox())
3059 {
3060 const bool cb_val = matrix_wa->checkBoxChecked();
3061 MusECore::Route this_route(mt);
3062 rem_route.channel = -1;
3063 const MusECore::Route& src = _isOutMenu ? this_route : rem_route;
3064 const MusECore::Route& dst = _isOutMenu ? rem_route : this_route;
3065 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::midiTrackPopupActivated: Omni checking operations\n");
3066 // Connect if route does not exist. Allow it to reconnect a partial route.
3067 if(cb_val && MusECore::routeCanConnect(src, dst))
3068 {
3069 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::midiTrackPopupActivated: Omni adding AddRoute operation\n");
3070 operations.add(MusECore::PendingOperationItem(src, dst, MusECore::PendingOperationItem::AddRoute));
3071 }
3072 // Disconnect if route exists. Allow it to reconnect a partial route.
3073 else if(!cb_val && MusECore::routeCanDisconnect(src, dst))
3074 {
3075 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::midiTrackPopupActivated: Omni adding DeleteRoute operation\n");
3076 operations.add(MusECore::PendingOperationItem(src, dst, MusECore::PendingOperationItem::DeleteRoute));
3077 }
3078 }
3079 }
3080 break;
3081
3082 case MusECore::Route::TRACK_ROUTE:
3083 case MusECore::Route::JACK_ROUTE:
3084 case MusECore::Route::MIDI_DEVICE_ROUTE:
3085 break;
3086 }
3087 #else
3088 MusECore::RouteList* rl = _isOutMenu ? mt->outRoutes() : mt->inRoutes();
3089 const int cols = matrix_wa->array()->columns();
3090 switch(rem_route.type)
3091 {
3092 case MusECore::Route::MIDI_PORT_ROUTE:
3093 {
3094 if(rem_route.isValid() && rem_route.midiPort != -1)
3095 {
3096 int chmask = 0;
3097 // Is there already a route?
3098 for(MusECore::ciRoute ir = rl->begin(); ir != rl->end(); ++ir)
3099 {
3100 switch(ir->type)
3101 {
3102 case MusECore::Route::MIDI_PORT_ROUTE:
3103 if(ir->midiPort == rem_route.midiPort)
3104 chmask = ir->channel; // Grab the channels.
3105 break;
3106 case MusECore::Route::TRACK_ROUTE:
3107 case MusECore::Route::JACK_ROUTE:
3108 case MusECore::Route::MIDI_DEVICE_ROUTE:
3109 break;
3110 }
3111 if(chmask != 0)
3112 break;
3113 }
3114
3115 int chbits = 0;
3116 for(int col = 0; col < cols; ++col)
3117 {
3118 if(matrix_wa->array()->value(col))
3119 chbits |= (1 << col);
3120 }
3121
3122 // Only if something changed...
3123 if(chmask != chbits)
3124 {
3125 if(chmask != 0)
3126 {
3127 MusECore::Route bRoute(mt, chmask);
3128 // Disconnect all existing channels.
3129 if(_isOutMenu)
3130 operations.add(MusECore::PendingOperationItem(bRoute, r, MusECore::PendingOperationItem::DeleteRoute));
3131 else
3132 operations.add(MusECore::PendingOperationItem(r, bRoute, MusECore::PendingOperationItem::DeleteRoute));
3133 }
3134 if(chbits != 0)
3135 {
3136 // Connect desired channels.
3137 MusECore::Route bRoute(mt, chbits);
3138 if(_isOutMenu)
3139 operations.add(MusECore::PendingOperationItem(bRoute, r, MusECore::PendingOperationItem::AddRoute));
3140 else
3141 operations.add(MusECore::PendingOperationItem(r, bRoute, MusECore::PendingOperationItem::AddRoute));
3142 }
3143 }
3144 }
3145 }
3146 break;
3147
3148 case MusECore::Route::TRACK_ROUTE:
3149 case MusECore::Route::JACK_ROUTE:
3150 case MusECore::Route::MIDI_DEVICE_ROUTE:
3151 break;
3152 }
3153 #endif
3154
3155 }
3156 else
3157 {
3158
3159 #ifdef _USE_MIDI_ROUTE_PER_CHANNEL_
3160
3161 MusECore::Route this_route(mt, rem_route.channel);
3162 const MusECore::Route& src = _isOutMenu ? this_route : rem_route;
3163 const MusECore::Route& dst = _isOutMenu ? rem_route : this_route;
3164 // Connect if route does not exist. Allow it to reconnect a partial route.
3165 if(action->isChecked() && MusECore::routeCanConnect(src, dst))
3166 {
3167 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::midiTrackPopupActivated: Route: adding AddRoute operation\n");
3168 operations.add(MusECore::PendingOperationItem(src, dst, MusECore::PendingOperationItem::AddRoute));
3169 }
3170 // Disconnect if route exists. Allow it to reconnect a partial route.
3171 else if(!action->isChecked() && MusECore::routeCanDisconnect(src, dst))
3172 {
3173 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::midiTrackPopupActivated: Route: adding DeleteRoute operation\n");
3174 operations.add(MusECore::PendingOperationItem(src, dst, MusECore::PendingOperationItem::DeleteRoute));
3175 }
3176
3177 #else
3178 int chbit = rem_route.channel;
3179 MusECore::Route bRoute(mt, chbit);
3180 int mdidx = rem_route.midiPort;
3181
3182 MusECore::MidiPort* mp = &MusEGlobal::midiPorts[mdidx];
3183 MusECore::MidiDevice* md = mp->device();
3184 //if(!md) // Rem. Allow connections to ports with no device.
3185 // return;
3186
3187 if(md && !(md->rwFlags() & (_isOutMenu ? 1 : 2)))
3188 return;
3189
3190 int chmask = 0;
3191 MusECore::ciRoute iir = rl->begin();
3192 for (; iir != rl->end(); ++iir)
3193 {
3194 if(iir->type == MusECore::Route::MIDI_PORT_ROUTE && iir->midiPort == mdidx) // Is there already a route to this port?
3195 {
3196 chmask = iir->channel; // Grab the channel mask.
3197 break;
3198 }
3199 }
3200 if ((chmask & chbit) == chbit) // Is the channel's bit(s) set?
3201 {
3202 // disconnect
3203 if(_isOutMenu)
3204 operations.add(MusECore::PendingOperationItem(bRoute, rem_route, MusECore::PendingOperationItem::DeleteRoute));
3205 else
3206 operations.add(MusECore::PendingOperationItem(rem_route, bRoute, MusECore::PendingOperationItem::DeleteRoute));
3207 }
3208 else
3209 {
3210 // connect
3211 if(_isOutMenu)
3212 operations.add(MusECore::PendingOperationItem(bRoute, rem_route, MusECore::PendingOperationItem::AddRoute));
3213 else
3214 operations.add(MusECore::PendingOperationItem(rem_route, bRoute, MusECore::PendingOperationItem::AddRoute));
3215 }
3216 #endif
3217
3218 }
3219 }
3220
3221 // If we are idling, we made some changes. Make sure to un-idle and update.
3222 if(MusEGlobal::audio->isIdle())
3223 {
3224 MusEGlobal::audio->msgIdle(false);
3225 MusEGlobal::audio->msgUpdateSoloStates();
3226 MusEGlobal::song->update(SC_ROUTE | ((changed & MusECore::MidiTrack::DrumMapChanged) ? SC_DRUMMAP : 0));
3227 }
3228 }
3229 // Midi device to Jack port routes - via Midi Track.
3230 // NOTE: When a submenu's action is activated, its top-most menu always gets the activation call.
3231 // Try this simple (ideal) method, otherwise it requires we abandon the '_route' member and store TWO
3232 // routes in EACH action: In this case one is the midi device route and the other is the jack route.
3233 else if(rem_route.type == MusECore::Route::JACK_ROUTE)
3234 {
3235 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::midiTrackPopupActivated rem_route is JACK_ROUTE\n");
3236 if(QAction* act = activeAction())
3237 {
3238 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::midiTrackPopupActivated active action:%p\n", act);
3239 if(act->data().canConvert<MusECore::Route>())
3240 {
3241 const MusECore::Route route = act->data().value<MusECore::Route>();
3242 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::midiTrackPopupActivated:Jack route: activePopupWidget:%p this:%p class:%s route type:%d\n",
3243 QApplication::activePopupWidget(), this, metaObject()->className(), route.type);
3244
3245 if(route.type == MusECore::Route::MIDI_PORT_ROUTE)
3246 {
3247 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::midiTrackPopupActivated Midi port to Jack route\n");
3248 jackRouteActivated(action, route, rem_route, operations);
3249 }
3250 }
3251 }
3252 }
3253 }
3254
trackPopupActivated(QAction * action,MusECore::Route & rem_route,MusECore::PendingOperationList & operations)3255 void RoutePopupMenu::trackPopupActivated(QAction* action, MusECore::Route& rem_route, MusECore::PendingOperationList& operations)
3256 {
3257 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::trackPopupActivated: action text:%s checked:%d name:%s\n",
3258 action->text().toLatin1().constData(), action->isChecked(),
3259 action->objectName().toLatin1().constData());
3260
3261 MusECore::Track* track = _route.track;
3262 // Make sure the track still exists.
3263 if(MusEGlobal::song->tracks()->find(track) == MusEGlobal::song->tracks()->end())
3264 return;
3265
3266 if(track->isMidiTrack())
3267 midiTrackPopupActivated(action, rem_route, operations);
3268 else
3269 audioTrackPopupActivated(action, rem_route, operations);
3270 }
3271
routePopupActivated(QAction * action)3272 void RoutePopupMenu::routePopupActivated(QAction* action)
3273 {
3274 if(!action || !_route.isValid() || actions().isEmpty())
3275 return;
3276
3277 MusECore::PendingOperationList operations;
3278
3279 // Handle any non-route items.
3280 if(!action->data().canConvert<MusECore::Route>())
3281 {
3282 bool ok = false;
3283 const int n = action->data().toInt(&ok);
3284 if(ok)
3285 {
3286 switch(n)
3287 {
3288 case _OPEN_MIDI_CONFIG_:
3289 MusEGlobal::muse->configMidiPorts();
3290 break;
3291
3292 #ifdef _USE_CUSTOM_WIDGET_ACTIONS_
3293 case _ALIASES_WIDGET_ACTION_:
3294 {
3295 // Check for custom widget action.
3296 RoutingMatrixWidgetAction* wa = qobject_cast<RoutingMatrixWidgetAction*>(action);
3297 if(wa)
3298 {
3299 if(wa->array()->value(0))
3300 MusEGlobal::config.preferredRouteNameOrAlias = MusEGlobal::RoutePreferFirstAlias;
3301 else if(wa->array()->value(1))
3302 MusEGlobal::config.preferredRouteNameOrAlias = MusEGlobal::RoutePreferSecondAlias;
3303 else
3304 MusEGlobal::config.preferredRouteNameOrAlias = MusEGlobal::RoutePreferCanonicalName;
3305
3306 MusEGlobal::song->update(SC_PORT_ALIAS_PREFERENCE);
3307 }
3308 }
3309 break;
3310
3311 case _GROUPING_CHANNELS_WIDGET_ACTION_:
3312 {
3313 // Check for custom widget action.
3314 RoutingMatrixWidgetAction* wa = qobject_cast<RoutingMatrixWidgetAction*>(action);
3315 if(wa)
3316 {
3317 if(wa->array()->value(0))
3318 MusEGlobal::config.routerGroupingChannels = 1;
3319 else
3320 MusEGlobal::config.routerGroupingChannels = 2;
3321
3322 MusEGlobal::song->update(SC_ROUTER_CHANNEL_GROUPING);
3323 }
3324 }
3325 break;
3326 #endif
3327
3328 case _SHOW_CANONICAL_NAMES_:
3329 {
3330 MusEGlobal::config.preferredRouteNameOrAlias = MusEGlobal::RoutePreferCanonicalName;
3331 MusEGlobal::song->update(SC_PORT_ALIAS_PREFERENCE);
3332 }
3333 break;
3334
3335 case _SHOW_FIRST_ALIASES_:
3336 {
3337 MusEGlobal::config.preferredRouteNameOrAlias = action->isChecked() ?
3338 MusEGlobal::RoutePreferFirstAlias : MusEGlobal::RoutePreferCanonicalName;
3339 MusEGlobal::song->update(SC_PORT_ALIAS_PREFERENCE);
3340 }
3341 break;
3342
3343 case _SHOW_SECOND_ALIASES_:
3344 {
3345 MusEGlobal::config.preferredRouteNameOrAlias = action->isChecked() ?
3346 MusEGlobal::RoutePreferSecondAlias : MusEGlobal::RoutePreferCanonicalName;
3347 MusEGlobal::song->update(SC_PORT_ALIAS_PREFERENCE);
3348 }
3349 break;
3350
3351 case _OPEN_ROUTING_DIALOG_:
3352 MusEGlobal::muse->startRouteDialog();
3353 break;
3354
3355 default:
3356 break;
3357 }
3358 }
3359
3360 return;
3361 }
3362
3363 DEBUG_PRST_ROUTES(stderr, "RoutePopupMenu::popupActivated: action data is a Route\n");
3364 // MusECore::Route rem_route = action->data().value<MusECore::Route>();
3365
3366 // Support grouping together of channels.
3367 int act_group_sz = 0;
3368 int act_idx = 0;
3369 int act_start = 0;
3370 // int act_count = 0;
3371 bool use_act_list = false;
3372 QList < QAction* > act_list;
3373 const QActionGroup* act_group = action->actionGroup();
3374 if(act_group && !act_group->isExclusive())
3375 {
3376 act_list = act_group->actions();
3377 act_idx = act_list.indexOf(action);
3378 if(act_idx != -1)
3379 {
3380 use_act_list = true;
3381 act_group_sz = act_list.size();
3382
3383 // Attempt to optimize by only doing the actions whose channels changed. Flawed. Just do the whole list.
3384 // act_start = act_idx;
3385 // if((act_start + MusEGlobal::config.routerGroupingChannels) > act_group_sz)
3386 // act_start = act_group_sz - MusEGlobal::config.routerGroupingChannels;
3387 // if(act_start < 0 )
3388 // act_start = 0;
3389 // act_count = MusEGlobal::config.routerGroupingChannels;
3390 // if((act_start + act_count) > act_group_sz)
3391 // act_count = act_group_sz - act_start;
3392
3393 // FIXME TODO: If an external event causes a connection 'behind our back' while the menu is open, we need to update the menu by
3394 // detecting the song changed SC_ROUTE flag. Otherwise when the menu checks all the actions against current connections, it finds
3395 // a connection and tries to turn it off because we've not updated the menu and that channel is 'off' in the menu right now.
3396 }
3397 }
3398
3399 while(1)
3400 {
3401 QAction* act = use_act_list ? act_list.at(act_start) : action;
3402 DEBUG_PRST_ROUTES_2(stderr, "RoutePopupMenu::popupActivated this:%p act:%p active action:%p act_group_sz:%d act_start:%d\n", // act_count:%d\n",
3403 this, act, activeAction(), act_group_sz, act_start); //, act_count);
3404 if(!act)
3405 break;
3406 MusECore::Route rem_route = act->data().value<MusECore::Route>();
3407 switch(_route.type)
3408 {
3409 case MusECore::Route::TRACK_ROUTE:
3410 trackPopupActivated(act, rem_route, operations);
3411 break;
3412
3413 case MusECore::Route::JACK_ROUTE:
3414 break;
3415
3416 case MusECore::Route::MIDI_DEVICE_ROUTE:
3417 jackRouteActivated(act, _route, rem_route, operations);
3418 break;
3419
3420 case MusECore::Route::MIDI_PORT_ROUTE:
3421 break;
3422
3423 }
3424 if(use_act_list)
3425 {
3426 ++act_start;
3427 // if(--act_count == 0)
3428 if(--act_group_sz == 0)
3429 break;
3430 }
3431 else
3432 break;
3433 }
3434
3435 if(!operations.empty())
3436 {
3437 DEBUG_PRST_ROUTES_2(stderr, "RoutePopupMenu::popupActivated: executing operations\n");
3438 MusEGlobal::audio->msgExecutePendingOperations(operations, true);
3439 // MusEGlobal::song->update(SC_ROUTE);
3440 }
3441 }
3442
prepare()3443 void RoutePopupMenu::prepare()
3444 {
3445 if(!_route.isValid())
3446 return;
3447
3448 connect(this, SIGNAL(triggered(QAction*)), SLOT(routePopupActivated(QAction*)));
3449
3450 QAction* route_act = addAction(tr("Advanced Router..."));
3451 route_act->setIcon(*routerSVGIcon);
3452 route_act->setCheckable(false);
3453 route_act->setData(_OPEN_ROUTING_DIALOG_);
3454
3455 switch(_route.type)
3456 {
3457 case MusECore::Route::TRACK_ROUTE:
3458 {
3459 MusECore::Track* const track = _route.track;
3460 if(track->isMidiTrack())
3461 {
3462 QAction* act = nullptr;
3463 // Warn if no devices available. Add an item to open midi config.
3464 int pi = 0;
3465 for( ; pi < MusECore::MIDI_PORTS; ++pi)
3466 {
3467 MusECore::MidiDevice* md = MusEGlobal::midiPorts[pi].device();
3468 //if(md && !md->isSynti() && (md->rwFlags() & 2))
3469 //if(md && (md->rwFlags() & 2 || md->isSynti()) ) // p4.0.27 Reverted p4.0.35
3470 if(md && (md->rwFlags() & (_isOutMenu ? 1 : 2))) // Allow synth as input.
3471 break;
3472 }
3473 if(pi == MusECore::MIDI_PORTS)
3474 {
3475 if(_isOutMenu)
3476 act = addAction(tr("Warning: No output devices!"));
3477 else
3478 act = addAction(tr("Warning: No input devices!"));
3479 act->setCheckable(false);
3480 act->setData(-1);
3481 }
3482 act = addAction(QIcon(*ankerSVGIcon), tr("Midi Ports/Soft Synths..."));
3483 act->setCheckable(false);
3484 act->setData(_OPEN_MIDI_CONFIG_);
3485 }
3486 }
3487 break;
3488
3489 default:
3490 break;
3491 }
3492
3493 addSeparator();
3494
3495 if(_isOutMenu)
3496 addAction(new MenuTitleItem(tr("Output Routes:"), this));
3497 else
3498 addAction(new MenuTitleItem(tr("Input Routes:"), this));
3499 //addSeparator();
3500
3501 switch(_route.type)
3502 {
3503 case MusECore::Route::TRACK_ROUTE:
3504 {
3505 MusECore::Track* const track = _route.track;
3506 if(track->isMidiTrack())
3507 {
3508 QAction* act = nullptr;
3509 addMidiPorts(track, this, _isOutMenu, true, _isOutMenu);
3510 if(_isOutMenu)
3511 {
3512
3513 #ifdef _USE_SIMPLIFIED_SOLO_CHAIN_
3514 // Support Midi Track to Audio Input track soloing chain routes.
3515 // Support omni routes only, because if channels are supported, the graphical router becomes more complicated.
3516 const MusECore::InputList* const il = MusEGlobal::song->inputs();
3517 if(!il->empty())
3518 {
3519 addSeparator();
3520 addAction(new MenuTitleItem(tr("Soloing Chain"), this));
3521 RoutePopupMenu* subp = new RoutePopupMenu(_route, this, _isOutMenu, _broadcastChanges);
3522 subp->setTitle(tr("Audio Returns"));
3523 for(MusECore::ciAudioInput ai = il->begin(); ai != il->end(); ++ai)
3524 {
3525 // Add omni route:
3526 MusECore::Track* t = *ai;
3527 act = subp->addAction(t->displayName());
3528 act->setCheckable(true);
3529 const MusECore::Route r(t, -1);
3530 act->setData(QVariant::fromValue(r));
3531 if(track->outRoutes()->contains(r))
3532 act->setChecked(true);
3533 }
3534 addMenu(subp);
3535 }
3536 #else // _USE_SIMPLIFIED_SOLO_CHAIN_
3537 // Support Midi Port to Audio Input track routes.
3538 int port = ((MusECore::MidiTrack*)track)->outPort();
3539 if(port >= 0 && port < MIDI_PORTS)
3540 {
3541 MusECore::MidiPort* mp = &MusEGlobal::midiPorts[port];
3542
3543 // Do not list synth devices! Requiring valid device is desirable,
3544 // but could lead to 'hidden' routes unless we add more support
3545 // such as removing the existing routes when user changes flags.
3546 // So for now, just list all valid ports whether read or write.
3547 if(mp->device() && !mp->device()->isSynti())
3548 {
3549 MusECore::RouteList* rl = mp->outRoutes();
3550 //int chbits = 1 << ((MusECore::MidiTrack*)track)->outChannel();
3551 //MusECore::MidiDevice* md = mp->device();
3552 //if(!md)
3553 // continue;
3554
3555 addSeparator();
3556 addAction(new MenuTitleItem(tr("Soloing chain"), this));
3557 PopupMenu* subp = new PopupMenu(this, true);
3558 subp->setTitle(tr("Audio returns"));
3559 addMenu(subp);
3560
3561 MusECore::InputList* al = MusEGlobal::song->inputs();
3562
3563 #ifdef _USE_CUSTOM_WIDGET_ACTIONS_
3564 for (MusECore::ciAudioInput ai = al->begin(); ai != al->end(); ++ai)
3565 {
3566 // Add omni route:
3567 MusECore::Track* t = *ai;
3568 act = subp->addAction(t->displayName());
3569 act->setCheckable(true);
3570 const MusECore::Route r(t, -1);
3571 act->setData(QVariant::fromValue(r));
3572 if(rl->exists(r))
3573 act->setChecked(true);
3574
3575 // Add channel routes:
3576 RoutePopupMenu* subp = new RoutePopupMenu(_route, this, _isOutMenu, _broadcastChanges);
3577 wa_subp->addAction(new MenuTitleItem(tr("Channels"), this));
3578 act->setMenu(wa_subp);
3579 //RoutingMatrixWidgetAction* wa = new RoutingMatrixWidgetAction(1, MusECore::MUSE_MIDI_CHANNELS, redLedIcon, darkRedLedIcon, this);
3580 RoutingMatrixWidgetAction* wa = new RoutingMatrixWidgetAction(1, MusECore::MUSE_MIDI_CHANNELS, 0, 0, this);
3581 wa->setData(QVariant::fromValue(r)); // Ignore the routing channel and channels - our action holds the channels.
3582 int chans = 0;
3583 // Is there already a route?
3584 for(MusECore::ciRoute ir = rl->begin(); ir != rl->end(); ++ir)
3585 {
3586 switch(ir->type)
3587 {
3588 case MusECore::Route::TRACK_ROUTE:
3589 if(ir->track == t)
3590 chans = ir->channel; // Grab the channels.
3591 break;
3592 case MusECore::Route::MIDI_PORT_ROUTE:
3593 case MusECore::Route::JACK_ROUTE:
3594 case MusECore::Route::MIDI_DEVICE_ROUTE:
3595 break;
3596 }
3597 if(chans != 0)
3598 break;
3599 }
3600 if(chans != 0 && chans != -1)
3601 {
3602 for(int col = 0; col < MusECore::MUSE_MIDI_CHANNELS; ++col)
3603 {
3604 if(chans & (1 << col))
3605 wa->array()->setValue(0, col, true);
3606 }
3607 }
3608
3609 // Must rebuild array after text changes.
3610 wa->updateChannelArray();
3611 wa_subp->addAction(wa);
3612 }
3613 #else
3614 int chbits = 1 << ((MusECore::MidiTrack*)track)->outChannel();
3615 for (MusECore::ciAudioInput i = al->begin(); i != al->end(); ++i)
3616 {
3617 MusECore::Track* t = *i;
3618 QString s(t->displayName());
3619 act = subp->addAction(s);
3620 act->setCheckable(true);
3621 MusECore::Route r(t, chbits);
3622 act->setData(QVariant::fromValue(r));
3623 for(MusECore::ciRoute ir = mprl->begin(); ir != mprl->end(); ++ir)
3624 {
3625 if(ir->type == MusECore::Route::TRACK_ROUTE && ir->track == t && (ir->channel & chbits))
3626 {
3627 act->setChecked(true);
3628 break;
3629 }
3630 }
3631 }
3632 #endif // _USE_CUSTOM_WIDGET_ACTIONS_
3633
3634 }
3635 }
3636 #endif // _USE_SIMPLIFIED_SOLO_CHAIN_
3637
3638 }
3639 else
3640 {
3641 #if 0
3642 // p4.0.17 List ports with no device and no in routes, in a separate popup.
3643 PopupMenu* morep = new PopupMenu(pup, true);
3644 morep->setTitle(tr("More..."));
3645 for(int i = 0; i < MIDI_PORTS; ++i)
3646 {
3647 MusECore::MidiPort* mp = &MusEGlobal::midiPorts[i];
3648 if(mp->device())
3649 continue;
3650
3651 PopupMenu* subp = new PopupMenu(morep, true);
3652 subp->setTitle(QString("%1:%2").arg(i).arg(tr("<none>")));
3653
3654 // MusE-2: Check this - needed with QMenu? Help says no. No - verified, it actually causes double triggers!
3655 //connect(subp, SIGNAL(triggered(QAction*)), pup, SIGNAL(triggered(QAction*)));
3656 //connect(subp, SIGNAL(aboutToHide()), pup, SIGNAL(aboutToHide()));
3657
3658 iRoute ir = rl->begin();
3659 for( ; ir != rl->end(); ++ir)
3660 {
3661 if(ir->type == MusECore::Route::MIDI_PORT_ROUTE && ir->midiPort == i)
3662 break;
3663 }
3664 if(ir != rl->end())
3665 continue;
3666
3667 for(int ch = 0; ch < MusECore::MUSE_MIDI_CHANNELS; ++ch)
3668 {
3669 act = subp->addAction(QString("Channel %1").arg(ch+1));
3670 act->setCheckable(true);
3671 act->setData(gid);
3672
3673 int chbit = 1 << ch;
3674 MusECore::Route srcRoute(i, chbit); // In accordance with new channel mask, use the bit position.
3675
3676 gRoutingMenuMap.insert( pRouteMenuMap(gid, srcRoute) );
3677
3678 //if(chanmask & chbit) // Is the channel already set? Show item check mark.
3679 // act->setChecked(true);
3680
3681 ++gid;
3682 }
3683 //gid = MIDI_PORTS * MusECore::MUSE_MIDI_CHANNELS + i; // Make sure each 'toggle' item gets a unique id.
3684 act = subp->addAction(QString("Toggle all"));
3685 //act->setCheckable(true);
3686 act->setData(gid);
3687 MusECore::Route togRoute(i, (1 << MusECore::MUSE_MIDI_CHANNELS) - 1); // Set all channel bits.
3688 gRoutingMenuMap.insert( pRouteMenuMap(gid, togRoute) );
3689 ++gid;
3690 morep->addMenu(subp);
3691 }
3692 pup->addMenu(morep);
3693 #endif
3694
3695 }
3696 return;
3697 }
3698 else
3699 {
3700 MusECore::AudioTrack* t = (MusECore::AudioTrack*)track;
3701 MusECore::RouteCapabilitiesStruct rcaps = t->routeCapabilities();
3702
3703
3704 if(_isOutMenu)
3705 {
3706 const int t_ochs = rcaps._trackChannels._outChannels;
3707 int gid = 0;
3708
3709 switch(track->type())
3710 {
3711 case MusECore::Track::AUDIO_OUTPUT:
3712 {
3713 addGroupingChannelsAction(this);
3714 addJackPorts(_route, this);
3715
3716 if(!MusEGlobal::song->inputs()->empty())
3717 {
3718 //
3719 // Display using separate menu for audio inputs:
3720 //
3721 addSeparator();
3722 addAction(new MenuTitleItem(tr("Soloing Chain"), this));
3723 RoutePopupMenu* subp = new RoutePopupMenu(_route, this, _isOutMenu, _broadcastChanges);
3724 subp->setTitle(tr("Audio Returns"));
3725 addMenu(subp);
3726 gid = addInPorts(t, subp, gid, -1, -1, true);
3727 //
3728 // Display all in the same menu:
3729 //
3730 //addSeparator();
3731 //MenuTitleItem* title = new MenuTitleItem(tr("Audio returns"), this);
3732 //addAction(title);
3733 //gid = addInPorts(t, this, gid, -1, -1, true);
3734 }
3735 }
3736 break;
3737
3738 case MusECore::Track::AUDIO_INPUT:
3739 case MusECore::Track::WAVE:
3740 case MusECore::Track::AUDIO_GROUP:
3741 case MusECore::Track::AUDIO_AUX:
3742 case MusECore::Track::AUDIO_SOFTSYNTH:
3743 if(t_ochs > 0)
3744 {
3745 addGroupingChannelsAction(this);
3746 addAction(new RoutingMatrixHeaderWidgetAction(tr("Omni"), tr("Tracks"), QString(), this));
3747 gid = addWavePorts( t, this, gid, -1, -1, true);
3748 gid = addOutPorts( t, this, gid, -1, -1, true);
3749 gid = addGroupPorts( t, this, gid, -1, -1, true);
3750 gid = addSynthPorts( t, this, gid, -1, -1, true);
3751 }
3752 break;
3753
3754 default:
3755 clear();
3756 return;
3757 break;
3758 }
3759
3760 #ifndef _USE_CUSTOM_WIDGET_ACTIONS_
3761 switch(_track->type())
3762 {
3763 case MusECore::Track::AUDIO_INPUT:
3764 case MusECore::Track::WAVE:
3765 case MusECore::Track::AUDIO_GROUP:
3766 case MusECore::Track::AUDIO_AUX:
3767 case MusECore::Track::AUDIO_SOFTSYNTH:
3768 if(t_ochs > 0)
3769 {
3770 addSeparator();
3771 addAction(new MenuTitleItem(tr("Channels"), this));
3772 for(int i = 0; i < t_ochs; ++i)
3773 {
3774 PopupMenu* subp = new PopupMenu(this, true);
3775 subp->setTitle(QString::number(i + 1));
3776 subp->addAction(new MenuTitleItem(tr("Destinations:"), this));
3777 addMenu(subp);
3778 gid = addWavePorts( t, subp, gid, i, 1, true);
3779 gid = addOutPorts( t, subp, gid, i, 1, true);
3780 gid = addGroupPorts(t, subp, gid, i, 1, true);
3781 gid = addSynthPorts(t, subp, gid, i, 1, true);
3782 }
3783 }
3784 break;
3785
3786 default:
3787 break;
3788 }
3789 #endif
3790
3791 }
3792 else
3793 {
3794 if(track->type() == MusECore::Track::AUDIO_AUX)
3795 return;
3796
3797 const int t_ichs = rcaps._trackChannels._inChannels;
3798 int gid = 0;
3799
3800 switch(track->type())
3801 {
3802 case MusECore::Track::AUDIO_INPUT:
3803 {
3804 addGroupingChannelsAction(this);
3805 addJackPorts(_route, this);
3806
3807 if(!MusEGlobal::song->outputs()->empty() || !MusEGlobal::song->midis()->empty())
3808 {
3809 RoutePopupMenu* subp;
3810 //
3811 // Display using separate menus for midi ports and audio outputs:
3812 //
3813 addSeparator();
3814 addAction(new MenuTitleItem(tr("Soloing Chain"), this));
3815 if(!MusEGlobal::song->outputs()->empty())
3816 {
3817 subp = new RoutePopupMenu(_route, this, _isOutMenu, _broadcastChanges);
3818 subp->setTitle(tr("Audio Sends"));
3819 addMenu(subp);
3820 gid = addOutPorts(t, subp, gid, -1, -1, false);
3821 }
3822 if(!MusEGlobal::song->midis()->empty())
3823 {
3824 subp = new RoutePopupMenu(_route, this, true, _broadcastChanges);
3825 subp->setTitle(tr("Midi Sends"));
3826 addMenu(subp);
3827 addMidiTracks(t, subp, false);
3828 //
3829 // Display all in the same menu:
3830 //
3831 //addAction(new MenuTitleItem(tr("Audio sends"), this));
3832 //gid = addOutPorts(t, this, gid, -1, -1, false);
3833 //addSeparator();
3834 //addAction(new MenuTitleItem(tr("Midi sends"), this));
3835 //addMidiPorts(t, this, gid, false);
3836 }
3837 }
3838 }
3839 break;
3840
3841 case MusECore::Track::AUDIO_OUTPUT:
3842 case MusECore::Track::WAVE:
3843 case MusECore::Track::AUDIO_GROUP:
3844 case MusECore::Track::AUDIO_SOFTSYNTH:
3845 if(t_ichs > 0)
3846 {
3847 addGroupingChannelsAction(this);
3848 addAction(new RoutingMatrixHeaderWidgetAction(tr("Omni"), tr("Tracks"), QString(), this));
3849 gid = addWavePorts( t, this, gid, -1, -1, false);
3850 gid = addInPorts( t, this, gid, -1, -1, false);
3851 gid = addGroupPorts(t, this, gid, -1, -1, false);
3852 gid = addAuxPorts( t, this, gid, -1, -1, false);
3853 gid = addSynthPorts(t, this, gid, -1, -1, false);
3854 }
3855 break;
3856
3857 default:
3858 clear();
3859 return;
3860 break;
3861 }
3862
3863 #ifndef _USE_CUSTOM_WIDGET_ACTIONS_
3864 switch(_track->type())
3865 {
3866 case MusECore::Track::AUDIO_OUTPUT:
3867 case MusECore::Track::WAVE:
3868 case MusECore::Track::AUDIO_GROUP:
3869 case MusECore::Track::AUDIO_SOFTSYNTH:
3870 if(t_ichs > 0)
3871 {
3872 addSeparator();
3873 addAction(new MenuTitleItem(tr("Channels"), this));
3874 for(int i = 0; i < t_ichs; ++i)
3875 {
3876 PopupMenu* subp = new PopupMenu(this, true);
3877 subp->setTitle(QString::number(i + 1));
3878 subp->addAction(new MenuTitleItem(tr("Sources:"), this));
3879 addMenu(subp);
3880 gid = addWavePorts( t, subp, gid, i, 1, false);
3881 gid = addInPorts( t, subp, gid, i, 1, false);
3882 gid = addGroupPorts(t, subp, gid, i, 1, false);
3883 gid = addAuxPorts( t, subp, gid, i, 1, false);
3884 gid = addSynthPorts(t, subp, gid, i, 1, false);
3885 }
3886 }
3887 break;
3888
3889 default:
3890 break;
3891 }
3892 #endif
3893
3894 }
3895 }
3896
3897
3898 }
3899 break;
3900
3901 case MusECore::Route::MIDI_DEVICE_ROUTE:
3902 addJackPorts(_route, this);
3903 break;
3904
3905 case MusECore::Route::JACK_ROUTE:
3906 case MusECore::Route::MIDI_PORT_ROUTE:
3907 break;
3908
3909 }
3910 }
3911
exec(const MusECore::Route & route,bool isOutput)3912 void RoutePopupMenu::exec(const MusECore::Route& route, bool isOutput)
3913 {
3914 if(route.isValid())
3915 {
3916 _route = route;
3917 _isOutMenu = isOutput;
3918 }
3919 prepare();
3920 PopupMenu::exec();
3921 }
3922
exec(const QPoint & p,const MusECore::Route & route,bool isOutput)3923 void RoutePopupMenu::exec(const QPoint& p, const MusECore::Route& route, bool isOutput)
3924 {
3925 if(route.isValid())
3926 {
3927 _route = route;
3928 _isOutMenu = isOutput;
3929 }
3930 prepare();
3931 PopupMenu::exec(p);
3932 }
3933
popup(const QPoint & p,const MusECore::Route & route,bool isOutput)3934 void RoutePopupMenu::popup(const QPoint& p, const MusECore::Route& route, bool isOutput)
3935 {
3936 if(route.isValid())
3937 {
3938 _route = route;
3939 _isOutMenu = isOutput;
3940 }
3941 prepare();
3942 PopupMenu::popup(p);
3943 }
3944
3945 } // namespace MusEGui
3946