#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.ActionOpen, SIGNAL(triggered(bool)), this, SLOT(OnActionOpen(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); ui.ActionPasteSelectedXforms->setEnabled(false); //View menu. connect(ui.ActionResetWorkspace, SIGNAL(triggered(bool)), this, SLOT(OnActionResetWorkspace(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(m_FinalRenderDialog, SIGNAL(finished(int)), this, SLOT(OnFinalRenderClose(int)), 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) { Ember ember; StopPreviewRender(); m_EmberFile.Clear(); m_EmberFile.m_Filename = EmberFile::DefaultFilename(); for (size_t i = 0; i < count; i++) { m_SheepTools->Random(ember, m_FilteredVariations, static_cast(QTIsaac::LockedFrand(-2, 2)), 0, MAX_CL_VARS); 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()); StopPreviewRender(); ParamsToEmber(ember); xform.m_Weight = T(0.25); xform.m_ColorX = m_Rand.Frand01(); 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; StopPreviewRender(); m_SheepTools->Random(ember, m_FilteredVariations, static_cast(QTIsaac::LockedFrand(-2, 2)), 0, MAX_CL_VARS); 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; StopPreviewRender(); 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(); } /// /// 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; StopPreviewRender(); emberFile.m_Filename = filenames[0]; for (auto& filename : filenames) { embers.clear(); if (parser.Parse(filename.toStdString().c_str(), embers) && !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. //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(); SetEmber(previousSize, false); } } /// /// Show a file open dialog to open ember Xml files. /// /// Ignored void Fractorium::OnActionOpen(bool checked) { m_Controller->OpenAndPrepFiles(SetupOpenXmlDialog(), 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."); } } 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)) { 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."); } } void Fractorium::OnActionSaveEntireFileAsXml(bool checked) { m_Controller->SaveEntireFileAsXml(); } /// /// 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(m_Controller->Renderer()); auto stats = m_Controller->Stats(); auto comments = renderer->ImageComments(stats, 0, true); 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(), renderer->NumChannels(), renderer->BytesPerChannel()); } /// /// 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. /// template uint FractoriumEmberController::SaveCurrentToOpenedFile() { uint i = 0; bool fileFound = false; if (!m_PreviewRunning) { 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) { StopPreviewRender(); m_EmberFile.m_Embers.push_back(m_Ember); m_EmberFile.MakeNamesUnique(); UpdateLibraryTree(); } else { RenderPreviews(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) { int index = m_Ember.GetTotalXformIndex(CurrentXform()); 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) 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) { int index = m_Ember.GetTotalXformIndex(CurrentXform()); m_LastEditWasUndoRedo = true; m_UndoIndex = std::min(m_UndoIndex + 1, m_UndoList.size() - 1); SetEmber(m_UndoList[m_UndoIndex], true, false); m_EditState = eEditUndoState::UNDO_REDO; if (index >= 0) 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(); StopPreviewRender(); parser.Parse(reinterpret_cast(const_cast(s.c_str())), "", embers); 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; 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(); StopPreviewRender(); m_EmberFile.m_Embers.clear();//Will invalidate the pointers contained in the EmberTreeWidgetItems, UpdateLibraryTree() will resync. parser.Parse(reinterpret_cast(const_cast(s.c_str())), "", m_EmberFile.m_Embers); errors = parser.ErrorReportString(); if (errors != "") { m_Fractorium->ShowCritical("Paste Error", QString::fromStdString(errors)); } if (m_EmberFile.Size()) { 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(); } } else { backupEmber->m_Index = 0; m_EmberFile.m_Embers.push_back(*backupEmber); } 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) { if (m_Ember.IsFinalXform(xform)) m_CopiedFinalXform = *xform; else m_CopiedXforms.push_back(*xform); }, 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([&]() { for (auto& it : m_CopiedXforms) m_Ember.AddXform(it); if (!m_CopiedFinalXform.Empty()) m_Ember.SetFinalXform(m_CopiedFinalXform); FillXforms(); }); } void Fractorium::OnActionPasteSelectedXforms(bool checked) { m_Controller->PasteSelectedXforms(); } /// /// 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(); } /// /// Add reflective symmetry to the current ember. /// Resets the rendering process. /// template void FractoriumEmberController::AddReflectiveSymmetry() { Update([&]() { m_Ember.AddSymmetry(-1, m_Rand); auto index = m_Ember.TotalXformCount() - (m_Ember.UseFinalXform() ? 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() { Update([&]() { m_Ember.AddSymmetry(2, m_Rand); auto index = m_Ember.TotalXformCount() - (m_Ember.UseFinalXform() ? 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() { Update([&]() { m_Ember.AddSymmetry(-2, m_Rand); auto index = m_Ember.TotalXformCount() - (m_Ember.UseFinalXform() ? 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() { UpdateXform([&] (Xform* xform) { m_Ember.Flatten(XmlToEmber::m_FlattenNames); FillVariationTreeWithXform(xform); }, eXformUpdate::UPDATE_CURRENT, false);//Don't update render, it'll update below. UpdateAll([&](Ember& ember) { ember.Flatten(XmlToEmber::m_FlattenNames); }, true, eProcessAction::FULL_RENDER, m_Fractorium->ApplyAll()); } 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() { UpdateXform([&] (Xform* xform) { m_Ember.Unflatten(); FillVariationTreeWithXform(xform); }, eXformUpdate::UPDATE_CURRENT, false);//Don't update render, it'll update below. UpdateAll([&](Ember& ember) { ember.Unflatten(); }, true, eProcessAction::FULL_RENDER, m_Fractorium->ApplyAll()); } 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->ParentEmber(&m_Ember); } } FillXforms(); }); } void Fractorium::OnActionClearFlame(bool checked) { m_Controller->ClearFlame(); } /// /// Re-render all previews. /// void Fractorium::OnActionRenderPreviews(bool checked) { m_Controller->RenderPreviews(); } /// /// 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->StopPreviewRender(); } /// /// 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->StopPreviewRender(); m_Controller->SaveCurrentToOpenedFile();//Save whatever was edited back to the current open file. m_RenderStatusLabel->setText("Renderer stopped."); m_FinalRenderDialog->show(); } /// /// Called when the final render dialog has been closed. /// /// Ignored void Fractorium::OnFinalRenderClose(int result) { m_RenderStatusLabel->setText("Renderer starting..."); StartRenderTimer();//Re-create the renderer and start rendering again. ui.ActionStartStopRenderer->setChecked(false);//Re-enable any controls that might have been disabled. OnActionStartStopRenderer(false); } /// /// 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) { if (m_OptionsDialog->exec()) { SyncOptionsToToolbar();//This won't trigger a recreate, the call below handles it. ShutdownAndRecreateFromOptions();//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