⛏️ index : haiku.git

/*
 * Copyright 2015-2025 Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT license.
 *
 * Authors:
 *		John Scipione, jscipione@gmail.com
 */


#include <DecimalSpinner.h>

#include <stdio.h>
#include <stdlib.h>

#include <PropertyInfo.h>
#include <TextView.h>


static double
roundTo(double value, uint32 n)
{
	return floor(value * pow(10.0, n) + 0.5) / pow(10.0, n);
}


static property_info sProperties[] = {
	{
		"MaxValue",
		{ B_GET_PROPERTY, 0 },
		{ B_DIRECT_SPECIFIER, 0 },
		"Returns the maximum value of the spinner.",
		0,
		{ B_DOUBLE_TYPE }
	},
	{
		"MaxValue",
		{ B_SET_PROPERTY, 0 },
		{ B_DIRECT_SPECIFIER, 0},
		"Sets the maximum value of the spinner.",
		0,
		{ B_DOUBLE_TYPE }
	},

	{
		"MinValue",
		{ B_GET_PROPERTY, 0 },
		{ B_DIRECT_SPECIFIER, 0 },
		"Returns the minimum value of the spinner.",
		0,
		{ B_DOUBLE_TYPE }
	},
	{
		"MinValue",
		{ B_SET_PROPERTY, 0 },
		{ B_DIRECT_SPECIFIER, 0},
		"Sets the minimum value of the spinner.",
		0,
		{ B_DOUBLE_TYPE }
	},

	{
		"Precision",
		{ B_SET_PROPERTY, 0 },
		{ B_DIRECT_SPECIFIER, 0},
		"Sets the number of decimal places of precision of the spinner.",
		0,
		{ B_UINT32_TYPE }
	},
	{
		"Precision",
		{ B_GET_PROPERTY, 0 },
		{ B_DIRECT_SPECIFIER, 0 },
		"Returns the number of decimal places of precision of the spinner.",
		0,
		{ B_UINT32_TYPE }
	},

	{
		"Step",
		{ B_GET_PROPERTY, 0 },
		{ B_DIRECT_SPECIFIER, 0 },
		"Returns the step size of the spinner.",
		0,
		{ B_DOUBLE_TYPE }
	},
	{
		"Step",
		{ B_SET_PROPERTY, 0 },
		{ B_DIRECT_SPECIFIER, 0},
		"Sets the step size of the spinner.",
		0,
		{ B_DOUBLE_TYPE }
	},

	{
		"Value",
		{ B_GET_PROPERTY, 0 },
		{ B_DIRECT_SPECIFIER, 0 },
		"Returns the value of the spinner.",
		0,
		{ B_DOUBLE_TYPE }
	},
	{
		"Value",
		{ B_SET_PROPERTY, 0 },
		{ B_DIRECT_SPECIFIER, 0},
		"Sets the value of the spinner.",
		0,
		{ B_DOUBLE_TYPE }
	},

	{ 0 }
};


//	#pragma mark - BDecimalSpinner


BDecimalSpinner::BDecimalSpinner(BRect frame, const char* name,
	const char* label, BMessage* message, uint32 resizingMode, uint32 flags)
	:
	BAbstractSpinner(frame, name, label, message, resizingMode, flags)
{
	_InitObject();
}


BDecimalSpinner::BDecimalSpinner(const char* name, const char* label,
	BMessage* message, uint32 flags)
	:
	BAbstractSpinner(name, label, message, flags)
{
	_InitObject();
}


BDecimalSpinner::BDecimalSpinner(BMessage* data)
	:
	BAbstractSpinner(data)
{
	_InitObject();

	if (data->FindDouble("_min", &fMinValue) != B_OK)
		fMinValue = 0.0;

	if (data->FindDouble("_max", &fMaxValue) != B_OK)
		fMaxValue = 100.0;

	if (data->FindUInt32("_precision", &fPrecision) != B_OK)
		fPrecision = 2;

	if (data->FindDouble("_step", &fStep) != B_OK)
		fStep = 1.0;

	if (data->FindDouble("_val", &fValue) != B_OK)
		fValue = 0.0;
}


BDecimalSpinner::~BDecimalSpinner()
{
	delete fNumberFormat;
}


BArchivable*
BDecimalSpinner::Instantiate(BMessage* data)
{
	if (validate_instantiation(data, "DecimalSpinner"))
		return new BDecimalSpinner(data);

	return NULL;
}


status_t
BDecimalSpinner::Archive(BMessage* data, bool deep) const
{
	status_t status = BAbstractSpinner::Archive(data, deep);
	data->AddString("class", "DecimalSpinner");

	if (status == B_OK)
		status = data->AddDouble("_min", fMinValue);

	if (status == B_OK)
		status = data->AddDouble("_max", fMaxValue);

	if (status == B_OK)
		status = data->AddUInt32("_precision", fPrecision);

	if (status == B_OK)
		status = data->AddDouble("_step", fStep);

	if (status == B_OK)
		status = data->AddDouble("_val", fValue);

	return status;
}


