fractorium/Source/Fractorium/SpinBox.cpp
Person 1dfbd4eff2 --User changes
-Add new preset dimensions to the right click menu of the width and height fields in the editor.
-Change QSS stylesheets to properly handle tabs.
-Make tabs rectangular by default. For some reason, they had always been triangular.

--Bug fixes
 -Incremental rendering times in the editor were wrong.

--Code changes
 -Migrate to Qt6. There is probably more work to be done here.
-Migrate to VS2022.
-Migrate to Wix 4 installer.
-Change installer to install to program files for all users.
-Fix many VS2022 code analysis warnings.
-No longer use byte typedef, because std::byte is now a type. Revert all back to unsigned char.
-Upgrade OpenCL headers to version 3.0 and keep locally now rather than trying to look for system files.
-No longer link to Nvidia or AMD specific OpenCL libraries. Use the generic installer located at OCL_ROOT too.
-Add the ability to change OpenCL grid dimensions. This was attempted for investigating possible performance improvments, but made no difference.

This has not been verified on Linux or Mac yet.
2023-04-25 17:59:54 -06:00

389 lines
10 KiB
C++

#include "FractoriumPch.h"
#include "SpinBox.h"
#include "FractoriumSettings.h"
QTimer SpinBox::s_Timer;
/// <summary>
/// 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
/// and change its value using the mouse wheel without explicitly having to click
/// inside of it.
/// </summary>
/// <param name="p">The parent widget. Default: nullptr.</param>
/// <param name="h">The height of the spin box. Default: 16.</param>
/// <param name="step">The step used to increment/decrement the spin box when using the mouse wheel. Default: 1.</param>
SpinBox::SpinBox(QWidget* p, int h, int step)
: QSpinBox(p)
{
m_DoubleClick = false;
m_DoubleClickLowVal = 0;
m_DoubleClickNonZero = 0;
m_DoubleClickZero = 1;
m_Step = step;
m_SmallStep = 1;
m_Settings = FractoriumSettings::DefInstance();
setSingleStep(step);
setFrame(false);
setButtonSymbols(QAbstractSpinBox::NoButtons);
setFocusPolicy(Qt::StrongFocus);
setMinimumHeight(h);//setGeometry() has no effect, so set both of these instead.
setMaximumHeight(h);
setContextMenuPolicy(Qt::PreventContextMenu);
lineEdit()->installEventFilter(this);
lineEdit()->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
connect(this, SIGNAL(valueChanged(int)), this, SLOT(onSpinBoxValueChanged(int)), Qt::QueuedConnection);
}
/// <summary>
/// Set the value of the control without triggering signals.
/// </summary>
/// <param name="d">The value to set it to</param>
void SpinBox::SetValueStealth(int d)
{
blockSignals(true);
setValue(d);
blockSignals(false);
}
void SpinBox::SetValueStealth(size_t d) { SetValueStealth(static_cast<int>(d)); }
/// <summary>
/// Set whether to respond to double click events.
/// </summary>
/// <param name="b">True if this should respond to double click events, else false.</param>
void SpinBox::DoubleClick(bool b)
{
m_DoubleClick = b;
}
/// <summary>
/// Set the value to be used instead of zero to represent the lower value
/// used when responding to a double click.
/// </summary>
/// <param name="val">The value to be used for the lower value instead of zero</param>
void SpinBox::DoubleClickLowVal(int val)
{
m_DoubleClickLowVal = val;
}
int SpinBox::DoubleClickLowVal()
{
return m_DoubleClickLowVal;
}
/// <summary>
/// Set the value to be used when the user double clicks the spinner while
/// it contains zero.
/// </summary>
/// <param name="val">The value to be used</param>
void SpinBox::DoubleClickZero(int val)
{
m_DoubleClickZero = val;
}
int SpinBox::DoubleClickZero()
{
return m_DoubleClickZero;
}
/// <summary>
/// Set the value to be used when the user double clicks the spinner while
/// it contains a non-zero value.
/// </summary>
/// <param name="val">The value to be used</param>
void SpinBox::DoubleClickNonZero(int val)
{
m_DoubleClickNonZero = val;
}
int SpinBox::DoubleClickNonZero()
{
return m_DoubleClickNonZero;
}
/// <summary>
/// Set the small step to be used when the user holds down shift while scrolling.
/// The default is step / 10, so use this if something else is needed.
/// </summary>
/// <param name="step">The small step to use for scrolling while the shift key is down</param>
void SpinBox::SmallStep(int step)
{
m_SmallStep = std::min(1, step);
}
/// <summary>
/// Expose the underlying QLineEdit control to the caller.
/// </summary>
/// <returns>A pointer to the QLineEdit</returns>
QLineEdit* SpinBox::lineEdit()
{
return QSpinBox::lineEdit();
}
/// <summary>
/// Another workaround for the persistent text selection bug in Qt.
/// </summary>
void SpinBox::onSpinBoxValueChanged(int i)
{
lineEdit()->deselect();//Gets rid of nasty "feature" that always has text selected.
}
/// <summary>
/// Called while the timer is activated due to the right mouse button being held down.
/// </summary>
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;
distance = Sqr(distance) * (distance < 0 ? -1 : 1);
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)
scale = 0.001;
else if (ctrl)
scale = 0.01;
else
scale = 0.01;
val = d + (distance * amount * scale);
setValue(int(val));
}
/// <summary>
/// Event filter for taking special action on double click events.
/// </summary>
/// <param name="o">The object</param>
/// <param name="e">The eevent</param>
/// <returns>false</returns>
bool SpinBox::eventFilter(QObject* o, QEvent* e)
{
if (!isEnabled())
return QSpinBox::eventFilter(o, e);
const auto me = dynamic_cast<QMouseEvent*>(e);
if (me)
{
if (!m_Settings->ToggleType() &&//Ensure double click toggles, not right click.
me->type() == QMouseEvent::MouseButtonPress &&
me->button() == Qt::RightButton)
{
m_MouseDownPoint = m_MouseMovePoint = me->pos();
StartTimer();
e->accept();
return true;
}
else if (!m_Settings->ToggleType() &&
me->type() == QMouseEvent::MouseButtonRelease &&
me->button() == Qt::RightButton)
{
StopTimer();
m_MouseDownPoint = m_MouseMovePoint = me->pos();
e->accept();
return true;
}
else if (!m_Settings->ToggleType() &&
me->type() == QMouseEvent::MouseMove &&
QGuiApplication::mouseButtons() & Qt::RightButton)
{
m_MouseMovePoint = me->pos();
e->accept();
return true;
}
else if (m_DoubleClick &&
((!m_Settings->ToggleType() && e->type() == QMouseEvent::MouseButtonDblClick && me->button() == Qt::LeftButton) ||
(m_Settings->ToggleType() && me->type() == QMouseEvent::MouseButtonRelease && me->button() == Qt::RightButton)))
{
if (m_DoubleClickLowVal == value())
{
m_DoubleClickZeroEvent(this, m_DoubleClickZero);
setValue(m_DoubleClickZero);
}
else
{
m_DoubleClickNonZeroEvent(this, m_DoubleClickNonZero);
setValue(m_DoubleClickNonZero);
}
e->accept();
return true;
}
}
else if (isEnabled())
{
if (e->type() == QEvent::Wheel)
{
if (QWheelEvent* we = dynamic_cast<QWheelEvent*>(e))
{
const auto shift = QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier);
const auto ctrl = QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier);
if (we->angleDelta().ry() > 0)
{
if (shift)
{
setSingleStep(m_SmallStep);
setValue(value() + m_SmallStep);
}
else
{
setSingleStep(m_Step);
setValue(value() + (ctrl ? m_Step * 10 : m_Step));
}
}
else
{
if (shift)
{
setSingleStep(m_SmallStep);
setValue(value() - m_SmallStep);
}
else
{
setSingleStep(m_Step);
setValue(value() - (ctrl ? m_Step * 10 : m_Step));
}
}
e->accept();
return true;
}
}
else if (dynamic_cast<QKeyEvent*>(e))
{
e->accept();
return true;
}
}
return QSpinBox::eventFilter(o, e);
}
/// <summary>
/// Override which is for handling specific key presses while this control is focused.
/// In particular, + = and up arrow increase the value, equivalent to scrolling the mouse wheel up, while also observing shift/ctrl modifiers.
/// Values are decreased in the same way by pressing - _ or down arrow.
/// </summary>
/// <param name="ke">The key event</param>
void SpinBox::keyPressEvent(QKeyEvent* ke)
{
bool shift = QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier);
bool ctrl = QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier);
if (ke->key() == Qt::Key_Up)
{
if (shift)
{
setSingleStep(m_SmallStep);
setValue(value() + m_SmallStep);
}
else
{
setSingleStep(m_Step);
setValue(value() + (ctrl ? m_Step * 10 : m_Step));
}
ke->accept();
}
else if (ke->key() == Qt::Key_Down)
{
if (shift)
{
setSingleStep(m_SmallStep);
setValue(value() - m_SmallStep);
}
else
{
setSingleStep(m_Step);
setValue(value() - (ctrl ? m_Step * 10 : m_Step));
}
ke->accept();
}
else if (ke->key() == Qt::Key_Space)
{
if (m_DoubleClickLowVal == value())
{
m_DoubleClickZeroEvent(this, m_DoubleClickZero);
setValue(m_DoubleClickZero);
}
else
{
m_DoubleClickNonZeroEvent(this, m_DoubleClickNonZero);
setValue(m_DoubleClickNonZero);
}
}
else
QSpinBox::keyPressEvent(ke);
}
/// <summary>
/// Called when focus enters the spinner.
/// </summary>
/// <param name="e">The event</param>
void SpinBox::focusInEvent(QFocusEvent* e)
{
StopTimer();
QSpinBox::focusInEvent(e);
}
/// <summary>
/// Called when focus leaves the spinner.
/// Qt has a nasty "feature" that leaves the text in a spinner selected
/// and the cursor visible, regardless of whether it has the focus.
/// Manually clear both here.
/// </summary>
/// <param name="e">The event</param>
void SpinBox::focusOutEvent(QFocusEvent* e)
{
StopTimer();
QSpinBox::focusOutEvent(e);
}
/// <summary>
/// Called when focus enters the spinner.
/// Must set the focus to make sure key down messages don't erroneously go to the GLWidget.
/// </summary>
/// <param name="e">The event</param>
void SpinBox::enterEvent(QEnterEvent* e)
{
StopTimer();
QSpinBox::enterEvent(e);
}
/// <summary>
/// Called when focus leaves the spinner.
/// Must clear the focus to make sure key down messages don't erroneously go to the GLWidget.
/// </summary>
/// <param name="e">The event</param>
void SpinBox::leaveEvent(QEvent* e)
{
StopTimer();
QSpinBox::leaveEvent(e);
}
/// <summary>
/// Start the timer in response to the right mouse button being pressed.
/// </summary>
void SpinBox::StartTimer()
{
s_Timer.stop();
connect(&s_Timer, SIGNAL(timeout()), this, SLOT(OnTimeout()));
s_Timer.start(300);
}
/// <summary>
/// Stop the timer in response to the left mouse button being pressed.
/// </summary>
void SpinBox::StopTimer()
{
s_Timer.stop();
disconnect(&s_Timer, SIGNAL(timeout()), this, SLOT(OnTimeout()));
}