#include "FractoriumPch.h" #include "Fractorium.h" /// /// Initialize the xforms affine UI. /// void Fractorium::InitXformsAffineUI() { int affinePrec = 6, spinHeight = 20; double affineStep = 0.01, affineMin = std::numeric_limits::lowest(), affineMax = std::numeric_limits::max(); auto table = ui.PreAffineTable; table->verticalHeader()->setVisible(true);//The designer continually clobbers these values, so must manually set them here. table->horizontalHeader()->setVisible(true); table->verticalHeader()->setSectionsClickable(true); table->horizontalHeader()->setSectionsClickable(true); table->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch); table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); connect(table->verticalHeader(), SIGNAL(sectionDoubleClicked(int)), this, SLOT(OnPreAffineRowDoubleClicked(int)), Qt::QueuedConnection); connect(table->horizontalHeader(), SIGNAL(sectionDoubleClicked(int)), this, SLOT(OnPreAffineColDoubleClicked(int)), Qt::QueuedConnection); //Pre affine spinners. SetupAffineSpinner(table, this, 0, 0, m_PreX1Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnX1Changed(double))); SetupAffineSpinner(table, this, 0, 1, m_PreX2Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnX2Changed(double))); SetupAffineSpinner(table, this, 1, 0, m_PreY1Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnY1Changed(double))); SetupAffineSpinner(table, this, 1, 1, m_PreY2Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnY2Changed(double))); SetupAffineSpinner(table, this, 2, 0, m_PreO1Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnO1Changed(double))); SetupAffineSpinner(table, this, 2, 1, m_PreO2Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnO2Changed(double))); table = ui.PostAffineTable; table->verticalHeader()->setVisible(true);//The designer continually clobbers these values, so must manually set them here. table->horizontalHeader()->setVisible(true); table->verticalHeader()->setSectionsClickable(true); table->horizontalHeader()->setSectionsClickable(true); table->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch); table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); connect(table->verticalHeader(), SIGNAL(sectionDoubleClicked(int)), this, SLOT(OnPostAffineRowDoubleClicked(int)), Qt::QueuedConnection); connect(table->horizontalHeader(), SIGNAL(sectionDoubleClicked(int)), this, SLOT(OnPostAffineColDoubleClicked(int)), Qt::QueuedConnection); //Post affine spinners. SetupAffineSpinner(table, this, 0, 0, m_PostX1Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnX1Changed(double))); SetupAffineSpinner(table, this, 0, 1, m_PostX2Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnX2Changed(double))); SetupAffineSpinner(table, this, 1, 0, m_PostY1Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnY1Changed(double))); SetupAffineSpinner(table, this, 1, 1, m_PostY2Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnY2Changed(double))); SetupAffineSpinner(table, this, 2, 0, m_PostO1Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnO1Changed(double))); SetupAffineSpinner(table, this, 2, 1, m_PostO2Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnO2Changed(double))); auto preRotateVal = new QDoubleValidator(ui.PreRotateCombo); preRotateVal->setLocale(QLocale::system()); auto preMoveVal = new QDoubleValidator(ui.PreMoveCombo); preMoveVal->setLocale(QLocale::system()); auto preScaleVal = new QDoubleValidator(ui.PreScaleCombo); preScaleVal->setLocale(QLocale::system()); auto postRotateVal = new QDoubleValidator(ui.PostRotateCombo); postRotateVal->setLocale(QLocale::system()); auto postMoveVal = new QDoubleValidator(ui.PostMoveCombo); postMoveVal->setLocale(QLocale::system()); auto postScaleVal = new QDoubleValidator(ui.PostScaleCombo); postScaleVal->setLocale(QLocale::system()); ui.PreRotateCombo->setValidator(preRotateVal); ui.PreMoveCombo->setValidator(preMoveVal); ui.PreScaleCombo->setValidator(preScaleVal); ui.PostRotateCombo->setValidator(postRotateVal); ui.PostMoveCombo->setValidator(postMoveVal); ui.PostScaleCombo->setValidator(postScaleVal); QStringList moveList; moveList.append(ToString(0.5)); moveList.append(ToString(0.25)); moveList.append(ToString(0.1)); moveList.append(ToString(0.05)); moveList.append(ToString(0.025)); moveList.append(ToString(0.01)); ui.PreMoveCombo->addItems(moveList); ui.PostMoveCombo->addItems(moveList); connect(ui.PreFlipHorizontalButton, SIGNAL(clicked(bool)), this, SLOT(OnFlipHorizontalButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PreFlipVerticalButton, SIGNAL(clicked(bool)), this, SLOT(OnFlipVerticalButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PreRotate90CButton, SIGNAL(clicked(bool)), this, SLOT(OnRotate90CButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PreRotate90CcButton, SIGNAL(clicked(bool)), this, SLOT(OnRotate90CcButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PreRotateCButton, SIGNAL(clicked(bool)), this, SLOT(OnRotateCButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PreRotateCcButton, SIGNAL(clicked(bool)), this, SLOT(OnRotateCcButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PreMoveUpButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveUpButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PreMoveDownButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveDownButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PreMoveLeftButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveLeftButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PreMoveRightButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveRightButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PreScaleDownButton, SIGNAL(clicked(bool)), this, SLOT(OnScaleDownButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PreScaleUpButton, SIGNAL(clicked(bool)), this, SLOT(OnScaleUpButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PreResetButton, SIGNAL(clicked(bool)), this, SLOT(OnResetAffineButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PreCopyButton, SIGNAL(clicked(bool)), this, SLOT(OnCopyAffineButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PrePasteButton, SIGNAL(clicked(bool)), this, SLOT(OnPasteAffineButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PreRandomButton, SIGNAL(clicked(bool)), this, SLOT(OnRandomAffineButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PostFlipHorizontalButton, SIGNAL(clicked(bool)), this, SLOT(OnFlipHorizontalButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PostFlipVerticalButton, SIGNAL(clicked(bool)), this, SLOT(OnFlipVerticalButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PostRotate90CcButton, SIGNAL(clicked(bool)), this, SLOT(OnRotate90CcButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PostRotateCcButton, SIGNAL(clicked(bool)), this, SLOT(OnRotateCcButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PostRotateCButton, SIGNAL(clicked(bool)), this, SLOT(OnRotateCButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PostRotate90CButton, SIGNAL(clicked(bool)), this, SLOT(OnRotate90CButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PostMoveUpButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveUpButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PostMoveDownButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveDownButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PostMoveLeftButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveLeftButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PostMoveRightButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveRightButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PostScaleDownButton, SIGNAL(clicked(bool)), this, SLOT(OnScaleDownButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PostScaleUpButton, SIGNAL(clicked(bool)), this, SLOT(OnScaleUpButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PostResetButton, SIGNAL(clicked(bool)), this, SLOT(OnResetAffineButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PostCopyButton, SIGNAL(clicked(bool)), this, SLOT(OnCopyAffineButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PostPasteButton, SIGNAL(clicked(bool)), this, SLOT(OnPasteAffineButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PostRandomButton, SIGNAL(clicked(bool)), this, SLOT(OnRandomAffineButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PreAffineGroupBox, SIGNAL(toggled(bool)), this, SLOT(OnAffineGroupBoxToggled(bool)), Qt::QueuedConnection); connect(ui.PostAffineGroupBox, SIGNAL(toggled(bool)), this, SLOT(OnAffineGroupBoxToggled(bool)), Qt::QueuedConnection); connect(ui.SwapAffinesButton, SIGNAL(clicked(bool)), this, SLOT(OnSwapAffinesButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PolarAffineCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnPolarAffineCheckBoxStateChanged(int)), Qt::QueuedConnection); #ifndef _WIN32 //For some reason linux makes these 24x24, even though the designer explicitly says 16x16. //Also, in order to get 4 pixels of spacing between elements in the grid layout, 0 must be specified. ui.PreFlipHorizontalButton->setIconSize(QSize(16, 16)); ui.PreFlipVerticalButton->setIconSize(QSize(16, 16)); ui.PreRotate90CButton->setIconSize(QSize(16, 16)); ui.PreRotate90CcButton->setIconSize(QSize(16, 16)); ui.PreRotateCButton->setIconSize(QSize(16, 16)); ui.PreRotateCcButton->setIconSize(QSize(16, 16)); ui.PreMoveUpButton->setIconSize(QSize(16, 16)); ui.PreMoveDownButton->setIconSize(QSize(16, 16)); ui.PreMoveLeftButton->setIconSize(QSize(16, 16)); ui.PreMoveRightButton->setIconSize(QSize(16, 16)); ui.PreScaleDownButton->setIconSize(QSize(16, 16)); ui.PreScaleUpButton->setIconSize(QSize(16, 16)); ui.PreResetButton->setIconSize(QSize(16, 16)); ui.PreAffineGridLayout->setHorizontalSpacing(0); ui.PreAffineGridLayout->setVerticalSpacing(0); ui.PostFlipHorizontalButton->setIconSize(QSize(16, 16)); ui.PostFlipVerticalButton->setIconSize(QSize(16, 16)); ui.PostRotate90CButton->setIconSize(QSize(16, 16)); ui.PostRotate90CcButton->setIconSize(QSize(16, 16)); ui.PostRotateCButton->setIconSize(QSize(16, 16)); ui.PostRotateCcButton->setIconSize(QSize(16, 16)); ui.PostMoveUpButton->setIconSize(QSize(16, 16)); ui.PostMoveDownButton->setIconSize(QSize(16, 16)); ui.PostMoveLeftButton->setIconSize(QSize(16, 16)); ui.PostMoveRightButton->setIconSize(QSize(16, 16)); ui.PostScaleDownButton->setIconSize(QSize(16, 16)); ui.PostScaleUpButton->setIconSize(QSize(16, 16)); ui.PostResetButton->setIconSize(QSize(16, 16)); ui.PostAffineGridLayout->setHorizontalSpacing(0); ui.PostAffineGridLayout->setVerticalSpacing(0); //Further, the size of the dock widget won't be properly adjusted until the xforms tab is shown. //So show it here and it will be switched back in Fractorium's constructor. //ui.ParamsTabWidget->setCurrentIndex(2); //ui.DockWidget->update(); #endif //Placing pointers to the spin boxes in arrays makes them easier to access in various places. m_PreSpins[0] = m_PreX1Spin;//A m_PreSpins[1] = m_PreY1Spin;//B m_PreSpins[2] = m_PreO1Spin;//C m_PreSpins[3] = m_PreX2Spin;//D m_PreSpins[4] = m_PreY2Spin;//E m_PreSpins[5] = m_PreO2Spin;//F m_PostSpins[0] = m_PostX1Spin; m_PostSpins[1] = m_PostY1Spin; m_PostSpins[2] = m_PostO1Spin; m_PostSpins[3] = m_PostX2Spin; m_PostSpins[4] = m_PostY2Spin; m_PostSpins[5] = m_PostO2Spin; ui.PreAffineGroupBox->setChecked(false);//Flip both once to force enabling/disabling the disabling of the group boxes and the corner buttons. ui.PreAffineGroupBox->setChecked(true);//Pre affine enabled. ui.PostAffineGroupBox->setChecked(false); ui.PostAffineGroupBox->setChecked(true);//Post affine enabled. } /// /// Set the scale used for drawing the affines to a default value. /// template void FractoriumEmberController::InitLockedScale() { m_LockedScale = (T)std::min(m_Ember.m_FinalRasW, m_Ember.m_FinalRasH) / 4.0; m_LockedX = m_Ember.m_CenterX; m_LockedY = m_Ember.m_CenterY; } /// /// Multiply the scale used for drawing the affines by the passed in value. /// /// The value to scale by template void FractoriumEmberController::ChangeLockedScale(T value) { T min_size = 25.0; m_LockedScale *= value; if (m_LockedScale < min_size) m_LockedScale = min_size; } /// /// Return the value needed to multiply the current scale by to get back to the locked scale. /// /// The scale value template double FractoriumEmberController::AffineScaleCurrentToLocked() { return LockedScale() / m_Ember.m_PixelsPerUnit; } /// /// Return the value needed to multiply the locked scale by to get to the current scale. /// /// The scale value template double FractoriumEmberController::AffineScaleLockedToCurrent() { return m_Ember.m_PixelsPerUnit / LockedScale(); } /// /// Toggle all pre affine values in one row for the selected xforms. /// Resets the rendering process. /// /// The index of the row that was double clicked void Fractorium::OnPreAffineRowDoubleClicked(int logicalIndex) { ToggleTableRow(ui.PreAffineTable, logicalIndex); } /// /// Toggle all pre affine values in one column for the selected xforms. /// Resets the rendering process. /// /// The index of the row that was double clicked void Fractorium::OnPreAffineColDoubleClicked(int logicalIndex) { ToggleTableCol(ui.PreAffineTable, logicalIndex); } /// /// Toggle all post affine values in one row for the selected xforms. /// Resets the rendering process. /// /// The index of the row that was double clicked void Fractorium::OnPostAffineRowDoubleClicked(int logicalIndex) { ToggleTableRow(ui.PostAffineTable, logicalIndex); } /// /// Toggle all post affine values in one column for the selected xforms. /// Resets the rendering process. /// /// The index of the row that was double clicked void Fractorium::OnPostAffineColDoubleClicked(int logicalIndex) { ToggleTableCol(ui.PostAffineTable, logicalIndex); } /// /// Helper for setting the value of a single pre/post affine coefficient. /// Resets the rendering process. /// /// The value to set /// The index to set, 0-5, corresponding to a, b, c, d, e, f /// True if pre affine, false if post affine. template void FractoriumEmberController::AffineSetHelper(double d, int index, bool pre) { UpdateXform([&] (Xform* xform, size_t xfindex, size_t selIndex) { auto& affine = pre ? xform->m_Affine : xform->m_Post; AffineDoubleSpinBox** spinners = pre ? m_Fractorium->m_PreSpins : m_Fractorium->m_PostSpins; if (m_Fractorium->ui.PolarAffineCheckBox->isChecked()) { switch (index) { case 0: case 3: affine.A(std::cos(spinners[0]->value() * DEG_2_RAD) * spinners[3]->value()); affine.D(std::sin(spinners[0]->value() * DEG_2_RAD) * spinners[3]->value()); break; case 1: case 4: affine.B(std::cos(spinners[1]->value() * DEG_2_RAD) * spinners[4]->value()); affine.E(std::sin(spinners[1]->value() * DEG_2_RAD) * spinners[4]->value()); break; case 2: case 5: default: affine.C(std::cos(spinners[2]->value() * DEG_2_RAD) * spinners[5]->value()); affine.F(std::sin(spinners[2]->value() * DEG_2_RAD) * spinners[5]->value()); break; } } else { switch (index) { case 0: affine.A(d); break; case 1: affine.B(d); break; case 2: affine.C(d); break; case 3: affine.D(d); break; case 4: affine.E(d); break; case 5: affine.F(d); break; } } }, eXformUpdate::UPDATE_SELECTED); } /// /// Pre and post affine spinner changed events. /// Resets the rendering process. /// void Fractorium::OnX1Changed(double d) { m_Controller->AffineSetHelper(d, 0, sender() == m_PreX1Spin); } void Fractorium::OnX2Changed(double d) { m_Controller->AffineSetHelper(d, 3, sender() == m_PreX2Spin); } void Fractorium::OnY1Changed(double d) { m_Controller->AffineSetHelper(d, 1, sender() == m_PreY1Spin); } void Fractorium::OnY2Changed(double d) { m_Controller->AffineSetHelper(d, 4, sender() == m_PreY2Spin); } void Fractorium::OnO1Changed(double d) { m_Controller->AffineSetHelper(d, 2, sender() == m_PreO1Spin); } void Fractorium::OnO2Changed(double d) { m_Controller->AffineSetHelper(d, 5, sender() == m_PreO2Spin); } /// /// Flip the selected pre/post affines vertically and/or horizontally. /// Resets the rendering process. /// /// True to flip horizontally /// True to flip vertically /// True if pre affine, else post affine. template void FractoriumEmberController::FlipXforms(bool horizontal, bool vertical, bool pre) { UpdateXform([&] (Xform* xform, size_t xfindex, size_t selIndex) { auto& affine = pre ? xform->m_Affine : xform->m_Post; if (horizontal) { affine.A(-affine.A()); affine.B(-affine.B()); if (!m_Fractorium->LocalPivot()) affine.C(-affine.C()); } if (vertical) { affine.D(-affine.D()); affine.E(-affine.E()); if (!m_Fractorium->LocalPivot()) affine.F(-affine.F()); } }, eXformUpdate::UPDATE_SELECTED); FillAffineWithXform(CurrentXform(), pre); } void Fractorium::OnFlipHorizontalButtonClicked(bool checked) { m_Controller->FlipXforms(true, false, sender() == ui.PreFlipHorizontalButton); } void Fractorium::OnFlipVerticalButtonClicked(bool checked) { m_Controller->FlipXforms(false, true, sender() == ui.PreFlipVerticalButton); } /// /// Rotate the selected pre/post affines transform x degrees. /// Resets the rendering process. /// /// The angle to rotate by /// True if pre affine, else post affine. template void FractoriumEmberController::RotateXformsByAngle(double angle, bool pre) { UpdateXform([&] (Xform* xform, size_t xfindex, size_t selIndex) { auto& affine = pre ? xform->m_Affine : xform->m_Post; affine.Rotate(angle * DEG_2_RAD_T); }, eXformUpdate::UPDATE_SELECTED); FillAffineWithXform(CurrentXform(), pre); } /// /// Rotate the selected pre/post affine transform 90 degrees clockwise/counter clockwise. /// Called when the rotate 90 c/cc pre/post buttons are clicked. /// Resets the rendering process. /// /// Ignored void Fractorium::OnRotate90CButtonClicked(bool checked) { m_Controller->RotateXformsByAngle(90, sender() == ui.PreRotate90CButton); } void Fractorium::OnRotate90CcButtonClicked(bool checked) { m_Controller->RotateXformsByAngle(-90, sender() == ui.PreRotate90CcButton); } /// /// Rotate the selected pre/post affine transform x degrees clockwise. /// Called when the rotate x c pre/post buttons are clicked. /// Resets the rendering process. /// /// Ignored void Fractorium::OnRotateCButtonClicked(bool checked) { bool ok; bool pre = sender() == ui.PreRotateCButton; auto combo = pre ? ui.PreRotateCombo : ui.PostRotateCombo; double d = ToDouble(combo->currentText(), &ok); if (ok) m_Controller->RotateXformsByAngle(d, pre); } /// /// Rotate the selected pre/post affine transform x degrees counter clockwise. /// Called when the rotate x cc pre/post buttons are clicked. /// Resets the rendering process. /// /// Ignored void Fractorium::OnRotateCcButtonClicked(bool checked) { bool ok; bool pre = sender() == ui.PreRotateCcButton; auto combo = pre ? ui.PreRotateCombo : ui.PostRotateCombo; double d = ToDouble(combo->currentText(), &ok); if (ok) m_Controller->RotateXformsByAngle(-d, pre); } /// /// Move the selected pre/post affines in the x and y directions by the specified amount. /// Resets the rendering process. /// /// The x direction to move /// The y direction to move /// True if pre affine, else post affine. template void FractoriumEmberController::MoveXforms(double x, double y, bool pre) { UpdateXform([&] (Xform* xform, size_t xfindex, size_t selIndex) { auto& affine = pre ? xform->m_Affine : xform->m_Post; affine.C(affine.C() + x); affine.F(affine.F() + y); }, eXformUpdate::UPDATE_SELECTED); FillAffineWithXform(CurrentXform(), pre); } /// /// Move the selected pre/post affine transform x units up. /// Called when the move pre/post up buttons are clicked. /// Resets the rendering process. /// /// Ignored void Fractorium::OnMoveUpButtonClicked(bool checked) { bool ok; bool pre = sender() == ui.PreMoveUpButton; auto combo = pre ? ui.PreMoveCombo : ui.PostMoveCombo; double d = ToDouble(combo->currentText(), &ok); if (ok) m_Controller->MoveXforms(0, m_Settings->YAxisUp() ? d : -d, pre); } /// /// Move the selected pre/post affine transform x units down. /// Called when the move pre/post down buttons are clicked. /// Resets the rendering process. /// /// Ignored void Fractorium::OnMoveDownButtonClicked(bool checked) { bool ok; bool pre = sender() == ui.PreMoveDownButton; auto combo = pre ? ui.PreMoveCombo : ui.PostMoveCombo; double d = ToDouble(combo->currentText(), &ok); if (ok) m_Controller->MoveXforms(0, m_Settings->YAxisUp() ? -d : d, pre); } /// /// Move the selected pre/post affine transform x units left. /// Called when the move pre/post left buttons are clicked. /// Resets the rendering process. /// /// Ignored void Fractorium::OnMoveLeftButtonClicked(bool checked) { bool ok; bool pre = sender() == ui.PreMoveLeftButton; auto combo = pre ? ui.PreMoveCombo : ui.PostMoveCombo; double d = ToDouble(combo->currentText(), &ok); if (ok) m_Controller->MoveXforms(-d, 0, pre); } /// /// Move the selected pre/post affine transform x units right. /// Called when the move pre/post right buttons are clicked. /// Resets the rendering process. /// /// Ignored void Fractorium::OnMoveRightButtonClicked(bool checked) { bool ok; bool pre = sender() == ui.PreMoveRightButton; auto combo = pre ? ui.PreMoveCombo : ui.PostMoveCombo; double d = ToDouble(combo->currentText(), &ok); if (ok) m_Controller->MoveXforms(d, 0, pre); } /// /// Scale the selected pre/post affines by the specified amount. /// Resets the rendering process. /// /// The scale value /// True if pre affine, else post affine. template void FractoriumEmberController::ScaleXforms(double scale, bool pre) { UpdateXform([&] (Xform* xform, size_t xfindex, size_t selIndex) { auto& affine = pre ? xform->m_Affine : xform->m_Post; affine.A(affine.A() * scale); affine.B(affine.B() * scale); affine.D(affine.D() * scale); affine.E(affine.E() * scale); }, eXformUpdate::UPDATE_SELECTED); FillAffineWithXform(CurrentXform(), pre); } /// /// Scale the selected pre/post affine transform x units down. /// Called when the scale pre/post down buttons are clicked. /// Resets the rendering process. /// /// Ignored void Fractorium::OnScaleDownButtonClicked(bool checked) { bool ok; bool pre = sender() == ui.PreScaleDownButton; auto combo = pre ? ui.PreScaleCombo : ui.PostScaleCombo; double d = ToDouble(combo->currentText(), &ok); if (ok) m_Controller->ScaleXforms(1.0 / (d / 100.0), pre); } /// /// Scale the selected pre/post affine transform x units up. /// Called when the scale pre/post up buttons are clicked. /// Resets the rendering process. /// /// Ignored void Fractorium::OnScaleUpButtonClicked(bool checked) { bool ok; bool pre = sender() == ui.PreScaleUpButton; auto combo = pre ? ui.PreScaleCombo : ui.PostScaleCombo; double d = ToDouble(combo->currentText(), &ok); if (ok) m_Controller->ScaleXforms(d / 100.0, pre); } /// /// Reset selected pre/post affines to the identity matrix. /// Called when reset pre/post affine buttons are clicked. /// Resets the rendering process. /// /// True for pre affine, false for post. template void FractoriumEmberController::ResetXformsAffine(bool pre) { UpdateXform([&] (Xform* xform, size_t xfindex, size_t selIndex) { auto& affine = pre ? xform->m_Affine : xform->m_Post; affine.MakeID(); }, eXformUpdate::UPDATE_SELECTED); FillAffineWithXform(CurrentXform(), pre); } void Fractorium::OnResetAffineButtonClicked(bool checked) { m_Controller->ResetXformsAffine(sender() == ui.PreResetButton); } /// /// Copy the pre or post affine transform values from the current xform. /// /// True for pre affine, false for post. template void FractoriumEmberController::CopyXformsAffine(bool pre) { if (auto xform = CurrentXform()) m_CopiedAffine = pre ? xform->m_Affine : xform->m_Post; } void Fractorium::OnCopyAffineButtonClicked(bool checked) { m_Controller->CopyXformsAffine(sender() == ui.PreCopyButton); } /// /// Paste the last copied affine transform values to the pre or post affine values in the current xform. /// /// True for pre affine, false for post. template void FractoriumEmberController::PasteXformsAffine(bool pre) { UpdateXform([&](Xform* xform, size_t xfindex, size_t selIndex) { auto& affine = pre ? xform->m_Affine : xform->m_Post; affine = m_CopiedAffine; }, eXformUpdate::UPDATE_CURRENT); FillAffineWithXform(CurrentXform(), pre); } void Fractorium::OnPasteAffineButtonClicked(bool checked) { m_Controller->PasteXformsAffine(sender() == ui.PrePasteButton); } /// /// Randomize all values in selected pre/post affines to a range of -1 to 1. /// Called when random pre/post affine buttons are clicked. /// Resets the rendering process. /// template void FractoriumEmberController::RandomXformsAffine(bool pre) { UpdateXform([&](Xform* xform, size_t xfindex, size_t selIndex) { auto& affine = pre ? xform->m_Affine : xform->m_Post; affine.A(m_Rand.Frand11()); affine.B(m_Rand.Frand11()); affine.C(m_Rand.Frand11()); affine.D(m_Rand.Frand11()); affine.E(m_Rand.Frand11()); affine.F(m_Rand.Frand11()); }, eXformUpdate::UPDATE_SELECTED); FillAffineWithXform(CurrentXform(), pre); } void Fractorium::OnRandomAffineButtonClicked(bool checked) { m_Controller->RandomXformsAffine(sender() == ui.PreRandomButton); } /// /// Fill the GUI with the pre and post affine xform values. /// template void FractoriumEmberController::FillBothAffines() { FillAffineWithXform(CurrentXform(), true); FillAffineWithXform(CurrentXform(), false); } /// /// Fill the GUI with the pre/post affine xform values. /// /// The xform to fill with /// True if pre affine, else post affine. template void FractoriumEmberController::FillAffineWithXform(Xform* xform, bool pre) { AffineDoubleSpinBox** spinners = pre ? m_Fractorium->m_PreSpins : m_Fractorium->m_PostSpins; auto& affine = pre ? xform->m_Affine : xform->m_Post; if (m_Fractorium->ui.PolarAffineCheckBox->isChecked()) { spinners[0]->SetValueStealth(NormalizeDeg360(RAD_2_DEG * std::atan2(affine.D(), affine.A()))); spinners[1]->SetValueStealth(NormalizeDeg360(RAD_2_DEG * std::atan2(affine.E(), affine.B()))); spinners[2]->SetValueStealth(NormalizeDeg360(RAD_2_DEG * std::atan2(affine.F(), affine.C()))); spinners[3]->SetValueStealth(VarFuncs::Hypot(affine.D(), affine.A())); spinners[4]->SetValueStealth(VarFuncs::Hypot(affine.E(), affine.B())); spinners[5]->SetValueStealth(VarFuncs::Hypot(affine.F(), affine.C())); } else { spinners[0]->SetValueStealth(affine.A()); spinners[1]->SetValueStealth(affine.B()); spinners[2]->SetValueStealth(affine.C()); spinners[3]->SetValueStealth(affine.D()); spinners[4]->SetValueStealth(affine.E()); spinners[5]->SetValueStealth(affine.F()); } } /// /// Trigger a redraw which will show or hide the circle affine transforms /// based on whether each group box is checked or not. /// Note that all sub buttons must manually be disabled/enabled in order to /// get the top left corner button in the proper state. This is needed so /// any style sheets can properly draw it based on its state. /// Without explicitly setting it, that button is never actually disabled. /// Called when the group box check box for pre or post affine is checked. /// /// Ignored void Fractorium::OnAffineGroupBoxToggled(bool on) { auto widgetList = sender()->findChildren(); for (auto& it : widgetList) it->setEnabled(on); ui.GLDisplay->update(); } /// /// Swap the values of the pre and post affines for the selected xforms. /// template void FractoriumEmberController::SwapAffines() { UpdateXform([&](Xform* xform, size_t xfindex, size_t selIndex) { auto pre = xform->m_Affine; auto post = xform->m_Post; xform->m_Affine = post; xform->m_Post = pre; }, eXformUpdate::UPDATE_SELECTED); FillBothAffines(); } void Fractorium::OnSwapAffinesButtonClicked(bool checked) { m_Controller->SwapAffines(); } /// /// Trigger a redraw which will show all, or only the current, circle affine transforms /// based on which radio buttons are selected. /// Called when and pre/post show all/current radio buttons are checked. /// /// Ignored void Fractorium::OnAffineDrawAllCurrentRadioButtonToggled(bool checked) { OnCurrentXformComboChanged(ui.CurrentXformCombo->currentIndex()); ui.GLDisplay->update(); } /// /// Set whether to display affine values in polar vs. rectangular coordinates. /// Updates the current affine display. /// /// The state of the checkbox void Fractorium::OnPolarAffineCheckBoxStateChanged(int state) { double mult = state ? 100 : 0.01; double step = m_PreX1Spin->Step() * mult; double small = m_PreX1Spin->SmallStep() * mult; double click = state ? 90 : 1; for (int i = 0; i < 3; i++) { m_PreSpins[i]->Step(step); m_PreSpins[i]->SmallStep(small); m_PostSpins[i]->Step(step); m_PostSpins[i]->SmallStep(small); m_PreSpins[i]->DoubleClickZero(click); m_PostSpins[i]->DoubleClickZero(click); } m_Controller->FillBothAffines(); } /// /// Setup a spinner to be placed in a table cell. /// Special setup function for affine spinners which differs slightly from the regular /// SetupSpinner() function. /// /// The table the spinner belongs to /// The receiver object /// The row in the table where this spinner resides /// The col in the table where this spinner resides /// Double pointer to spin box which will hold the spinner upon exit /// The height of the spinner /// The minimum value of the spinner /// The maximum value of the spinner /// The step of the spinner /// The precision of the spinner /// The signal the spinner emits /// The slot to receive the signal void Fractorium::SetupAffineSpinner(QTableWidget* table, const QObject* receiver, int row, int col, AffineDoubleSpinBox*& spinBox, int height, double min, double max, double step, double prec, const char* signal, const char* slot) { spinBox = new AffineDoubleSpinBox(table, height, step); spinBox->setRange(min, max); spinBox->setDecimals(prec); table->setCellWidget(row, col, spinBox); connect(spinBox, signal, receiver, slot, Qt::QueuedConnection); spinBox->DoubleClick(true); spinBox->DoubleClickNonZero(0); spinBox->DoubleClickZero(1); } /// /// GUI wrapper function, getter only. /// bool Fractorium::LocalPivot() { return ui.LocalPivotRadio->isChecked(); } template class FractoriumEmberController; #ifdef DO_DOUBLE template class FractoriumEmberController; #endif