blob: a99d0786353f423494dbb407ae465a295c3dc71b [file] [log] [blame]
// Copyright (C) 2015 The Android Open Source Project
// This software is licensed under the terms of the GNU General Public
// License version 2, as published by the Free Software Foundation, and
// may be copied, distributed, and modified under those terms.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
#include "android/skin/qt/angle-input-widget.h"
#include <QtMath>
// Helper function for AngleInputWidget ctor
static void setUpLineEdit(QLineEdit* editor, const QValidator* validator) {
editor->setValidator(validator);
editor->setProperty("class", "EditableValue");
}
// Another helper
static void setUpLabel(QLabel* label, const QString& title) {
label->setText(title);
label->setProperty("ColorGroup", "Title");
}
AngleInputWidget::AngleInputWidget(QWidget* parent) :
QWidget(parent),
mDecimalModeFrame(this),
mSexagesimalModeFrame(this),
mDecimalValueEditor(&mDecimalModeFrame),
mDegreesValueEditor(&mSexagesimalModeFrame),
mMinutesValueEditor(&mSexagesimalModeFrame),
mSecondsValueEditor(&mSexagesimalModeFrame),
mDegreesLabel(&mSexagesimalModeFrame),
mMinutesLabel(&mSexagesimalModeFrame),
mSecondsLabel(&mSexagesimalModeFrame),
mMinValue(0.0),
mMaxValue(0.0),
mDecimalValue(0.0),
mCurrentInputMode(InputMode::Decimal) {
// Set up validators for the editors.
setMinValue(-180.0);
setMaxValue(180.0);
mDecimalDegreeValidator.setNotation(QDoubleValidator::StandardNotation);
mDecimalDegreeValidator.setDecimals(100);
mDecimalDegreeValidator.setLocale(QLocale::C);
mMinValidator.setBottom(0);
mMinValidator.setTop(59);
mSecValidator.setBottom(0.0);
mSecValidator.setTop(59.0);
mSecValidator.setLocale(QLocale::C);
// Set up the editors.
setUpLineEdit(&mDecimalValueEditor, &mDecimalDegreeValidator);
setUpLineEdit(&mDegreesValueEditor, &mIntegerDegreeValidator);
setUpLineEdit(&mMinutesValueEditor, &mMinValidator);
setUpLineEdit(&mSecondsValueEditor, &mSecValidator);
setUpLabel(&mDegreesLabel, QString("\u00B0")); // U+00B0 is the "degree" sign in Unicode.
setUpLabel(&mMinutesLabel, QString("'"));
setUpLabel(&mSecondsLabel, QString("''"));
// Lay out the subwidgets horizontally.
mDecimalFrameLayout.addWidget(&mDecimalValueEditor);
mDecimalModeFrame.setLayout(&mDecimalFrameLayout);
mSexagesimalFrameLayout.addWidget(&mDegreesValueEditor);
mSexagesimalFrameLayout.addWidget(&mDegreesLabel);
mSexagesimalFrameLayout.addWidget(&mMinutesValueEditor);
mSexagesimalFrameLayout.addWidget(&mMinutesLabel);
mSexagesimalFrameLayout.addWidget(&mSecondsValueEditor);
mSexagesimalFrameLayout.addWidget(&mSecondsLabel);
mSexagesimalModeFrame.setLayout(&mSexagesimalFrameLayout);
mLayout.addWidget(&mDecimalModeFrame);
mLayout.addWidget(&mSexagesimalModeFrame);
setLayout(&mLayout);
// Default input mode is "decimal".
setInputMode(InputMode::Decimal);
// Ensure the internal value is updated when changes are made in the editor subwidgets.
connect(&mDecimalValueEditor, SIGNAL(editingFinished()),
this, SLOT(updateValueFromDecimalInput()));
connect(&mDegreesValueEditor, SIGNAL(editingFinished()),
this, SLOT(updateValueFromSexagesimalInput()));
connect(&mMinutesValueEditor, SIGNAL(editingFinished()),
this, SLOT(updateValueFromSexagesimalInput()));
connect(&mSecondsValueEditor, SIGNAL(editingFinished()),
this, SLOT(updateValueFromSexagesimalInput()));
// The line edit controls must not be squished by the spacing
// introduced by additional box layouts. The spacing is removed
// by setting margins to 0.
setContentsMargins(0, 0, 0, 0);
mDecimalModeFrame.setContentsMargins(0, 0, 0, 0);
mSexagesimalModeFrame.setContentsMargins(0, 0, 0, 0);
mSexagesimalFrameLayout.setContentsMargins(0, 0, 0, 0);
mDecimalFrameLayout.setContentsMargins(0, 0, 0, 0);
mLayout.setContentsMargins(0, 0, 0, 0);
}
void AngleInputWidget::setValue(double value) {
mDecimalValue =
(value <= mMinValue ? mMinValue : (value >= mMaxValue ? mMaxValue : value));
updateView();
}
void AngleInputWidget::setMinValue(double value) {
if (value > mMaxValue) {
return;
}
mMinValue = value;
mDecimalDegreeValidator.setBottom(mMinValue);
mIntegerDegreeValidator.setBottom(qFloor(mMinValue));
if (mDecimalValue < mMinValue) {
mDecimalValue = mMinValue;
}
}
void AngleInputWidget::setMaxValue(double value) {
if (value < mMinValue) {
return;
}
mMaxValue = value;
mDecimalDegreeValidator.setTop(mMaxValue);
mIntegerDegreeValidator.setTop(qFloor(mMaxValue));
if (mDecimalValue > mMaxValue) {
mDecimalValue = mMaxValue;
}
}
void AngleInputWidget::forceUpdate() {
switch (mCurrentInputMode) {
case InputMode::Decimal:
updateValueFromDecimalInput();
break;
case InputMode::Sexagesimal:
updateValueFromSexagesimalInput();
break;
}
}
const int MINUTES_IN_DEGREE = 60;
const int SECONDS_IN_MINUTE = 60;
const int SECONDS_IN_DEGREE = MINUTES_IN_DEGREE * SECONDS_IN_MINUTE;
static int sgn(double x) {
return (x > 0.0) - (x < 0.0);
}
void AngleInputWidget::updateView() {
switch(mCurrentInputMode) {
case InputMode::Decimal:
// Update the visible sub-widgets.
mDecimalModeFrame.show();
mSexagesimalModeFrame.hide();
// Ensure the correct value is displayed in the editor.
mDecimalValueEditor.setText(QString::number(mDecimalValue));
break;
case InputMode::Sexagesimal:
// Update the visible sub-widgets.
mDecimalModeFrame.hide();
mSexagesimalModeFrame.show();
// Convert the decimal value to sexagesimal representation.
int sign = sgn(mDecimalValue);
double seconds = qFabs(mDecimalValue) * SECONDS_IN_DEGREE;
int whole_seconds = qFloor(seconds);
double remainder_seconds = seconds - whole_seconds;
int degrees = sign * whole_seconds / SECONDS_IN_DEGREE;
whole_seconds = whole_seconds % SECONDS_IN_DEGREE;
int minutes = whole_seconds / SECONDS_IN_MINUTE;
seconds = whole_seconds % SECONDS_IN_MINUTE + remainder_seconds;
// Ensure the correct value is displayed in the editor.
// There is a slight tweak to handle the case of negative angles
// whose absolute value is > 0 and < 1. We want to display the
// always display the "-" sign in the first box, even if degrees
// is 0.
mDegreesValueEditor.setText(
QString(degrees == 0 && sign == -1 ? "-" : "") +
QString::number(degrees));
mMinutesValueEditor.setText(QString::number(minutes));
mSecondsValueEditor.setText(QString::number(seconds));
break;
}
}
void AngleInputWidget::setInputMode(AngleInputWidget::InputMode mode) {
mCurrentInputMode = mode;
updateView();
}
void AngleInputWidget::updateValueFromDecimalInput() {
validateAndUpdateValue(mDecimalValueEditor.text().toDouble());
}
static bool hasValidValue(const QLineEdit& le) {
QString str = le.text();
int pos = 0;
return le.validator()->validate(str, pos) == QValidator::Acceptable;
}
void AngleInputWidget::updateValueFromSexagesimalInput() {
// Ensure that ALL input boxes contain a valid value.
if (!hasValidValue(mDegreesValueEditor) ||
!hasValidValue(mMinutesValueEditor) ||
!hasValidValue(mSecondsValueEditor)) {
return;
}
int degrees = mDegreesValueEditor.text().toInt();
int minutes = mMinutesValueEditor.text().toInt();
double seconds = mSecondsValueEditor.text().toDouble();
// Again, there's a tweak to handle the case when the abs
// value of a negative angle is between 0 and 1 degrees,
// i.e. 0 deg 30 min 25 sec. "-" sign is always in the "degrees"
// box, even if "degrees" is 0.
int sign =
degrees == 0 && mDegreesValueEditor.text()[0] == '-'
? -1
: sgn(degrees);
validateAndUpdateValue(
(sign == 0 ? 1 : sign) * (abs(degrees) +
minutes / static_cast<double>(MINUTES_IN_DEGREE) +
seconds / static_cast<double>(SECONDS_IN_DEGREE)));
}
void AngleInputWidget::validateAndUpdateValue(double new_value) {
if (new_value >= mMinValue && new_value <= mMaxValue) {
mDecimalValue = new_value;
emit(valueChanged(mDecimalValue));
} else {
// Force display the old value if the provided value was out of range.
updateView();
}
}