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