fractorium/Source/Fractorium/SpinBox.cpp
Person de613404de Features:
--Add support for Exr files which use 32-bit floats for each RGBA channel. Seems to come out too washed out.
--Allow for clearing an individual color curve.
--Allow for saving multiple image types in EmberRender and EmberAnimate. All writes are threaded.
--Remove --bpc command line argument. Add format png16 as a replacement.
--Remove --enable_jpg_comments and --enable_png_comments command line arguments, and replace them with --enable_comments which applies to jpg, png and exr.
--Add menu items to variations and affine spinners which allow for easy entry of specific numeric values like pi.
--Make final render dialog be wider rather than so tall.

Bug fixes:
--Fix some OpenCL compile errors on Mac.
--Remove ability to save bitmap files on all platforms but Windows.

Code changes:
--New dependency on OpenEXR.
--Allow Curves class to interact with objects of a different template type.
--Make m_Curves member of Ember always use float as template type.
--Set the length of the curves array to always be 2^17 which should offer enough precision with new 32-bit float pixel types.
--Set pixel types to always be 32-bit float. This results in a major reduction of code in the final accumulation part of Renderer.h/cpp.
--Remove corresponding code from RendererCL and FinalAccumOpenCLKernelCreator.
--Remove Transparency, NumChannels and BytesPerPixel setters from Renderer.h/cpp.
--Add new global functions to format final image buffers and place all alpha calculation and scaling code in them.
--Blending is no longer needed in OpenGLWidget because of the new pixel type.
--Make new class, AffineDoubleSpinBox.
--Attempt to make file save dialog code work the same on all OSes.
--Remove some unused functions.
2017-07-22 13:43:35 -07:00

267 lines
7.4 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);
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(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;
}
/// <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;
}
/// <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;
}
/// <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;
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)
{
auto me = dynamic_cast<QMouseEvent*>(e);
if (isEnabled() && 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();
}
else if (!m_Settings->ToggleType() &&
me->type() == QMouseEvent::MouseButtonRelease &&
me->button() == Qt::RightButton)
{
StopTimer();
m_MouseDownPoint = m_MouseMovePoint = me->pos();
}
else if (!m_Settings->ToggleType() &&
me->type() == QMouseEvent::MouseMove &&
QGuiApplication::mouseButtons() & Qt::RightButton)
{
m_MouseMovePoint = me->pos();
}
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 (IsClose(m_DoubleClickLowVal, value()))
setValue(m_DoubleClickZero);
else
setValue(m_DoubleClickNonZero);
}
}
else
{
if (e->type() == QEvent::Wheel)
{
//Take special action for shift to reduce the scroll amount. Control already
//increases it automatically.
if (QWheelEvent* we = dynamic_cast<QWheelEvent*>(e))
{
Qt::KeyboardModifiers mod = we->modifiers();
if (mod.testFlag(Qt::ShiftModifier))
setSingleStep(m_SmallStep);
else
setSingleStep(m_Step);
}
}
}
return QSpinBox::eventFilter(o, e);
}
/// <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(QEvent* 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()));
}