status_t
BDecimalSpinner::GetSupportedSuites(BMessage* message)
{
	message->AddString("suites", "suite/vnd.Haiku-decimal-spinner");

	BPropertyInfo prop_info(sProperties);
	message->AddFlat("messages", &prop_info);

	return BView::GetSupportedSuites(message);
}


void
BDecimalSpinner::AttachedToWindow()
{
	SetValue(fValue);

	BAbstractSpinner::AttachedToWindow();
}


void
BDecimalSpinner::Decrement()
{
	SetValue(Value() - Step());
}


void
BDecimalSpinner::Increment()
{
	SetValue(Value() + Step());
}


void
BDecimalSpinner::SetEnabled(bool enable)
{
	if (IsEnabled() == enable)
		return;

	SetIncrementEnabled(enable && Value() < fMaxValue);
	SetDecrementEnabled(enable && Value() > fMinValue);

	BAbstractSpinner::SetEnabled(enable);
}


void
BDecimalSpinner::SetPrecision(uint32 precision)
{
	if (precision == fPrecision)
		return;

	fPrecision = precision;
	fNumberFormat->SetPrecision(precision);
}


void
BDecimalSpinner::SetMinValue(double min)
{
	fMinValue = min;
	SetValue(Value());
}


void
BDecimalSpinner::SetMaxValue(double max)
{
	fMaxValue = max;
	SetValue(Value());
}


void
BDecimalSpinner::Range(double* min, double* max)
{
	*min = fMinValue;
	*max = fMaxValue;
}


void
BDecimalSpinner::SetRange(double min, double max)
{
	SetMinValue(min);
	SetMaxValue(max);
}


void
BDecimalSpinner::SetValue(int32 value)
{
	SetValue((double)value);
}


void
BDecimalSpinner::SetValue(double value)
{
	// clip to range
	if (value < fMinValue)
		value = fMinValue;
	else if (value > fMaxValue)
		value = fMaxValue;

	// update the text view
	BString valueString;
	fNumberFormat->Format(valueString, value);

	TextView()->SetText(valueString.String());

	// update the up and down arrows
	SetIncrementEnabled(IsEnabled() && value < fMaxValue);
	SetDecrementEnabled(IsEnabled() && value > fMinValue);

	if (value == fValue)
		return;

	fValue = value;
	ValueChanged();

	Invoke();
	Invalidate();
}


void
BDecimalSpinner::SetValueFromText()
{
	double parsedValue;
	if (fNumberFormat->Parse(TextView()->Text(), parsedValue) == B_OK)
		SetValue(roundTo(parsedValue, Precision()));
}


//	#pragma mark - BDecimalSpinner private methods


void
BDecimalSpinner::_InitObject()
{
	fMinValue = 0.0;
	fMaxValue = 100.0;
	fPrecision = 2;
	fStep = 1.0;
	fValue = 0.0;

	fNumberFormat = new BNumberFormat();
	fNumberFormat->SetPrecision(fPrecision);

	TextView()->SetAlignment(B_ALIGN_RIGHT);
	for (uint32 c = 0; c <= 42; c++)
		TextView()->DisallowChar(c);

	TextView()->DisallowChar('/');
	for (uint32 c = 58; c <= 127; c++)
		TextView()->DisallowChar(c);
}


// FBC padding

void BDecimalSpinner::_ReservedDecimalSpinner20() {}
void BDecimalSpinner::_ReservedDecimalSpinner19() {}
void BDecimalSpinner::_ReservedDecimalSpinner18() {}
void BDecimalSpinner::_ReservedDecimalSpinner17() {}
void BDecimalSpinner::_ReservedDecimalSpinner16() {}
void BDecimalSpinner::_ReservedDecimalSpinner15() {}
void BDecimalSpinner::_ReservedDecimalSpinner14() {}
void BDecimalSpinner::_ReservedDecimalSpinner13() {}
void BDecimalSpinner::_ReservedDecimalSpinner12() {}
void BDecimalSpinner::_ReservedDecimalSpinner11() {}
void BDecimalSpinner::_ReservedDecimalSpinner10() {}
void BDecimalSpinner::_ReservedDecimalSpinner9() {}
void BDecimalSpinner::_ReservedDecimalSpinner8() {}
void BDecimalSpinner::_ReservedDecimalSpinner7() {}
void BDecimalSpinner::_ReservedDecimalSpinner6() {}
void BDecimalSpinner::_ReservedDecimalSpinner5() {}
void BDecimalSpinner::_ReservedDecimalSpinner4() {}
void BDecimalSpinner::_ReservedDecimalSpinner3() {}
void BDecimalSpinner::_ReservedDecimalSpinner2() {}
void BDecimalSpinner::_ReservedDecimalSpinner1() {}