1 //=========================================================
2 //  MusE
3 //  Linux Music Editor
4 //    $Id: helper.cpp,v 1.1.1.1 2003/10/27 18:51:27 wschweer Exp $
5 //  (C) Copyright 2003 Werner Schweer (ws@seh.de)
6 //
7 //  This program is free software; you can redistribute it and/or
8 //  modify it under the terms of the GNU General Public License
9 //  as published by the Free Software Foundation; version 2 of
10 //  the License, or (at your option) any later version.
11 //
12 //  This program is distributed in the hope that it will be useful,
13 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
14 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 //  GNU General Public License for more details.
16 //
17 //  You should have received a copy of the GNU General Public License
18 //  along with this program; if not, write to the Free Software
19 //  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21 //=========================================================
22 
23 #include <list>
24 
25 #include "helper.h"
26 #include "song.h"
27 #include "app.h"
28 #include "icons.h"
29 #include "synth.h"
30 #include "functions.h"
31 #include "operations.h"
32 #include "gconfig.h"
33 
34 #include "driver/jackmidi.h"
35 #include "route.h"
36 #include "mididev.h"
37 #include "midiport.h"
38 #include "globaldefs.h"
39 #include "globals.h"
40 #include "audio.h"
41 #include "audiodev.h"
42 #include "midi_consts.h"
43 #include "midiseq.h"
44 #include "midictrl.h"
45 #include "menutitleitem.h"
46 #include "dssihost.h"
47 #include "lv2host.h"
48 #include "vst_native.h"
49 #include "appearance.h"
50 #include "event.h"
51 #include "popupmenu.h"
52 #include "mpevent.h"
53 #include "track.h"
54 #include "part.h"
55 #include "drummap.h"
56 #include "xml.h"
57 #include "conf.h"
58 #include "synthdialog.h"
59 #include "shortcuts.h"
60 
61 #include <strings.h>
62 
63 #include <QApplication>
64 #include <QDir>
65 #include <QFile>
66 #include <QFileInfo>
67 #include <QFileDialog>
68 #include <QByteArray>
69 #include <QStyle>
70 #include <QStyleFactory>
71 #include <QVector>
72 #include <QMessageBox>
73 #include <QActionGroup>
74 #include <QMenu>
75 #include <QWidget>
76 
77 using std::set;
78 
79 namespace MusEGlobal {
80 extern bool hIsB;
81 }
82 
83 namespace MusECore {
84 
85 
86 static const char* vall[] = {
87       "c","c#","d","d#","e","f","f#","g","g#","a","a#","h"
88       };
89 static const char* valu[] = {
90       "C","C#","D","D#","E","F","F#","G","G#","A","A#","H"
91       };
92 
93 //---------------------------------------------------------
94 //   pitch2string
95 //---------------------------------------------------------
96 
pitch2string(int v)97 QString pitch2string(int v)
98       {
99       if (v < 0 || v > 127)
100             return QString("----");
101       int octave = (v / 12) - 2;
102       QString o = QString::number(octave);
103       int i = v % 12;
104       QString s(octave < 0 ? valu[i] : vall[i]);
105       if (MusEGlobal::hIsB) {
106             if (s == "h")
107                   s = "b";
108             else if (s == "H")
109                   s = "B";
110             }
111       return s + o;
112       }
113 
114 //---------------------------------------------------------
115 //   string2pitch
116 //---------------------------------------------------------
117 
string2pitch(const QString & s)118 int string2pitch(const QString &s)
119 {
120     if (validatePitch(s) != QValidator::Acceptable)
121         return 0;
122 
123     QString p;
124     int oct = 0;
125     if (s.length() == 4) {
126         p = s.left(2);
127         oct = s.mid(2, 2).toInt();
128     } else if (s.length() == 3) {
129         if (s.at(1) == '#') {
130             p = s.left(2);
131             oct = s.mid(2, 1).toInt();
132         } else {
133             p = s.left(1);
134             oct = s.mid(1, 2).toInt();
135         }
136     } else {
137         p = s.left(1);
138         oct = s.mid(1, 1).toInt();
139     }
140 
141     int cnt = 0;
142     for (const auto& it : vall) {
143         if (QString::compare(QString(it), p, Qt::CaseInsensitive) == 0)
144             break;
145         cnt++;
146     }
147 
148     return (oct + 2) * 12 + cnt;
149 }
150 
validatePitch(const QString & s)151 QValidator::State validatePitch(const QString &s) {
152     static const QRegularExpression regExp("\\A[A-H]#?-[12]|[a-h]#?[0-8]\\z");
153      Q_ASSERT(regExp.isValid());
154 
155      const QRegularExpressionMatch match = regExp.match(s, 0, QRegularExpression::PartialPreferCompleteMatch);
156      if (match.hasMatch())
157          return QValidator::Acceptable;
158      else if (match.hasPartialMatch())
159          return QValidator::Intermediate;
160      else
161          return QValidator::Invalid;
162 }
163 
164 //---------------------------------------------------------
165 //   dumpMPEvent
166 //---------------------------------------------------------
167 
dumpMPEvent(const MEvent * ev)168 void dumpMPEvent(const MEvent* ev)
169       {
170       fprintf(stderr, "time:%d port:%d chan:%d ", ev->time(), ev->port(), ev->channel()+1);
171       if (ev->type() == ME_NOTEON) {
172             QString s = pitch2string(ev->dataA());
173             fprintf(stderr, "NoteOn %s(0x%x) %d\n", s.toLatin1().constData(), ev->dataA(), ev->dataB());
174            }
175       else if (ev->type() == ME_NOTEOFF) {
176             QString s = pitch2string(ev->dataA());
177             fprintf(stderr, "NoteOff %s(0x%x) %d\n", s.toLatin1().constData(), ev->dataA(), ev->dataB());
178            }
179       else if (ev->type() == ME_SYSEX) {
180             fprintf(stderr, "SysEx len %d 0x%0x ...\n", ev->len(), ev->constData()[0]);
181             }
182       else
183             fprintf(stderr, "type:0x%02x a=%d b=%d\n", ev->type(), ev->dataA(), ev->dataB());
184       }
185 
186 #if 0
187 
188 // -------------------------------------------------------------------------------------------------------
189 // enumerateJackMidiDevices()
190 // This version creates separate devices for Jack midi input and outputs.
191 // It does not attempt to pair them together.
192 // -------------------------------------------------------------------------------------------------------
193 
194 void enumerateJackMidiDevices()
195 {
196   if(!MusEGlobal::checkAudioDevice())
197     return;
198 
199   MidiDevice* dev = 0;
200   PendingOperationList operations;
201 
202   // If Jack is running.
203   if(MusEGlobal::audioDevice->deviceType() == AudioDevice::JACK_AUDIO)
204   {
205     char good_name[ROUTE_PERSISTENT_NAME_SIZE];
206     std::list<QString> sl;
207 //     sl = MusEGlobal::audioDevice->inputPorts(true, 1);  // Ask for second aliases.
208     sl = MusEGlobal::audioDevice->inputPorts(true);
209     for(std::list<QString>::iterator i = sl.begin(); i != sl.end(); ++i)
210     {
211       QByteArray ba = (*i).toLatin1();
212       const char* port_name = ba.constData();
213       void* const port = MusEGlobal::audioDevice->findPort(port_name);
214       if(port)
215       {
216         //dev = MidiJackDevice::createJackMidiDevice(*i, 1);
217         dev = MidiJackDevice::createJackMidiDevice(QString(), 1); // Let it pick the name
218         if(dev)
219         {
220           // Get a good routing name.
221           MusEGlobal::audioDevice->portName(port, good_name, ROUTE_PERSISTENT_NAME_SIZE);
222 
223           const Route dstRoute(Route::JACK_ROUTE, -1, NULL, -1, -1, -1, good_name); // Persistent route.
224           // If audio is running, this calls jack_connect() and waits for the audio thread to execute addRoute().
225           // If audio is not running, this directly executes addRoute(), bypassing the audio messaging system,
226           //  and jack_connect() is not called.
227           //MusEGlobal::audio->msgAddRoute(srcRoute, dstRoute);
228           //
229           // We only want to add the route, not call jack_connect - jack may not have been activated yet.
230           // If it has been, we should be calling our graph changed handler soon, it will handle actual connections.
231           // If audio is not running yet, this directly executes addRoute(), bypassing the audio messaging system,
232           if(!dev->outRoutes()->contains(dstRoute))
233             operations.add(MusECore::PendingOperationItem(dev->outRoutes(), dstRoute, MusECore::PendingOperationItem::AddRouteNode));
234         }
235       }
236     }
237 
238     //sl = MusEGlobal::audioDevice->outputPorts(true, 1); // Ask for second aliases.
239     sl = MusEGlobal::audioDevice->outputPorts(true);
240     for(std::list<QString>::iterator i = sl.begin(); i != sl.end(); ++i)
241     {
242       QByteArray ba = (*i).toLatin1();
243       const char* port_name = ba.constData();
244       void* const port = MusEGlobal::audioDevice->findPort(port_name);
245       if(port)
246       {
247         dev = MidiJackDevice::createJackMidiDevice(QString(), 2); // Let it pick the name
248         if(dev)
249         {
250           // Get a good routing name.
251           MusEGlobal::audioDevice->portName(port, good_name, ROUTE_PERSISTENT_NAME_SIZE);
252           const Route srcRoute(Route::JACK_ROUTE, -1, NULL, -1, -1, -1, good_name); // Persistent route.
253           if(!dev->inRoutes()->contains(srcRoute))
254             operations.add(MusECore::PendingOperationItem(dev->inRoutes(), srcRoute, MusECore::PendingOperationItem::AddRouteNode));
255         }
256       }
257     }
258   }
259   if(!operations.empty())
260   {
261     //operations.add(MusECore::PendingOperationItem((TrackList*)NULL, PendingOperationItem::UpdateSoloStates));
262     MusEGlobal::audio->msgExecutePendingOperations(operations); // Don't update here.
263     //MusEGlobal::song->update(SC_ROUTE);
264   }
265 }
266 
267 #else
268 
269 // -------------------------------------------------------------------------------------------------------
270 // enumerateJackMidiDevices()
271 // This version attempts to pair together Jack midi input and outputs into single MidiDevices,
272 //  similar to how ALSA presents pairs of inputs and outputs.
273 // -------------------------------------------------------------------------------------------------------
274 
enumerateJackMidiDevices()275 void enumerateJackMidiDevices()
276 {
277   if(!MusEGlobal::checkAudioDevice())
278     return;
279 
280   PendingOperationList operations;
281 
282   // If Jack is running.
283   if(MusEGlobal::audioDevice->deviceType() == AudioDevice::JACK_AUDIO)
284   {
285     MidiDevice* dev = 0;
286     char w_good_name[ROUTE_PERSISTENT_NAME_SIZE];
287     char r_good_name[ROUTE_PERSISTENT_NAME_SIZE];
288     std::list<QString> wsl;
289     std::list<QString> rsl;
290     wsl = MusEGlobal::audioDevice->inputPorts(true);
291     rsl = MusEGlobal::audioDevice->outputPorts(true);
292 
293     for(std::list<QString>::iterator wi = wsl.begin(); wi != wsl.end(); ++wi)
294     {
295       QByteArray w_ba = (*wi).toLatin1();
296       const char* w_port_name = w_ba.constData();
297 
298       bool match_found = false;
299       void* const w_port = MusEGlobal::audioDevice->findPort(w_port_name);
300       if(w_port)
301       {
302         // Get a good routing name.
303         MusEGlobal::audioDevice->portName(w_port, w_good_name, ROUTE_PERSISTENT_NAME_SIZE);
304 
305         for(std::list<QString>::iterator ri = rsl.begin(); ri != rsl.end(); ++ri)
306         {
307           QByteArray r_ba = (*ri).toLatin1();
308           const char* r_port_name = r_ba.constData();
309 
310           void* const r_port = MusEGlobal::audioDevice->findPort(r_port_name);
311           if(r_port)
312           {
313             // Get a good routing name.
314             MusEGlobal::audioDevice->portName(r_port, r_good_name, ROUTE_PERSISTENT_NAME_SIZE);
315 
316             const size_t w_sz = strlen(w_good_name);
317             const size_t r_sz = strlen(r_good_name);
318             size_t start_c = 0;
319             size_t w_end_c = w_sz;
320             size_t r_end_c = r_sz;
321 
322             while(start_c < w_sz && start_c < r_sz &&
323                   w_good_name[start_c] == r_good_name[start_c])
324               ++start_c;
325 
326             while(w_end_c > 0 && r_end_c > 0)
327             {
328               if(w_good_name[w_end_c - 1] != r_good_name[r_end_c - 1])
329                 break;
330               --w_end_c;
331               --r_end_c;
332             }
333 
334             if(w_end_c > start_c && r_end_c > start_c)
335             {
336               const char* w_str = w_good_name + start_c;
337               const char* r_str = r_good_name + start_c;
338               const size_t w_len = w_end_c - start_c;
339               const size_t r_len = r_end_c - start_c;
340 
341               // Do we have a matching pair?
342               if((w_len == 7 && r_len == 8 &&
343                   strncasecmp(w_str, "capture", w_len) == 0 &&
344                   strncasecmp(r_str, "playback", r_len) == 0) ||
345 
346                  (w_len == 8 && r_len == 7 &&
347                   strncasecmp(w_str, "playback", w_len) == 0 &&
348                   strncasecmp(r_str, "capture", r_len) == 0) ||
349 
350                  (w_len == 5 && r_len == 6 &&
351                   strncasecmp(w_str, "input", w_len) == 0 &&
352                   strncasecmp(r_str, "output", r_len) == 0) ||
353 
354                  (w_len == 6 && r_len == 5 &&
355                   strncasecmp(w_str, "output", w_len) == 0 &&
356                   strncasecmp(r_str, "input", r_len) == 0) ||
357 
358                  (w_len == 2 && r_len == 3 &&
359                   strncasecmp(w_str, "in", w_len) == 0 &&
360                   strncasecmp(r_str, "out", r_len) == 0) ||
361 
362                  (w_len == 3 && r_len == 2 &&
363                   strncasecmp(w_str, "out", w_len) == 0 &&
364                   strncasecmp(r_str, "in", r_len) == 0) ||
365 
366                  (w_len == 1 && r_len == 1 &&
367                   strncasecmp(w_str, "p", w_len) == 0 &&
368                   strncasecmp(r_str, "c", r_len) == 0) ||
369 
370                  (w_len == 1 && r_len == 1 &&
371                   strncasecmp(w_str, "c", w_len) == 0 &&
372                   strncasecmp(r_str, "p", r_len) == 0))
373               {
374                 dev = MidiJackDevice::createJackMidiDevice(QString(), 3); // Let it pick the name
375                 if(dev)
376                 {
377                   const Route srcRoute(Route::JACK_ROUTE, -1, NULL, -1, -1, -1, r_good_name); // Persistent route.
378                   const Route dstRoute(Route::JACK_ROUTE, -1, NULL, -1, -1, -1, w_good_name); // Persistent route.
379                   // We only want to add the route, not call jack_connect - jack may not have been activated yet.
380                   // If it has been, we should be calling our graph changed handler soon, it will handle actual connections.
381                   // If audio is not running yet, this directly executes addRoute(), bypassing the audio messaging system,
382                   if(!dev->inRoutes()->contains(srcRoute))
383                     operations.add(MusECore::PendingOperationItem(dev->inRoutes(), srcRoute, MusECore::PendingOperationItem::AddRouteNode));
384                   if(!dev->outRoutes()->contains(dstRoute))
385                     operations.add(MusECore::PendingOperationItem(dev->outRoutes(), dstRoute, MusECore::PendingOperationItem::AddRouteNode));
386                 }
387 
388                 rsl.erase(ri);  // Done with this read port. Remove.
389                 match_found = true;
390                 break;
391               }
392             }
393           }
394         }
395       }
396 
397       if(!match_found)
398       {
399         // No match was found. Create a single writeable device.
400         dev = MidiJackDevice::createJackMidiDevice(QString(), 1); // Let it pick the name
401         if(dev)
402         {
403           const Route dstRoute(Route::JACK_ROUTE, -1, NULL, -1, -1, -1, w_good_name); // Persistent route.
404           // We only want to add the route, not call jack_connect - jack may not have been activated yet.
405           // If it has been, we should be calling our graph changed handler soon, it will handle actual connections.
406           // If audio is not running yet, this directly executes addRoute(), bypassing the audio messaging system,
407           if(!dev->outRoutes()->contains(dstRoute))
408             operations.add(MusECore::PendingOperationItem(dev->outRoutes(), dstRoute, MusECore::PendingOperationItem::AddRouteNode));
409         }
410       }
411 
412     }
413 
414     // Create the remaining readable ports as single readable devices.
415     for(std::list<QString>::iterator ri = rsl.begin(); ri != rsl.end(); ++ri)
416     {
417       dev = MidiJackDevice::createJackMidiDevice(QString(), 2); // Let it pick the name
418       if(dev)
419       {
420         QByteArray r_ba = (*ri).toLatin1();
421         const char* r_port_name = r_ba.constData();
422 
423         void* const r_port = MusEGlobal::audioDevice->findPort(r_port_name);
424         if(r_port)
425         {
426           // Get a good routing name.
427           MusEGlobal::audioDevice->portName(r_port, r_good_name, ROUTE_PERSISTENT_NAME_SIZE);
428           const Route srcRoute(Route::JACK_ROUTE, -1, NULL, -1, -1, -1, r_good_name); // Persistent route.
429           if(!dev->inRoutes()->contains(srcRoute))
430             operations.add(MusECore::PendingOperationItem(dev->inRoutes(), srcRoute, MusECore::PendingOperationItem::AddRouteNode));
431         }
432       }
433     }
434   }
435 
436   if(!operations.empty())
437   {
438     //operations.add(MusECore::PendingOperationItem((TrackList*)NULL, PendingOperationItem::UpdateSoloStates));
439     MusEGlobal::audio->msgExecutePendingOperations(operations); // Don't update here.
440     //MusEGlobal::song->update(SC_ROUTE);
441   }
442 }
443 #endif   // enumerateJackMidiDevices
444 
445 // -------------------------------------------------------------------------------------------------------
446 // populateMidiPorts()
447 // Attempts to auto-populate midi ports with found devices.
448 // -------------------------------------------------------------------------------------------------------
449 
populateMidiPorts()450 void populateMidiPorts()
451 {
452   if(!MusEGlobal::checkAudioDevice())
453     return;
454 
455   MusECore::MidiDevice* dev = 0;
456   int port_num = 0;
457   int jack_midis_found = 0;
458   bool def_in_found = false;
459 //  bool def_out_found = false;
460 
461   // If Jack is running, prefer Jack midi devices over ALSA.
462   if(MusEGlobal::audioDevice->deviceType() == MusECore::AudioDevice::JACK_AUDIO)
463   {
464     for(MusECore::iMidiDevice i = MusEGlobal::midiDevices.begin(); i != MusEGlobal::midiDevices.end(); ++i)
465     {
466       dev = *i;
467       if(dev)
468       {
469         ++jack_midis_found;
470         MidiPort* mp = &MusEGlobal::midiPorts[port_num];
471         MusEGlobal::audio->msgSetMidiDevice(mp, dev);
472 
473 // robert: removing the default init on several places to allow for the case
474 // where you rather want the midi track to default to the last created port
475 // this can only happen if there is _no_ default set
476 
477 //        // Global function initMidiPorts() already sets defs to port #1, but this will override.
478 //        if(!def_out_found && dev->rwFlags() & 0x1)
479 //        {
480 //          mp->setDefaultOutChannels(1);
481 //          def_out_found = true;
482 //        }
483 //        else
484           mp->setDefaultOutChannels(0);
485 
486         if(!def_in_found && dev->rwFlags() & 0x2)
487         {
488           mp->setDefaultInChannels(1);
489           def_in_found = true;
490         }
491         else
492           mp->setDefaultInChannels(0);
493 
494         if(++port_num == MusECore::MIDI_PORTS)
495           return;
496       }
497     }
498   }
499   //else
500   // If Jack is not running, use ALSA devices.
501   // Try to do the user a favour: If we still have no Jack devices, even if Jack is running, fill with ALSA.
502   // It is possible user has Jack running on ALSA back-end but without midi support.
503   // IE. They use Jack for audio but use ALSA for midi!
504   // If unwanted, remove "|| jack_midis_found == 0".
505   if(MusEGlobal::audioDevice->deviceType() == MusECore::AudioDevice::DUMMY_AUDIO || jack_midis_found == 0)
506   {
507     for(MusECore::iMidiDevice i = MusEGlobal::midiDevices.begin(); i != MusEGlobal::midiDevices.end(); ++i)
508     {
509       if((*i)->deviceType() != MusECore::MidiDevice::ALSA_MIDI)
510         continue;
511       dev = *i;
512       MidiPort* mp = &MusEGlobal::midiPorts[port_num];
513       MusEGlobal::audio->msgSetMidiDevice(mp, dev);
514 
515       // robert: removing the default init on several places to allow for the case
516       // where you rather want the midi track to default to the last created port
517       // this can only happen if there is _no_ default set
518 
519 //      // Global function initMidiPorts() already sets defs to port #1, but this will override.
520 //      if(!def_out_found && dev->rwFlags() & 0x1)
521 //      {
522 //        mp->setDefaultOutChannels(1);
523 //        def_out_found = true;
524 //      }
525 //      else
526         mp->setDefaultOutChannels(0);
527 
528       if(!def_in_found && dev->rwFlags() & 0x2)
529       {
530         mp->setDefaultInChannels(1);
531         def_in_found = true;
532       }
533       else
534         mp->setDefaultInChannels(0);
535 
536       if(++port_num == MusECore::MIDI_PORTS)
537         return;
538     }
539   }
540 }
541 
partFromSerialNumber(int serial)542 Part* partFromSerialNumber(int serial)
543 {
544         TrackList* tl = MusEGlobal::song->tracks();
545 	for (iTrack it = tl->begin(); it != tl->end(); ++it)
546 	{
547 		PartList* pl = (*it)->parts();
548 		iPart ip;
549 		for (ip = pl->begin(); ip != pl->end(); ++ip)
550 			if (ip->second->sn() == serial)
551 				return ip->second;
552 	}
553 
554 	printf("ERROR: partFromSerialNumber(%i) wasn't able to find an appropriate part!\n",serial);
555 	return NULL;
556 }
557 
any_event_selected(const set<const Part * > & parts,bool in_range,RelevantSelectedEvents_t relevant)558 bool any_event_selected(const set<const Part*>& parts, bool in_range, RelevantSelectedEvents_t relevant)
559 {
560   return !get_events(parts, in_range ? 3 : 1, relevant).empty();
561 }
562 
drummaps_almost_equal(const DrumMap * one,const DrumMap * two,int len)563 bool drummaps_almost_equal(const DrumMap* one, const DrumMap* two, int len)
564 {
565   for (int i=0; i<len; i++)
566     if (!one[i].almost_equals(two[i]))
567       return false;
568 
569   return true;
570 }
571 
572 
parts_at_tick(unsigned tick)573 QSet<Part*> parts_at_tick(unsigned tick)
574 {
575   using MusEGlobal::song;
576 
577   QSet<Track*> tmp;
578   for (iTrack it=song->tracks()->begin(); it!=song->tracks()->end(); it++)
579     tmp.insert(*it);
580 
581   return parts_at_tick(tick, tmp);
582 }
583 
parts_at_tick(unsigned tick,Track * track)584 QSet<Part*> parts_at_tick(unsigned tick, Track* track)
585 {
586   QSet<Track*> tmp;
587   tmp.insert(track);
588 
589   return parts_at_tick(tick, tmp);
590 }
591 
parts_at_tick(unsigned tick,const QSet<Track * > & tracks)592 QSet<Part*> parts_at_tick(unsigned tick, const QSet<Track*>& tracks)
593 {
594   QSet<Part*> result;
595 
596   for (QSet<Track*>::const_iterator it=tracks.begin(); it!=tracks.end(); it++)
597   {
598     Track* track=*it;
599 
600     for (iPart p_it=track->parts()->begin(); p_it!=track->parts()->end(); p_it++)
601       if (tick >= p_it->second->tick() && tick <= p_it->second->endTick())
602         result.insert(p_it->second);
603   }
604 
605   return result;
606 }
607 
parse_range(const QString & str,int * from,int * to)608 bool parse_range(const QString& str, int* from, int* to)
609 {
610   int idx = str.indexOf("-");
611   if (idx<0) // no "-" in str
612   {
613     bool ok;
614     int i = str.toInt(&ok);
615     if (!ok)
616     {
617       *from=-1; *to=-1;
618       return false;
619     }
620     else
621     {
622       *from=i; *to=i;
623       return true;
624     }
625   }
626   else // there is a "-" in str
627   {
628     QString str1=str.mid(0,idx);
629     QString str2=str.mid(idx+1);
630 
631     bool ok;
632     int i = str1.toInt(&ok);
633     if (!ok)
634     {
635       *from=-1; *to=-1;
636       return false;
637     }
638     else
639     {
640       *from=i;
641 
642       i = str2.toInt(&ok);
643       if (!ok)
644       {
645         *from=-1; *to=-1;
646         return false;
647       }
648       else
649       {
650         *to=i;
651         return true;
652       }
653     }
654   }
655 }
656 
write_new_style_drummap(int level,Xml & xml,const char * tagname,DrumMap * drummap,bool full)657 void write_new_style_drummap(int level, Xml& xml, const char* tagname,
658                              DrumMap* drummap, bool full)
659 {
660   xml.tag(level++, tagname);
661 
662   for (int i=0;i<128;i++)
663   {
664     DrumMap* dm = &drummap[i];
665     const DrumMap* idm = &iNewDrumMap[i];
666 
667     if ( (dm->name != idm->name) || (dm->vol != idm->vol) ||
668          (dm->quant != idm->quant) || (dm->len != idm->len) ||
669          (dm->lv1 != idm->lv1) || (dm->lv2 != idm->lv2) ||
670          (dm->lv3 != idm->lv3) || (dm->lv4 != idm->lv4) ||
671          (dm->enote != idm->enote) || (dm->mute != idm->mute) ||
672          (dm->port != idm->port) || (dm->channel != idm->channel) ||
673          (dm->anote != idm->anote) ||
674          (dm->hide != idm->hide) || full)
675     {
676       xml.tag(level++, "entry pitch=\"%d\"", i);
677 
678       // when any of these "if"s changes, also update the large "if"
679       // above (this scope's parent)
680       if (full || dm->name != idm->name)   xml.strTag(level, "name", dm->name);
681       if (full || dm->vol != idm->vol)     xml.intTag(level, "vol", dm->vol);
682       if (full || dm->quant != idm->quant) xml.intTag(level, "quant", dm->quant);
683       if (full || dm->len != idm->len)     xml.intTag(level, "len", dm->len);
684       if (full || dm->channel != idm->channel) xml.intTag(level, "channel", dm->channel);
685       if (full || dm->port != idm->port)   xml.intTag(level, "port", dm->port);
686       if (full || dm->lv1 != idm->lv1)     xml.intTag(level, "lv1", dm->lv1);
687       if (full || dm->lv2 != idm->lv2)     xml.intTag(level, "lv2", dm->lv2);
688       if (full || dm->lv3 != idm->lv3)     xml.intTag(level, "lv3", dm->lv3);
689       if (full || dm->lv4 != idm->lv4)     xml.intTag(level, "lv4", dm->lv4);
690       if (full || dm->enote != idm->enote) xml.intTag(level, "enote", dm->enote);
691       if (full || dm->anote != idm->anote) xml.intTag(level, "anote", dm->anote);
692       if (full || dm->mute != idm->mute)   xml.intTag(level, "mute", dm->mute);
693       if (full || dm->hide != idm->hide)   xml.intTag(level, "hide", dm->hide);
694 
695       xml.tag(--level, "/entry");
696     }
697   }
698 
699   xml.etag(level, tagname);
700 }
701 
read_new_style_drummap(Xml & xml,const char * tagname,DrumMap * drummap,bool compatibility)702 void read_new_style_drummap(Xml& xml, const char* tagname,
703                             DrumMap* drummap, bool compatibility)
704 {
705 	for (;;)
706 	{
707 		Xml::Token token = xml.parse();
708 		if (token == Xml::Error || token == Xml::End)
709 			break;
710 		const QString& tag = xml.s1();
711 		switch (token)
712 		{
713 			case Xml::TagStart:
714 				if (tag == "entry")  // then read that entry with a nested loop
715         {
716           DrumMap* dm=NULL;
717           DrumMap temporaryMap;
718           for (;;) // nested loop
719           {
720             Xml::Token token = xml.parse();
721             const QString& tag = xml.s1();
722             switch (token)
723             {
724               case Xml::Error:
725               case Xml::End:
726                 goto end_of_nested_for;
727 
728               case Xml::Attribut:
729                 if (tag == "pitch")
730                 {
731                   int pitch = xml.s2().toInt() & 0x7f;
732                   if (pitch < 0 || pitch > 127)
733                     printf("ERROR: THIS SHOULD NEVER HAPPEN: invalid pitch in read_new_style_drummap()!\n");
734                   else
735                   {
736                     dm = &drummap[pitch];
737                   }
738                 }
739                 break;
740 
741               case Xml::TagStart:
742                 if (dm==NULL && compatibility == false)
743                   printf("ERROR: THIS SHOULD NEVER HAPPEN: no valid 'pitch' attribute in <entry> tag, but sub-tags follow in read_new_style_drummap()!\n");
744                 else if (dm ==NULL && compatibility == true)
745                 {
746                    dm = &temporaryMap;
747                 }
748                 if (tag == "name")
749                   dm->name = xml.parse(QString("name"));
750                 else if (tag == "vol")
751                   dm->vol = (unsigned char)xml.parseInt();
752                 else if (tag == "quant")
753                   dm->quant = xml.parseInt();
754                 else if (tag == "len")
755                   dm->len = xml.parseInt();
756                 else if (tag == "channel")
757                   dm->channel = xml.parseInt();
758                 else if (tag == "port")
759                   dm->port = xml.parseInt();
760                 else if (tag == "lv1")
761                   dm->lv1 = xml.parseInt();
762                 else if (tag == "lv2")
763                   dm->lv2 = xml.parseInt();
764                 else if (tag == "lv3")
765                   dm->lv3 = xml.parseInt();
766                 else if (tag == "lv4")
767                   dm->lv4 = xml.parseInt();
768                 else if (tag == "enote") {
769                   dm->enote = xml.parseInt();
770                   if (compatibility) {
771                       int pitch = temporaryMap.enote;
772                       drummap[pitch] = temporaryMap;
773                       dm = &drummap[pitch];
774                       dm->anote = pitch;
775                   }
776                 }
777                 else if (tag == "anote")
778                   dm->anote = xml.parseInt();
779                 else if (tag == "mute")
780                   dm->mute = xml.parseInt();
781                 else if (tag == "hide")
782                   dm->hide = xml.parseInt();
783                 else
784                   xml.unknown("read_new_style_drummap");
785                 break;
786 
787               case Xml::TagEnd:
788                 if (tag == "entry")
789                   goto end_of_nested_for;
790 
791               default:
792                 break;
793             }
794           } // end of nested loop
795           end_of_nested_for: ;
796         } // end of 'if (tag == "entry")'
797 				else
798 					xml.unknown("read_new_style_drummap");
799 				break;
800 
801 			case Xml::TagEnd:
802 				if (tag == tagname)
803 					return;
804 
805 			default:
806 				break;
807 		}
808 	}
809 }
810 
readDrummapsEntryPatchCollection(Xml & xml)811 int readDrummapsEntryPatchCollection(Xml& xml)
812 {
813   int hbank = (CTRL_PROGRAM_VAL_DONT_CARE >> 16) & 0xff;
814   int lbank = (CTRL_PROGRAM_VAL_DONT_CARE >> 8) & 0xff;
815   int prog  = CTRL_PROGRAM_VAL_DONT_CARE & 0xff;
816   int last_prog, last_hbank, last_lbank; // OBSOLETE. Not used.
817 
818   for (;;)
819   {
820     Xml::Token token = xml.parse();
821     const QString& tag = xml.s1();
822     switch (token)
823     {
824       case Xml::Error:
825       case Xml::End:
826         goto read_end;
827 
828       case Xml::TagStart:
829         xml.unknown("readDrummapsEntryPatchCollection");
830         break;
831 
832       case Xml::Attribut:
833         // last_prog, last_hbank, last_lbank are OBSOLETE. Not used.
834         if (tag == "prog")
835           parse_range(xml.s2(), &prog, &last_prog);
836         else if (tag == "lbank")
837           parse_range(xml.s2(), &lbank, &last_lbank);
838         else if (tag == "hbank")
839           parse_range(xml.s2(), &hbank, &last_hbank);
840         break;
841 
842       case Xml::TagEnd:
843         if (tag == "patch_collection")
844           return ((hbank & 0xff) << 16) | ((lbank & 0xff) << 8) | (prog & 0xff);
845 
846       default:
847         break;
848     }
849   }
850 
851 read_end:
852   fprintf(stderr, "ERROR: End or Error in readDrummapsEntryPatchCollection()!\n");
853   return CTRL_VAL_UNKNOWN; // an invalid collection
854 }
855 
record_controller_change_and_maybe_send(unsigned tick,int ctrl_num,int val,MidiTrack * mt)856 void record_controller_change_and_maybe_send(unsigned tick, int ctrl_num, int val, MidiTrack* mt)
857 {
858 	MusECore::Event a(MusECore::Controller);
859 	a.setTick(tick);
860 	a.setA(ctrl_num);
861 	a.setB(val);
862 	MusEGlobal::song->recordEvent(mt, a);
863 
864 	if (MusEGlobal::song->cpos() < mt->getControllerValueLifetime(tick, ctrl_num))
865 	{
866 		// this CC has an immediate effect? so send it out to the device.
867 		MusECore::MidiPlayEvent ev(0, mt->outPort(), mt->outChannel(), MusECore::ME_CONTROLLER, ctrl_num, val);
868 		MusEGlobal::audio->msgPlayMidiEvent(&ev);
869 	}
870 }
871 
872 } // namespace MusECore
873 
874 namespace MusEGui {
875 
876 //---------------------------------------------------------
877 //   midiPortsPopup
878 //---------------------------------------------------------
879 
midiPortsPopup(QWidget * parent,int checkPort,bool includeDefaultEntry)880 QMenu* midiPortsPopup(QWidget* parent, int checkPort, bool includeDefaultEntry)
881       {
882       QMenu* p = new QMenu(parent);
883       QMenu* subp = nullptr;
884       QAction *act = nullptr;
885       QString name;
886       const int openConfigId = MusECore::MIDI_PORTS;
887       const int defaultId    = MusECore::MIDI_PORTS + 1;
888 
889       // Warn if no devices available. Add an item to open midi config.
890       int pi = 0;
891       for( ; pi < MusECore::MIDI_PORTS; ++pi)
892       {
893         MusECore::MidiDevice* md = MusEGlobal::midiPorts[pi].device();
894         if(md && (md->rwFlags() & 1))
895           break;
896       }
897       if(pi == MusECore::MIDI_PORTS)
898       {
899         act = p->addAction(qApp->translate("@default", QT_TRANSLATE_NOOP("@default", "Warning: No output devices!")));
900         act->setCheckable(false);
901         act->setData(-1);
902         p->addSeparator();
903       }
904       act = p->addAction(*MusEGui::ankerSVGIcon, qApp->translate("@default", QT_TRANSLATE_NOOP("@default", "MIDI Ports/Soft Synths...")));
905       act->setCheckable(false);
906       act->setData(openConfigId);
907       p->addSeparator();
908 
909       p->addAction(new MusEGui::MenuTitleItem(qApp->translate("@default", QT_TRANSLATE_NOOP("@default", "Output Port/Device")), p));
910 
911       p->addSeparator();
912 
913       if(includeDefaultEntry)
914       {
915         act = p->addAction(qApp->translate("@default", QT_TRANSLATE_NOOP("@default", "default")));
916         act->setCheckable(false);
917         act->setData(defaultId);
918       }
919 
920       QVector<int> alsa_list;
921       QVector<int> jack_list;
922       QVector<int> synth_list;
923       QVector<int> *cur_list = nullptr;
924       QVector<int> unused_list;
925 
926       for (int i = 0; i < MusECore::MIDI_PORTS; ++i)
927       {
928         MusECore::MidiPort* port = &MusEGlobal::midiPorts[i];
929         MusECore::MidiDevice* md = port->device();
930         if(!md)
931         {
932           unused_list.push_back(i);
933           continue;
934         }
935 
936         // Make deleted audio softsynths not show in select dialog
937         if(md->isSynti())
938         {
939             MusECore::AudioTrack *_track = static_cast<MusECore::AudioTrack *>(static_cast<MusECore::SynthI *>(md));
940             MusECore::TrackList* tl = MusEGlobal::song->tracks();
941             if(tl->find(_track) == tl->end())
942               continue;
943         }
944 
945         // Only writeable ports, or current one.
946         if(!(md->rwFlags() & 1) && (i != checkPort))
947           continue;
948 
949         switch(md->deviceType())
950         {
951           case MusECore::MidiDevice::ALSA_MIDI:
952             alsa_list.push_back(i);
953           break;
954 
955           case MusECore::MidiDevice::JACK_MIDI:
956             jack_list.push_back(i);
957           break;
958 
959           case MusECore::MidiDevice::SYNTH_MIDI:
960             synth_list.push_back(i);
961           break;
962         }
963       }
964 
965       // Order the entire listing by device type.
966       for(int dtype = 0; dtype <= MusECore::MidiDevice::SYNTH_MIDI; ++dtype)
967       {
968         switch(dtype)
969         {
970           case MusECore::MidiDevice::ALSA_MIDI:
971             if(!alsa_list.isEmpty())
972               p->addAction(new MusEGui::MenuTitleItem(qApp->translate("@default", QT_TRANSLATE_NOOP("@default", "ALSA")), p));
973             cur_list = &alsa_list;
974           break;
975 
976           case MusECore::MidiDevice::JACK_MIDI:
977             if(!jack_list.isEmpty())
978               p->addAction(new MusEGui::MenuTitleItem(qApp->translate("@default", QT_TRANSLATE_NOOP("@default", "JACK")), p));
979             cur_list = &jack_list;
980           break;
981 
982           case MusECore::MidiDevice::SYNTH_MIDI:
983             if(!synth_list.isEmpty())
984               p->addAction(new MusEGui::MenuTitleItem(qApp->translate("@default", QT_TRANSLATE_NOOP("@default", "Synth")), p));
985             cur_list = &synth_list;
986           break;
987         }
988 
989         if(cur_list->isEmpty())
990           continue;
991 
992         int row = 0;
993         int sz = cur_list->size();
994 
995         for (int i = 0; i < sz; ++i)
996         {
997           const int port = cur_list->at(i);
998           if(port < 0 || port >= MusECore::MIDI_PORTS)
999             continue;
1000           MusECore::MidiPort* mp = &MusEGlobal::midiPorts[port];
1001           name = QString("%1:%2")
1002               .arg(port + 1)
1003               .arg(mp->portname());
1004 
1005           act = p->addAction(name);
1006           act->setData(port);
1007           act->setCheckable(true);
1008           act->setChecked(port == checkPort);
1009 
1010           ++row;
1011         }
1012       }
1013 
1014       int sz = unused_list.size();
1015       if(sz > 0)
1016       {
1017         p->addSeparator();
1018         for (int i = 0; i < sz; ++i)
1019         {
1020           const int port = unused_list.at(i);
1021           // No submenu yet? Create it now.
1022           if(!subp)
1023           {
1024             subp = new QMenu(p);
1025             subp->setTitle(qApp->translate("@default", QT_TRANSLATE_NOOP("@default", "Empty Ports")));
1026           }
1027           act = subp->addAction(QString().setNum(port + 1));
1028           act->setData(port);
1029           act->setCheckable(true);
1030           act->setChecked(port == checkPort);
1031         }
1032       }
1033 
1034       if(subp)
1035         p->addMenu(subp);
1036       return p;
1037       }
1038 
1039 //---------------------------------------------------------
1040 //   midiPortsPopupMenu
1041 //---------------------------------------------------------
midiPortsPopupMenu(MusECore::Track * t,int x,int y,bool allClassPorts,const QWidget * widget,bool includeDefaultEntry)1042 void midiPortsPopupMenu(MusECore::Track* t, int x, int y, bool allClassPorts,
1043                         const QWidget* widget, bool includeDefaultEntry)
1044 {
1045   switch(t->type()) {
1046       case MusECore::Track::MIDI:
1047       case MusECore::Track::DRUM:
1048       case MusECore::Track::AUDIO_SOFTSYNTH:
1049       {
1050             MusECore::MidiTrack* track = nullptr;
1051             MusECore::SynthI* synthi = nullptr;
1052 
1053             int potential_new_port_no=-1;
1054             int port = -1;
1055 
1056             if(t->isSynthTrack())
1057             {
1058               // Cast as SynthI which inherits MidiDevice.
1059               synthi = static_cast<MusECore::SynthI*>(t);
1060               if(!synthi)
1061                 return;
1062               port = synthi->midiPort();
1063             }
1064             else
1065             {
1066               track = static_cast<MusECore::MidiTrack*>(t);
1067               port = track->outPort();
1068             }
1069 
1070             // NOTE: If parent is given, causes accelerators to be returned in QAction::text() !
1071             QMenu* p = MusEGui::midiPortsPopup(nullptr, port, includeDefaultEntry);
1072 
1073             // find first free port number
1074             // do not permit numbers already used in other tracks!
1075             // except if it's only used in this track.
1076             int no;
1077             for (no=0;no<MusECore::MIDI_PORTS;no++)
1078               if (MusEGlobal::midiPorts[no].device()==nullptr)
1079               {
1080                 MusECore::ciMidiTrack it;
1081                 for (it=MusEGlobal::song->midis()->begin(); it!=MusEGlobal::song->midis()->end(); ++it)
1082                 {
1083                   MusECore::MidiTrack* mt=*it;
1084                   if (mt!=t && mt->outPort()==no)
1085                     break;
1086                 }
1087                 if (it == MusEGlobal::song->midis()->end())
1088                   break;
1089 
1090                 // TODO Ports which are used by synths ??
1091               }
1092 
1093             if (no==MusECore::MIDI_PORTS)
1094             {
1095               delete p;
1096               printf("THIS IS VERY UNLIKELY TO HAPPEN: no free midi ports! you have used all %i!\n",MusECore::MIDI_PORTS);
1097               break;
1098             }
1099 
1100             potential_new_port_no=no;
1101 
1102             // Do not include unused devices if the track is a synth track.
1103             if(!synthi)
1104             {
1105               typedef std::map<std::string, int > asmap;
1106               typedef std::map<std::string, int >::iterator imap;
1107 
1108               asmap mapALSA;
1109               asmap mapJACK;
1110               asmap mapSYNTH;
1111 
1112               int aix = 0x10000000;
1113               int jix = 0x20000000;
1114               int six = 0x30000000;
1115               for(MusECore::iMidiDevice i = MusEGlobal::midiDevices.begin(); i != MusEGlobal::midiDevices.end(); ++i)
1116               {
1117                 // don't add devices which are used somewhere
1118                 if((*i)->midiPort() >= 0 && (*i)->midiPort() < MusECore::MIDI_PORTS)
1119                   continue;
1120 
1121                 switch((*i)->deviceType())
1122                 {
1123                   case MusECore::MidiDevice::ALSA_MIDI:
1124                     mapALSA.insert(std::pair<std::string, int> ((*i)->name().toStdString(), aix));
1125                     ++aix;
1126                   break;
1127 
1128                   case MusECore::MidiDevice::JACK_MIDI:
1129                     mapJACK.insert(std::pair<std::string, int> ((*i)->name().toStdString(), jix));
1130                     ++jix;
1131                   break;
1132 
1133                   case MusECore::MidiDevice::SYNTH_MIDI:
1134                     mapSYNTH.insert(std::pair<std::string, int> ((*i)->name().toStdString(), six));
1135                     ++six;
1136                   break;
1137                 }
1138               }
1139 
1140               if (!mapALSA.empty() || !mapJACK.empty() || !mapSYNTH.empty())
1141               {
1142                 QMenu* pup = p->addMenu(qApp->translate("@default", QT_TRANSLATE_NOOP("@default", "Unused Devices")));
1143                 QAction* act;
1144 
1145                 if (!mapALSA.empty())
1146                 {
1147                   pup->addAction(new MusEGui::MenuTitleItem("ALSA", pup));
1148 
1149                   for(imap i = mapALSA.begin(); i != mapALSA.end(); ++i)
1150                   {
1151                     int idx = i->second;
1152                     QString s(i->first.c_str());
1153                     MusECore::MidiDevice* md = MusEGlobal::midiDevices.find(s, MusECore::MidiDevice::ALSA_MIDI);
1154                     if(md)
1155                     {
1156                       if(md->deviceType() != MusECore::MidiDevice::ALSA_MIDI)
1157                         continue;
1158 
1159                       act = pup->addAction(md->name());
1160                       act->setData(idx);
1161                     }
1162                   }
1163                 }
1164 
1165                 if (!mapJACK.empty())
1166                 {
1167                   pup->addAction(new MusEGui::MenuTitleItem("JACK", pup));
1168 
1169                   for(imap i = mapJACK.begin(); i != mapJACK.end(); ++i)
1170                   {
1171                     int idx = i->second;
1172                     QString s(i->first.c_str());
1173                     MusECore::MidiDevice* md = MusEGlobal::midiDevices.find(s, MusECore::MidiDevice::JACK_MIDI);
1174                     if(md)
1175                     {
1176                       if(md->deviceType() != MusECore::MidiDevice::JACK_MIDI)
1177                         continue;
1178 
1179                       act = pup->addAction(md->name());
1180                       act->setData(idx);
1181                     }
1182                   }
1183                 }
1184 
1185                 if (!mapSYNTH.empty())
1186                 {
1187                   pup->addAction(new MusEGui::MenuTitleItem("Synth", pup));
1188 
1189                   for(imap i = mapSYNTH.begin(); i != mapSYNTH.end(); ++i)
1190                   {
1191                     int idx = i->second;
1192                     QString s(i->first.c_str());
1193                     MusECore::MidiDevice* md = MusEGlobal::midiDevices.find(s, MusECore::MidiDevice::SYNTH_MIDI);
1194                     if(md)
1195                     {
1196                       if(md->deviceType() != MusECore::MidiDevice::SYNTH_MIDI)
1197                         continue;
1198 
1199                       act = pup->addAction(md->name());
1200                       act->setData(idx);
1201                     }
1202                   }
1203                 }
1204               }
1205             }
1206 
1207             QAction* act = widget ? p->exec(widget->mapToGlobal(QPoint(x, y)), nullptr) : p->exec();
1208             if(!act)
1209             {
1210               delete p;
1211               break;
1212             }
1213 
1214             QString acttext=act->text();
1215             int n = act->data().toInt();
1216             delete p;
1217 
1218             if(n < 0)              // Invalid item.
1219               break;
1220 
1221             if(n == MusECore::MIDI_PORTS)    // Show port config dialog.
1222             {
1223               MusEGlobal::muse->configMidiPorts();
1224               break;
1225             }
1226             else if (n >= 0x10000000)
1227             {
1228               // Error, should not happen.
1229               if(synthi)
1230                 break;
1231 
1232               int typ;
1233               if (n < 0x20000000)
1234                 typ = MusECore::MidiDevice::ALSA_MIDI;
1235               else if (n < 0x30000000)
1236                 typ = MusECore::MidiDevice::JACK_MIDI;
1237               else
1238                 typ = MusECore::MidiDevice::SYNTH_MIDI;
1239 
1240               MusECore::MidiDevice* sdev = MusEGlobal::midiDevices.find(acttext, typ);
1241 
1242               MusEGlobal::audio->msgSetMidiDevice(&MusEGlobal::midiPorts[potential_new_port_no], sdev);
1243               n=potential_new_port_no;
1244 
1245               MusEGlobal::song->update();
1246             }
1247 
1248             MusECore::MidiTrack::ChangedType_t changed = MusECore::MidiTrack::NothingChanged;
1249             MusEGlobal::audio->msgIdle(true);
1250 
1251             // In the case of synths, multiple synths cannot be assigned to the same port.
1252             if(synthi || (!allClassPorts && !t->selected()))
1253             {
1254               if(synthi)
1255               {
1256                 MusEGlobal::audio->msgSetMidiDevice(&MusEGlobal::midiPorts[n], synthi);
1257                 MusEGlobal::song->update();
1258                 changed |= MusECore::MidiTrack::PortChanged;
1259               }
1260               else if(track)
1261               {
1262                 if(n != track->outPort())
1263                   changed |= track->setOutPortAndUpdate(n, false);
1264               }
1265             }
1266             else
1267             {
1268               if(track)
1269               {
1270                 for(const auto& mt : *MusEGlobal::song->midis())
1271                 {
1272                     if (allClassPorts && (mt->type() != track->type()))
1273                         continue;
1274 
1275                     if(n != mt->outPort() && (allClassPorts || mt->selected()))
1276                         changed |= mt->setOutPortAndUpdate(n, false);
1277                 }
1278               }
1279             }
1280 
1281             MusEGlobal::audio->msgIdle(false);
1282             MusEGlobal::audio->msgUpdateSoloStates();
1283             MusEGlobal::song->update(SC_ROUTE | ((changed & MusECore::MidiTrack::DrumMapChanged) ? SC_DRUMMAP : 0));
1284 
1285             // Prompt and send init sequences.
1286             MusEGlobal::audio->msgInitMidiDevices(false);
1287       }
1288       break;
1289 
1290       case MusECore::Track::WAVE:
1291       case MusECore::Track::AUDIO_OUTPUT:
1292       case MusECore::Track::AUDIO_INPUT:
1293       case MusECore::Track::AUDIO_GROUP:
1294       case MusECore::Track::AUDIO_AUX:    //TODO
1295             break;
1296       }
1297 }
1298 
1299 //---------------------------------------------------------
1300 //   populateAddSynth
1301 //---------------------------------------------------------
1302 
populateAddSynth(QWidget * parent)1303 QMenu* populateAddSynth(QWidget* parent)
1304 {
1305   QMenu* synp = new PopupMenu(parent);
1306 
1307   typedef std::multimap<std::string, int > asmap;
1308   typedef std::multimap<std::string, int >::iterator imap;
1309 
1310   const int ntypes = MusECore::Synth::SYNTH_TYPE_END;
1311   asmap smaps[ntypes];
1312   PopupMenu* mmaps[ntypes];
1313   for(int itype = 0; itype < ntypes; ++itype)
1314     mmaps[itype] = nullptr;
1315 
1316   MusECore::Synth* synth;
1317   MusECore::Synth::Type type;
1318 
1319 //  QVector<QAction*> favActions;
1320   QMap<QString, QAction*> favActions;
1321 
1322   int ii = 0;
1323   for(std::vector<MusECore::Synth*>::iterator i = MusEGlobal::synthis.begin(); i != MusEGlobal::synthis.end(); ++i)
1324   {
1325     synth = *i;
1326     type = synth->synthType();
1327 
1328 // dssi-vst is dead, really no point in keeping this case around
1329 //#ifdef DSSI_SUPPORT
1330 //    if (type == MusECore::Synth::DSSI_SYNTH && ((MusECore::DssiSynth*)synth)->isDssiVst() ) // Place Wine VSTs in a separate sub menu
1331 //      type = MusECore::Synth::VST_SYNTH;
1332 //#endif
1333 
1334     if(type >= ntypes)
1335       continue;
1336     smaps[type].insert( std::pair<std::string, int> (synth->description().toLower().toStdString(), ii) );
1337 
1338     ++ii;
1339   }
1340 
1341   int sz = MusEGlobal::synthis.size();
1342   for(int itype = 0; itype < ntypes; ++itype)
1343   {
1344     for(imap i = smaps[itype].begin(); i != smaps[itype].end(); ++i)
1345     {
1346       int idx = i->second;
1347       if(idx > sz)           // Sanity check
1348         continue;
1349       synth = MusEGlobal::synthis[idx];
1350       if(synth)
1351       {
1352         // No sub-menu yet? Create it now.
1353         if(!mmaps[itype])
1354         {
1355           mmaps[itype] = new PopupMenu(parent);
1356           mmaps[itype]->setToolTipsVisible(true);
1357           mmaps[itype]->setIcon(*synthSVGIcon);
1358           mmaps[itype]->setTitle(MusECore::synthType2String((MusECore::Synth::Type)itype));
1359           synp->addMenu(mmaps[itype]);
1360         }
1361         //QAction* act = mmaps[itype]->addAction(synth->description() + " <" + synth->name() + ">");
1362         QAction* act = mmaps[itype]->addAction(synth->description());
1363         act->setData( MENU_ADD_SYNTH_ID_BASE * (itype + 1) + idx );
1364         if(!synth->uri().isEmpty())
1365           act->setToolTip(synth->uri());
1366 
1367         if (SynthDialog::isFav(synth))
1368             favActions.insert(synth->description().toLower(), act);
1369       }
1370     }
1371   }
1372 
1373   if (!favActions.isEmpty()) {
1374       QAction *fa = synp->actions().at(0);
1375       synp->insertAction(fa, new MusEGui::MenuTitleItem("Favorites", synp));
1376       for (const auto& it : favActions)
1377           synp->insertAction(fa, it);
1378 
1379       synp->insertAction(fa, new MusEGui::MenuTitleItem("All", synp));
1380   }
1381 
1382   return synp;
1383 }
1384 
1385 //---------------------------------------------------------
1386 //   populateAddTrack
1387 //    this is also used in "mixer"
1388 //---------------------------------------------------------
1389 
populateAddTrack(QMenu * addTrack,bool populateAll,bool insert,bool addHeader)1390 QActionGroup* populateAddTrack(QMenu* addTrack, bool populateAll, bool insert, bool addHeader)
1391       {
1392       QActionGroup* grp = new QActionGroup(addTrack);
1393 
1394       if (MusEGlobal::config.addHiddenTracks)
1395         populateAll=true;
1396 
1397       if (addHeader)
1398           addTrack->addAction(new MusEGui::MenuTitleItem(qApp->translate("@default", QT_TRANSLATE_NOOP("@default", "Add Track")), addTrack));
1399 
1400       if (populateAll || MusECore::MidiTrack::visible()) {
1401         QAction* midi = addTrack->addAction(*pianorollSVGIcon,
1402                                           qApp->translate("@default", QT_TRANSLATE_NOOP("@default", "Midi Track")));
1403         midi->setData(MusECore::Track::MIDI);
1404         midi->setShortcut(shortcuts[insert ? SHRT_INSERT_MIDI_TRACK : SHRT_ADD_MIDI_TRACK].key);
1405         grp->addAction(midi);
1406 
1407         QAction* drum = addTrack->addAction(*drumeditSVGIcon,
1408                                           qApp->translate("@default", QT_TRANSLATE_NOOP("@default", "Drum Track")));
1409         drum->setData(MusECore::Track::DRUM);
1410         drum->setShortcut(shortcuts[insert ? SHRT_INSERT_DRUM_TRACK : SHRT_ADD_DRUM_TRACK].key);
1411         grp->addAction(drum);
1412       }
1413       if (populateAll || MusECore::WaveTrack::visible()) {
1414         QAction* wave = addTrack->addAction(*waveeditorSVGIcon,
1415                                           qApp->translate("@default", QT_TRANSLATE_NOOP("@default", "Wave Track")));
1416        wave->setData(MusECore::Track::WAVE);
1417        wave->setShortcut(shortcuts[insert ? SHRT_INSERT_WAVE_TRACK : SHRT_ADD_WAVE_TRACK].key);
1418        grp->addAction(wave);
1419       }
1420 
1421       if (populateAll || MusECore::AudioOutput::visible()) {
1422         QAction* aoutput = addTrack->addAction(*trackOutputSVGIcon,
1423                                                qApp->translate("@default", QT_TRANSLATE_NOOP("@default", "Audio Output")));
1424         aoutput->setData(MusECore::Track::AUDIO_OUTPUT);
1425         aoutput->setShortcut(shortcuts[insert ? SHRT_INSERT_AUDIO_OUTPUT : SHRT_ADD_AUDIO_OUTPUT].key);
1426         grp->addAction(aoutput);
1427       }
1428 
1429       if (populateAll || MusECore::AudioGroup::visible()) {
1430         QAction* agroup = addTrack->addAction(*trackGroupVGIcon,
1431                                               qApp->translate("@default", QT_TRANSLATE_NOOP("@default", "Audio Group")));
1432         agroup->setData(MusECore::Track::AUDIO_GROUP);
1433         agroup->setShortcut(shortcuts[insert ? SHRT_INSERT_AUDIO_GROUP : SHRT_ADD_AUDIO_GROUP].key);
1434         grp->addAction(agroup);
1435       }
1436 
1437       if (populateAll || MusECore::AudioInput::visible()) {
1438         QAction* ainput = addTrack->addAction(*trackInputSVGIcon,
1439                                               qApp->translate("@default", QT_TRANSLATE_NOOP("@default", "Audio Input")));
1440         ainput->setData(MusECore::Track::AUDIO_INPUT);
1441         ainput->setShortcut(shortcuts[insert ? SHRT_INSERT_AUDIO_INPUT : SHRT_ADD_AUDIO_INPUT].key);
1442         grp->addAction(ainput);
1443       }
1444 
1445       if (populateAll || MusECore::AudioAux::visible()) {
1446         QAction* aaux = addTrack->addAction(*trackAuxSVGIcon,
1447                                             qApp->translate("@default", QT_TRANSLATE_NOOP("@default", "Aux Send")));
1448         aaux->setData(MusECore::Track::AUDIO_AUX);
1449         aaux->setShortcut(shortcuts[insert ? SHRT_INSERT_AUDIO_AUX : SHRT_ADD_AUDIO_AUX].key);
1450         grp->addAction(aaux);
1451       }
1452 
1453       if (populateAll || MusECore::SynthI::visible()) {
1454           addTrack->addSeparator();
1455           QAction *asynthd = addTrack->addAction(*synthSVGIcon,
1456                                                    qApp->translate("@default", QT_TRANSLATE_NOOP("@default", "Synth (Show Dialog)...")));
1457           asynthd->setData(MusECore::Track::AUDIO_SOFTSYNTH);
1458           asynthd->setShortcut(shortcuts[insert ? SHRT_INSERT_SYNTH_TRACK : SHRT_ADD_SYNTH_TRACK].key);
1459           grp->addAction(asynthd);
1460 
1461           // Create a sub-menu and fill it with found synth types. Make addTrack the owner.
1462           QMenu* synp = populateAddSynth(addTrack);
1463           synp->setTitle(qApp->translate("@default", QT_TRANSLATE_NOOP("@default", "Synth")));
1464 
1465           // Add the sub-menu to the given menu.
1466           addTrack->addMenu(synp);
1467       }
1468 
1469       return grp;
1470       }
1471 
1472 //---------------------------------------------------------
1473 //   getFilterExtension
1474 //---------------------------------------------------------
1475 
getFilterExtension(const QString & filter)1476 QString getFilterExtension(const QString &filter)
1477 {
1478   // Return the first extension found. Must contain at least one * character.
1479 
1480   int pos = filter.indexOf('*');
1481   if(pos == -1)
1482     return QString();
1483 
1484   QString filt;
1485   int len = filter.length();
1486   ++pos;
1487   for( ; pos < len; ++pos)
1488   {
1489     QChar c = filter[pos];
1490     if((c == ')') || (c == ';') || (c == ',') || (c == ' '))
1491       break;
1492     filt += filter[pos];
1493   }
1494   return filt;
1495 }
1496 
localizedStringListFromCharArray(const char ** array,const char * context)1497 QStringList localizedStringListFromCharArray(const char** array, const char* context)
1498 {
1499   QStringList temp;
1500   for (int i=0;array[i];i++)
1501     temp << qApp->translate(context, array[i]);
1502 
1503   return temp;
1504 }
1505 
browseProjectFolder(QWidget * parent)1506 QString browseProjectFolder(QWidget* parent)
1507 {
1508   QString path;
1509   if(!MusEGlobal::config.projectBaseFolder.isEmpty())
1510   {
1511     QDir d(MusEGlobal::config.projectBaseFolder);
1512     path = d.absolutePath();
1513   }
1514 
1515   QString dir = QFileDialog::getExistingDirectory(parent, qApp->translate("@default", QT_TRANSLATE_NOOP("@default", "Select project directory")), path);
1516   if(dir.isEmpty())
1517     dir = MusEGlobal::config.projectBaseFolder;
1518   return dir;
1519 }
1520 
projectTitleFromFilename(QString filename)1521 QString projectTitleFromFilename(QString filename)
1522 {
1523   int idx;
1524   idx = filename.lastIndexOf(".med.bz2", -1, Qt::CaseInsensitive);
1525   if(idx == -1)
1526     idx = filename.lastIndexOf(".med.gz", -1, Qt::CaseInsensitive);
1527   if(idx == -1)
1528     idx = filename.lastIndexOf(".med", -1, Qt::CaseInsensitive);
1529 
1530   if(idx != -1)
1531     filename.truncate(idx);
1532 
1533   QFileInfo fi(filename);
1534 
1535   return fi.fileName();
1536 }
1537 
projectPathFromFilename(QString filename)1538 QString projectPathFromFilename(QString filename)
1539 {
1540   QFileInfo fi(filename);
1541   return QDir::cleanPath(fi.absolutePath());
1542 }
1543 
projectExtensionFromFilename(QString filename)1544 QString projectExtensionFromFilename(QString filename)
1545 {
1546   int idx;
1547   idx = filename.lastIndexOf(".med.bz2", -1, Qt::CaseInsensitive);
1548   if(idx == -1)
1549     idx = filename.lastIndexOf(".med.gz", -1, Qt::CaseInsensitive);
1550   if(idx == -1)
1551     idx = filename.lastIndexOf(".med", -1, Qt::CaseInsensitive);
1552   if(idx == -1)
1553     idx = filename.lastIndexOf(".bz2", -1, Qt::CaseInsensitive);
1554   if(idx == -1)
1555     idx = filename.lastIndexOf(".gz", -1, Qt::CaseInsensitive);
1556 
1557   return (idx == -1) ? QString() : filename.right(filename.size() - idx);
1558 }
1559 
getUniqueUntitledName()1560 QString getUniqueUntitledName()
1561 {
1562   QString filename("untitled");
1563 
1564   QString fbase(MusEGlobal::config.projectBaseFolder);
1565 
1566   QString nfb = fbase;
1567   if(MusEGlobal::config.projectStoreInFolder)
1568     nfb += "/" + filename;
1569   QFileInfo fi(nfb + "/" + filename + ".med");  // TODO p4.0.40 Check other extensions.
1570   if(!fi.exists())
1571     return fi.filePath();
1572 
1573   // Find a new filename
1574   QString nfn = filename;
1575   int idx;
1576   for (idx=2; idx<10000; idx++) {
1577       QString num = QString::number(idx);
1578       nfn = filename + "_" + num;
1579       nfb = fbase;
1580       if(MusEGlobal::config.projectStoreInFolder)
1581         nfb += "/" + nfn;
1582       QFileInfo fi(nfb + "/" + nfn + ".med");
1583       if(!fi.exists())
1584         return fi.filePath();
1585   }
1586 
1587   printf("MusE error: Could not make untitled project name (10000 or more untitled projects in project dir - clean up!\n");
1588 
1589   nfb = fbase;
1590   if(MusEGlobal::config.projectStoreInFolder)
1591     nfb += "/" + filename;
1592   return nfb + "/" + filename + ".med";
1593 }
1594 
1595 struct CI {
1596             int num;
1597             QString s;
1598             bool used;
1599             bool off;
1600             bool instrument;
CIMusEGui::CI1601             CI(int n, const QString& ss, bool u, bool o, bool i) : num(n), s(ss), used(u), off(o), instrument(i) {}
1602             };
1603 
1604 //---------------------------------------------------
1605 //  populateMidiCtrlMenu
1606 //  Returns estimated width of the completed menu.
1607 //---------------------------------------------------
1608 
populateMidiCtrlMenu(PopupMenu * menu,MusECore::PartList * part_list,MusECore::Part * cur_part,int curDrumPitch)1609 int populateMidiCtrlMenu(PopupMenu* menu, MusECore::PartList* part_list, MusECore::Part* cur_part, int curDrumPitch)
1610       {
1611       //---------------------------------------------------
1612       // build list of midi controllers for current
1613       // MusECore::MidiPort/channel
1614       //---------------------------------------------------
1615 
1616       MusECore::MidiTrack* track = (MusECore::MidiTrack*)(cur_part->track());
1617       int channel      = track->outChannel();
1618       MusECore::MidiPort* port   = &MusEGlobal::midiPorts[track->outPort()];
1619       bool isNewDrum   = track->type() == MusECore::Track::DRUM;
1620       bool isMidi      = track->type() == MusECore::Track::MIDI;
1621       MusECore::MidiInstrument* instr = port->instrument();
1622       MusECore::MidiCtrlValListList* cll = port->controller();
1623       const int min = channel << 24;
1624       const int max = min + 0x1000000;
1625       const int edit_ins = max + 3;
1626       const int velo = max + 0x101;
1627       int est_width = 0;
1628       const int patch = port->hwCtrlState(channel, MusECore::CTRL_PROGRAM);
1629 
1630       std::list<CI> sList;
1631       typedef std::list<CI>::iterator isList;
1632       std::set<int> already_added_nums;
1633 
1634       for (MusECore::iMidiCtrlValList it = cll->lower_bound(min); it != cll->lower_bound(max); ++it) {
1635             MusECore::MidiCtrlValList* cl = it->second;
1636             MusECore::MidiController* c   = port->midiController(cl->num(), channel);
1637             bool isDrumCtrl = (c->isPerNoteController());
1638             int show = c->showInTracks();
1639             int cnum = c->num();
1640             int num = cl->num();
1641             if (isDrumCtrl) {
1642                   // Only show controller for current pitch:
1643                   if (isNewDrum)
1644                   {
1645                     if ((curDrumPitch < 0) || ((num & 0xff) != track->drummap()[curDrumPitch].anote))
1646                           continue;
1647 
1648                   }
1649                   else if (isMidi)
1650                   {
1651                     if ((curDrumPitch < 0) || ((num & 0xff) != curDrumPitch)) // FINDMICH does this work?
1652                           continue;
1653                   }
1654                   else
1655                     continue;
1656                   }
1657             isList i = sList.begin();
1658             for (; i != sList.end(); ++i) {
1659                   if (i->num == num)
1660                         break;
1661                   }
1662 
1663             if (i == sList.end()) {
1664                   bool used = false;
1665                   for (MusECore::iPart ip = part_list->begin(); ip != part_list->end(); ++ip) {
1666                         const MusECore::EventList& el = ip->second->events();
1667                         for (MusECore::ciEvent ie = el.begin(); ie != el.end(); ++ie) {
1668                               const MusECore::Event& e = ie->second;
1669                               if(e.type() != MusECore::Controller)
1670                                 continue;
1671                               int ctl_num = e.dataA();
1672                               // Is it a drum controller event, according to the track port's instrument?
1673                               MusECore::MidiController *mc = port->drumController(ctl_num);
1674                               if(mc)
1675                               {
1676                                 if((ctl_num & 0xff) != curDrumPitch)
1677                                   continue;
1678                                 if(isNewDrum)
1679                                   ctl_num = (ctl_num & ~0xff) | track->drummap()[ctl_num & 0x7f].anote;
1680                               }
1681                               if(ctl_num == num)
1682                               {
1683                                     used = true;
1684                                     break;
1685                               }
1686 
1687                               }
1688                         if (used)
1689                               break;
1690                         }
1691                   bool off = cl->hwVal() == MusECore::CTRL_VAL_UNKNOWN;  // Does it have a value or is it 'off'?
1692                   // Filter if not used and off. But if there's something there, we must show it.
1693                   if(!used && off &&
1694                      (((isDrumCtrl || isNewDrum) && !(show & MusECore::MidiController::ShowInDrum)) ||
1695                      (isMidi && !(show & MusECore::MidiController::ShowInMidi))))
1696                     continue;
1697                   const bool isinstr = instr->findController(cnum, channel, patch) != nullptr;
1698                   // Need to distinguish between global default controllers and
1699                   //  instrument defined controllers. Instrument takes priority over global
1700                   //  ie they 'overtake' definition of a global controller such that the
1701                   //  global def is no longer available.
1702                   sList.push_back(CI(num,
1703                                   isinstr ? MusECore::midiCtrlNumString(cnum, true) + c->name() : MusECore::midiCtrlName(cnum, true),
1704                                   used, off, isinstr));
1705                   already_added_nums.insert(num);
1706                   }
1707             }
1708 
1709       QString stext = QWidget::tr("Instrument-defined");
1710 // Width() is obsolete. Qt >= 5.11 use horizontalAdvance().
1711 #if QT_VERSION >= 0x050b00
1712       int fmw = menu->fontMetrics().horizontalAdvance(stext);
1713 #else
1714       int fmw = menu->fontMetrics().width(stext);
1715 #endif
1716       if(fmw > est_width)
1717         est_width = fmw;
1718       menu->addAction(new MenuTitleItem(stext, menu));
1719 
1720       // Don't allow editing instrument if it's a synth
1721       if(!port->device() || port->device()->deviceType() != MusECore::MidiDevice::SYNTH_MIDI)
1722       {
1723         stext = QWidget::tr("Edit Instrument...");
1724 // Width() is obsolete. Qt >= 5.11 use horizontalAdvance().
1725 #if QT_VERSION >= 0x050b00
1726         fmw = menu->fontMetrics().horizontalAdvance(stext);
1727 #else
1728         fmw = menu->fontMetrics().width(stext);
1729 #endif
1730         if(fmw > est_width)
1731           est_width = fmw;
1732         menu->addAction(*editInstrumentSVGIcon, QWidget::tr("Edit Instrument..."))->setData(edit_ins);
1733         menu->addSeparator();
1734       }
1735 
1736       //
1737       // populate popup with all controllers available for
1738       // current instrument
1739       //
1740 
1741       stext = QWidget::tr("Add");
1742 // Width() is obsolete. Qt >= 5.11 use horizontalAdvance().
1743 #if QT_VERSION >= 0x050b00
1744       fmw = menu->fontMetrics().horizontalAdvance(stext);
1745 #else
1746       fmw = menu->fontMetrics().width(stext);
1747 #endif
1748       if(fmw > est_width)
1749         est_width = fmw;
1750       PopupMenu * ctrlSubPop = new PopupMenu(stext, menu, true);  // true = enable stay open
1751       MusECore::MidiControllerList* mcl = new MusECore::MidiControllerList();
1752       instr->getControllers(mcl, channel, patch);
1753 
1754       for (MusECore::iMidiController ci = mcl->begin(); ci != mcl->end(); ++ci)
1755       {
1756           int show = ci->second->showInTracks();
1757           if((isNewDrum && !(show & MusECore::MidiController::ShowInDrum)) ||
1758              (isMidi && !(show & MusECore::MidiController::ShowInMidi)))
1759             continue;
1760           int cnum = ci->second->num();
1761           int num = cnum;
1762           if(ci->second->isPerNoteController())
1763           {
1764             if (isNewDrum && curDrumPitch >= 0)
1765               num = (cnum & ~0xff) | track->drummap()[curDrumPitch].anote;
1766             else if (isMidi && curDrumPitch >= 0)
1767               num = (cnum & ~0xff) | curDrumPitch; //FINDMICH does this work?
1768             else
1769               continue;
1770           }
1771 
1772           // If it's not already in the parent menu...
1773           if(cll->find(channel, num) == cll->end())
1774           {
1775             ctrlSubPop->addAction(MusECore::midiCtrlNumString(cnum, true) + ci->second->name())->setData(num);
1776             already_added_nums.insert(num); //cnum);
1777           }
1778       }
1779       delete mcl;
1780 
1781       menu->addMenu(ctrlSubPop);
1782 
1783       menu->addSeparator();
1784 
1785       // Add instrument-defined controllers:
1786       for (isList i = sList.begin(); i != sList.end(); ++i)
1787       {
1788         if(!i->instrument)
1789           continue;
1790 
1791 // Width() is obsolete. Qt >= 5.11 use horizontalAdvance().
1792 #if QT_VERSION >= 0x050b00
1793         fmw = menu->fontMetrics().horizontalAdvance(i->s);
1794 #else
1795         fmw = menu->fontMetrics().width(i->s);
1796 #endif
1797         if(fmw > est_width)
1798           est_width = fmw;
1799 
1800         if (i->used && !i->off)
1801           menu->addAction(*ledYellowSVGIcon, i->s)->setData(i->num);
1802         else if (i->used)
1803           menu->addAction(*ledGreenSVGIcon, i->s)->setData(i->num);
1804         else if(!i->off)
1805           menu->addAction(*ledBlueSVGIcon, i->s)->setData(i->num);
1806         else
1807           menu->addAction(i->s)->setData(i->num);
1808       }
1809 
1810       stext = QWidget::tr("Others");
1811 // Width() is obsolete. Qt >= 5.11 use horizontalAdvance().
1812 #if QT_VERSION >= 0x050b00
1813       fmw = menu->fontMetrics().horizontalAdvance(stext);
1814 #else
1815       fmw = menu->fontMetrics().width(stext);
1816 #endif
1817       if(fmw > est_width)
1818         est_width = fmw;
1819       menu->addAction(new MenuTitleItem(stext, menu));
1820 
1821       // Add a.k.a. Common Controls not found in instrument:
1822       stext = QWidget::tr("Common Controls");
1823 // Width() is obsolete. Qt >= 5.11 use horizontalAdvance().
1824 #if QT_VERSION >= 0x050b00
1825       fmw = menu->fontMetrics().horizontalAdvance(stext);
1826 #else
1827       fmw = menu->fontMetrics().width(stext);
1828 #endif
1829       if(fmw > est_width)
1830         est_width = fmw;
1831       PopupMenu* ccSubPop = new PopupMenu(stext, menu, true);  // true = enable stay open
1832       for(int num = 0; num < 128; ++num)
1833         // If it's not already in the parent menu...
1834         if(already_added_nums.find(num) == already_added_nums.end())
1835           ccSubPop->addAction(MusECore::midiCtrlName(num, true))->setData(num);
1836 
1837       menu->addMenu(ccSubPop);
1838 
1839       menu->addSeparator();
1840 
1841       // Add the special case velocity:
1842       stext = QWidget::tr("Velocity");
1843 // Width() is obsolete. Qt >= 5.11 use horizontalAdvance().
1844 #if QT_VERSION >= 0x050b00
1845       fmw = menu->fontMetrics().horizontalAdvance(stext);
1846 #else
1847       fmw = menu->fontMetrics().width(stext);
1848 #endif
1849       if(fmw > est_width)
1850         est_width = fmw;
1851       menu->addAction(stext)->setData(velo);
1852 
1853       // Add global default controllers (all controllers not found in instrument).
1854       for (isList i = sList.begin(); i != sList.end(); ++i)
1855       {
1856         if(i->instrument)
1857           continue;
1858 
1859 // Width() is obsolete. Qt >= 5.11 use horizontalAdvance().
1860 #if QT_VERSION >= 0x050b00
1861         fmw = menu->fontMetrics().horizontalAdvance(i->s);
1862 #else
1863         fmw = menu->fontMetrics().width(i->s);
1864 #endif
1865         if(fmw > est_width)
1866           est_width = fmw;
1867 
1868         if (i->used && !i->off)
1869           menu->addAction(*ledYellowSVGIcon, i->s)->setData(i->num);
1870         else if (i->used)
1871           menu->addAction(*ledGreenSVGIcon, i->s)->setData(i->num);
1872         else if(!i->off)
1873           menu->addAction(*ledBlueSVGIcon, i->s)->setData(i->num);
1874         else
1875           menu->addAction(i->s)->setData(i->num);
1876       }
1877 
1878       est_width += 60; // Add about 60 for the coloured lights on the left.
1879 
1880       return est_width;
1881       }
1882 
1883 //---------------------------------------------------
1884 //  openSynthGui
1885 //---------------------------------------------------
1886 
openSynthGui(MusECore::Track * t)1887 void openSynthGui(MusECore::Track* t) {
1888 
1889     //auto curTrack = MusEGlobal::muse->arranger()->curTrack();
1890 
1891     MusECore::SynthI* synth = nullptr;
1892 
1893     if (t->isMidiTrack()) {
1894 
1895         int oPort = ((MusECore::MidiTrack*)t)->outPort();
1896         MusECore::MidiPort* port = &MusEGlobal::midiPorts[oPort];
1897 
1898         if (port->device() && port->device()->isSynti())
1899             synth = static_cast<MusECore::SynthI*>(port->device());
1900 
1901     } else if (t->isSynthTrack()) {
1902         synth = static_cast<MusECore::SynthI*>(t);
1903     } else {
1904         return;
1905     }
1906 
1907     if (!synth || !synth->synth())
1908         return;
1909 
1910     if (synth->hasNativeGui()) {
1911         synth->showNativeGui(!synth->nativeGuiVisible());
1912     }
1913     else if (synth->hasGui()) {
1914         synth->showGui(!synth->guiVisible());
1915     }
1916 }
1917 
1918 //---------------------------------------------------
1919 //  clipQLine
1920 //---------------------------------------------------
1921 
clipQLine(int x1,int y1,int x2,int y2,const QRect & rect)1922 QLine clipQLine(int x1, int y1, int x2, int y2, const QRect& rect)
1923 {
1924   const int rect_x     = rect.x();
1925   const int rect_y     = rect.y();
1926   const int rect_right = rect_x + rect.width();
1927   const int rect_bot   = rect_y + rect.height();
1928 
1929   if(x1 < rect_x)
1930   {
1931     if(x2 < rect_x)
1932       return QLine();
1933     x1 = rect_x;
1934   }
1935   else
1936   if(x1 > rect_right)
1937   {
1938     if(x2 > rect_right)
1939       return QLine();
1940     x1 = rect_right;
1941   }
1942 
1943   if(x2 < rect_x)
1944     x2 = rect_x;
1945   else
1946   if(x2 > rect_right)
1947     x2 = rect_right;
1948 
1949   if(y1 < rect_y)
1950   {
1951     if(y2 < rect_y)
1952       return QLine();
1953     y1 = rect_y;
1954   }
1955   else
1956   if(y1 > rect_bot)
1957   {
1958     if(y2 > rect_bot)
1959       return QLine();
1960     y1 = rect_bot;
1961   }
1962 
1963   if(y2 < rect_y)
1964     y2 = rect_y;
1965   if(y2 > rect_bot)
1966     y2 = rect_bot;
1967 
1968   return QLine(x1, y1, x2, y2);
1969 }
1970 
normalizeQRect(const QRect & rect)1971 QRect normalizeQRect(const QRect& rect)
1972 {
1973   int x = rect.x();
1974   int y = rect.y();
1975   int w = rect.width();
1976   int h = rect.height();
1977   if(w < 0)
1978   {
1979     x += w;
1980     w = -w;
1981   }
1982 
1983   if(h < 0)
1984   {
1985     y += h;
1986     h = -h;
1987   }
1988 
1989   return QRect(x, y, w, h);
1990 }
1991 
1992 //---------------------------------------------------------
1993 //   loadQtStyle
1994 //---------------------------------------------------------
1995 
1996 //void loadQtStyle(const QString& style)
1997 //{
1998 //    // Style sheets take priority over styles, and actually
1999 //    //  reset the style object name to empty when set.
2000 //    const QString curStyle(qApp->style()->objectName());
2001 //    QStringList styleList = QStyleFactory::keys();
2002 
2003 //    if (styleList.indexOf(style) == -1) {
2004 
2005 //        if (MusEGlobal::debugMsg)
2006 //            printf("Application Qt style does not exist, setting default.\n");
2007 
2008 //        // To find the name of the current style, use objectName().
2009 //        if (curStyle.compare(Appearance::getSetDefaultStyle(), Qt::CaseInsensitive) != 0)
2010 //        {
2011 //            qApp->setStyle(Appearance::getSetDefaultStyle());
2012 
2013 //            if (MusEGlobal::debugMsg)
2014 //            {
2015 //                fprintf(stderr, "loadQtStyle: Setting app style to default: %s\n", Appearance::getSetDefaultStyle().toLatin1().constData());
2016 //                fprintf(stderr, "   App style is now: %s\n", qApp->style()->objectName().toLatin1().constData());
2017 //            }
2018 
2019 //            // No style object name? It will happen when a stylesheet is active.
2020 //            // Give it a name. NOTE: The object names always seem to be lower case while
2021 //            //  the style factory key names are not.
2022 //            if (qApp->style()->objectName().isEmpty())
2023 //            {
2024 //                qApp->style()->setObjectName(Appearance::getSetDefaultStyle().toLower());
2025 //                if (MusEGlobal::debugMsg)
2026 //                    fprintf(stderr, "   Setting empty style object name. App style is now: %s\n", qApp->style()->objectName().toLatin1().constData());
2027 //            }
2028 //        }
2029 //    }
2030 //    else if (curStyle.compare(style, Qt::CaseInsensitive) != 0)
2031 //    {
2032 //        qApp->setStyle(style);
2033 //        // Do the style again to fix a bug where the arranger is non-responsive.
2034 
2035 //        if(MusEGlobal::debugMsg)
2036 //        {
2037 //            fprintf(stderr, "loadTheme setting app style to: %s\n", style.toLatin1().constData());
2038 //            fprintf(stderr, "   app style is now: %s\n", qApp->style()->objectName().toLatin1().constData());
2039 //        }
2040 
2041 //        // No style object name? It will happen when a stylesheet is active.
2042 //        // Give it a name. NOTE: The object names always seem to be lower case while
2043 //        //  the style factory key names are not.
2044 //        if(qApp->style()->objectName().isEmpty())
2045 //        {
2046 //            qApp->style()->setObjectName(style.toLower());
2047 //            if(MusEGlobal::debugMsg)
2048 //                fprintf(stderr, "   Setting empty style object name. App style is now: %s\n", qApp->style()->objectName().toLatin1().constData());
2049 //        }
2050 //    }
2051 //}
2052 
2053 //---------------------------------------------------------
2054 //   loadTheme
2055 //---------------------------------------------------------
2056 
loadTheme(const QString & theme)2057 void loadTheme(const QString& theme)
2058 {
2059     if (theme.isEmpty())
2060         return;
2061 
2062     if(MusEGlobal::debugMsg)
2063         fprintf(stderr, "loadTheme: %s\n", theme.toLatin1().constData());
2064 
2065     QString stylePathUser = MusEGlobal::configPath + "/themes/" + theme + ".qss";
2066     QString stylePathDef = MusEGlobal::museGlobalShare + "/themes/" + theme + ".qss";
2067 
2068     QFile fdef(stylePathDef);
2069     if (!fdef.open(QIODevice::ReadOnly)) {
2070         printf("loading style sheet <%s> failed\n", qPrintable(theme));
2071         return;
2072     }
2073     QByteArray sdef = fdef.readAll();
2074     fdef.close();
2075 
2076     QByteArray suser;
2077     if (QFile::exists(stylePathUser)) {
2078         QFile fuser(stylePathUser);
2079         if (fuser.open(QIODevice::ReadOnly)) {
2080             suser = fuser.readAll();
2081         } else {
2082             printf("loading style sheet <%s> failed\n", qPrintable(theme));
2083         }
2084         fuser.close();
2085     }
2086 
2087     QString sheet;
2088     if (suser.isEmpty()) {
2089         sheet = QString::fromUtf8(sdef.data());
2090     } else {
2091         if (MusEGlobal::config.cascadeStylesheets)
2092             sheet = QString::fromUtf8(sdef.data()) + '\n' + QString::fromUtf8(suser.data());
2093         else
2094             sheet = QString::fromUtf8(suser.data());
2095     }
2096 
2097 // QTBUG-78238, QTBUG-80506 etc.
2098 #if QT_VERSION < QT_VERSION_CHECK(5, 12, 6)
2099     if (theme == "Dark Flat" || theme == "Deep Ocean")
2100         sheet += "QMenu::item { padding-left: 26px; }";
2101 #elif QT_VERSION < QT_VERSION_CHECK(5, 14, 1) //
2102     if (theme == "Dark Flat" || theme == "Deep Ocean")
2103         sheet += "QMenu#CheckmarkOnly::item { padding-left: 26px; }";
2104 #endif
2105 
2106     qApp->setStyleSheet(sheet);
2107 
2108     loadThemeColors(theme);
2109 }
2110 
2111 //---------------------------------------------------------
2112 //   loadThemeColors
2113 //---------------------------------------------------------
2114 
loadThemeColors(const QString & theme)2115 void loadThemeColors(const QString& theme)
2116 {
2117     if (MusEGlobal::debugMsg)
2118         fprintf(stderr, "loadThemeColors: %s\n", theme.toLatin1().constData());
2119 
2120     QString configColorPath = MusEGlobal::configPath + "/themes/" + theme + ".cfc";
2121     if (!QFile::exists(configColorPath)) {
2122         configColorPath = MusEGlobal::museGlobalShare + "/themes/" + theme + ".cfc";
2123     }
2124 
2125     MusECore::readConfiguration(qPrintable(configColorPath));
2126 }
2127 
2128 
2129 } // namespace MusEGui
2130 
2131