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