diff --git a/Source/Ember/EmberToXml.h b/Source/Ember/EmberToXml.h index b184e7f..1b68219 100644 --- a/Source/Ember/EmberToXml.h +++ b/Source/Ember/EmberToXml.h @@ -231,6 +231,10 @@ public: if (ember.UseFinalXform()) os << ToString(*ember.NonConstFinalXform(), ember.XformCount(), true, false);//Final, don't do motion. + //Note that only embedded palettes are saved. The old style of specifying a palette index to look up in a default palette file + //is no longer supported, as it makes no sense when using multiple palette files. The only way it could work is if the index was + //always meant to refer to the default file, or if the filename was embedded as well. It's easier, more straightforward and + //less error prone to just embed the palette. if (hexPalette) { os << " \n"; diff --git a/Source/Ember/Palette.h b/Source/Ember/Palette.h index 87915f6..ea3f78b 100644 --- a/Source/Ember/Palette.h +++ b/Source/Ember/Palette.h @@ -145,6 +145,7 @@ public: { m_Index = palette.m_Index; m_Name = palette.m_Name; + m_Filename = palette.m_Filename; CopyVec(m_Entries, palette.m_Entries); return *this; @@ -204,6 +205,7 @@ public: { palette.m_Index = m_Index; palette.m_Name = m_Name; + palette.m_Filename = m_Filename; palette.m_Entries.resize(Size()); for (uint i = 0; i < Size(); i++) @@ -263,6 +265,7 @@ public: } palette.m_Name = m_Name; + palette.m_Filename = m_Filename; } else { @@ -340,6 +343,7 @@ public: { palette.m_Index = m_Index; palette.m_Name = m_Name; + palette.m_Filename = m_Filename; if (palette.Size() != Size()) palette.m_Entries.resize(Size()); @@ -574,6 +578,7 @@ public: int m_Index;//Index in the xml palette file of this palette, use -1 for random. string m_Name;//Name of this palette. + string m_Filename;//Name of the parent file this palette came from, can be empty. vector m_Entries;//Storage for the color values. }; } diff --git a/Source/Ember/PaletteList.h b/Source/Ember/PaletteList.h index 02bfaf2..1fbd65f 100644 --- a/Source/Ember/PaletteList.h +++ b/Source/Ember/PaletteList.h @@ -55,7 +55,7 @@ public: palettes.clear(); palettes.reserve(buf.size() / 2048);//Roughly what it takes per palette. - ParsePalettes(rootNode, palettes); + ParsePalettes(rootNode, filename, palettes); xmlFreeDoc(doc); added = true; } @@ -81,6 +81,7 @@ public: auto p = m_Palettes.begin(); int i = 0, paletteFileIndex = QTIsaac::GlobalRand->Rand() % Size(); + //Move p forward i elements. while (i < paletteFileIndex && p != m_Palettes.end()) { ++i; @@ -218,13 +219,14 @@ public: private: /// - /// Parses an Xml node for all palettes present and store in the passed in palette vector. + /// Parses an Xml node for all palettes present and stores them in the passed in palette vector. /// Note that although the Xml color values are expected to be 0-255, they are converted and /// stored as normalized colors, with values from 0-1. /// /// The parent note of all palettes in the Xml file. + /// The name of the Xml file. /// The vector to store the paresed palettes associated with this file in. - void ParsePalettes(xmlNode* node, vector>& palettes) + void ParsePalettes(xmlNode* node, const string& filename, vector>& palettes) { bool hexError = false; char* val; @@ -287,12 +289,13 @@ private: if (!hexError) { + palette.m_Filename = filename; palettes.push_back(palette); } } else { - ParsePalettes(node->children, palettes); + ParsePalettes(node->children, filename, palettes); } node = node->next; diff --git a/Source/Fractorium/DoubleSpinBox.cpp b/Source/Fractorium/DoubleSpinBox.cpp index 63b004e..17115b5 100644 --- a/Source/Fractorium/DoubleSpinBox.cpp +++ b/Source/Fractorium/DoubleSpinBox.cpp @@ -1,6 +1,8 @@ #include "FractoriumPch.h" #include "DoubleSpinBox.h" +QTimer DoubleSpinBox::m_Timer; + /// /// Constructor that passes parent to the base and sets up height and step. /// Specific focus policy is used to allow the user to hover over the control @@ -21,14 +23,15 @@ DoubleSpinBox::DoubleSpinBox(QWidget* p, int h, double step) m_SmallStep = step / 10.0; setSingleStep(step); setFrame(false); + //setAttribute(Qt::WA_PaintOnScreen); setButtonSymbols(QAbstractSpinBox::NoButtons); setFocusPolicy(Qt::StrongFocus); setMinimumHeight(h);//setGeometry() has no effect, so must set both of these instead. setMaximumHeight(h); + setContextMenuPolicy(Qt::PreventContextMenu); lineEdit()->installEventFilter(this); lineEdit()->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); - connect(this, SIGNAL(valueChanged(double)), this, SLOT(onSpinBoxValueChanged(double)), Qt::QueuedConnection); - + connect(this, SIGNAL(valueChanged(double)), this, SLOT(OnSpinBoxValueChanged(double)), Qt::QueuedConnection); } /// @@ -118,11 +121,44 @@ QLineEdit* DoubleSpinBox::lineEdit() /// /// Another workaround for the persistent text selection bug in Qt. /// -void DoubleSpinBox::onSpinBoxValueChanged(double d) +void DoubleSpinBox::OnSpinBoxValueChanged(double d) { lineEdit()->deselect();//Gets rid of nasty "feature" that always has text selected. } +/// +/// Called while the timer is activated due to the right mouse button being held down. +/// +void DoubleSpinBox::OnTimeout() +{ + int xdistance = m_MouseMovePoint.x() - m_MouseDownPoint.x(); + int ydistance = m_MouseMovePoint.y() - m_MouseDownPoint.y(); + int distance = abs(xdistance) > abs(ydistance) ? xdistance : ydistance; + double scale, val; + double d = value(); + bool shift = QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier); + //bool ctrl = QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier); + double amount = (m_SmallStep + m_Step) * 0.5; + + if (shift) + { + //qDebug() << "Shift pressed"; + scale = 0.0001; + } + /*else if (ctrl) + { + qDebug() << "Control pressed"; + scale = 0.01; + }*/ + else + scale = 0.001; + + val = d + (distance * amount * scale); + setValue(val); + + //qDebug() << "Timer on, orig val: " << d << ", new val: " << val << ", distance " << distance; +} + /// /// Event filter for taking special action on double click events. /// @@ -131,8 +167,16 @@ void DoubleSpinBox::onSpinBoxValueChanged(double d) /// false bool DoubleSpinBox::eventFilter(QObject* o, QEvent* e) { - if (e->type() == QMouseEvent::MouseButtonPress && isEnabled()) + QMouseEvent* me = dynamic_cast(e); + + if (isEnabled() && + me && + me->type() == QMouseEvent::MouseButtonPress && + me->button() == Qt::RightButton) { + m_MouseDownPoint = m_MouseMovePoint = me->pos(); + StartTimer(); + //qDebug() << "Right mouse down"; // QPoint pt; // // if (QMouseEvent* me = (QMouseEvent*)e) @@ -154,6 +198,23 @@ bool DoubleSpinBox::eventFilter(QObject* o, QEvent* e) // return true; // } } + else if (isEnabled() && + me && + me->type() == QMouseEvent::MouseButtonRelease && + me->button() == Qt::RightButton) + { + StopTimer(); + m_MouseDownPoint = m_MouseMovePoint = me->pos(); + //qDebug() << "Right mouse up"; + } + else if (isEnabled() && + me && + me->type() == QMouseEvent::MouseMove && + QGuiApplication::mouseButtons() & Qt::RightButton) + { + m_MouseMovePoint = me->pos(); + qDebug() << "Mouse move while right down. Pt = " << me->pos() << ", global: " << mapToGlobal(me->pos()); + } else if (m_DoubleClick && e->type() == QMouseEvent::MouseButtonDblClick && isEnabled()) { if (IsNearZero(value())) @@ -189,6 +250,7 @@ bool DoubleSpinBox::eventFilter(QObject* o, QEvent* e) void DoubleSpinBox::focusInEvent(QFocusEvent* e) { //lineEdit()->setReadOnly(false); + StopTimer(); QDoubleSpinBox::focusInEvent(e); } @@ -203,7 +265,8 @@ void DoubleSpinBox::focusOutEvent(QFocusEvent* e) { //lineEdit()->deselect();//Clear selection when leaving. //lineEdit()->setReadOnly(true);//Clever hack to clear the cursor when leaving. - QDoubleSpinBox::focusOutEvent(e); + StopTimer(); + QDoubleSpinBox::focusOutEvent(e); } /// @@ -215,6 +278,7 @@ void DoubleSpinBox::enterEvent(QEvent* e) { //m_Select = true; //setFocus(); + StopTimer(); QDoubleSpinBox::enterEvent(e); } @@ -226,6 +290,26 @@ void DoubleSpinBox::enterEvent(QEvent* e) void DoubleSpinBox::leaveEvent(QEvent* e) { //m_Select = false; - //clearFocus(); + //clearFocus();. + StopTimer(); QDoubleSpinBox::leaveEvent(e); } + +/// +/// Start the timer in response to the right mouse button being pressed. +/// +void DoubleSpinBox::StartTimer() +{ + m_Timer.stop(); + connect(&m_Timer, SIGNAL(timeout()), this, SLOT(OnTimeout())); + m_Timer.start(300); +} + +/// +/// Stop the timer in response to the left mouse button being pressed. +/// +void DoubleSpinBox::StopTimer() +{ + m_Timer.stop(); + disconnect(&m_Timer, SIGNAL(timeout()), this, SLOT(OnTimeout())); +} \ No newline at end of file diff --git a/Source/Fractorium/DoubleSpinBox.h b/Source/Fractorium/DoubleSpinBox.h index c07a0fa..f0cb880 100644 --- a/Source/Fractorium/DoubleSpinBox.h +++ b/Source/Fractorium/DoubleSpinBox.h @@ -29,7 +29,8 @@ public: QLineEdit* lineEdit(); public slots: - void onSpinBoxValueChanged(double d); + void OnSpinBoxValueChanged(double d); + void OnTimeout(); protected: virtual bool eventFilter(QObject* o, QEvent* e) override; @@ -39,12 +40,18 @@ protected: virtual void leaveEvent(QEvent* e); private: + void StartTimer(); + void StopTimer(); + bool m_Select; bool m_DoubleClick; double m_DoubleClickNonZero; double m_DoubleClickZero; double m_Step; double m_SmallStep; + QPoint m_MouseDownPoint; + QPoint m_MouseMovePoint; + static QTimer m_Timer; }; /// diff --git a/Source/Fractorium/Fractorium.cpp b/Source/Fractorium/Fractorium.cpp index 96bc709..43ed366 100644 --- a/Source/Fractorium/Fractorium.cpp +++ b/Source/Fractorium/Fractorium.cpp @@ -723,6 +723,78 @@ void Fractorium::SetTabOrders() w = SetTabOrder(this, w, ui.InfoRenderingTextEdit); } +/// +/// Toggle all table spinner values in one row. +/// The logic is: +/// If any cell in the row is non zero, set all cells to zero, else 1. +/// If shift is held down, reverse the logic. +/// Resets the rendering process. +/// +/// The index of the row that was double clicked +void Fractorium::ToggleTableRow(TableWidget* table, int logicalIndex) +{ + bool allZero = true; + int cols = table->columnCount(); + bool shift = QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier); + + for (int i = 0; i < cols; i++) + { + if (auto* spinBox = dynamic_cast(table->cellWidget(logicalIndex, i))) + { + if (!IsNearZero(spinBox->value())) + { + allZero = false; + break; + } + } + } + + if (shift) + allZero = !allZero; + + double val = allZero ? 1.0 : 0.0; + + for (int i = 0; i < cols; i++) + if (auto* spinBox = dynamic_cast(table->cellWidget(logicalIndex, i))) + spinBox->setValue(val); +} + +/// +/// Toggle all table spinner values in one column. +/// The logic is: +/// If any cell in the column is non zero, set all cells to zero, else 1. +/// If shift is held down, reverse the logic. +/// Resets the rendering process. +/// +/// The index of the column that was double clicked +void Fractorium::ToggleTableCol(TableWidget* table, int logicalIndex) +{ + bool allZero = true; + int rows = table->rowCount(); + bool shift = QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier); + + for (int i = 0; i < rows; i++) + { + if (auto* spinBox = dynamic_cast(table->cellWidget(i, logicalIndex))) + { + if (!IsNearZero(spinBox->value())) + { + allZero = false; + break; + } + } + } + + if (shift) + allZero = !allZero; + + double val = allZero ? 1.0 : 0.0; + + for (int i = 0; i < rows; i++) + if (auto* spinBox = dynamic_cast(table->cellWidget(i, logicalIndex))) + spinBox->setValue(val); +} + /// /// This is no longer needed and was used to compensate for a different bug /// however the code is interesting, so keep it around for possible future use. diff --git a/Source/Fractorium/Fractorium.h b/Source/Fractorium/Fractorium.h index 992b0c6..8d99d89 100644 --- a/Source/Fractorium/Fractorium.h +++ b/Source/Fractorium/Fractorium.h @@ -194,6 +194,11 @@ public slots: void OnXformNameChanged(int row, int col); //Xforms Affine. + void OnPreAffineRowDoubleClicked(int logicalIndex); + void OnPreAffineColDoubleClicked(int logicalIndex); + void OnPostAffineRowDoubleClicked(int logicalIndex); + void OnPostAffineColDoubleClicked(int logicalIndex); + void OnX1Changed(double d); void OnX2Changed(double d); void OnY1Changed(double d); @@ -299,6 +304,9 @@ private: void InitLibraryUI(); void SetTabOrders(); + void ToggleTableRow(TableWidget* table, int logicalIndex); + void ToggleTableCol(TableWidget* table, int logicalIndex); + //Embers. bool HaveFinal(); @@ -324,6 +332,7 @@ private: //Palette. void ResetPaletteControls(); + void SetPaletteFileComboIndex(const string& filename); //Info. void UpdateHistogramBounds(); diff --git a/Source/Fractorium/Fractorium.ui b/Source/Fractorium/Fractorium.ui index fece377..c7f0580 100644 --- a/Source/Fractorium/Fractorium.ui +++ b/Source/Fractorium/Fractorium.ui @@ -365,6 +365,9 @@ Library + + All flames in the currently opened file or randomly generated flock + 5 @@ -472,6 +475,9 @@ Flame + + General flame parameters + 0 @@ -1884,6 +1890,9 @@ Xforms + + Xforms in the current flame + 5 @@ -1962,11 +1971,14 @@ SpinBox - Color palette index for the current xform, and curve adjustment. + Color + + Color palette index for the current xform, and curve adjustment + QFormLayout::AllNonFixedFieldsGrow @@ -2583,7 +2595,7 @@ SpinBox - Affine transforms for the current xform. + true @@ -2591,6 +2603,9 @@ SpinBox Affine + + Affine transforms for the current xform + 5 @@ -3944,11 +3959,14 @@ SpinBox - Full list of available variations and their weights for the currently selected xform. + Variations + + Full list of available variations and their weights for the currently selected xform + 6 @@ -4175,7 +4193,7 @@ SpinBox - Select multiple xforms to apply operations to. + true @@ -4183,6 +4201,9 @@ SpinBox Select + + Select multiple xforms to apply operations to + 3 @@ -4673,6 +4694,9 @@ SpinBox Xaos + + Xaos weights between xforms + 4 @@ -4886,6 +4910,9 @@ SpinBox Palette + + List of available palette files and their palettes + QLayout::SetDefaultConstraint @@ -5272,6 +5299,9 @@ SpinBox Info + + Diagnostic information of engineering interest + 6 diff --git a/Source/Fractorium/FractoriumPalette.cpp b/Source/Fractorium/FractoriumPalette.cpp index 697990f..3206ef1 100644 --- a/Source/Fractorium/FractoriumPalette.cpp +++ b/Source/Fractorium/FractoriumPalette.cpp @@ -83,55 +83,56 @@ int FractoriumEmberController::InitPaletteList(const string& s) template bool FractoriumEmberController::FillPaletteTable(const string& s) { - QTableWidget* paletteTable = m_Fractorium->ui.PaletteListTable; - QTableWidget* palettePreviewTable = m_Fractorium->ui.PalettePreviewTable; - - m_CurrentPaletteFilePath = m_Fractorium->ui.PaletteFilenameCombo->property("path").toString().toStdString() + s; - size_t paletteSize = m_PaletteList.Size(m_CurrentPaletteFilePath); - - if (paletteSize) + if (!s.empty())//This occasionally seems to get called with an empty string for reasons unknown. { - paletteTable->clear(); - paletteTable->blockSignals(true); - paletteTable->setRowCount(paletteSize); - - //Headers get removed when clearing, so must re-create here. - QTableWidgetItem* nameHeader = new QTableWidgetItem("Name"); - QTableWidgetItem* paletteHeader = new QTableWidgetItem("Palette"); + QTableWidget* paletteTable = m_Fractorium->ui.PaletteListTable; + QTableWidget* palettePreviewTable = m_Fractorium->ui.PalettePreviewTable; - nameHeader->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); - paletteHeader->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); + m_CurrentPaletteFilePath = m_Fractorium->ui.PaletteFilenameCombo->property("path").toString().toStdString() + s; - paletteTable->setHorizontalHeaderItem(0, nameHeader); - paletteTable->setHorizontalHeaderItem(1, paletteHeader); - - //Palette list table. - for (size_t i = 0; i < paletteSize; i++) + if (size_t paletteSize = m_PaletteList.Size(m_CurrentPaletteFilePath)) { - Palette* p = m_PaletteList.GetPalette(m_CurrentPaletteFilePath, i); - vector v = p->MakeRgbPaletteBlock(PALETTE_CELL_HEIGHT); - QTableWidgetItem* nameCol = new QTableWidgetItem(p->m_Name.c_str()); + paletteTable->clear(); + paletteTable->blockSignals(true); + paletteTable->setRowCount(paletteSize); - nameCol->setToolTip(p->m_Name.c_str()); - paletteTable->setItem(i, 0, nameCol); + //Headers get removed when clearing, so must re-create here. + QTableWidgetItem* nameHeader = new QTableWidgetItem("Name"); + QTableWidgetItem* paletteHeader = new QTableWidgetItem("Palette"); - QImage image(v.data(), p->Size(), PALETTE_CELL_HEIGHT, QImage::Format_RGB888); - QTableWidgetItem* paletteItem = new QTableWidgetItem(); + nameHeader->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); + paletteHeader->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); - paletteItem->setData(Qt::DecorationRole, QPixmap::fromImage(image)); - paletteTable->setItem(i, 1, paletteItem); + paletteTable->setHorizontalHeaderItem(0, nameHeader); + paletteTable->setHorizontalHeaderItem(1, paletteHeader); + + //Palette list table. + for (size_t i = 0; i < paletteSize; i++) + { + Palette* p = m_PaletteList.GetPalette(m_CurrentPaletteFilePath, i); + vector v = p->MakeRgbPaletteBlock(PALETTE_CELL_HEIGHT); + QTableWidgetItem* nameCol = new QTableWidgetItem(p->m_Name.c_str()); + + nameCol->setToolTip(p->m_Name.c_str()); + paletteTable->setItem(i, 0, nameCol); + + QImage image(v.data(), p->Size(), PALETTE_CELL_HEIGHT, QImage::Format_RGB888); + QTableWidgetItem* paletteItem = new QTableWidgetItem(); + + paletteItem->setData(Qt::DecorationRole, QPixmap::fromImage(image)); + paletteTable->setItem(i, 1, paletteItem); + } + + paletteTable->blockSignals(false); + return true; } + else + { + vector errors = m_PaletteList.ErrorReport(); - paletteTable->blockSignals(false); - m_Fractorium->OnPaletteRandomSelectButtonClicked(true); - return true; - } - else - { - vector errors = m_PaletteList.ErrorReport(); - - m_Fractorium->ErrorReportToQTextEdit(errors, m_Fractorium->ui.InfoFileOpeningTextEdit); - m_Fractorium->ShowCritical("Palette Read Error", "Could not load palette file, all images will be black. See info tab for details."); + m_Fractorium->ErrorReportToQTextEdit(errors, m_Fractorium->ui.InfoFileOpeningTextEdit); + m_Fractorium->ShowCritical("Palette Read Error", "Could not load palette file, all images will be black. See info tab for details."); + } } return false; @@ -226,7 +227,6 @@ template void FractoriumEmberController::PaletteCellClicked(int row, int col) { Palette* palette = m_PaletteList.GetPalette(m_CurrentPaletteFilePath, row); - QTableWidgetItem* nameItem = m_Fractorium->ui.PaletteListTable->item(row, 0); if (palette) { @@ -266,7 +266,7 @@ void Fractorium::OnPaletteCellDoubleClicked(int row, int col) /// Called when the Random Palette button is clicked. /// Resets the rendering process. /// -/// True to clear the current adjustments, else leave current adjustments. +/// True to clear the current adjustments, else leave current adjustments and apply them to the newly selected palette. void Fractorium::OnPaletteRandomSelectButtonClicked(bool checked) { uint i = 0; @@ -323,6 +323,18 @@ void Fractorium::ResetPaletteControls() m_PaletteFrequencySpin->SetValueStealth(1); } +/// +/// Set the index of the palette file combo box. +/// This is for display purposes only so the user can see which file, if any, +/// the current palette came from. +/// +/// The string to set the index to +void Fractorium::SetPaletteFileComboIndex(const string& filename) +{ + if (!filename.empty()) + ui.PaletteFilenameCombo->setCurrentText(QFileInfo(QString::fromStdString(filename)).fileName()); +} + template class FractoriumEmberController; #ifdef DO_DOUBLE diff --git a/Source/Fractorium/FractoriumParams.cpp b/Source/Fractorium/FractoriumParams.cpp index 101df9d..9dca265 100644 --- a/Source/Fractorium/FractoriumParams.cpp +++ b/Source/Fractorium/FractoriumParams.cpp @@ -512,6 +512,7 @@ void FractoriumEmberController::SetCenter(double x, double y) /// /// Fill the parameter tables and palette widgets with values from the current ember. +/// This takes ~1-2ms. /// template void FractoriumEmberController::FillParamTablesAndPalette() @@ -551,26 +552,18 @@ void FractoriumEmberController::FillParamTablesAndPalette() m_Fractorium->m_AffineInterpTypeCombo->SetCurrentIndexStealth(m_Ember.m_AffineInterp); m_Fractorium->m_InterpTypeCombo->SetCurrentIndexStealth(m_Ember.m_Interp); + //Xaos. + FillXaos(); + //Palette. m_Fractorium->ResetPaletteControls(); m_Fractorium->m_PaletteHueSpin->SetValueStealth(NormalizeDeg180(m_Ember.m_Hue * 360.0));//Convert -0.5 to 0.5 range to -180 - 180. - //Use -1 as a placeholder to mean either generate a random palette from the list or - //to just use the values "as-is" without looking them up in the list. - if (m_Ember.m_Palette.m_Index >= 0) - { - m_Fractorium->OnPaletteCellClicked(Clamp(m_Ember.m_Palette.m_Index, 0, m_Fractorium->ui.PaletteListTable->rowCount() - 1), 1); - } - else - { - //An ember with an embedded palette was loaded, rather than one from the list, so assign it directly to the controls without applying adjustments. - //Normally, temp palette is assigned whenever the user clicks on a palette cell. But since that is skipped here just make a copy of the ember's palette. - m_TempPalette = m_Ember.m_Palette; - UpdateAdjustedPaletteGUI(m_Ember.m_Palette);//Will clear name string since embedded palettes have no name. This will trigger a full render. - } - - //Xaos. - FillXaos(); + //Use the ember's embedded palette, rather than one from the list, so assign it directly to the controls without applying adjustments. + //Normally, the temp palette is assigned whenever the user clicks on a palette cell. But since that is skipped here, must do it manually. + m_TempPalette = m_Ember.m_Palette; + m_Fractorium->SetPaletteFileComboIndex(m_Ember.m_Palette.m_Filename); + UpdateAdjustedPaletteGUI(m_Ember.m_Palette);//Setting the palette will trigger a full render. } /// diff --git a/Source/Fractorium/FractoriumXaos.cpp b/Source/Fractorium/FractoriumXaos.cpp index 6fceac7..df3efce 100644 --- a/Source/Fractorium/FractoriumXaos.cpp +++ b/Source/Fractorium/FractoriumXaos.cpp @@ -188,74 +188,22 @@ void Fractorium::OnRandomXaosButtonClicked(bool checked) { m_Controller->RandomX /// /// Toggle all xaos values in one row. -/// The logic is: -/// If any cell in the row is non zero, set all cells to zero, else 1. -/// If shift is held down, reverse the logic. /// Resets the rendering process. /// /// The index of the row that was double clicked void Fractorium::OnXaosRowDoubleClicked(int logicalIndex) { - bool allZero = true; - int cols = ui.XaosTable->columnCount(); - bool shift = QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier); - - for (int i = 0; i < cols; i++) - { - if (auto* spinBox = dynamic_cast(ui.XaosTable->cellWidget(logicalIndex, i))) - { - if (!IsNearZero(spinBox->value())) - { - allZero = false; - break; - } - } - } - - if (shift) - allZero = !allZero; - - double val = allZero ? 1.0 : 0.0; - - for (int i = 0; i < cols; i++) - if (auto* spinBox = dynamic_cast(ui.XaosTable->cellWidget(logicalIndex, i))) - spinBox->setValue(val); + ToggleTableRow(ui.XaosTable, logicalIndex); } /// /// Toggle all xaos values in one column. -/// The logic is: -/// If any cell in the column is non zero, set all cells to zero, else 1. -/// If shift is held down, reverse the logic. /// Resets the rendering process. /// /// The index of the column that was double clicked void Fractorium::OnXaosColDoubleClicked(int logicalIndex) { - bool allZero = true; - int rows = ui.XaosTable->rowCount(); - bool shift = QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier); - - for (int i = 0; i < rows; i++) - { - if (auto* spinBox = dynamic_cast(ui.XaosTable->cellWidget(i, logicalIndex))) - { - if (!IsNearZero(spinBox->value())) - { - allZero = false; - break; - } - } - } - - if (shift) - allZero = !allZero; - - double val = allZero ? 1.0 : 0.0; - - for (int i = 0; i < rows; i++) - if (auto* spinBox = dynamic_cast(ui.XaosTable->cellWidget(i, logicalIndex))) - spinBox->setValue(val); + ToggleTableCol(ui.XaosTable, logicalIndex); } template class FractoriumEmberController; diff --git a/Source/Fractorium/FractoriumXformsAffine.cpp b/Source/Fractorium/FractoriumXformsAffine.cpp index 783d0a8..082e350 100644 --- a/Source/Fractorium/FractoriumXformsAffine.cpp +++ b/Source/Fractorium/FractoriumXformsAffine.cpp @@ -10,9 +10,16 @@ void Fractorium::InitXformsAffineUI() double affineStep = 0.01, affineMin = std::numeric_limits::lowest(), affineMax = std::numeric_limits::max(); QTableWidget* table = ui.PreAffineTable; - SetFixedTableHeader(table->horizontalHeader(), QHeaderView::Stretch);//The designer continually clobbers these values, so must manually set them here. - SetFixedTableHeader(table->verticalHeader()); - + 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))); @@ -22,9 +29,16 @@ void Fractorium::InitXformsAffineUI() SetupAffineSpinner(table, this, 2, 1, m_PreO2Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnO2Changed(double))); table = ui.PostAffineTable; - SetFixedTableHeader(table->horizontalHeader(), QHeaderView::Stretch);//The designer continually clobbers these values, so must manually set them here. - SetFixedTableHeader(table->verticalHeader()); - + 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))); @@ -159,6 +173,46 @@ void Fractorium::InitXformsAffineUI() ui.PostAffineGroupBox->setChecked(false); } +/// +/// 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. diff --git a/Source/Fractorium/SpinBox.cpp b/Source/Fractorium/SpinBox.cpp index 923c375..13d05a4 100644 --- a/Source/Fractorium/SpinBox.cpp +++ b/Source/Fractorium/SpinBox.cpp @@ -1,6 +1,8 @@ #include "FractoriumPch.h" #include "SpinBox.h" +QTimer SpinBox::m_Timer; + /// /// Constructor that passes parent to the base and sets up height and step. /// Specific focus policy is used to allow the user to hover over the control @@ -97,6 +99,39 @@ void SpinBox::onSpinBoxValueChanged(int i) lineEdit()->deselect();//Gets rid of nasty "feature" that always has text selected. } +/// +/// Called while the timer is activated due to the right mouse button being held down. +/// +void SpinBox::OnTimeout() +{ + int xdistance = m_MouseMovePoint.x() - m_MouseDownPoint.x(); + int ydistance = m_MouseMovePoint.y() - m_MouseDownPoint.y(); + int distance = abs(xdistance) > abs(ydistance) ? xdistance : ydistance; + double scale, val; + int d = value(); + bool shift = QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier); + //bool ctrl = QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier); + double amount = (m_SmallStep + m_Step) * 0.5; + + if (shift) + { + //qDebug() << "Shift pressed"; + scale = 0.001; + } + /*else if (ctrl) + { + qDebug() << "Control pressed"; + scale = 0.01; + }*/ + else + scale = 0.01; + + val = d + (distance * amount * scale); + setValue(int(val)); + + //qDebug() << "Timer on, orig val: " << d << ", new val: " << val << ", distance " << distance; +} + /// /// Event filter for taking special action on double click events. /// @@ -105,28 +140,53 @@ void SpinBox::onSpinBoxValueChanged(int i) /// false bool SpinBox::eventFilter(QObject* o, QEvent* e) { - if (e->type() == QMouseEvent::MouseButtonPress && isEnabled()) + QMouseEvent* me = dynamic_cast(e); + + if (isEnabled() && + me && + me->type() == QMouseEvent::MouseButtonPress && + me->button() == Qt::RightButton) { - //QPoint pt; + m_MouseDownPoint = m_MouseMovePoint = me->pos(); + StartTimer(); + //qDebug() << "Right mouse down"; + // QPoint pt; // - //if (QMouseEvent* me = (QMouseEvent*)e) - // pt = me->localPos().toPoint(); + // if (QMouseEvent* me = (QMouseEvent*)e) + // pt = me->localPos().toPoint(); // - //int pos = lineEdit()->cursorPositionAt(pt); + // int pos = lineEdit()->cursorPositionAt(pt); // - //if (lineEdit()->selectedText() != "") - //{ - // lineEdit()->deselect(); - // lineEdit()->setCursorPosition(pos); - // return true; - //} - //else if (m_Select) - //{ - // lineEdit()->setCursorPosition(pos); - // selectAll(); - // m_Select = false; - // return true; - //} + // if (lineEdit()->selectedText() != "") + // { + // lineEdit()->deselect(); + // lineEdit()->setCursorPosition(pos); + // return true; + // } + // else if (m_Select) + // { + // lineEdit()->setCursorPosition(pos); + // selectAll(); + // m_Select = false; + // return true; + // } + } + else if (isEnabled() && + me && + me->type() == QMouseEvent::MouseButtonRelease && + me->button() == Qt::RightButton) + { + StopTimer(); + m_MouseDownPoint = m_MouseMovePoint = me->pos(); + //qDebug() << "Right mouse up"; + } + else if (isEnabled() && + me && + me->type() == QMouseEvent::MouseMove && + QGuiApplication::mouseButtons() & Qt::RightButton) + { + m_MouseMovePoint = me->pos(); + qDebug() << "Mouse move while right down. Pt = " << me->pos() << ", global: " << mapToGlobal(me->pos()); } else if (m_DoubleClick && e->type() == QMouseEvent::MouseButtonDblClick && isEnabled()) { @@ -163,6 +223,7 @@ bool SpinBox::eventFilter(QObject* o, QEvent* e) void SpinBox::focusInEvent(QFocusEvent* e) { //lineEdit()->setReadOnly(false); + StopTimer(); QSpinBox::focusInEvent(e); } @@ -177,7 +238,8 @@ void SpinBox::focusOutEvent(QFocusEvent* e) { //lineEdit()->deselect();//Clear selection when leaving. //lineEdit()->setReadOnly(true);//Clever hack to clear the cursor when leaving. - QSpinBox::focusOutEvent(e); + StopTimer(); + QSpinBox::focusOutEvent(e); } /// @@ -189,6 +251,7 @@ void SpinBox::enterEvent(QEvent* e) { //m_Select = true; //setFocus(); + StopTimer(); QSpinBox::enterEvent(e); } @@ -201,5 +264,25 @@ void SpinBox::leaveEvent(QEvent* e) { //m_Select = false; //clearFocus(); + StopTimer(); QSpinBox::leaveEvent(e); } + +/// +/// Start the timer in response to the right mouse button being pressed. +/// +void SpinBox::StartTimer() +{ + m_Timer.stop(); + connect(&m_Timer, SIGNAL(timeout()), this, SLOT(OnTimeout())); + m_Timer.start(300); +} + +/// +/// Stop the timer in response to the left mouse button being pressed. +/// +void SpinBox::StopTimer() +{ + m_Timer.stop(); + disconnect(&m_Timer, SIGNAL(timeout()), this, SLOT(OnTimeout())); +} diff --git a/Source/Fractorium/SpinBox.h b/Source/Fractorium/SpinBox.h index ee6b146..140ea64 100644 --- a/Source/Fractorium/SpinBox.h +++ b/Source/Fractorium/SpinBox.h @@ -27,6 +27,7 @@ public: public slots: void onSpinBoxValueChanged(int i); + void OnTimeout(); protected: bool eventFilter(QObject* o, QEvent* e); @@ -36,10 +37,16 @@ protected: virtual void leaveEvent(QEvent* e); private: + void StartTimer(); + void StopTimer(); + bool m_Select; bool m_DoubleClick; int m_DoubleClickNonZero; int m_DoubleClickZero; int m_Step; int m_SmallStep; + QPoint m_MouseDownPoint; + QPoint m_MouseMovePoint; + static QTimer m_Timer; };