#pragma once #include "FractoriumPch.h" /// /// Fractorium global utility functions. /// /// /// Setup a spinner to be placed in a table cell. /// Due to a serious compiler bug in MSVC, this must be declared as an outside function instead of a static member of Fractorium. /// The reason is that the default arguments of type valType will not be interpreted correctly by the compiler. /// If the bug is ever fixed, put it back as a static member function. /// /// The table the spinner belongs to /// The receiver object /// The row in the table where this spinner resides /// The col in the table where this spinner resides /// Double pointer to spin box which will hold the spinner upon exit /// The height of the spinner /// The minimum value of the spinner /// The maximum value of the spinner /// The step of the spinner /// The signal the spinner emits /// The slot to receive the signal /// Whether to increment the row value /// The default value for the spinner /// When the spinner has a value of zero and is double clicked, assign this value /// When the spinner has a value of non-zero and is double clicked, assign this value template static void SetupSpinner(QTableWidget* table, const QObject* receiver, int& row, int col, spinType*& spinBox, int height, valType min, valType max, valType step, const char* signal, const char* slot, bool incRow = true, valType val = 0, valType doubleClickZero = -999, valType doubleClickNonZero = -999) { spinBox = new spinType(table, height, step); spinBox->setRange(min, max); spinBox->setValue(val); if (col >= 0) table->setCellWidget(row, col, spinBox); if (string(signal) != "" && string(slot) != "") receiver->connect(spinBox, signal, receiver, slot, Qt::QueuedConnection); if (doubleClickNonZero != -999 && doubleClickZero != -999) { spinBox->DoubleClick(true); spinBox->DoubleClickZero(valType(doubleClickZero)); spinBox->DoubleClickNonZero(valType(doubleClickNonZero)); } if (incRow) row++; } /// /// Wrapper around QWidget::setTabOrder() to return the second widget. /// This makes it easy to chain multiple calls without having to retype /// all of them if the order changes or if a new widget is inserted. /// /// The parent widget that w1 and w2 belong to /// The widget to come first in the tab order /// The widget to come second in the tab order static QWidget* SetTabOrder(QWidget* p, QWidget* w1, QWidget* w2) { p->setTabOrder(w1, w2); return w2; } /// /// Wrapper around QLocale::system().toDouble(). /// /// The string to convert /// Pointer to boolean which stores the success value of the conversion /// The converted value if successful, else 0. static double ToDouble(const QString &s, bool *ok) { return QLocale::system().toDouble(s, ok); } /// /// Wrapper around QLocale::system().toString(). /// /// The value to convert /// The string value if successful, else "". template static QString ToString(T val) { return QLocale::system().toString(val); } /// /// Force a QString to end with the specified value. /// /// The string to append a suffix to /// The suffix to append /// The original string value if it already ended in e, else the original value appended with e. template static QString MakeEnd(const QString& s, T e) { if (!s.endsWith(e)) return s + e; else return s; } /// /// Check if a path is not empty and exists on the file system. /// /// The path to check /// True if s was not empty and existed, else false. static bool Exists(const QString& s) { return s != "" && QDir(s).exists(); } /// /// Convert a color to one that is displayable on any background. /// /// The color to convert /// The converted color static QColor VisibleColor(const QColor& color) { int threshold = 105; int delta = (color.red() * 0.299) + //Magic numbers gotten from a Stack Overflow post. (color.green() * 0.587) + (color.blue() * 0.114); QColor textColor = (255 - delta < threshold) ? QColor(0, 0, 0) : QColor(255, 255, 255); return textColor; } /// /// Determine whether an xform in an ember is linked to any other xform /// in the ember. /// /// The ember which contains the xform /// The xform to inspect /// The index of the xform that the xform argument is linked to, else -1 template static intmax_t IsXformLinked(Ember& ember, Xform* xform) { auto count = ember.XformCount(); auto index = ember.GetXformIndex(xform); intmax_t linked = -1; size_t toOneCount = 0; size_t toZeroCount = 0; size_t toOneIndex = 0; size_t fromOneCount = 0; size_t fromZeroCount = 0; size_t fromOneIndex = 0; if (index >= 0) { for (auto i = 0; i < count; i++) { if (xform->Xaos(i) == 0) toZeroCount++; else if (xform->Xaos(i) == 1) { toOneIndex = i; toOneCount++; } } if ((toZeroCount == (count - 1)) && toOneCount == 1) { for (auto i = 0; i < count; i++) { if (auto fromXform = ember.GetXform(i)) { if (fromXform->Xaos(toOneIndex) == 0) fromZeroCount++; else if (fromXform->Xaos(toOneIndex) == 1) { fromOneIndex = i; fromOneCount++; } } } if ((fromZeroCount == (count - 1)) && fromOneCount == 1) { linked = toOneIndex; } } } return linked; } /// /// Convert the passed in QList of absolute device indices to a vector> of platform,device /// index pairs. /// /// The absolute device indices /// The converted device vector of platform,device index pairs static vector> Devices(const QList& selectedDevices) { vector> vec; OpenCLInfo& info = OpenCLInfo::Instance(); auto& devices = info.DeviceIndices(); vec.reserve(selectedDevices.size()); for (size_t i = 0; i < selectedDevices.size(); i++) { auto index = selectedDevices[i].toUInt(); if (index < devices.size()) vec.push_back(devices[index]); } return vec; } /// /// Setup a table showing all available OpenCL devices on the system. /// Create checkboxes and radio buttons which allow the user to specify /// which devices to use, and which one to make the primary device. /// Used in the options dialog and the final render dialog. /// /// The QTableWidget to setup /// The absolute indices of the devices to use, with the first being the primary. static void SetupDeviceTable(QTableWidget* table, const QList& settingsDevices) { bool primary = false; auto& deviceNames = OpenCLInfo::Instance().AllDeviceNames(); table->clearContents(); table->setRowCount(deviceNames.size()); for (size_t i = 0; i < deviceNames.size(); i++) { auto checkItem = new QTableWidgetItem(); auto radio = new QRadioButton(); auto deviceItem = new QTableWidgetItem(QString::fromStdString(deviceNames[i])); table->setItem(i, 0, checkItem); table->setCellWidget(i, 1, radio); table->setItem(i, 2, deviceItem); if (settingsDevices.contains(QVariant::fromValue(i))) { checkItem->setCheckState(Qt::Checked); if (!primary) { radio->setChecked(true); primary = true; } } else checkItem->setCheckState(Qt::Unchecked); } if (!primary && table->rowCount() > 0)//Primary was never set, so just default to the first device and hope it was the one detected as the main display. { table->item(0, 0)->setCheckState(Qt::Checked); qobject_cast(table->cellWidget(0, 1))->setChecked(true); } } /// /// Copy the passed in selected absolute device indices to the controls on the passed in table. /// Used in the options dialog and the final render dialog. /// /// The QTableWidget to copy values to /// The absolute indices of the devices to use, with the first being the primary. static void SettingsToDeviceTable(QTableWidget* table, QList& settingsDevices) { if (settingsDevices.empty() && table->rowCount() > 0) { table->item(0, 0)->setCheckState(Qt::Checked); qobject_cast(table->cellWidget(0, 1))->setChecked(true); for (int row = 1; row < table->rowCount(); row++) if (auto item = table->item(row, 0)) item->setCheckState(Qt::Unchecked); } else { for (int row = 0; row < table->rowCount(); row++) { if (auto item = table->item(row, 0)) { if (settingsDevices.contains(row)) { item->setCheckState(Qt::Checked); if (!settingsDevices.indexOf(QVariant::fromValue(row))) if (auto radio = qobject_cast(table->cellWidget(row, 1))) radio->setChecked(true); } else { item->setCheckState(Qt::Unchecked); } } } } } /// /// Copy the values of the controls on the passed in table to a list of absolute device indices. /// Used in the options dialog and the final render dialog. /// /// The QTableWidget to copy values from /// The list of absolute device indices static QList DeviceTableToSettings(QTableWidget* table) { QList devices; auto rows = table->rowCount(); for (int row = 0; row < rows; row++) { auto checkItem = table->item(row, 0); auto radio = qobject_cast(table->cellWidget(row, 1)); if (checkItem->checkState() == Qt::Checked) { if (radio && radio->isChecked()) devices.push_front(row); else devices.push_back(row); } } return devices; } /// /// Ensure device selection on the passed in table make sense. /// /// The QTableWidget to setup /// The row of the cell /// The column of the cell static void HandleDeviceTableCheckChanged(QTableWidget* table, int row, int col) { int primaryRow = -1; QRadioButton* primaryRadio = nullptr; for (int i = 0; i < table->rowCount(); i++) { if (auto radio = qobject_cast(table->cellWidget(i, 1))) { if (radio->isChecked()) { primaryRow = i; primaryRadio = radio; break; } } } if (primaryRow == -1) primaryRow = 0; if (auto primaryItem = table->item(primaryRow, 0)) if (primaryItem->checkState() == Qt::Unchecked) primaryItem->setCheckState(Qt::Checked); }