#include "FractoriumPch.h" #include "Fractorium.h" /// /// Initialize the menus UI. /// void Fractorium::InitMenusUI() { //File menu. connect(ui.ActionNewFlock, SIGNAL(triggered(bool)), this, SLOT(OnActionNewFlock(bool)), Qt::QueuedConnection); connect(ui.ActionNewEmptyFlameInCurrentFile, SIGNAL(triggered(bool)), this, SLOT(OnActionNewEmptyFlameInCurrentFile(bool)), Qt::QueuedConnection); connect(ui.ActionNewRandomFlameInCurrentFile, SIGNAL(triggered(bool)), this, SLOT(OnActionNewRandomFlameInCurrentFile(bool)), Qt::QueuedConnection); connect(ui.ActionCopyFlameInCurrentFile, SIGNAL(triggered(bool)), this, SLOT(OnActionCopyFlameInCurrentFile(bool)), Qt::QueuedConnection); connect(ui.ActionCreateReferenceFile, SIGNAL(triggered(bool)), this, SLOT(OnActionCreateReferenceFile(bool)), Qt::QueuedConnection); connect(ui.ActionOpen, SIGNAL(triggered(bool)), this, SLOT(OnActionOpen(bool)), Qt::QueuedConnection); connect(ui.ActionOpenExamples, SIGNAL(triggered(bool)), this, SLOT(OnActionOpenExamples(bool)), Qt::QueuedConnection); connect(ui.ActionSaveCurrentAsXml, SIGNAL(triggered(bool)), this, SLOT(OnActionSaveCurrentAsXml(bool)), Qt::QueuedConnection); connect(ui.ActionSaveEntireFileAsXml, SIGNAL(triggered(bool)), this, SLOT(OnActionSaveEntireFileAsXml(bool)), Qt::QueuedConnection); connect(ui.ActionSaveCurrentScreen, SIGNAL(triggered(bool)), this, SLOT(OnActionSaveCurrentScreen(bool)), Qt::QueuedConnection); connect(ui.ActionExit, SIGNAL(triggered(bool)), this, SLOT(OnActionExit(bool)), Qt::QueuedConnection); //Edit menu. connect(ui.ActionUndo, SIGNAL(triggered(bool)), this, SLOT(OnActionUndo(bool)), Qt::QueuedConnection); connect(ui.ActionRedo, SIGNAL(triggered(bool)), this, SLOT(OnActionRedo(bool)), Qt::QueuedConnection); connect(ui.ActionCopyXml, SIGNAL(triggered(bool)), this, SLOT(OnActionCopyXml(bool)), Qt::QueuedConnection); connect(ui.ActionCopyAllXml, SIGNAL(triggered(bool)), this, SLOT(OnActionCopyAllXml(bool)), Qt::QueuedConnection); connect(ui.ActionPasteXmlAppend, SIGNAL(triggered(bool)), this, SLOT(OnActionPasteXmlAppend(bool)), Qt::QueuedConnection); connect(ui.ActionPasteXmlOver, SIGNAL(triggered(bool)), this, SLOT(OnActionPasteXmlOver(bool)), Qt::QueuedConnection); connect(ui.ActionCopySelectedXforms, SIGNAL(triggered(bool)), this, SLOT(OnActionCopySelectedXforms(bool)), Qt::QueuedConnection); connect(ui.ActionPasteSelectedXforms, SIGNAL(triggered(bool)), this, SLOT(OnActionPasteSelectedXforms(bool)), Qt::QueuedConnection); connect(ui.ActionCopyKernel, SIGNAL(triggered(bool)), this, SLOT(OnActionCopyKernel(bool)), Qt::QueuedConnection); ui.ActionPasteSelectedXforms->setEnabled(false); //View menu. connect(ui.ActionResetWorkspace, SIGNAL(triggered(bool)), this, SLOT(OnActionResetWorkspace(bool)), Qt::QueuedConnection); connect(ui.ActionAlternateEditorImage, SIGNAL(triggered(bool)), this, SLOT(OnActionAlternateEditorImage(bool)), Qt::QueuedConnection); connect(ui.ActionResetScale, SIGNAL(triggered(bool)), this, SLOT(OnActionResetScale(bool)), Qt::QueuedConnection); //Tools menu. connect(ui.ActionAddReflectiveSymmetry, SIGNAL(triggered(bool)), this, SLOT(OnActionAddReflectiveSymmetry(bool)), Qt::QueuedConnection); connect(ui.ActionAddRotationalSymmetry, SIGNAL(triggered(bool)), this, SLOT(OnActionAddRotationalSymmetry(bool)), Qt::QueuedConnection); connect(ui.ActionAddBothSymmetry, SIGNAL(triggered(bool)), this, SLOT(OnActionAddBothSymmetry(bool)), Qt::QueuedConnection); connect(ui.ActionClearFlame, SIGNAL(triggered(bool)), this, SLOT(OnActionClearFlame(bool)), Qt::QueuedConnection); connect(ui.ActionFlatten, SIGNAL(triggered(bool)), this, SLOT(OnActionFlatten(bool)), Qt::QueuedConnection); connect(ui.ActionUnflatten, SIGNAL(triggered(bool)), this, SLOT(OnActionUnflatten(bool)), Qt::QueuedConnection); connect(ui.ActionStopRenderingPreviews, SIGNAL(triggered(bool)), this, SLOT(OnActionStopRenderingPreviews(bool)), Qt::QueuedConnection); connect(ui.ActionRenderPreviews, SIGNAL(triggered(bool)), this, SLOT(OnActionRenderPreviews(bool)), Qt::QueuedConnection); connect(ui.ActionFinalRender, SIGNAL(triggered(bool)), this, SLOT(OnActionFinalRender(bool)), Qt::QueuedConnection); connect(ui.ActionOptions, SIGNAL(triggered(bool)), this, SLOT(OnActionOptions(bool)), Qt::QueuedConnection); //Help menu. connect(ui.ActionAbout, SIGNAL(triggered(bool)), this, SLOT(OnActionAbout(bool)), Qt::QueuedConnection); } /// /// Create a new flock of random embers, with the specified length. /// /// The number of embers to include in the flock template void FractoriumEmberController::NewFlock(size_t count) { bool nv = false; Ember ember; StopAllPreviewRenderers(); m_EmberFile.Clear(); m_EmberFile.m_Filename = EmberFile::DefaultFilename(); vector filteredVariations; vector* filteredVariationsRef = &m_FilteredVariations; auto& deviceNames = OpenCLInfo::Instance()->AllDeviceNames(); for (auto& dev : deviceNames) if (Find(ToLower(dev), "nvidia")) nv = true; if (nv)//Nvidia cannot handle synth. It takes over a minute to compile and uses about 4GB of memory. { filteredVariations = m_FilteredVariations; filteredVariations.erase(std::remove_if(filteredVariations.begin(), filteredVariations.end(), [&](const eVariationId & id) -> bool { return id == eVariationId::VAR_SYNTH || id == eVariationId::VAR_PRE_SYNTH || id == eVariationId::VAR_POST_SYNTH; } ), filteredVariations.end()); filteredVariationsRef = &filteredVariations; } for (size_t i = 0; i < count; i++) { m_SheepTools->Random(ember, *filteredVariationsRef, static_cast(QTIsaac::LockedFrand(-2, 2)), 0, 8); ParamsToEmber(ember); ember.m_Index = i; ember.m_Name = m_EmberFile.m_Filename.toStdString() + "_" + ToString(i + 1ULL).toStdString(); m_EmberFile.m_Embers.push_back(ember); } m_LastSaveAll = ""; FillLibraryTree(); } /// /// Create a new flock and assign the first ember as the current one. /// /// Ignored void Fractorium::OnActionNewFlock(bool checked) { m_Controller->NewFlock(m_Settings->RandomCount()); m_Controller->SetEmber(0, false); } /// /// Create and add a new empty ember in the currently opened file /// and set it as the current one. /// It will have one empty xform in it. /// Resets the rendering process. /// template void FractoriumEmberController::NewEmptyFlameInCurrentFile() { Ember ember; Xform xform; QDateTime local(QDateTime::currentDateTime()); StopAllPreviewRenderers(); ParamsToEmber(ember); xform.m_Weight = T(0.25); xform.m_ColorX = m_Rand.Frand01(); xform.AddVariation(m_VariationList->GetVariationCopy(eVariationId::VAR_LINEAR)); ember.AddXform(xform); ember.m_Palette = *m_PaletteList->GetRandomPalette(); ember.m_Name = EmberFile::DefaultEmberName(m_EmberFile.Size() + 1).toStdString(); ember.m_Index = m_EmberFile.Size(); m_EmberFile.m_Embers.push_back(ember);//Will invalidate the pointers contained in the EmberTreeWidgetItems, UpdateLibraryTree() will resync. m_EmberFile.MakeNamesUnique(); UpdateLibraryTree(); SetEmber(m_EmberFile.Size() - 1, false); } void Fractorium::OnActionNewEmptyFlameInCurrentFile(bool checked) { m_Controller->NewEmptyFlameInCurrentFile(); } /// /// Create and add a new random ember in the currently opened file /// and set it as the current one. /// Resets the rendering process. /// template void FractoriumEmberController::NewRandomFlameInCurrentFile() { Ember ember; StopAllPreviewRenderers(); m_SheepTools->Random(ember, m_FilteredVariations, static_cast(QTIsaac::LockedFrand(-2, 2)), 0, 8); ParamsToEmber(ember); ember.m_Name = EmberFile::DefaultEmberName(m_EmberFile.Size() + 1).toStdString(); ember.m_Index = m_EmberFile.Size(); m_EmberFile.m_Embers.push_back(ember);//Will invalidate the pointers contained in the EmberTreeWidgetItems, UpdateLibraryTree() will resync. m_EmberFile.MakeNamesUnique(); UpdateLibraryTree(); SetEmber(m_EmberFile.Size() - 1, false); } void Fractorium::OnActionNewRandomFlameInCurrentFile(bool checked) { m_Controller->NewRandomFlameInCurrentFile(); } /// /// Create and add a a copy of the current ember in the currently opened file /// and set it as the current one. /// Resets the rendering process. /// template void FractoriumEmberController::CopyFlameInCurrentFile() { auto ember = m_Ember; StopAllPreviewRenderers(); ember.m_Name = EmberFile::DefaultEmberName(m_EmberFile.Size() + 1).toStdString(); ember.m_Index = m_EmberFile.Size(); m_EmberFile.m_Embers.push_back(ember);//Will invalidate the pointers contained in the EmberTreeWidgetItems, UpdateLibraryTree() will resync. m_EmberFile.MakeNamesUnique(); UpdateLibraryTree(); SetEmber(m_EmberFile.Size() - 1, false); } void Fractorium::OnActionCopyFlameInCurrentFile(bool checked) { m_Controller->CopyFlameInCurrentFile(); } /// /// Create a reference file containing one ember for every possible regular variation, plus one for post_smartcrop /// since it only exists in post form. /// This will replace whatever file the user has open. /// Clears the undo state. /// Resets the rendering process. /// template void FractoriumEmberController::CreateReferenceFile() { bool nv = false; size_t i; StopAllPreviewRenderers(); auto temppal = m_Ember.m_Palette; m_EmberFile.Clear(); m_EmberFile.m_Filename = QString("Reference_") + EMBER_VERSION; auto varList = VariationList::Instance(); auto& regVars = varList->RegVars(); auto count = regVars.size(); auto addsquaresfunc = [&](size_t i, const Variation* var) { Ember ember; Xform xf0, xf1, xf2, xf3, xf4, xffinal; // xf0.AddVariation(varList->GetVariationCopy(eVariationId::VAR_SQUARE, T(0.5))); // xf1.m_Affine.C(T(0.5)); xf1.m_Affine.F(T(0.5)); xf1.AddVariation(varList->GetVariationCopy(eVariationId::VAR_LINEAR)); // xf2.m_Affine.C(T(-0.5)); xf2.m_Affine.F(T(0.5)); xf2.AddVariation(varList->GetVariationCopy(eVariationId::VAR_LINEAR)); // xf3.m_Affine.C(T(-0.5)); xf3.m_Affine.F(T(-0.5)); xf3.AddVariation(varList->GetVariationCopy(eVariationId::VAR_LINEAR)); // xf4.m_Affine.C(T(0.5)); xf4.m_Affine.F(T(-0.5)); xf4.AddVariation(varList->GetVariationCopy(eVariationId::VAR_LINEAR)); // xffinal.AddVariation(var->Copy()); // ember.AddXform(xf0); ember.AddXform(xf1); ember.AddXform(xf2); ember.AddXform(xf3); ember.AddXform(xf4); ember.SetFinalXform(xffinal); ember.EqualizeWeights(); ember.m_Index = i; ember.m_MaxRadDE = 0; ember.m_Name = var->Name() + " Squares"; ember.m_Palette = temppal; m_EmberFile.m_Embers.push_back(ember); }; auto addsbarsfunc = [&](size_t i, const Variation* var) { Ember ember; Xform xf0, xf1, xf2, xffinal; // xf0.AddVariation(varList->GetVariationCopy(eVariationId::VAR_PRE_BLUR, T(100))); xf0.AddVariation(varList->GetVariationCopy(eVariationId::VAR_CYLINDER, T(0.1))); // xf1.m_Affine.C(T(0.4)); xf1.AddVariation(varList->GetVariationCopy(eVariationId::VAR_LINEAR)); // xf2.m_Affine.C(T(-0.4)); xf2.AddVariation(varList->GetVariationCopy(eVariationId::VAR_LINEAR)); // xffinal.AddVariation(var->Copy()); ember.AddXform(xf0); ember.AddXform(xf1); ember.AddXform(xf2); ember.SetFinalXform(xffinal); ember.EqualizeWeights(); ember.m_Index = i; ember.m_MaxRadDE = 0; ember.m_Name = var->Name() + " Bars"; ember.m_Palette = temppal; m_EmberFile.m_Embers.push_back(ember); }; for (i = 0; i < count; i++) { addsquaresfunc(i, regVars[i]); addsbarsfunc(i, regVars[i]); } addsquaresfunc(i, varList->GetVariation(eVariationId::VAR_POST_SMARTCROP));//post_smartcrop is the only variation that exists only in post form, so it must be done manually here. addsbarsfunc(i, varList->GetVariation(eVariationId::VAR_POST_SMARTCROP)); m_LastSaveAll = ""; FillLibraryTree(); } void Fractorium::OnActionCreateReferenceFile(bool checked) { m_Controller->CreateReferenceFile(); } /// /// Open a list of ember Xml files, apply various values from the GUI widgets. /// Either append these newly read embers to the existing open embers, /// or clear the current ember file first. /// When appending, add the new embers the the end of library tree. /// When not appending, clear and populate the library tree with the new embers. /// Set the current ember to the first one in the newly opened list. /// Clears the undo state. /// Resets the rendering process. /// /// A list of full paths and filenames /// True to append the embers in the new files to the end of the currently open embers, false to clear and replace them template void FractoriumEmberController::OpenAndPrepFiles(const QStringList& filenames, bool append) { if (!filenames.empty()) { size_t i; EmberFile emberFile; XmlToEmber parser; vector> embers; vector errors; uint previousSize = append ? uint(m_EmberFile.Size()) : 0u; StopAllPreviewRenderers(); emberFile.m_Filename = filenames[0]; for (auto& filename : filenames) { embers.clear(); if (parser.Parse(filename.toStdString().c_str(), embers, true) && !embers.empty()) { for (i = 0; i < embers.size(); i++) { ConstrainDimensions(embers[i]);//Do not exceed the max texture size. //Also ensure it has a name. if (embers[i].m_Name == "" || embers[i].m_Name == "No name") embers[i].m_Name = ToString(i).toStdString(); embers[i].m_Quality = m_Fractorium->m_QualitySpin->value(); embers[i].m_Supersample = m_Fractorium->m_SupersampleSpin->value(); } m_LastSaveAll = ""; emberFile.m_Embers.insert(emberFile.m_Embers.end(), embers.begin(), embers.end()); errors = parser.ErrorReport(); } else { errors = parser.ErrorReport(); m_Fractorium->ShowCritical("Open Failed", "Could not open file, see info tab for details."); } if (!errors.empty()) m_Fractorium->ErrorReportToQTextEdit(errors, m_Fractorium->ui.InfoFileOpeningTextEdit, false);//Concat errors from all files. } if (append) { if (m_EmberFile.m_Filename == "") m_EmberFile.m_Filename = filenames[0]; m_EmberFile.m_Embers.insert(m_EmberFile.m_Embers.end(), emberFile.m_Embers.begin(), emberFile.m_Embers.end()); } else if (emberFile.Size() > 0)//Ensure at least something was read. m_EmberFile = std::move(emberFile);//Move the temp to avoid creating dupes because we no longer need it. else if (!m_EmberFile.Size()) { //Loading failed, so fill it with a dummy. Ember ember; m_EmberFile.m_Embers.push_back(ember); } //Resync indices and names. i = 0; for (auto& it : m_EmberFile.m_Embers) it.m_Index = i++; m_EmberFile.MakeNamesUnique(); if (append) UpdateLibraryTree(); else FillLibraryTree(append ? previousSize - 1 : 0); ClearUndo(); m_GLController->ClearControl(); SetEmber(previousSize, false); } } /// /// Show a file open dialog to open ember Xml files. /// /// Ignored void Fractorium::OnActionOpen(bool checked) { m_Controller->OpenAndPrepFiles(SetupOpenXmlDialog(), false); } /// /// Show a file open dialog to open examples Xml files. /// /// Ignored void Fractorium::OnActionOpenExamples(bool checked) { m_Controller->OpenAndPrepFiles(SetupOpenXmlDialog(true), false); } /// /// Save current ember as Xml, using the Xml saving template values from the options. /// This will first save the current ember back to the opened file in memory before /// saving it to disk. /// template void FractoriumEmberController::SaveCurrentAsXml() { QString filename; auto s = m_Fractorium->m_Settings; if (s->SaveAutoUnique() && m_LastSaveCurrent != "") { filename = EmberFile::UniqueFilename(m_LastSaveCurrent); } else if (QFile::exists(m_LastSaveCurrent)) { filename = m_LastSaveCurrent; } else { if (m_EmberFile.Size() == 1) filename = m_Fractorium->SetupSaveXmlDialog(m_EmberFile.m_Filename);//If only one ember present, just use parent filename. else filename = m_Fractorium->SetupSaveXmlDialog(QString::fromStdString(m_Ember.m_Name));//More than one ember present, use individual ember name. } if (filename != "") { auto ember = m_Ember; EmberToXml writer; QFileInfo fileInfo(filename); auto tempEdit = ember.m_Edits; SaveCurrentToOpenedFile();//Save the current ember back to the opened file before writing to disk. ApplyXmlSavingTemplate(ember); ember.m_Edits = writer.CreateNewEditdoc(&ember, nullptr, "edit", s->Nick().toStdString(), s->Url().toStdString(), s->Id().toStdString(), "", 0, 0); if (tempEdit) xmlFreeDoc(tempEdit); if (writer.Save(filename.toStdString().c_str(), ember, 0, true, true)) { s->SaveFolder(fileInfo.canonicalPath()); if (!s->SaveAutoUnique() || m_LastSaveCurrent == "")//Only save filename on first time through when doing auto unique names. m_LastSaveCurrent = filename; } else m_Fractorium->ShowCritical("Save Failed", "Could not save file, try saving to a different folder."); } m_GLController->ClearControl(); } void Fractorium::OnActionSaveCurrentAsXml(bool checked) { m_Controller->SaveCurrentAsXml(); } /// /// Save entire opened file Xml, using the Xml saving template values from the options on each ember. /// This will first save the current ember back to the opened file in memory before /// saving all to disk. /// template void FractoriumEmberController::SaveEntireFileAsXml() { QString filename; auto s = m_Fractorium->m_Settings; if (s->SaveAutoUnique() && m_LastSaveAll != "") filename = EmberFile::UniqueFilename(m_LastSaveAll); else if (QFile::exists(m_LastSaveAll)) filename = m_LastSaveAll; else filename = m_Fractorium->SetupSaveXmlDialog(m_EmberFile.m_Filename); if (filename != "") { EmberFile emberFile; EmberToXml writer; QFileInfo fileInfo(filename); SaveCurrentToOpenedFile();//Save the current ember back to the opened file before writing to disk. emberFile = m_EmberFile; for (auto& ember : emberFile.m_Embers) ApplyXmlSavingTemplate(ember); if (writer.Save(filename.toStdString().c_str(), emberFile.m_Embers, 0, true, true, false, false, false)) { if (!s->SaveAutoUnique() || m_LastSaveAll == "")//Only save filename on first time through when doing auto unique names. m_LastSaveAll = filename; s->SaveFolder(fileInfo.canonicalPath()); } else m_Fractorium->ShowCritical("Save Failed", "Could not save file, try saving to a different folder."); } m_GLController->ClearControl(); } void Fractorium::OnActionSaveEntireFileAsXml(bool checked) { m_Controller->SaveEntireFileAsXml(); } template void FractoriumEmberController::SaveCurrentFileOnShutdown() { EmberToXml writer; auto path = GetDefaultUserPath(); QDir dir(path); if (!dir.exists()) dir.mkpath("."); string filename = path.toStdString() + "/lastonshutdown.flame"; writer.Save(filename, m_EmberFile.m_Embers, 0, true, true, false, false, false); } /// /// Show a file save dialog and save what is currently shown in the render window to disk as an image. /// /// Ignored void Fractorium::OnActionSaveCurrentScreen(bool checked) { auto filename = SetupSaveImageDialog(m_Controller->Name()); auto renderer = m_Controller->Renderer(); auto& pixels = *m_Controller->FinalImage(); auto rendererCL = dynamic_cast(renderer); auto stats = m_Controller->Stats(); auto comments = renderer->ImageComments(stats, 0, true); auto settings = FractoriumSettings::Instance(); if (rendererCL && renderer->PrepFinalAccumVector(pixels)) { if (!rendererCL->ReadFinal(pixels.data())) { ShowCritical("GPU Read Error", "Could not read image from the GPU, aborting image save.", false); return; } } m_Controller->SaveCurrentRender(filename, comments, pixels, renderer->FinalRasW(), renderer->FinalRasH(), settings->Png16Bit(), settings->Transparency()); } /// /// Save the current ember back to its position in the opened file. /// This will not take any action if the previews are still rendering because /// this writes to memory the preview renderer might be reading, and also stops the /// preview renderer. /// This does not save to disk. /// /// Whether to re-render the preview thumbnail after saving to the open file. Default: true. template uint FractoriumEmberController::SaveCurrentToOpenedFile(bool render) { uint i = 0; bool fileFound = false; if (!m_LibraryPreviewRenderer->Running()) { for (auto& it : m_EmberFile.m_Embers) { if (&it == m_EmberFilePointer)//Just compare memory addresses. { it = m_Ember;//Save it to the opened file in memory. fileFound = true; break; } i++; } if (!fileFound) { StopAllPreviewRenderers(); m_EmberFile.m_Embers.push_back(m_Ember); m_EmberFile.MakeNamesUnique(); if (render) UpdateLibraryTree(); } else if (render) RenderLibraryPreviews(i, i + 1); } return i; } /// /// Exit the application. /// /// Ignore. void Fractorium::OnActionExit(bool checked) { closeEvent(nullptr); QApplication::exit(); } /// /// Undoes this instance. /// template void FractoriumEmberController::Undo() { if (m_UndoList.size() > 1 && m_UndoIndex > 0) { bool forceFinal = m_Fractorium->HaveFinal(); auto current = CurrentXform(); int index = m_Ember.GetTotalXformIndex(current, forceFinal); m_LastEditWasUndoRedo = true; m_UndoIndex = std::max(0u, m_UndoIndex - 1u); SetEmber(m_UndoList[m_UndoIndex], true, false);//Don't update pointer because it's coming from the undo list. m_EditState = eEditUndoState::UNDO_REDO; if (index >= 0 && index < m_Fractorium->ui.CurrentXformCombo->count()) m_Fractorium->CurrentXform(index); m_Fractorium->ui.ActionUndo->setEnabled(m_UndoList.size() > 1 && (m_UndoIndex > 0)); m_Fractorium->ui.ActionRedo->setEnabled(m_UndoList.size() > 1 && !(m_UndoIndex == m_UndoList.size() - 1)); } } void Fractorium::OnActionUndo(bool checked) { m_Controller->Undo(); } /// /// Redoes this instance. /// template void FractoriumEmberController::Redo() { if (m_UndoList.size() > 1 && m_UndoIndex < m_UndoList.size() - 1) { bool forceFinal = m_Fractorium->HaveFinal(); auto current = CurrentXform(); int index = m_Ember.GetTotalXformIndex(current, forceFinal); m_LastEditWasUndoRedo = true; m_UndoIndex = std::min(m_UndoIndex + 1, m_UndoList.size() - 1); SetEmber(m_UndoList[m_UndoIndex], true, false);//Don't update pointer because it's coming from the undo list. m_EditState = eEditUndoState::UNDO_REDO; if (index >= 0 && index < m_Fractorium->ui.CurrentXformCombo->count()) m_Fractorium->CurrentXform(index); m_Fractorium->ui.ActionUndo->setEnabled(m_UndoList.size() > 1 && (m_UndoIndex > 0)); m_Fractorium->ui.ActionRedo->setEnabled(m_UndoList.size() > 1 && !(m_UndoIndex == m_UndoList.size() - 1)); } } void Fractorium::OnActionRedo(bool checked) { m_Controller->Redo(); } /// /// Copy the current ember Xml to the clipboard. /// Apply Xml saving settings from the options first. /// template void FractoriumEmberController::CopyXml() { auto ember = m_Ember; EmberToXml emberToXml; auto settings = m_Fractorium->m_Settings; ember.m_Quality = settings->XmlQuality(); ember.m_Supersample = settings->XmlSupersample(); ember.m_TemporalSamples = settings->XmlTemporalSamples(); QApplication::clipboard()->setText(QString::fromStdString(emberToXml.ToString(ember, "", 0, false, true))); } void Fractorium::OnActionCopyXml(bool checked) { m_Controller->CopyXml(); } /// /// Copy the Xmls for all open embers as a single string to the clipboard, enclosed with the tag. /// Apply Xml saving settings from the options first for each ember. /// template void FractoriumEmberController::CopyAllXml() { ostringstream os; EmberToXml emberToXml; os << "\n"; for (auto& e : m_EmberFile.m_Embers) { Ember ember = e; ApplyXmlSavingTemplate(ember); os << emberToXml.ToString(ember, "", 0, false, true); } os << "\n"; QApplication::clipboard()->setText(QString::fromStdString(os.str())); } void Fractorium::OnActionCopyAllXml(bool checked) { m_Controller->CopyAllXml(); } /// /// Convert the Xml text from the clipboard to an ember, add it to the end /// of the current file and set it as the current ember. If multiple Xmls were /// copied to the clipboard and were enclosed in tags, then all of them will be added. /// Clears the undo state. /// Resets the rendering process. /// template void FractoriumEmberController::PasteXmlAppend() { size_t previousSize = m_EmberFile.Size(); string s, errors; XmlToEmber parser; vector> embers; auto codec = QTextCodec::codecForName("UTF-8"); auto b = codec->fromUnicode(QApplication::clipboard()->text()); s.reserve(b.size()); for (auto i = 0; i < b.size(); i++) { if (uint(b[i]) < 128u) s.push_back(b[i]); } b.clear(); StopAllPreviewRenderers(); parser.Parse(reinterpret_cast(const_cast(s.c_str())), "", embers, true); errors = parser.ErrorReportString(); if (errors != "") { m_Fractorium->ShowCritical("Paste Error", QString::fromStdString(errors)); } if (!embers.empty()) { for (auto i = 0; i < embers.size(); i++) { embers[i].m_Index = m_EmberFile.Size(); ConstrainDimensions(embers[i]);//Do not exceed the max texture size. //Also ensure it has a name. if (embers[i].m_Name == "" || embers[i].m_Name == "No name") embers[i].m_Name = ToString(embers[i].m_Index).toStdString(); m_EmberFile.m_Embers.push_back(embers[i]);//Will invalidate the pointers contained in the EmberTreeWidgetItems, UpdateLibraryTree() will resync. } m_EmberFile.MakeNamesUnique(); UpdateLibraryTree(); SetEmber(previousSize, false); } } void Fractorium::OnActionPasteXmlAppend(bool checked) { m_Controller->PasteXmlAppend(); } /// /// Convert the Xml text from the clipboard to an ember, overwrite the /// current file and set the first as the current ember. If multiple Xmls were /// copied to the clipboard and were enclosed in tags, then the current file will contain all of them. /// template void FractoriumEmberController::PasteXmlOver() { size_t i = 0; string s, errors; XmlToEmber parser; list> embers; auto backupEmber = *m_EmberFile.m_Embers.begin(); auto codec = QTextCodec::codecForName("UTF-8"); auto b = codec->fromUnicode(QApplication::clipboard()->text()); s.reserve(b.size()); for (auto i = 0; i < b.size(); i++) { if (uint(b[i]) < 128u) s.push_back(b[i]); } b.clear(); StopAllPreviewRenderers(); parser.Parse(reinterpret_cast(const_cast(s.c_str())), "", embers, true); errors = parser.ErrorReportString(); if (errors != "") { m_Fractorium->ShowCritical("Paste Error", QString::fromStdString(errors)); } if (embers.size()) { m_EmberFile.m_Embers = std::move(embers);//Will invalidate the pointers contained in the EmberTreeWidgetItems, UpdateLibraryTree() will resync. for (auto it : m_EmberFile.m_Embers) { it.m_Index = i++; ConstrainDimensions(it);//Do not exceed the max texture size. //Also ensure it has a name. if (it.m_Name == "" || it.m_Name == "No name") it.m_Name = ToString(it.m_Index).toStdString(); } m_EmberFile.MakeNamesUnique(); FillLibraryTree(); SetEmber(0, false); } } void Fractorium::OnActionPasteXmlOver(bool checked) { m_Controller->PasteXmlOver(); } /// /// Copy the selected xforms. /// Note this will also copy final if selected. /// If none selected, just copy current. /// template void FractoriumEmberController::CopySelectedXforms() { m_CopiedXforms.clear(); m_CopiedFinalXform.Clear(); UpdateXform([&](Xform* xform, size_t xfindex, size_t selIndex) { if (m_Ember.IsFinalXform(xform)) m_CopiedFinalXform = *xform; else m_CopiedXforms.emplace_back(*xform, xfindex); }, eXformUpdate::UPDATE_SELECTED, false); m_Fractorium->ui.ActionPasteSelectedXforms->setEnabled(true); } void Fractorium::OnActionCopySelectedXforms(bool checked) { m_Controller->CopySelectedXforms(); } /// /// Paste the selected xforms. /// Note this will also paste/overwrite final if previously copied. /// Resets the rendering process. /// template void FractoriumEmberController::PasteSelectedXforms() { Update([&]() { AddXformsWithXaos(m_Ember, m_CopiedXforms, true); if (!m_CopiedFinalXform.Empty()) m_Ember.SetFinalXform(m_CopiedFinalXform); FillXforms(); }); } void Fractorium::OnActionPasteSelectedXforms(bool checked) { m_Controller->PasteSelectedXforms(); } /// /// Copy the text of the OpenCL iteration kernel to the clipboard. /// This performs no action if the renderer is not of type RendererCL. /// template void FractoriumEmberController::CopyKernel() { if (auto rendererCL = dynamic_cast*>(m_Renderer.get())) QApplication::clipboard()->setText(QString::fromStdString(rendererCL->IterKernel())); } void Fractorium::OnActionCopyKernel(bool checked) { m_Controller->CopyKernel(); } /// /// Reset dock widgets and tabs to their default position. /// Note that there is a bug in Qt, where it will only move them all to the left side if at least /// one is on the left side, or they are all floating. If one or more are docked right, and none are docked /// left, then it will put them all on the right side. Hopefully this isn't too much of a problem. /// void Fractorium::OnActionResetWorkspace(bool checked) { QDockWidget* firstDock = nullptr; for (auto dock : m_Docks) { dock->setFloating(true); dock->setGeometry(QRect(100, 100, dock->width(), dock->height()));//Doesn't seem to have an effect. dock->setFloating(false); dock->show(); if (firstDock) tabifyDockWidget(firstDock, dock); firstDock = dock; } ui.LibraryDockWidget->raise(); ui.LibraryDockWidget->show(); } /// /// Alternate between Editor/Image. /// /// Ignored void Fractorium::OnActionAlternateEditorImage(bool checked) { if (DrawPreAffines() || DrawPostAffines()) { SaveAffineState(); ui.ActionDrawPreAffines->setChecked(false); ui.ActionDrawAllPreAffines->setChecked(false); ui.ActionDrawPostAffines->setChecked(false); ui.ActionDrawAllPostAffines->setChecked(false); ui.ActionDrawGrid->setChecked(false); ui.ActionDrawImage->setChecked(true); } else { ui.ActionDrawImage->setChecked(false); ui.ActionDrawGrid->setChecked(true); SyncAffineStateToToolbar(); } ui.GLDisplay->update(); } /// /// Reset the scale used to draw affines, which was adjusted by zooming with the Alt key pressed. /// /// Ignored void Fractorium::OnActionResetScale(bool checked) { m_Controller->InitLockedScale(); ui.GLDisplay->update(); } /// /// Add reflective symmetry to the current ember. /// Resets the rendering process. /// template void FractoriumEmberController::AddReflectiveSymmetry() { bool forceFinal = m_Fractorium->HaveFinal(); Update([&]() { m_Ember.AddSymmetry(-1, m_Rand); auto index = m_Ember.TotalXformCount(forceFinal) - (forceFinal ? 2 : 1);//Set index to the last item before final. FillXforms(int(index)); }); } void Fractorium::OnActionAddReflectiveSymmetry(bool checked) { m_Controller->AddReflectiveSymmetry(); } /// /// Add rotational symmetry to the current ember. /// Resets the rendering process. /// template void FractoriumEmberController::AddRotationalSymmetry() { bool forceFinal = m_Fractorium->HaveFinal(); Update([&]() { m_Ember.AddSymmetry(2, m_Rand); auto index = m_Ember.TotalXformCount(forceFinal) - (forceFinal ? 2 : 1);//Set index to the last item before final. FillXforms(int(index)); }); } void Fractorium::OnActionAddRotationalSymmetry(bool checked) { m_Controller->AddRotationalSymmetry(); } /// /// Add both reflective and rotational symmetry to the current ember. /// Resets the rendering process. /// template void FractoriumEmberController::AddBothSymmetry() { bool forceFinal = m_Fractorium->HaveFinal(); Update([&]() { m_Ember.AddSymmetry(-2, m_Rand); auto index = m_Ember.TotalXformCount(forceFinal) - (forceFinal ? 2 : 1);//Set index to the last item before final. FillXforms(int(index)); }); } void Fractorium::OnActionAddBothSymmetry(bool checked) { m_Controller->AddBothSymmetry(); } /// /// Adds a FlattenVariation to every xform in the current ember. /// Resets the rendering process. /// template void FractoriumEmberController::Flatten() { UpdateAll([&](Ember& ember, bool isMain) { ember.Flatten(XmlToEmber::m_FlattenNames); }, true, eProcessAction::FULL_RENDER, m_Fractorium->ApplyAll()); FillVariationTreeWithCurrentXform(); } void Fractorium::OnActionFlatten(bool checked) { m_Controller->Flatten(); } /// /// Removes pre/reg/post FlattenVariation from every xform in the current ember. /// Resets the rendering process. /// template void FractoriumEmberController::Unflatten() { UpdateAll([&](Ember& ember, bool isMain) { ember.Unflatten(); }, true, eProcessAction::FULL_RENDER, m_Fractorium->ApplyAll()); FillVariationTreeWithCurrentXform(); } void Fractorium::OnActionUnflatten(bool checked) { m_Controller->Unflatten(); } /// /// Delete all but one xform in the current ember. /// Clear that xform's variations. /// Resets the rendering process. /// template void FractoriumEmberController::ClearFlame() { Update([&]() { while (m_Ember.TotalXformCount() > 1) m_Ember.DeleteTotalXform(m_Ember.TotalXformCount() - 1); if (m_Ember.XformCount() == 1) { if (auto xform = m_Ember.GetXform(0)) { xform->Clear(); xform->AddVariation(m_VariationList->GetVariationCopy(eVariationId::VAR_LINEAR)); xform->ParentEmber(&m_Ember); } } m_Ember.m_Curves.Init(); FillXforms(); FillCurvesControl(); }); } void Fractorium::OnActionClearFlame(bool checked) { m_Controller->ClearFlame(); } /// /// Re-render all previews. /// void Fractorium::OnActionRenderPreviews(bool checked) { m_Controller->RenderLibraryPreviews(); } /// /// Stop all previews from being rendered. This is handy if the user /// opens a large file with many embers in it, such as an animation sequence. /// void Fractorium::OnActionStopRenderingPreviews(bool checked) { m_Controller->StopLibraryPreviewRender(); } /// /// Show the final render dialog as a modeless dialog to allow /// the user to minimize the main window while doing a lengthy final render. /// Note: The user probably should not be otherwise interacting with the main GUI /// while the final render is taking place. /// /// Ignored void Fractorium::OnActionFinalRender(bool checked) { //First completely stop what the current rendering process is doing. m_Controller->DeleteRenderer();//Delete the renderer, but not the controller. m_Controller->StopAllPreviewRenderers(); m_Controller->SaveCurrentToOpenedFile(false);//Save whatever was edited back to the current open file. m_RenderStatusLabel->setText("Renderer stopped."); SetupFinalRenderDialog(); if (m_FinalRenderDialog) m_FinalRenderDialog->Show(false); } /// /// Called when the final render dialog has been closed. /// /// Ignored void Fractorium::OnFinalRenderClose(int result) { m_RenderStatusLabel->setText("Renderer starting..."); StartRenderTimer(false);//Re-create the renderer and start rendering again. ui.ActionStartStopRenderer->setChecked(false);//Re-enable any controls that might have been disabled. OnActionStartStopRenderer(false); delete m_FinalRenderDialog; m_FinalRenderDialog = nullptr; } /// /// Show the final options dialog. /// Restart rendering and sync options after the options dialog is dismissed with Ok. /// Called when the options dialog is finished with ok. /// /// Ignored void Fractorium::OnActionOptions(bool checked) { bool ec = m_Settings->EarlyClip(); bool yup = m_Settings->YAxisUp(); bool trans = m_Settings->Transparency(); if (m_OptionsDialog->exec()) { bool updatePreviews = ec != m_Settings->EarlyClip() || yup != m_Settings->YAxisUp() || trans != m_Settings->Transparency(); SyncOptionsToToolbar();//This won't trigger a recreate, the call below handles it. ShutdownAndRecreateFromOptions(updatePreviews);//This will recreate the controller and/or the renderer from the options if necessary, then start the render timer. } } /// /// Show the about dialog. /// /// Ignored void Fractorium::OnActionAbout(bool checked) { m_AboutDialog->exec(); } template class FractoriumEmberController; #ifdef DO_DOUBLE template class FractoriumEmberController; #endif