<<<<<<< HEAD #include "FractoriumPch.h" #include "Fractorium.h" #include "PaletteTableWidgetItem.h" /// /// Initialize the palette UI. /// void Fractorium::InitPaletteUI() { int spinHeight = 20, row = 0; auto paletteTable = ui.PaletteListTable; auto palettePreviewTable = ui.PalettePreviewTable; connect(ui.PaletteFilenameCombo, SIGNAL(currentTextChanged(const QString&)), this, SLOT(OnPaletteFilenameComboChanged(const QString&)), Qt::QueuedConnection); connect(paletteTable, SIGNAL(cellClicked(int, int)), this, SLOT(OnPaletteCellClicked(int, int)), Qt::QueuedConnection); connect(paletteTable, SIGNAL(cellDoubleClicked(int, int)), this, SLOT(OnPaletteCellDoubleClicked(int, int)), Qt::QueuedConnection); connect(palettePreviewTable, SIGNAL(MouseDragged(const QPointF&, const QPointF&)), this, SLOT(OnPreviewPaletteMouseDragged(const QPointF&, const QPointF&)), Qt::QueuedConnection); connect(palettePreviewTable, SIGNAL(MouseReleased()), this, SLOT(OnPreviewPaletteMouseReleased()), Qt::QueuedConnection); connect(palettePreviewTable, SIGNAL(cellDoubleClicked(int, int)), this, SLOT(OnPreviewPaletteCellDoubleClicked(int, int)), Qt::QueuedConnection); connect(palettePreviewTable, SIGNAL(cellPressed(int, int)), this, SLOT(OnPreviewPaletteCellPressed(int, int)), Qt::QueuedConnection); //Palette adjustment table. auto table = ui.PaletteAdjustTable; table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);//Split width over all columns evenly. SetupSpinner(table, this, row, 1, m_PaletteHueSpin, spinHeight, -180, 180, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0); SetupSpinner(table, this, row, 1, m_PaletteSaturationSpin, spinHeight, -100, 100, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0); SetupSpinner(table, this, row, 1, m_PaletteBrightnessSpin, spinHeight, -255, 255, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0); row = 0; SetupSpinner(table, this, row, 3, m_PaletteContrastSpin, spinHeight, -100, 100, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0); SetupSpinner(table, this, row, 3, m_PaletteBlurSpin, spinHeight, 0, 127, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0); SetupSpinner(table, this, row, 3, m_PaletteFrequencySpin, spinHeight, 1, 10, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 1, 1, 1); connect(ui.PaletteRandomSelectButton, SIGNAL(clicked(bool)), this, SLOT(OnPaletteRandomSelectButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PaletteRandomAdjustButton, SIGNAL(clicked(bool)), this, SLOT(OnPaletteRandomAdjustButtonClicked(bool)), Qt::QueuedConnection); //Palette editor. connect(ui.PaletteEditorButton, SIGNAL(clicked(bool)), this, SLOT(OnPaletteEditorButtonClicked(bool)), Qt::QueuedConnection); //Preview table. palettePreviewTable->setRowCount(1); palettePreviewTable->setColumnWidth(1, 260);//256 plus small margin on each side. auto previewNameCol = new QTableWidgetItem(""); palettePreviewTable->setItem(0, 0, previewNameCol); auto previewPaletteItem = new QTableWidgetItem(); palettePreviewTable->setItem(0, 1, previewPaletteItem); connect(ui.PaletteFilterLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(OnPaletteFilterLineEditTextChanged(const QString&))); connect(ui.PaletteFilterClearButton, SIGNAL(clicked(bool)), this, SLOT(OnPaletteFilterClearButtonClicked(bool))); paletteTable->setColumnWidth(1, 260);//256 plus small margin on each side. paletteTable->horizontalHeader()->setSectionsClickable(true); connect(paletteTable->horizontalHeader(), SIGNAL(sectionClicked(int)), this, SLOT(OnPaletteHeaderSectionClicked(int)), Qt::QueuedConnection); connect(ui.ResetCurvesButton, SIGNAL(clicked(bool)), this, SLOT(OnResetCurvesButtonClicked(bool)), Qt::QueuedConnection); connect(ui.CurvesView, SIGNAL(PointChangedSignal(int, int, const QPointF&)), this, SLOT(OnCurvesPointChanged(int, int, const QPointF&)), Qt::QueuedConnection); connect(ui.CurvesView, SIGNAL(PointAddedSignal(size_t, const QPointF&)), this, SLOT(OnCurvesPointAdded(size_t, const QPointF&)), Qt::QueuedConnection); connect(ui.CurvesView, SIGNAL(PointRemovedSignal(size_t, int)), this, SLOT(OnCurvesPointRemoved(size_t, int)), Qt::QueuedConnection); connect(ui.CurvesAllRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesAllRadioButtonToggled(bool)), Qt::QueuedConnection); connect(ui.CurvesRedRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesRedRadioButtonToggled(bool)), Qt::QueuedConnection); connect(ui.CurvesGreenRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesGreenRadioButtonToggled(bool)), Qt::QueuedConnection); connect(ui.CurvesBlueRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesBlueRadioButtonToggled(bool)), Qt::QueuedConnection); } /// /// Read all palette Xml files in the specified folder and populate the palette list with the contents. /// This will clear any previous contents. /// Called upon initialization, or controller type change. /// /// The full path to the palette files folder /// The number of palettes successfully added template size_t FractoriumEmberController::InitPaletteList(const QString& s) { QDirIterator it(s, QStringList() << "*.xml" << "*.ugr" << "*.gradient" << "*.gradients", QDir::Files, QDirIterator::FollowSymlinks); while (it.hasNext()) { auto path = it.next(); auto qfilename = it.fileName(); try { if (QFile::exists(path) && m_PaletteList->Add(path.toStdString())) m_Fractorium->ui.PaletteFilenameCombo->addItem(qfilename); } catch (const std::exception& e) { QMessageBox::critical(nullptr, "Palette Parsing Error", QString::fromStdString(e.what())); } catch (const char* e) { QMessageBox::critical(nullptr, "Palette Parsing Error", e); } } m_Fractorium->ui.PaletteFilenameCombo->model()->sort(0); return m_PaletteList->Size(); } /// /// Read a palette Xml file and populate the palette table with the contents. /// This will clear any previous contents. /// Called upon initialization, palette combo index change, and controller type change. /// /// The name of the palette file without the path /// True if successful, else false. template bool FractoriumEmberController::FillPaletteTable(const string& s) { if (!s.empty())//This occasionally seems to get called with an empty string for reasons unknown. { auto paletteTable = m_Fractorium->ui.PaletteListTable; m_CurrentPaletteFilePath = s; if (::FillPaletteTable(m_CurrentPaletteFilePath, paletteTable, m_PaletteList)) { return true; } else { vector errors = m_PaletteList->ErrorReport(); m_Fractorium->ErrorReportToQTextEdit(errors, m_Fractorium->ui.InfoFileOpeningTextEdit); m_Fractorium->ShowCritical("Palette Read Error", "Could not load palette file, all images will be black. See info tab for details."); m_PaletteList->ClearErrorReport(); } } return false; } /// /// Fill the palette table with the passed in string. /// Called when the palette name combo box changes. /// /// The full path to the palette file void Fractorium::OnPaletteFilenameComboChanged(const QString& text) { auto s = text.toStdString(); m_Controller->FillPaletteTable(s); auto fullname = m_Controller->m_PaletteList->GetFullPathFromFilename(s); ui.PaletteFilenameCombo->setToolTip(QString::fromStdString(fullname)); ui.PaletteListTable->sortItems(0, m_PaletteSortMode == 0 ? Qt::AscendingOrder : Qt::DescendingOrder); } /// /// Apply adjustments to the current ember's palette. /// template void FractoriumEmberController::ApplyPaletteToEmber() { const uint blur = m_Fractorium->m_PaletteBlurSpin->value(); const uint freq = m_Fractorium->m_PaletteFrequencySpin->value(); const auto sat = m_Fractorium->m_PaletteSaturationSpin->value() / 100.0; const auto brightness = m_Fractorium->m_PaletteBrightnessSpin->value() / 255.0; const auto contrast = double(m_Fractorium->m_PaletteContrastSpin->value() > 0 ? m_Fractorium->m_PaletteContrastSpin->value() * 2.0 : m_Fractorium->m_PaletteContrastSpin->value()) / 100.0; const auto hue = m_Fractorium->m_PaletteHueSpin->value() / 360.0; //Use the temp palette as the base and apply the adjustments gotten from the GUI and save the result in the ember palette. m_TempPalette.MakeAdjustedPalette(m_Ember.m_Palette, m_Fractorium->m_PreviewPaletteRotation, hue, sat, brightness, contrast, blur, freq); } /// /// Use adjusted palette to update all related GUI controls with new color values. /// Resets the rendering process. /// /// The palette to use /// Name of the palette template void FractoriumEmberController::UpdateAdjustedPaletteGUI(Palette& palette) { const auto xform = CurrentXform(); const auto palettePreviewTable = m_Fractorium->ui.PalettePreviewTable; const auto paletteName = QString::fromStdString(m_Ember.m_Palette.m_Name); auto previewPaletteItem = palettePreviewTable->item(0, 1); if (previewPaletteItem)//This can be null if the palette file was moved or corrupted. { //Use the adjusted palette to fill the preview palette control so the user can see the effects of applying the adjustements. vector v = palette.MakeRgbPaletteBlock(PALETTE_CELL_HEIGHT);//Make the palette repeat for PALETTE_CELL_HEIGHT rows. m_FinalPaletteImage = QImage(int(palette.Size()), PALETTE_CELL_HEIGHT, QImage::Format_RGB888);//Create a QImage out of it. memcpy(m_FinalPaletteImage.scanLine(0), v.data(), v.size() * sizeof(v[0]));//Memcpy the data in. QPixmap pixmap(QPixmap::fromImage(m_FinalPaletteImage));//Create a QPixmap out of the QImage. previewPaletteItem->setData(Qt::DecorationRole, pixmap.scaled(QSize(pixmap.width(), palettePreviewTable->rowHeight(0) + 2), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));//Set the pixmap on the palette tab. m_Fractorium->SetPaletteTableItem(&pixmap, m_Fractorium->ui.XformPaletteRefTable, m_Fractorium->m_PaletteRefItem, 0, 0);//Set the palette ref table on the xforms | color tab. if (auto previewNameItem = palettePreviewTable->item(0, 0)) { previewNameItem->setText(paletteName);//Finally, set the name of the palette to be both the text and the tooltip. previewNameItem->setToolTip(paletteName); } } //Update the current xform's color and reset the rendering process. //Update all controls to be safe. if (xform) XformColorIndexChanged(xform->m_ColorX, true, true, true); } /// /// Apply all adjustments to the selected palette, show it /// and assign it to the current ember. /// Called when any adjustment spinner is modified. /// Resets the rendering process. /// template void FractoriumEmberController::PaletteAdjust() { Update([&]() { ApplyPaletteToEmber(); UpdateAdjustedPaletteGUI(m_Ember.m_Palette); }); } void Fractorium::OnPaletteAdjust(int d) { m_Controller->PaletteAdjust(); } /// /// Set the passed in palette as the current one, /// applying any adjustments previously specified. /// Resets the rendering process. /// /// The palette to assign to the temporary palette template void FractoriumEmberController::SetBasePaletteAndAdjust(const Palette& palette) { //The temp palette is assigned the palette read when the file was parsed/saved. The user can apply adjustments on the GUI later. //These adjustments will be applied to the temp palette, then assigned back to m_Ember.m_Palette. m_TempPalette = palette;//Deep copy. ApplyPaletteToEmber();//Copy temp palette to ember palette and apply adjustments. UpdateAdjustedPaletteGUI(m_Ember.m_Palette);//Show the adjusted palette. } /// /// Set the selected palette as the current one, /// applying any adjustments previously specified. /// Called when a palette cell is clicked. Unfortunately, /// this will get called twice on a double click when moving /// from one palette to another. It happens quickly so it shouldn't /// be too much of a problem. /// Resets the rendering process. /// /// The table row clicked /// The table column clicked template void FractoriumEmberController::PaletteCellClicked(int row, int col) { if (const auto palette = m_PaletteList->GetPaletteByFilename(m_CurrentPaletteFilePath, row)) SetBasePaletteAndAdjust(*palette); } /// /// Map the palette in the clicked row index to the index /// in the palette list, then pass that index to PaletteCellClicked(). /// This resolves the case where the sort order of the palette table /// is different than the internal order of the palette list. /// /// The table row clicked /// The table column clicked, ignored void Fractorium::OnPaletteCellClicked(int row, int col) { if (const auto item = dynamic_cast(ui.PaletteListTable->item(row, 1))) { const auto index = int(item->Index()); if (m_PreviousPaletteRow != index) { m_Controller->PaletteCellClicked(index, col); m_PreviousPaletteRow = index;//Save for comparison on next click. } } } /// /// Called when the mouse has been moved while pressed on the palette preview table. /// Computes the difference between where the mouse was clicked and where it is now, then /// uses that difference as a rotation value to pass into the palette adjustment. /// Updates the palette and resets the rendering process. /// /// The local mouse coordinates relative to the palette preview table /// The global mouse coordinates void Fractorium::OnPreviewPaletteMouseDragged(const QPointF& local, const QPointF& global) { if (m_PreviewPaletteMouseDown) { m_PreviewPaletteRotation = m_PreviewPaletteMouseDownRotation + (global.x() - m_PreviewPaletteMouseDownPosition.x()); //qDebug() << "Palette preview table drag reached main window event: " << local.x() << ' ' << local.y() << ", global: " << global.x() << ' ' << global.y() << ", final: " << m_PreviewPaletteRotation; m_Controller->PaletteAdjust(); } } /// /// Called when the mouse has been released over the palette preview table. /// Does nothing but set the dragging state to false. /// void Fractorium::OnPreviewPaletteMouseReleased() { m_PreviewPaletteMouseDown = false; } /// /// Sets the palette rotation to zero. /// Updates the palette and resets the rendering process. /// /// Ignored /// Ignored void Fractorium::OnPreviewPaletteCellDoubleClicked(int row, int col) { m_PreviewPaletteRotation = m_PreviewPaletteMouseDownRotation = 0; m_PreviewPaletteMouseDown = false; m_Controller->PaletteAdjust(); } /// /// Called when the mouse has been pressed on the palette preview table. /// Subsequent mouse movements will compute a rotation value to pass into the palette adjustment, based on the location /// of the mouse when this slot is called. /// /// Ignored /// Ignored void Fractorium::OnPreviewPaletteCellPressed(int row, int col) { m_PreviewPaletteMouseDown = true; m_PreviewPaletteMouseDownPosition = QCursor::pos();//Get the global mouse position. m_PreviewPaletteMouseDownRotation = m_PreviewPaletteRotation; //qDebug() << "Mouse down with initial pos: " << m_PreviewPaletteMouseDownPosition.x() << " and initial rotation: " << m_PreviewPaletteMouseDownRotation; } /// /// Set the selected palette as the current one, /// resetting any adjustments previously specified. /// Called when a palette cell is double clicked. /// Resets the rendering process. /// /// The table row clicked /// The table column clicked void Fractorium::OnPaletteCellDoubleClicked(int row, int col) { ResetPaletteControls(); m_PreviousPaletteRow = -1; OnPaletteCellClicked(row, col); } /// /// Set the selected palette to a randomly selected one, /// applying any adjustments previously specified if the checked parameter is true. /// Called when the Random Palette button is clicked. /// Resets the rendering process. /// /// True to clear the current adjustments, else leave current adjustments and apply them to the newly selected palette. void Fractorium::OnPaletteRandomSelectButtonClicked(bool checked) { uint i = 0; const auto rowCount = ui.PaletteListTable->rowCount(); if (rowCount > 1)//If only one palette in the current palette file, just use it. while (((i = QTIsaac::LockedRand(rowCount)) == uint(m_PreviousPaletteRow)) || i >= static_cast(rowCount)); if (checked) OnPaletteCellDoubleClicked(i, 1);//Will clear the adjustments. else OnPaletteCellClicked(i, 1); } /// /// Apply random adjustments to the selected palette. /// Called when the Random Adjustment button is clicked. /// Resets the rendering process. /// void Fractorium::OnPaletteRandomAdjustButtonClicked(bool checked) { m_PaletteHueSpin->setValue(-180 + QTIsaac::LockedRand(361)); m_PaletteSaturationSpin->setValue(-50 + QTIsaac::LockedRand(101));//Full range of these leads to bad palettes, so clamp range. m_PaletteBrightnessSpin->setValue(-50 + QTIsaac::LockedRand(101)); m_PaletteContrastSpin->setValue(-50 + QTIsaac::LockedRand(101)); //Doing frequency and blur together gives bad palettes that are just a solid color. if (QTIsaac::LockedRandBit()) { m_PaletteBlurSpin->setValue(QTIsaac::LockedRand(21)); m_PaletteFrequencySpin->setValue(1); } else { m_PaletteBlurSpin->setValue(0); m_PaletteFrequencySpin->setValue(1 + QTIsaac::LockedRand(10)); } OnPaletteAdjust(0); } /// /// Open the palette editor dialog. /// Called when the palette editor button is clicked. /// template void FractoriumEmberController::PaletteEditorButtonClicked() { size_t i = 0; const auto ed = m_Fractorium->m_PaletteEditor.get(); map colorIndices; const auto forceFinal = m_Fractorium->HaveFinal(); m_PreviousTempPalette = m_TempPalette; // it's necessary because m_TempPalette is changed when the user make changes in palette editor ed->SetPalette(m_TempPalette); while (auto xform = m_Ember.GetTotalXform(i, forceFinal)) colorIndices[i++] = xform->m_ColorX; ed->SetColorIndices(colorIndices); ed->SetPreviousColorIndices(colorIndices); // also necessary because the colors are changed in palette editor ed->SetPaletteFile(m_CurrentPaletteFilePath); #ifdef __linux__ ed->show(); #else SyncPalette(ed->exec() == QDialog::Accepted); #endif } /// /// Slot called when the palette editor changes the palette and the Sync checkbox is checked. /// bool Fractorium::PaletteChanged() { return m_PaletteChanged; } /// /// Open the palette editor dialog. /// This creates the palette editor dialog if it has not been created at least once. /// Called when the palette editor button is clicked. /// /// Ignored void Fractorium::OnPaletteEditorButtonClicked(bool checked) { if (!m_PaletteEditor.get()) { m_PaletteEditor = std::make_unique(this); connect(m_PaletteEditor.get(), SIGNAL(PaletteChanged()), this, SLOT(OnPaletteEditorColorChanged()), Qt::QueuedConnection); connect(m_PaletteEditor.get(), SIGNAL(PaletteFileChanged()), this, SLOT(OnPaletteEditorFileChanged()), Qt::QueuedConnection); connect(m_PaletteEditor.get(), SIGNAL(ColorIndexChanged(size_t, float)), this, SLOT(OnPaletteEditorColorIndexChanged(size_t, float)), Qt::QueuedConnection); #ifdef __linux__ connect(m_PaletteEditor.get(), SIGNAL(finished(int)), this, SLOT(OnPaletteEditorFinished(int)), Qt::QueuedConnection); #endif } m_PaletteChanged = false; m_PaletteFileChanged = false; m_Controller->PaletteEditorButtonClicked(); } /// /// Slot called when palette editor window is closed. /// template void FractoriumEmberController::SyncPalette(bool accepted) { const auto ed = m_Fractorium->m_PaletteEditor.get(); Palette edPal; Palette prevPal = m_PreviousTempPalette; map colorIndices; const auto forceFinal = m_Fractorium->HaveFinal(); if (accepted) { //Copy all just to be safe, because they may or may not have synced. colorIndices = ed->GetColorIndices(); for (auto& index : colorIndices) if (auto xform = m_Ember.GetTotalXform(index.first, forceFinal)) xform->m_ColorX = index.second; edPal = ed->GetPalette(static_cast(prevPal.Size())); SetBasePaletteAndAdjust(edPal);//This will take care of updating the color index controls. if (edPal.m_Filename.get() && !edPal.m_Filename->empty()) m_Fractorium->SetPaletteFileComboIndex(*edPal.m_Filename); } else if (m_Fractorium->PaletteChanged())//They clicked cancel, but synced at least once, restore the previous palette. { colorIndices = ed->GetPreviousColorIndices(); for (auto& index : colorIndices) if (auto xform = m_Ember.GetTotalXform(index.first, forceFinal)) xform->m_ColorX = index.second; SetBasePaletteAndAdjust(prevPal);//This will take care of updating the color index controls. } //Whether the current palette file was changed or not, if it's modifiable then reload it just to be safe (even though it might be overkill). if (m_PaletteList->IsModifiable(m_CurrentPaletteFilePath)) m_Fractorium->OnPaletteFilenameComboChanged(QString::fromStdString(m_CurrentPaletteFilePath)); } /// /// Slot called every time a color is changed in the palette editor. /// template void FractoriumEmberController::PaletteEditorColorChanged() { SetBasePaletteAndAdjust(m_Fractorium->m_PaletteEditor->GetPalette(static_cast(m_TempPalette.Size()))); } void Fractorium::OnPaletteEditorColorChanged() { m_PaletteChanged = true; m_Controller->PaletteEditorColorChanged(); } /// /// Slot called every time a palette file is changed in the palette editor. /// void Fractorium::OnPaletteEditorFileChanged() { m_PaletteFileChanged = true; } /// /// Slot called every time an xform color index is changed in the palette editor. /// If a special value of size_t max is passed for index, it means update all color indices. /// /// The index of the xform whose color index has been changed. Special value of size_t max to update all /// The value of the color index void Fractorium::OnPaletteEditorColorIndexChanged(size_t index, float value) { if (index == std::numeric_limits::max())//Update all in this case. { auto indices = m_PaletteEditor->GetColorIndices(); for (auto& it : indices) OnXformColorIndexChanged(it.second, true, true, true, eXformUpdate::UPDATE_SPECIFIC, it.first); } else//Only update the xform index that was selected and dragged inside of the palette editor. OnXformColorIndexChanged(value, true, true, true, eXformUpdate::UPDATE_SPECIFIC, index); } /// Slot called after EditPallete is closed. /// /// Cancel/OK action void Fractorium::OnPaletteEditorFinished(int result) { m_Controller->SyncPalette(result == QDialog::Accepted); } /// /// Apply the text in the palette filter text box to only show palettes whose names /// contain the substring. /// Called when the user types in the palette filter text box. /// /// The text to filter on void Fractorium::OnPaletteFilterLineEditTextChanged(const QString& text) { auto table = ui.PaletteListTable; table->setUpdatesEnabled(false); for (int i = 0; i < table->rowCount(); i++) { if (auto item = table->item(i, 0)) { if (!item->text().contains(text, Qt::CaseInsensitive)) table->hideRow(i); else table->showRow(i); } } ui.PaletteListTable->sortItems(0, m_PaletteSortMode == 0 ? Qt::AscendingOrder : Qt::DescendingOrder);//Must re-sort every time the filter changes. table->setUpdatesEnabled(true); } /// /// Clear the palette name filter, which will display all palettes. /// Called when clear palette filter button is clicked. /// /// Ignored void Fractorium::OnPaletteFilterClearButtonClicked(bool checked) { ui.PaletteFilterLineEdit->clear(); } /// /// Change the sorting to be either ascending or descending. /// Called when user clicks the table headers. /// /// Column index of the header clicked, ignored. void Fractorium::OnPaletteHeaderSectionClicked(int col) { m_PaletteSortMode = !m_PaletteSortMode; ui.PaletteListTable->sortItems(0, m_PaletteSortMode == 0 ? Qt::AscendingOrder : Qt::DescendingOrder); } /// /// Reset the palette controls. /// Usually in response to a palette cell double click. /// void Fractorium::ResetPaletteControls() { m_PreviewPaletteRotation = m_PreviewPaletteMouseDownRotation = 0; m_PaletteHueSpin->SetValueStealth(0); m_PaletteSaturationSpin->SetValueStealth(0); m_PaletteBrightnessSpin->SetValueStealth(0); m_PaletteContrastSpin->SetValueStealth(0); m_PaletteBlurSpin->SetValueStealth(0); m_PaletteFrequencySpin->SetValueStealth(1); } /// /// Set the index of the palette file combo box. /// This is for display purposes only so the user can see which file, if any, /// the current palette came from. /// For embedded palettes with no filename, this will have no effect. /// /// The string to set the index to void Fractorium::SetPaletteFileComboIndex(const string& filename) { if (!filename.empty()) ui.PaletteFilenameCombo->setCurrentText(QFileInfo(QString::fromStdString(filename)).fileName()); } /// /// Reset the color curve values for the selected curve in the current ember to their default state and also update the curves control. /// Called when ResetCurvesButton is clicked. /// Note if they click Reset Curves when the ctrl is pressed, then it clears all curves. /// Resets the rendering process at either ACCUM_ONLY by default, or FILTER_AND_ACCUM when using early clip. /// /// The index of the curve to be cleared, 0 to clear all. template void FractoriumEmberController::ClearColorCurves(int i) { UpdateAll([&](Ember& ember, bool isMain) { if (i < 0) ember.m_Curves.Init(); else ember.m_Curves.Init(i); }, true, m_Renderer->EarlyClip() ? eProcessAction::FILTER_AND_ACCUM : eProcessAction::ACCUM_ONLY, m_Fractorium->ApplyAll()); FillCurvesControl(); } void Fractorium::OnResetCurvesButtonClicked(bool checked) { if (!QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) { if (ui.CurvesAllRadio->isChecked()) m_Controller->ClearColorCurves(0); else if (ui.CurvesRedRadio->isChecked()) m_Controller->ClearColorCurves(1); else if (ui.CurvesGreenRadio->isChecked()) m_Controller->ClearColorCurves(2); else if (ui.CurvesBlueRadio->isChecked()) m_Controller->ClearColorCurves(3); else m_Controller->ClearColorCurves(0); } else { m_Controller->ClearColorCurves(-1); } } /// /// Set the coordinate of the curve point. /// Called when the position of any of the points in the curves editor is is changed. /// Resets the rendering process at either ACCUM_ONLY by default, or FILTER_AND_ACCUM when using early clip. /// /// The curve index, 0-3/ /// The point index within the selected curve, 1-2. /// The new coordinate of the point in terms of the curves control rect. template void FractoriumEmberController::ColorCurveChanged(int curveIndex, int pointIndex, const QPointF& point) { Update([&] { m_Ember.m_Curves.m_Points[curveIndex][pointIndex].x = point.x(); m_Ember.m_Curves.m_Points[curveIndex][pointIndex].y = point.y(); }, true, m_Renderer->EarlyClip() ? eProcessAction::FILTER_AND_ACCUM : eProcessAction::ACCUM_ONLY); } void Fractorium::OnCurvesPointChanged(int curveIndex, int pointIndex, const QPointF& point) { m_Controller->ColorCurveChanged(curveIndex, pointIndex, point); } /// /// Remove curve point. /// Called when right clicking on a color curve point. /// Resets the rendering process at either ACCUM_ONLY by default, or FILTER_AND_ACCUM when using early clip. /// /// The curve index./ /// The point index within the selected curve. template void FractoriumEmberController::ColorCurvesPointRemoved(size_t curveIndex, int pointIndex) { Update([&] { if (m_Ember.m_Curves.m_Points[curveIndex].size() > 2) { m_Ember.m_Curves.m_Points[curveIndex].erase(m_Ember.m_Curves.m_Points[curveIndex].begin() + pointIndex); std::sort(m_Ember.m_Curves.m_Points[curveIndex].begin(), m_Ember.m_Curves.m_Points[curveIndex].end(), [&](auto & lhs, auto & rhs) { return lhs.x < rhs.x; }); } }, true, m_Renderer->EarlyClip() ? eProcessAction::FILTER_AND_ACCUM : eProcessAction::ACCUM_ONLY); FillCurvesControl(); } void Fractorium::OnCurvesPointRemoved(size_t curveIndex, int pointIndex) { m_Controller->ColorCurvesPointRemoved(curveIndex, pointIndex); } /// /// Add a curve point. /// Called when clicking in between points on a color curve. /// Resets the rendering process at either ACCUM_ONLY by default, or FILTER_AND_ACCUM when using early clip. /// /// The curve index./ /// The point to add to the selected curve. template void FractoriumEmberController::ColorCurvesPointAdded(size_t curveIndex, const QPointF& point) { Update([&] { m_Ember.m_Curves.m_Points[curveIndex].push_back({ point.x(), point.y() }); std::sort(m_Ember.m_Curves.m_Points[curveIndex].begin(), m_Ember.m_Curves.m_Points[curveIndex].end(), [&](auto & lhs, auto & rhs) { return lhs.x < rhs.x; }); }, true, m_Renderer->EarlyClip() ? eProcessAction::FILTER_AND_ACCUM : eProcessAction::ACCUM_ONLY); FillCurvesControl(); } void Fractorium::OnCurvesPointAdded(size_t curveIndex, const QPointF& point) { m_Controller->ColorCurvesPointAdded(curveIndex, point); } /// /// Set the top most points in the curves control, which makes it easier to /// select a point by putting it on top of all the others. /// Called when the any of the curve color radio buttons are toggled. /// /// Ignored void Fractorium::OnCurvesAllRadioButtonToggled(bool checked) { if (checked) ui.CurvesView->SetTop(CurveIndex::ALL); } void Fractorium::OnCurvesRedRadioButtonToggled(bool checked) { if (checked) ui.CurvesView->SetTop(CurveIndex::RED); } void Fractorium::OnCurvesGreenRadioButtonToggled(bool checked) { if (checked) ui.CurvesView->SetTop(CurveIndex::GREEN); } void Fractorium::OnCurvesBlueRadioButtonToggled(bool checked) { if (checked) ui.CurvesView->SetTop(CurveIndex::BLUE); } /// /// Set the points in the curves control to the values of the curve points in the current ember. /// template void FractoriumEmberController::FillCurvesControl() { m_Fractorium->ui.CurvesView->blockSignals(true); m_Fractorium->ui.CurvesView->Set(m_Ember.m_Curves); m_Fractorium->ui.CurvesView->blockSignals(false); m_Fractorium->ui.CurvesView->update(); } template class FractoriumEmberController; #ifdef DO_DOUBLE template class FractoriumEmberController; #endif ======= #include "FractoriumPch.h" #include "Fractorium.h" #include "PaletteTableWidgetItem.h" /// /// Initialize the palette UI. /// void Fractorium::InitPaletteUI() { int spinHeight = 20, row = 0; auto paletteTable = ui.PaletteListTable; auto palettePreviewTable = ui.PalettePreviewTable; connect(ui.PaletteFilenameCombo, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(OnPaletteFilenameComboChanged(const QString&)), Qt::QueuedConnection); connect(paletteTable, SIGNAL(cellClicked(int, int)), this, SLOT(OnPaletteCellClicked(int, int)), Qt::QueuedConnection); connect(paletteTable, SIGNAL(cellDoubleClicked(int, int)), this, SLOT(OnPaletteCellDoubleClicked(int, int)), Qt::QueuedConnection); connect(palettePreviewTable, SIGNAL(MouseDragged(const QPointF&, const QPoint&)), this, SLOT(OnPreviewPaletteMouseDragged(const QPointF&, const QPoint&)), Qt::QueuedConnection); connect(palettePreviewTable, SIGNAL(MouseReleased()), this, SLOT(OnPreviewPaletteMouseReleased()), Qt::QueuedConnection); connect(palettePreviewTable, SIGNAL(cellDoubleClicked(int, int)), this, SLOT(OnPreviewPaletteCellDoubleClicked(int, int)), Qt::QueuedConnection); connect(palettePreviewTable, SIGNAL(cellPressed(int, int)), this, SLOT(OnPreviewPaletteCellPressed(int, int)), Qt::QueuedConnection); //Palette adjustment table. auto table = ui.PaletteAdjustTable; table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);//Split width over all columns evenly. SetupSpinner(table, this, row, 1, m_PaletteHueSpin, spinHeight, -180, 180, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0); SetupSpinner(table, this, row, 1, m_PaletteSaturationSpin, spinHeight, -100, 100, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0); SetupSpinner(table, this, row, 1, m_PaletteBrightnessSpin, spinHeight, -255, 255, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0); row = 0; SetupSpinner(table, this, row, 3, m_PaletteContrastSpin, spinHeight, -100, 100, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0); SetupSpinner(table, this, row, 3, m_PaletteBlurSpin, spinHeight, 0, 127, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0); SetupSpinner(table, this, row, 3, m_PaletteFrequencySpin, spinHeight, 1, 10, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 1, 1, 1); connect(ui.PaletteRandomSelectButton, SIGNAL(clicked(bool)), this, SLOT(OnPaletteRandomSelectButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PaletteRandomAdjustButton, SIGNAL(clicked(bool)), this, SLOT(OnPaletteRandomAdjustButtonClicked(bool)), Qt::QueuedConnection); //Palette editor. connect(ui.PaletteEditorButton, SIGNAL(clicked(bool)), this, SLOT(OnPaletteEditorButtonClicked(bool)), Qt::QueuedConnection); //Preview table. palettePreviewTable->setRowCount(1); palettePreviewTable->setColumnWidth(1, 260);//256 plus small margin on each side. auto previewNameCol = new QTableWidgetItem(""); palettePreviewTable->setItem(0, 0, previewNameCol); auto previewPaletteItem = new QTableWidgetItem(); palettePreviewTable->setItem(0, 1, previewPaletteItem); connect(ui.PaletteFilterLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(OnPaletteFilterLineEditTextChanged(const QString&))); connect(ui.PaletteFilterClearButton, SIGNAL(clicked(bool)), this, SLOT(OnPaletteFilterClearButtonClicked(bool))); paletteTable->setColumnWidth(1, 260);//256 plus small margin on each side. paletteTable->horizontalHeader()->setSectionsClickable(true); connect(paletteTable->horizontalHeader(), SIGNAL(sectionClicked(int)), this, SLOT(OnPaletteHeaderSectionClicked(int)), Qt::QueuedConnection); connect(ui.ResetCurvesButton, SIGNAL(clicked(bool)), this, SLOT(OnResetCurvesButtonClicked(bool)), Qt::QueuedConnection); connect(ui.CurvesView, SIGNAL(PointChangedSignal(int, int, const QPointF&)), this, SLOT(OnCurvesPointChanged(int, int, const QPointF&)), Qt::QueuedConnection); connect(ui.CurvesView, SIGNAL(PointAddedSignal(size_t, const QPointF&)), this, SLOT(OnCurvesPointAdded(size_t, const QPointF&)), Qt::QueuedConnection); connect(ui.CurvesView, SIGNAL(PointRemovedSignal(size_t, int)), this, SLOT(OnCurvesPointRemoved(size_t, int)), Qt::QueuedConnection); connect(ui.CurvesAllRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesAllRadioButtonToggled(bool)), Qt::QueuedConnection); connect(ui.CurvesRedRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesRedRadioButtonToggled(bool)), Qt::QueuedConnection); connect(ui.CurvesGreenRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesGreenRadioButtonToggled(bool)), Qt::QueuedConnection); connect(ui.CurvesBlueRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesBlueRadioButtonToggled(bool)), Qt::QueuedConnection); } /// /// Read all palette Xml files in the specified folder and populate the palette list with the contents. /// This will clear any previous contents. /// Called upon initialization, or controller type change. /// /// The full path to the palette files folder /// The number of palettes successfully added template size_t FractoriumEmberController::InitPaletteList(const QString& s) { QDirIterator it(s, QStringList() << "*.xml" << "*.ugr" << "*.gradient" << "*.gradients", QDir::Files, QDirIterator::FollowSymlinks); while (it.hasNext()) { auto path = it.next(); auto qfilename = it.fileName(); try { if (QFile::exists(path) && m_PaletteList->Add(path.toStdString())) m_Fractorium->ui.PaletteFilenameCombo->addItem(qfilename); } catch (const std::exception& e) { QMessageBox::critical(nullptr, "Palette Parsing Error", QString::fromStdString(e.what())); } catch (const char* e) { QMessageBox::critical(nullptr, "Palette Parsing Error", e); } } m_Fractorium->ui.PaletteFilenameCombo->model()->sort(0); return m_PaletteList->Size(); } /// /// Read a palette Xml file and populate the palette table with the contents. /// This will clear any previous contents. /// Called upon initialization, palette combo index change, and controller type change. /// /// The name of the palette file without the path /// True if successful, else false. template bool FractoriumEmberController::FillPaletteTable(const string& s) { if (!s.empty())//This occasionally seems to get called with an empty string for reasons unknown. { auto paletteTable = m_Fractorium->ui.PaletteListTable; m_CurrentPaletteFilePath = s; if (::FillPaletteTable(m_CurrentPaletteFilePath, paletteTable, m_PaletteList)) { return true; } else { vector errors = m_PaletteList->ErrorReport(); m_Fractorium->ErrorReportToQTextEdit(errors, m_Fractorium->ui.InfoFileOpeningTextEdit); m_Fractorium->ShowCritical("Palette Read Error", "Could not load palette file, all images will be black. See info tab for details."); m_PaletteList->ClearErrorReport(); } } return false; } /// /// Fill the palette table with the passed in string. /// Called when the palette name combo box changes. /// /// The full path to the palette file void Fractorium::OnPaletteFilenameComboChanged(const QString& text) { auto s = text.toStdString(); m_Controller->FillPaletteTable(s); auto fullname = m_Controller->m_PaletteList->GetFullPathFromFilename(s); ui.PaletteFilenameCombo->setToolTip(QString::fromStdString(fullname)); ui.PaletteListTable->sortItems(0, m_PaletteSortMode == 0 ? Qt::AscendingOrder : Qt::DescendingOrder); } /// /// Apply adjustments to the current ember's palette. /// template void FractoriumEmberController::ApplyPaletteToEmber() { const uint blur = m_Fractorium->m_PaletteBlurSpin->value(); const uint freq = m_Fractorium->m_PaletteFrequencySpin->value(); const auto sat = m_Fractorium->m_PaletteSaturationSpin->value() / 100.0; const auto brightness = m_Fractorium->m_PaletteBrightnessSpin->value() / 255.0; const auto contrast = double(m_Fractorium->m_PaletteContrastSpin->value() > 0 ? m_Fractorium->m_PaletteContrastSpin->value() * 2.0 : m_Fractorium->m_PaletteContrastSpin->value()) / 100.0; const auto hue = m_Fractorium->m_PaletteHueSpin->value() / 360.0; //Use the temp palette as the base and apply the adjustments gotten from the GUI and save the result in the ember palette. m_TempPalette.MakeAdjustedPalette(m_Ember.m_Palette, m_Fractorium->m_PreviewPaletteRotation, hue, sat, brightness, contrast, blur, freq); } /// /// Use adjusted palette to update all related GUI controls with new color values. /// Resets the rendering process. /// /// The palette to use /// Name of the palette template void FractoriumEmberController::UpdateAdjustedPaletteGUI(Palette& palette) { const auto xform = CurrentXform(); const auto palettePreviewTable = m_Fractorium->ui.PalettePreviewTable; const auto paletteName = QString::fromStdString(m_Ember.m_Palette.m_Name); auto previewPaletteItem = palettePreviewTable->item(0, 1); if (previewPaletteItem)//This can be null if the palette file was moved or corrupted. { //Use the adjusted palette to fill the preview palette control so the user can see the effects of applying the adjustements. vector v = palette.MakeRgbPaletteBlock(PALETTE_CELL_HEIGHT);//Make the palette repeat for PALETTE_CELL_HEIGHT rows. m_FinalPaletteImage = QImage(int(palette.Size()), PALETTE_CELL_HEIGHT, QImage::Format_RGB888);//Create a QImage out of it. memcpy(m_FinalPaletteImage.scanLine(0), v.data(), v.size() * sizeof(v[0]));//Memcpy the data in. QPixmap pixmap(QPixmap::fromImage(m_FinalPaletteImage));//Create a QPixmap out of the QImage. previewPaletteItem->setData(Qt::DecorationRole, pixmap.scaled(QSize(pixmap.width(), palettePreviewTable->rowHeight(0) + 2), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));//Set the pixmap on the palette tab. m_Fractorium->SetPaletteTableItem(&pixmap, m_Fractorium->ui.XformPaletteRefTable, m_Fractorium->m_PaletteRefItem, 0, 0);//Set the palette ref table on the xforms | color tab. if (auto previewNameItem = palettePreviewTable->item(0, 0)) { previewNameItem->setText(paletteName);//Finally, set the name of the palette to be both the text and the tooltip. previewNameItem->setToolTip(paletteName); } } //Update the current xform's color and reset the rendering process. //Update all controls to be safe. if (xform) XformColorIndexChanged(xform->m_ColorX, true, true, true); } /// /// Apply all adjustments to the selected palette, show it /// and assign it to the current ember. /// Called when any adjustment spinner is modified. /// Resets the rendering process. /// template void FractoriumEmberController::PaletteAdjust() { Update([&]() { ApplyPaletteToEmber(); UpdateAdjustedPaletteGUI(m_Ember.m_Palette); }); } void Fractorium::OnPaletteAdjust(int d) { m_Controller->PaletteAdjust(); } /// /// Set the passed in palette as the current one, /// applying any adjustments previously specified. /// Resets the rendering process. /// /// The palette to assign to the temporary palette template void FractoriumEmberController::SetBasePaletteAndAdjust(const Palette& palette) { //The temp palette is assigned the palette read when the file was parsed/saved. The user can apply adjustments on the GUI later. //These adjustments will be applied to the temp palette, then assigned back to m_Ember.m_Palette. m_TempPalette = palette;//Deep copy. ApplyPaletteToEmber();//Copy temp palette to ember palette and apply adjustments. UpdateAdjustedPaletteGUI(m_Ember.m_Palette);//Show the adjusted palette. } /// /// Set the selected palette as the current one, /// applying any adjustments previously specified. /// Called when a palette cell is clicked. Unfortunately, /// this will get called twice on a double click when moving /// from one palette to another. It happens quickly so it shouldn't /// be too much of a problem. /// Resets the rendering process. /// /// The table row clicked /// The table column clicked template void FractoriumEmberController::PaletteCellClicked(int row, int col) { if (const auto palette = m_PaletteList->GetPaletteByFilename(m_CurrentPaletteFilePath, row)) SetBasePaletteAndAdjust(*palette); } /// /// Map the palette in the clicked row index to the index /// in the palette list, then pass that index to PaletteCellClicked(). /// This resolves the case where the sort order of the palette table /// is different than the internal order of the palette list. /// /// The table row clicked /// The table column clicked, ignored void Fractorium::OnPaletteCellClicked(int row, int col) { if (const auto item = dynamic_cast(ui.PaletteListTable->item(row, 1))) { const auto index = int(item->Index()); if (m_PreviousPaletteRow != index) { m_Controller->PaletteCellClicked(index, col); m_PreviousPaletteRow = index;//Save for comparison on next click. } } } /// /// Called when the mouse has been moved while pressed on the palette preview table. /// Computes the difference between where the mouse was clicked and where it is now, then /// uses that difference as a rotation value to pass into the palette adjustment. /// Updates the palette and resets the rendering process. /// /// The local mouse coordinates relative to the palette preview table /// The global mouse coordinates void Fractorium::OnPreviewPaletteMouseDragged(const QPointF& local, const QPoint& global) { if (m_PreviewPaletteMouseDown) { m_PreviewPaletteRotation = m_PreviewPaletteMouseDownRotation + (global.x() - m_PreviewPaletteMouseDownPosition.x()); //qDebug() << "Palette preview table drag reached main window event: " << local.x() << ' ' << local.y() << ", global: " << global.x() << ' ' << global.y() << ", final: " << m_PreviewPaletteRotation; m_Controller->PaletteAdjust(); } } /// /// Called when the mouse has been released over the palette preview table. /// Does nothing but set the dragging state to false. /// void Fractorium::OnPreviewPaletteMouseReleased() { m_PreviewPaletteMouseDown = false; } /// /// Sets the palette rotation to zero. /// Updates the palette and resets the rendering process. /// /// Ignored /// Ignored void Fractorium::OnPreviewPaletteCellDoubleClicked(int row, int col) { m_PreviewPaletteRotation = m_PreviewPaletteMouseDownRotation = 0; m_PreviewPaletteMouseDown = false; m_Controller->PaletteAdjust(); } /// /// Called when the mouse has been pressed on the palette preview table. /// Subsequent mouse movements will compute a rotation value to pass into the palette adjustment, based on the location /// of the mouse when this slot is called. /// /// Ignored /// Ignored void Fractorium::OnPreviewPaletteCellPressed(int row, int col) { m_PreviewPaletteMouseDown = true; m_PreviewPaletteMouseDownPosition = QCursor::pos();//Get the global mouse position. m_PreviewPaletteMouseDownRotation = m_PreviewPaletteRotation; //qDebug() << "Mouse down with initial pos: " << m_PreviewPaletteMouseDownPosition.x() << " and initial rotation: " << m_PreviewPaletteMouseDownRotation; } /// /// Set the selected palette as the current one, /// resetting any adjustments previously specified. /// Called when a palette cell is double clicked. /// Resets the rendering process. /// /// The table row clicked /// The table column clicked void Fractorium::OnPaletteCellDoubleClicked(int row, int col) { ResetPaletteControls(); m_PreviousPaletteRow = -1; OnPaletteCellClicked(row, col); } /// /// Set the selected palette to a randomly selected one, /// applying any adjustments previously specified if the checked parameter is true. /// Called when the Random Palette button is clicked. /// Resets the rendering process. /// /// True to clear the current adjustments, else leave current adjustments and apply them to the newly selected palette. void Fractorium::OnPaletteRandomSelectButtonClicked(bool checked) { uint i = 0; const auto rowCount = ui.PaletteListTable->rowCount(); if (rowCount > 1)//If only one palette in the current palette file, just use it. while (((i = QTIsaac::LockedRand(rowCount)) == uint(m_PreviousPaletteRow)) || i >= static_cast(rowCount)); if (checked) OnPaletteCellDoubleClicked(i, 1);//Will clear the adjustments. else OnPaletteCellClicked(i, 1); } /// /// Apply random adjustments to the selected palette. /// Called when the Random Adjustment button is clicked. /// Resets the rendering process. /// void Fractorium::OnPaletteRandomAdjustButtonClicked(bool checked) { m_PaletteHueSpin->setValue(-180 + QTIsaac::LockedRand(361)); m_PaletteSaturationSpin->setValue(-50 + QTIsaac::LockedRand(101));//Full range of these leads to bad palettes, so clamp range. m_PaletteBrightnessSpin->setValue(-50 + QTIsaac::LockedRand(101)); m_PaletteContrastSpin->setValue(-50 + QTIsaac::LockedRand(101)); //Doing frequency and blur together gives bad palettes that are just a solid color. if (QTIsaac::LockedRandBit()) { m_PaletteBlurSpin->setValue(QTIsaac::LockedRand(21)); m_PaletteFrequencySpin->setValue(1); } else { m_PaletteBlurSpin->setValue(0); m_PaletteFrequencySpin->setValue(1 + QTIsaac::LockedRand(10)); } OnPaletteAdjust(0); } /// /// Open the palette editor dialog. /// Called when the palette editor button is clicked. /// template void FractoriumEmberController::PaletteEditorButtonClicked() { size_t i = 0; const auto ed = m_Fractorium->m_PaletteEditor.get(); map colorIndices; const auto forceFinal = m_Fractorium->HaveFinal(); m_PreviousTempPalette = m_TempPalette; // it's necessary because m_TempPalette is changed when the user make changes in palette editor ed->SetPalette(m_TempPalette); while (auto xform = m_Ember.GetTotalXform(i, forceFinal)) colorIndices[i++] = xform->m_ColorX; ed->SetColorIndices(colorIndices); ed->SetPreviousColorIndices(colorIndices); // also necessary because the colors are changed in palette editor ed->SetPaletteFile(m_CurrentPaletteFilePath); #ifdef __linux__ ed->show(); #else SyncPalette(ed->exec() == QDialog::Accepted); #endif } /// /// Slot called when the palette editor changes the palette and the Sync checkbox is checked. /// bool Fractorium::PaletteChanged() { return m_PaletteChanged; } /// /// Open the palette editor dialog. /// This creates the palette editor dialog if it has not been created at least once. /// Called when the palette editor button is clicked. /// /// Ignored void Fractorium::OnPaletteEditorButtonClicked(bool checked) { if (!m_PaletteEditor.get()) { m_PaletteEditor = std::make_unique(this); connect(m_PaletteEditor.get(), SIGNAL(PaletteChanged()), this, SLOT(OnPaletteEditorColorChanged()), Qt::QueuedConnection); connect(m_PaletteEditor.get(), SIGNAL(PaletteFileChanged()), this, SLOT(OnPaletteEditorFileChanged()), Qt::QueuedConnection); connect(m_PaletteEditor.get(), SIGNAL(ColorIndexChanged(size_t, float)), this, SLOT(OnPaletteEditorColorIndexChanged(size_t, float)), Qt::QueuedConnection); #ifdef __linux__ connect(m_PaletteEditor.get(), SIGNAL(finished(int)), this, SLOT(OnPaletteEditorFinished(int)), Qt::QueuedConnection); #endif } m_PaletteChanged = false; m_PaletteFileChanged = false; m_Controller->PaletteEditorButtonClicked(); } /// /// Slot called when palette editor window is closed. /// template void FractoriumEmberController::SyncPalette(bool accepted) { const auto ed = m_Fractorium->m_PaletteEditor.get(); Palette edPal; Palette prevPal = m_PreviousTempPalette; map colorIndices; const auto forceFinal = m_Fractorium->HaveFinal(); if (accepted) { //Copy all just to be safe, because they may or may not have synced. colorIndices = ed->GetColorIndices(); for (auto& index : colorIndices) if (auto xform = m_Ember.GetTotalXform(index.first, forceFinal)) xform->m_ColorX = index.second; edPal = ed->GetPalette(static_cast(prevPal.Size())); SetBasePaletteAndAdjust(edPal);//This will take care of updating the color index controls. if (edPal.m_Filename.get() && !edPal.m_Filename->empty()) m_Fractorium->SetPaletteFileComboIndex(*edPal.m_Filename); } else if (m_Fractorium->PaletteChanged())//They clicked cancel, but synced at least once, restore the previous palette. { colorIndices = ed->GetPreviousColorIndices(); for (auto& index : colorIndices) if (auto xform = m_Ember.GetTotalXform(index.first, forceFinal)) xform->m_ColorX = index.second; SetBasePaletteAndAdjust(prevPal);//This will take care of updating the color index controls. } //Whether the current palette file was changed or not, if it's modifiable then reload it just to be safe (even though it might be overkill). if (m_PaletteList->IsModifiable(m_CurrentPaletteFilePath)) m_Fractorium->OnPaletteFilenameComboChanged(QString::fromStdString(m_CurrentPaletteFilePath)); } /// /// Slot called every time a color is changed in the palette editor. /// template void FractoriumEmberController::PaletteEditorColorChanged() { SetBasePaletteAndAdjust(m_Fractorium->m_PaletteEditor->GetPalette(static_cast(m_TempPalette.Size()))); } void Fractorium::OnPaletteEditorColorChanged() { m_PaletteChanged = true; m_Controller->PaletteEditorColorChanged(); } /// /// Slot called every time a palette file is changed in the palette editor. /// void Fractorium::OnPaletteEditorFileChanged() { m_PaletteFileChanged = true; } /// /// Slot called every time an xform color index is changed in the palette editor. /// If a special value of size_t max is passed for index, it means update all color indices. /// /// The index of the xform whose color index has been changed. Special value of size_t max to update all /// The value of the color index void Fractorium::OnPaletteEditorColorIndexChanged(size_t index, float value) { if (index == std::numeric_limits::max())//Update all in this case. { auto indices = m_PaletteEditor->GetColorIndices(); for (auto& it : indices) OnXformColorIndexChanged(it.second, true, true, true, eXformUpdate::UPDATE_SPECIFIC, it.first); } else//Only update the xform index that was selected and dragged inside of the palette editor. OnXformColorIndexChanged(value, true, true, true, eXformUpdate::UPDATE_SPECIFIC, index); } /// Slot called after EditPallete is closed. /// /// Cancel/OK action void Fractorium::OnPaletteEditorFinished(int result) { m_Controller->SyncPalette(result == QDialog::Accepted); } /// /// Apply the text in the palette filter text box to only show palettes whose names /// contain the substring. /// Called when the user types in the palette filter text box. /// /// The text to filter on void Fractorium::OnPaletteFilterLineEditTextChanged(const QString& text) { auto table = ui.PaletteListTable; table->setUpdatesEnabled(false); for (int i = 0; i < table->rowCount(); i++) { if (auto item = table->item(i, 0)) { if (!item->text().contains(text, Qt::CaseInsensitive)) table->hideRow(i); else table->showRow(i); } } ui.PaletteListTable->sortItems(0, m_PaletteSortMode == 0 ? Qt::AscendingOrder : Qt::DescendingOrder);//Must re-sort every time the filter changes. table->setUpdatesEnabled(true); } /// /// Clear the palette name filter, which will display all palettes. /// Called when clear palette filter button is clicked. /// /// Ignored void Fractorium::OnPaletteFilterClearButtonClicked(bool checked) { ui.PaletteFilterLineEdit->clear(); } /// /// Change the sorting to be either ascending or descending. /// Called when user clicks the table headers. /// /// Column index of the header clicked, ignored. void Fractorium::OnPaletteHeaderSectionClicked(int col) { m_PaletteSortMode = !m_PaletteSortMode; ui.PaletteListTable->sortItems(0, m_PaletteSortMode == 0 ? Qt::AscendingOrder : Qt::DescendingOrder); } /// /// Reset the palette controls. /// Usually in response to a palette cell double click. /// void Fractorium::ResetPaletteControls() { m_PreviewPaletteRotation = m_PreviewPaletteMouseDownRotation = 0; m_PaletteHueSpin->SetValueStealth(0); m_PaletteSaturationSpin->SetValueStealth(0); m_PaletteBrightnessSpin->SetValueStealth(0); m_PaletteContrastSpin->SetValueStealth(0); m_PaletteBlurSpin->SetValueStealth(0); m_PaletteFrequencySpin->SetValueStealth(1); } /// /// Set the index of the palette file combo box. /// This is for display purposes only so the user can see which file, if any, /// the current palette came from. /// For embedded palettes with no filename, this will have no effect. /// /// The string to set the index to void Fractorium::SetPaletteFileComboIndex(const string& filename) { if (!filename.empty()) ui.PaletteFilenameCombo->setCurrentText(QFileInfo(QString::fromStdString(filename)).fileName()); } /// /// Reset the color curve values for the selected curve in the current ember to their default state and also update the curves control. /// Called when ResetCurvesButton is clicked. /// Note if they click Reset Curves when the ctrl is pressed, then it clears all curves. /// Resets the rendering process at either ACCUM_ONLY by default, or FILTER_AND_ACCUM when using early clip. /// /// The index of the curve to be cleared, 0 to clear all. template void FractoriumEmberController::ClearColorCurves(int i) { UpdateAll([&](Ember& ember, bool isMain) { if (i < 0) ember.m_Curves.Init(); else ember.m_Curves.Init(i); }, true, m_Renderer->EarlyClip() ? eProcessAction::FILTER_AND_ACCUM : eProcessAction::ACCUM_ONLY, m_Fractorium->ApplyAll()); FillCurvesControl(); } void Fractorium::OnResetCurvesButtonClicked(bool checked) { if (!QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) { if (ui.CurvesAllRadio->isChecked()) m_Controller->ClearColorCurves(0); else if (ui.CurvesRedRadio->isChecked()) m_Controller->ClearColorCurves(1); else if (ui.CurvesGreenRadio->isChecked()) m_Controller->ClearColorCurves(2); else if (ui.CurvesBlueRadio->isChecked()) m_Controller->ClearColorCurves(3); else m_Controller->ClearColorCurves(0); } else { m_Controller->ClearColorCurves(-1); } } /// /// Set the coordinate of the curve point. /// Called when the position of any of the points in the curves editor is is changed. /// Resets the rendering process at either ACCUM_ONLY by default, or FILTER_AND_ACCUM when using early clip. /// /// The curve index, 0-3/ /// The point index within the selected curve, 1-2. /// The new coordinate of the point in terms of the curves control rect. template void FractoriumEmberController::ColorCurveChanged(int curveIndex, int pointIndex, const QPointF& point) { Update([&] { m_Ember.m_Curves.m_Points[curveIndex][pointIndex].x = point.x(); m_Ember.m_Curves.m_Points[curveIndex][pointIndex].y = point.y(); }, true, m_Renderer->EarlyClip() ? eProcessAction::FILTER_AND_ACCUM : eProcessAction::ACCUM_ONLY); } void Fractorium::OnCurvesPointChanged(int curveIndex, int pointIndex, const QPointF& point) { m_Controller->ColorCurveChanged(curveIndex, pointIndex, point); } /// /// Remove curve point. /// Called when right clicking on a color curve point. /// Resets the rendering process at either ACCUM_ONLY by default, or FILTER_AND_ACCUM when using early clip. /// /// The curve index./ /// The point index within the selected curve. template void FractoriumEmberController::ColorCurvesPointRemoved(size_t curveIndex, int pointIndex) { Update([&] { if (m_Ember.m_Curves.m_Points[curveIndex].size() > 2) { m_Ember.m_Curves.m_Points[curveIndex].erase(m_Ember.m_Curves.m_Points[curveIndex].begin() + pointIndex); std::sort(m_Ember.m_Curves.m_Points[curveIndex].begin(), m_Ember.m_Curves.m_Points[curveIndex].end(), [&](auto & lhs, auto & rhs) { return lhs.x < rhs.x; }); } }, true, m_Renderer->EarlyClip() ? eProcessAction::FILTER_AND_ACCUM : eProcessAction::ACCUM_ONLY); FillCurvesControl(); } void Fractorium::OnCurvesPointRemoved(size_t curveIndex, int pointIndex) { m_Controller->ColorCurvesPointRemoved(curveIndex, pointIndex); } /// /// Add a curve point. /// Called when clicking in between points on a color curve. /// Resets the rendering process at either ACCUM_ONLY by default, or FILTER_AND_ACCUM when using early clip. /// /// The curve index./ /// The point to add to the selected curve. template void FractoriumEmberController::ColorCurvesPointAdded(size_t curveIndex, const QPointF& point) { Update([&] { m_Ember.m_Curves.m_Points[curveIndex].push_back({ point.x(), point.y() }); std::sort(m_Ember.m_Curves.m_Points[curveIndex].begin(), m_Ember.m_Curves.m_Points[curveIndex].end(), [&](auto & lhs, auto & rhs) { return lhs.x < rhs.x; }); }, true, m_Renderer->EarlyClip() ? eProcessAction::FILTER_AND_ACCUM : eProcessAction::ACCUM_ONLY); FillCurvesControl(); } void Fractorium::OnCurvesPointAdded(size_t curveIndex, const QPointF& point) { m_Controller->ColorCurvesPointAdded(curveIndex, point); } /// /// Set the top most points in the curves control, which makes it easier to /// select a point by putting it on top of all the others. /// Called when the any of the curve color radio buttons are toggled. /// /// Ignored void Fractorium::OnCurvesAllRadioButtonToggled(bool checked) { if (checked) ui.CurvesView->SetTop(CurveIndex::ALL); } void Fractorium::OnCurvesRedRadioButtonToggled(bool checked) { if (checked) ui.CurvesView->SetTop(CurveIndex::RED); } void Fractorium::OnCurvesGreenRadioButtonToggled(bool checked) { if (checked) ui.CurvesView->SetTop(CurveIndex::GREEN); } void Fractorium::OnCurvesBlueRadioButtonToggled(bool checked) { if (checked) ui.CurvesView->SetTop(CurveIndex::BLUE); } /// /// Set the points in the curves control to the values of the curve points in the current ember. /// template void FractoriumEmberController::FillCurvesControl() { m_Fractorium->ui.CurvesView->blockSignals(true); m_Fractorium->ui.CurvesView->Set(m_Ember.m_Curves); m_Fractorium->ui.CurvesView->blockSignals(false); m_Fractorium->ui.CurvesView->update(); } template class FractoriumEmberController; #ifdef DO_DOUBLE template class FractoriumEmberController; #endif >>>>>>> a4bfffaa3f55362a86915c700186ee3ed03b90fa