1 /*
2  * Common Carla code
3  * Copyright (C) 2011-2019 Filipe Coelho <falktx@falktx.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of
8  * the License, or any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * For a full copy of the GNU General Public License see the doc/GPL.txt file.
16  */
17 
18 #include "carla_shared.hpp"
19 
20 //---------------------------------------------------------------------------------------------------------------------
21 // Imports (Global)
22 
23 #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
24 # pragma GCC diagnostic push
25 # pragma GCC diagnostic ignored "-Wconversion"
26 # pragma GCC diagnostic ignored "-Weffc++"
27 # pragma GCC diagnostic ignored "-Wsign-conversion"
28 #endif
29 
30 #include <QtGui/QFontMetrics>
31 
32 #include <QtWidgets/QFileDialog>
33 #include <QtWidgets/QGridLayout>
34 #include <QtWidgets/QLineEdit>
35 
36 #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
37 # pragma GCC diagnostic pop
38 #endif
39 
40 //---------------------------------------------------------------------------------------------------------------------
41 
42 #ifdef CARLA_OS_UNIX
43 # include <signal.h>
44 #endif
45 
46 //---------------------------------------------------------------------------------------------------------------------
47 // Imports (Custom)
48 
49 #include "carla_host.hpp"
50 
51 #include "CarlaUtils.h"
52 #include "CarlaMathUtils.hpp"
53 
54 //---------------------------------------------------------------------------------------------------------------------
55 // Global Carla object
56 
CarlaObject()57 CarlaObject::CarlaObject() noexcept
58     : host(nullptr),
59       gui(nullptr),
60       nogui(false),
61       term(false) {}
62 
63 CarlaObject gCarla;
64 
65 //---------------------------------------------------------------------------------------------------------------------
66 // Get Icon from user theme, using our own as backup (Oxygen)
67 
getIcon(const QString icon,const int size)68 QIcon getIcon(const QString icon, const int size)
69 {
70     return QIcon::fromTheme(icon, QIcon(QString(":/%1x%1/%2.png").arg(size).arg(icon)));
71 }
72 
73 //---------------------------------------------------------------------------------------------------------------------
74 // Handle some basic command-line arguments shared between all carla variants
75 
handleInitialCommandLineArguments(const int argc,char * argv[])76 QString handleInitialCommandLineArguments(const int argc, char* argv[])
77 {
78     static const QStringList listArgsNoGUI   = { "-n", "--n", "-no-gui", "--no-gui", "-nogui", "--nogui" };
79     static const QStringList listArgsHelp    = { "-h", "--h", "-help", "--help" };
80     static const QStringList listArgsVersion = { "-v", "--v", "-version", "--version" };
81 
82     QString initName(argv[0]); // = os.path.basename(file) if (file is not None and os.path.dirname(file) in PATH) else sys.argv[0]
83     // libPrefix = None
84 
85     for (int i=1; i<argc; ++i)
86     {
87         const QString arg(argv[i]);
88 
89         if (arg.startsWith("--with-appname="))
90         {
91             // initName = os.path.basename(arg.replace("--with-appname=", ""));
92         }
93         else if (arg.startsWith("--with-libprefix=") || arg == "--gdb")
94         {
95             pass();
96         }
97         else if (listArgsNoGUI.contains(arg))
98         {
99             gCarla.nogui = true;
100         }
101         else if (listArgsHelp.contains(arg))
102         {
103             carla_stdout("Usage: %s [OPTION]... [FILE|URL]", initName);
104             carla_stdout("");
105             carla_stdout(" where FILE can be a Carla project or preset file to be loaded, or URL if using Carla-Control");
106             carla_stdout("");
107             carla_stdout(" and OPTION can be one or more of the following:");
108             carla_stdout("");
109             carla_stdout("    --gdb    \t Run Carla inside gdb.");
110             carla_stdout(" -n,--no-gui \t Run Carla headless, don't show UI.");
111             carla_stdout("");
112             carla_stdout(" -h,--help   \t Print this help text and exit.");
113             carla_stdout(" -v,--version\t Print version information and exit.");
114             carla_stdout("");
115 
116             std::exit(0);
117         }
118         else if (listArgsVersion.contains(arg))
119         {
120             /*
121             QString pathBinaries, pathResources = getPaths();
122             */
123 
124             carla_stdout("Using Carla version %s", CARLA_VERSION_STRING);
125             /*
126             carla_stdout("  Qt version:     %s", QT_VERSION_STR);
127             carla_stdout("  Binary dir:     %s", pathBinaries.toUtf8());
128             carla_stdout("  Resources dir:  %s", pathResources.toUtf8());
129             */
130 
131             std::exit(0);
132         }
133     }
134 
135     return initName;
136 }
137 
138 //---------------------------------------------------------------------------------------------------------------------
139 // Get initial project file (as passed in the command-line parameters)
140 
getInitialProjectFile(bool)141 QString getInitialProjectFile(bool)
142 {
143     // TODO
144     return "";
145 }
146 
147 //---------------------------------------------------------------------------------------------------------------------
148 // Get paths (binaries, resources)
149 
getPaths(QString & pathBinaries,QString & pathResources)150 bool getPaths(QString& pathBinaries, QString& pathResources)
151 {
152     const QString libFolder(carla_get_library_folder());
153 
154     QDir dir(libFolder);
155 
156     // FIXME need to completely rework this in C++ mode, so check if all cases are valid
157 
158     if (libFolder.endsWith("bin"))
159     {
160         CARLA_SAFE_ASSERT_RETURN(dir.cd("resources"), false);
161         pathBinaries  = libFolder;
162         pathResources = dir.absolutePath();
163         return true;
164     }
165     else if (libFolder.endsWith("carla"))
166     {
167         for (int i=2; --i>=0;)
168         {
169             CARLA_SAFE_ASSERT_INT_RETURN(dir.cdUp(), i, false);
170             CARLA_SAFE_ASSERT_INT_RETURN(dir.cd("share"), i, false);
171 
172             if (dir.exists())
173             {
174                 CARLA_SAFE_ASSERT_INT_RETURN(dir.cd("carla"), i, false);
175                 CARLA_SAFE_ASSERT_INT_RETURN(dir.cd("resources"), i, false);
176                 pathBinaries  = libFolder;
177                 pathResources = dir.absolutePath();
178                 return true;
179             }
180         }
181     }
182 
183     return false;
184 }
185 
186 //---------------------------------------------------------------------------------------------------------------------
187 // Signal handler
188 
signalHandler(const int sig)189 static void signalHandler(const int sig)
190 {
191     switch (sig)
192     {
193     case SIGINT:
194     case SIGTERM:
195         gCarla.term = true;
196         if (gCarla.host != nullptr)
197             emit gCarla.host->SignalTerminate();
198         break;
199     case SIGUSR1:
200         if (gCarla.host != nullptr)
201             emit gCarla.host->SignalSave();
202         break;
203     }
204 }
205 
206 #ifdef CARLA_OS_WIN
winSignalHandler(DWORD dwCtrlType)207 static BOOL WINAPI winSignalHandler(DWORD dwCtrlType) noexcept
208 {
209     if (dwCtrlType == CTRL_C_EVENT)
210     {
211         signalHandler(SIGINT);
212         return TRUE;
213     }
214     return FALSE;
215 }
216 #endif
217 
setUpSignals()218 void setUpSignals()
219 {
220 #if defined(CARLA_OS_UNIX)
221     struct sigaction sig;
222     carla_zeroStruct(sig);
223     sig.sa_handler = signalHandler;
224     sig.sa_flags   = SA_RESTART;
225     sigemptyset(&sig.sa_mask);
226     sigaction(SIGTERM, &sig, nullptr);
227     sigaction(SIGINT, &sig, nullptr);
228     sigaction(SIGUSR1, &sig, nullptr);
229 #elif defined(CARLA_OS_WIN)
230     SetConsoleCtrlHandler(winSignalHandler, TRUE);
231 #endif
232 }
233 
234 //---------------------------------------------------------------------------------------------------------------------
235 // QLineEdit and QPushButton combo
236 
getAndSetPath(QWidget * const parent,QLineEdit * const lineEdit)237 QString getAndSetPath(QWidget* const parent, QLineEdit* const lineEdit)
238 {
239     const QCarlaString newPath = QFileDialog::getExistingDirectory(parent, parent->tr("Set Path"), lineEdit->text(), QFileDialog::ShowDirsOnly);
240     if (newPath.isNotEmpty())
241         lineEdit->setText(newPath);
242     return newPath;
243 }
244 
245 //---------------------------------------------------------------------------------------------------------------------
246 // fill up a qlists from a C arrays
247 
fillQStringListFromStringArray(QStringList & list,const char * const * const stringArray)248 void fillQStringListFromStringArray(QStringList& list, const char* const* const stringArray)
249 {
250     int count = 0;
251 
252     // count number of strings first
253     for (; stringArray[count] != nullptr; ++count) {}
254 
255     // allocate list
256     list.reserve(count);
257 
258     // fill in strings
259     for (count = 0; stringArray[count] != nullptr; ++count)
260         list.append(stringArray[count]);
261 }
262 
fillQDoubleListFromDoubleArray(QList<double> & list,const double * const doubleArray)263 void fillQDoubleListFromDoubleArray(QList<double>& list, const double* const doubleArray)
264 {
265     int count = 0;
266 
267     // count number of strings first
268     for (; carla_isNotZero(doubleArray[count]); ++count) {}
269 
270     // allocate list
271     list.reserve(count);
272 
273     // fill in strings
274     for (count = 0; carla_isNotZero(doubleArray[count]); ++count)
275         list.append(doubleArray[count]);
276 }
277 
fillQUIntListFromUIntArray(QList<uint> & list,const uint * const uintArray)278 void fillQUIntListFromUIntArray(QList<uint>& list, const uint* const uintArray)
279 {
280     int count = 0;
281 
282     // count number of strings first
283     for (; uintArray[count] != 0; ++count) {}
284 
285     // allocate list
286     list.reserve(count);
287 
288     // fill in strings
289     for (count = 0; uintArray[count] != 0; ++count)
290         list.append(uintArray[count]);
291 }
292 
293 //---------------------------------------------------------------------------------------------------------------------
294 // Backwards-compatible horizontalAdvance/width call, depending on Qt version
295 
fontMetricsHorizontalAdvance(const QFontMetrics & fm,const QString & s)296 int fontMetricsHorizontalAdvance(const QFontMetrics& fm, const QString& s)
297 {
298 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
299     return fm.horizontalAdvance(s);
300 #else
301     return fm.width(s);
302 #endif
303 }
304 
305 //---------------------------------------------------------------------------------------------------------------------
306 // Check if a string array contains a string
307 
stringArrayContainsString(const char * const * const stringArray,const char * const string)308 bool stringArrayContainsString(const char* const* const stringArray, const char* const string) noexcept
309 {
310     for (uint i=0; stringArray[i] != nullptr; ++i)
311     {
312         if (std::strcmp(stringArray[i], string) == 0)
313             return true;
314     }
315 
316     return false;
317 }
318 
319 //---------------------------------------------------------------------------------------------------------------------
320 // Get index of a QList<double> value
321 
getIndexOfQDoubleListValue(const QList<double> & list,const double value)322 int getIndexOfQDoubleListValue(const QList<double>& list, const double value)
323 {
324     if (list.size() > 0)
325     {
326         for (QList<double>::const_iterator n = list.cbegin(), e = list.cend(); n != e; ++n)
327             if (carla_isEqual(*n, value))
328                 return int(n - list.cbegin());
329     }
330 
331     return -1;
332 }
333 
334 //---------------------------------------------------------------------------------------------------------------------
335 // Check if two QList<double> instances match
336 
isQDoubleListEqual(const QList<double> & list1,const QList<double> & list2)337 bool isQDoubleListEqual(const QList<double>& list1, const QList<double>& list2)
338 {
339     if (list1.size() != list2.size())
340         return false;
341     if (list1.isEmpty())
342         return true;
343 
344     for (QList<double>::const_iterator l1n = list1.cbegin(), l2n = list2.cbegin(), l1e = list1.cend(); l1n != l1e; ++l1n, ++l2n)
345         if (carla_isNotEqual(*l1n, *l2n))
346             return false;
347 
348     return true;
349 }
350 
351 //---------------------------------------------------------------------------------------------------------------------
352 // Custom QMessageBox which resizes itself to fit text
353 
showEvent(QShowEvent * const event)354 void QMessageBoxWithBetterWidth::showEvent(QShowEvent* const event)
355 {
356     const QFontMetrics metrics(fontMetrics());
357     const QStringList lines(text().trimmed().split("\n") + informativeText().trimmed().split("\n"));
358 
359     if (lines.size() > 0)
360     {
361         int width = 0;
362 
363         for (const QString& line : lines)
364             width = std::max(fontMetricsHorizontalAdvance(metrics, line), width);
365 
366         if (QGridLayout* const layout_ = dynamic_cast<QGridLayout*>(layout()))
367             layout_->setColumnMinimumWidth(2, width + 12);
368     }
369 
370     QMessageBox::showEvent(event);
371 }
372 
373 //---------------------------------------------------------------------------------------------------------------------
374 // Safer QSettings class, which does not throw if type mismatches
375 
valueBool(const QString key,const bool defaultValue) const376 bool QSafeSettings::valueBool(const QString key, const bool defaultValue) const
377 {
378     QVariant var(value(key, defaultValue));
379 
380     if (var.isNull())
381         return defaultValue;
382 
383     CARLA_SAFE_ASSERT_RETURN(var.convert(QVariant::Bool), defaultValue);
384 
385     return var.isValid() ? var.toBool() : defaultValue;
386 }
387 
valueCheckState(const QString key,const Qt::CheckState defaultValue) const388 Qt::CheckState QSafeSettings::valueCheckState(const QString key, const Qt::CheckState defaultValue) const
389 {
390     QVariant var(value(key, defaultValue));
391 
392     if (var.isNull())
393         return defaultValue;
394 
395     CARLA_SAFE_ASSERT_RETURN(var.convert(QVariant::UInt), defaultValue);
396 
397     if (! var.isValid())
398         return defaultValue;
399 
400     const uint value = var.toUInt();
401 
402     switch (value)
403     {
404     case Qt::Unchecked:
405     case Qt::PartiallyChecked:
406     case Qt::Checked:
407         return static_cast<Qt::CheckState>(value);
408     default:
409         return defaultValue;
410     }
411 }
412 
valueIntPositive(const QString key,const int defaultValue) const413 int QSafeSettings::valueIntPositive(const QString key, const int defaultValue) const
414 {
415     CARLA_SAFE_ASSERT_INT(defaultValue >= 0, defaultValue);
416 
417     QVariant var(value(key, defaultValue));
418 
419     if (var.isNull())
420         return defaultValue;
421 
422     CARLA_SAFE_ASSERT_RETURN(var.convert(QVariant::Int), defaultValue);
423     CARLA_SAFE_ASSERT_RETURN(var.isValid(), defaultValue);
424 
425     const int value = var.toInt();
426     CARLA_SAFE_ASSERT_RETURN(value >= 0, defaultValue);
427 
428     return value;
429 }
430 
valueUInt(const QString key,const uint defaultValue) const431 uint QSafeSettings::valueUInt(const QString key, const uint defaultValue) const
432 {
433     QVariant var(value(key, defaultValue));
434 
435     if (var.isNull())
436         return defaultValue;
437 
438     CARLA_SAFE_ASSERT_RETURN(var.convert(QVariant::UInt), defaultValue);
439 
440     return var.isValid() ? var.toUInt() : defaultValue;
441 }
442 
valueDouble(const QString key,const double defaultValue) const443 double QSafeSettings::valueDouble(const QString key, const double defaultValue) const
444 {
445     QVariant var(value(key, defaultValue));
446 
447     if (var.isNull())
448         return defaultValue;
449 
450     CARLA_SAFE_ASSERT_RETURN(var.convert(QVariant::Double), defaultValue);
451 
452     return var.isValid() ? var.toDouble() : defaultValue;
453 }
454 
valueString(const QString key,const QString defaultValue) const455 QString QSafeSettings::valueString(const QString key, const QString defaultValue) const
456 {
457     QVariant var(value(key, defaultValue));
458 
459     if (var.isNull())
460         return defaultValue;
461 
462     CARLA_SAFE_ASSERT_RETURN(var.convert(QVariant::String), defaultValue);
463 
464     return var.isValid() ? var.toString() : defaultValue;
465 }
466 
valueByteArray(const QString key,const QByteArray defaultValue) const467 QByteArray QSafeSettings::valueByteArray(const QString key, const QByteArray defaultValue) const
468 {
469     QVariant var(value(key, defaultValue));
470 
471     if (var.isNull())
472         return defaultValue;
473 
474     CARLA_SAFE_ASSERT_RETURN(var.convert(QVariant::ByteArray), defaultValue);
475 
476     return var.isValid() ? var.toByteArray() : defaultValue;
477 }
478 
valueStringList(const QString key,const QStringList defaultValue) const479 QStringList QSafeSettings::valueStringList(const QString key, const QStringList defaultValue) const
480 {
481     QVariant var(value(key, defaultValue));
482 
483     if (var.isNull())
484         return defaultValue;
485 
486     CARLA_SAFE_ASSERT_RETURN(var.convert(QVariant::StringList), defaultValue);
487 
488     return var.isValid() ? var.toStringList() : defaultValue;
489 }
490 
491 //---------------------------------------------------------------------------------------------------------------------
492 // Custom MessageBox
493 
CustomMessageBox(QWidget * const parent,const QMessageBox::Icon icon,const QString title,const QString text,const QString extraText,const QMessageBox::StandardButtons buttons,const QMessageBox::StandardButton defButton)494 int CustomMessageBox(QWidget* const parent,
495                      const QMessageBox::Icon icon,
496                      const QString title,
497                      const QString text,
498                      const QString extraText,
499                      const QMessageBox::StandardButtons buttons,
500                      const QMessageBox::StandardButton defButton)
501 {
502     QMessageBoxWithBetterWidth msgBox(parent);
503     msgBox.setIcon(icon);
504     msgBox.setWindowTitle(title);
505     msgBox.setText(text);
506     msgBox.setInformativeText(extraText);
507     msgBox.setStandardButtons(buttons);
508     msgBox.setDefaultButton(defButton);
509     return msgBox.exec();
510 }
511 
512 //---------------------------------------------------------------------------------------------------------------------
513