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