Implement degree mode in DeskCalc.

Default is radian mode,  You set the option in the right click menu
like the other options.

Note: degree mode does not affect hyperbolic trigonometric functions.
This is how Mac Calculator, Windows Calculator, and Google Calculator
work.
This commit is contained in:
John Scipione 2012-07-30 22:35:57 -04:00
parent 93aac98d0a
commit 8ffd0477dd
7 changed files with 98 additions and 3 deletions

View File

@ -42,6 +42,9 @@ class ExpressionParser {
ExpressionParser();
~ExpressionParser();
bool DegreeMode();
void SetDegreeMode(bool degrees);
void SetSupportHexInput(bool enabled);
BString Evaluate(const char* expressionString);
@ -49,7 +52,6 @@ class ExpressionParser {
double EvaluateToDouble(const char* expressionString);
private:
MAPM _ParseBinary();
MAPM _ParseSum();
MAPM _ParseProduct();
@ -64,6 +66,8 @@ class ExpressionParser {
void _EatToken(int32 type);
Tokenizer* fTokenizer;
bool fDegreeMode;
};
#endif // EXPRESSION_PARSER_H

View File

@ -20,6 +20,7 @@ CalcOptions::CalcOptions()
:
auto_num_lock(false),
audio_feedback(false),
degree_mode(false),
keypad_mode(KEYPAD_MODE_BASIC)
{
}
@ -37,6 +38,9 @@ CalcOptions::LoadSettings(const BMessage* archive)
if (archive->FindBool("audio feedback", &option) == B_OK)
audio_feedback = option;
if (archive->FindBool("degree mode", &option) == B_OK)
degree_mode = option;
if (archive->FindUInt8("keypad mode", &keypad_mode_option) == B_OK)
keypad_mode = keypad_mode_option;
}
@ -50,9 +54,11 @@ CalcOptions::SaveSettings(BMessage* archive) const
if (ret == B_OK)
ret = archive->AddBool("audio feedback", audio_feedback);
if (ret == B_OK)
ret = archive->AddBool("degree mode", degree_mode);
if (ret == B_OK)
ret = archive->AddUInt8("keypad mode", keypad_mode);
return ret;
}

View File

@ -24,6 +24,7 @@ class BMessage;
struct CalcOptions {
bool auto_num_lock; // automatically activate numlock
bool audio_feedback; // provide audio feedback
bool degree_mode; // radian or degree mode
uint8 keypad_mode; // keypad mode options
CalcOptions();

View File

@ -244,6 +244,10 @@ CalcView::MessageReceived(BMessage* message)
case MSG_OPTIONS_AUDIO_FEEDBACK:
ToggleAudioFeedback();
return;
case MSG_OPTIONS_ANGLE_MODE:
ToggleAngleMode();
return;
}
}
@ -931,6 +935,7 @@ CalcView::Evaluate()
try {
ExpressionParser parser;
parser.SetDegreeMode(fOptions->degree_mode);
value = parser.Evaluate(expression.String());
} catch (ParseException e) {
BString error(e.message.String());
@ -970,6 +975,16 @@ CalcView::ToggleAudioFeedback(void)
fAudioFeedbackItem->SetMarked(fOptions->audio_feedback);
}
void
CalcView::ToggleAngleMode(void)
{
fOptions->degree_mode = !fOptions->degree_mode;
fAngleModeRadianItem->SetMarked(!fOptions->degree_mode);
fAngleModeDegreeItem->SetMarked(fOptions->degree_mode);
}
void
CalcView::SetKeypadMode(uint8 mode)
{
@ -1257,6 +1272,10 @@ CalcView::_CreatePopUpMenu(bool addKeypadModeMenuItems)
new BMessage(MSG_OPTIONS_AUTO_NUM_LOCK));
fAudioFeedbackItem = new BMenuItem(B_TRANSLATE("Audio Feedback"),
new BMessage(MSG_OPTIONS_AUDIO_FEEDBACK));
fAngleModeRadianItem = new BMenuItem(B_TRANSLATE("Radian Mode"),
new BMessage(MSG_OPTIONS_ANGLE_MODE));
fAngleModeDegreeItem = new BMenuItem(B_TRANSLATE("Degree Mode"),
new BMessage(MSG_OPTIONS_ANGLE_MODE));
if (addKeypadModeMenuItems) {
fKeypadModeCompactItem = new BMenuItem(B_TRANSLATE("Compact"),
new BMessage(MSG_OPTIONS_KEYPAD_MODE_COMPACT), '0');
@ -1269,6 +1288,8 @@ CalcView::_CreatePopUpMenu(bool addKeypadModeMenuItems)
// apply current settings
fAutoNumlockItem->SetMarked(fOptions->auto_num_lock);
fAudioFeedbackItem->SetMarked(fOptions->audio_feedback);
fAngleModeRadianItem->SetMarked(!fOptions->degree_mode);
fAngleModeDegreeItem->SetMarked(fOptions->degree_mode);
// construct menu
fPopUpMenu = new BPopUpMenu("pop-up", false, false);
@ -1277,6 +1298,9 @@ CalcView::_CreatePopUpMenu(bool addKeypadModeMenuItems)
// TODO: Enable this when we use beep events which can be configured
// in the Sounds preflet.
//fPopUpMenu->AddItem(fAudioFeedbackItem);
fPopUpMenu->AddSeparatorItem();
fPopUpMenu->AddItem(fAngleModeRadianItem);
fPopUpMenu->AddItem(fAngleModeDegreeItem);
if (addKeypadModeMenuItems) {
fPopUpMenu->AddSeparatorItem();
fPopUpMenu->AddItem(fKeypadModeCompactItem);

View File

@ -16,6 +16,7 @@
enum {
MSG_OPTIONS_AUTO_NUM_LOCK = 'oanl',
MSG_OPTIONS_AUDIO_FEEDBACK = 'oafb',
MSG_OPTIONS_ANGLE_MODE = 'oamd',
MSG_OPTIONS_KEYPAD_MODE_COMPACT = 'okmc',
MSG_OPTIONS_KEYPAD_MODE_BASIC = 'okmb',
MSG_OPTIONS_KEYPAD_MODE_SCIENTIFIC = 'okms',
@ -90,6 +91,9 @@ class CalcView : public BView {
// (option currently disabled)
void ToggleAudioFeedback(void);
// Toggle radian/degree mode
void ToggleAngleMode(void);
// Set the keypad mode
void SetKeypadMode(uint8 mode);
@ -147,6 +151,10 @@ class CalcView : public BView {
BPopUpMenu* fPopUpMenu;
BMenuItem* fAutoNumlockItem;
BMenuItem* fAudioFeedbackItem;
BMenuItem* fAngleModeRadianItem;
BMenuItem* fAngleModeDegreeItem;
BMenuItem* fKeypadModeCompactItem;
BMenuItem* fKeypadModeBasicItem;
BMenuItem* fKeypadModeScientificItem;

View File

@ -88,6 +88,10 @@ CalcWindow::MessageReceived(BMessage* message)
fCalcView->ToggleAudioFeedback();
break;
case MSG_OPTIONS_ANGLE_MODE:
fCalcView->ToggleAngleMode();
break;
case MSG_OPTIONS_KEYPAD_MODE_COMPACT:
fCalcView->SetKeypadMode(KEYPAD_MODE_COMPACT);
break;

View File

@ -338,7 +338,8 @@ class Tokenizer {
ExpressionParser::ExpressionParser()
: fTokenizer(new Tokenizer())
: fTokenizer(new Tokenizer()),
fDegreeMode(false)
{
}
@ -349,6 +350,20 @@ ExpressionParser::~ExpressionParser()
}
bool
ExpressionParser::DegreeMode()
{
return fDegreeMode;
}
void
ExpressionParser::SetDegreeMode(bool degrees)
{
fDegreeMode = degrees;
}
void
ExpressionParser::SetSupportHexInput(bool enabled)
{
@ -594,19 +609,36 @@ ExpressionParser::_ParseFunction(const Token& token)
return _ParseFactorial(values[0].abs());
} else if (strcasecmp("acos", token.string.String()) == 0) {
_InitArguments(values, 1);
if (fDegreeMode)
values[0] = values[0] * MM_PI / 180;
if (values[0] < -1 || values[0] > 1)
throw ParseException("out of domain", token.position);
return _ParseFactorial(values[0].acos());
} else if (strcasecmp("asin", token.string.String()) == 0) {
_InitArguments(values, 1);
if (fDegreeMode)
values[0] = values[0] * MM_PI / 180;
if (values[0] < -1 || values[0] > 1)
throw ParseException("out of domain", token.position);
return _ParseFactorial(values[0].asin());
} else if (strcasecmp("atan", token.string.String()) == 0) {
_InitArguments(values, 1);
if (fDegreeMode)
values[0] = values[0] * MM_PI / 180;
return _ParseFactorial(values[0].atan());
} else if (strcasecmp("atan2", token.string.String()) == 0) {
_InitArguments(values, 2);
if (fDegreeMode) {
values[0] = values[0] * MM_PI / 180;
values[1] = values[1] * MM_PI / 180;
}
return _ParseFactorial(values[0].atan2(values[1]));
} else if (strcasecmp("cbrt", token.string.String()) == 0) {
_InitArguments(values, 1);
@ -616,9 +648,13 @@ ExpressionParser::_ParseFunction(const Token& token)
return _ParseFactorial(values[0].ceil());
} else if (strcasecmp("cos", token.string.String()) == 0) {
_InitArguments(values, 1);
if (fDegreeMode)
values[0] = values[0] * MM_PI / 180;
return _ParseFactorial(values[0].cos());
} else if (strcasecmp("cosh", token.string.String()) == 0) {
_InitArguments(values, 1);
// This function always uses radians
return _ParseFactorial(values[0].cosh());
} else if (strcasecmp("exp", token.string.String()) == 0) {
_InitArguments(values, 1);
@ -630,34 +666,46 @@ ExpressionParser::_ParseFunction(const Token& token)
_InitArguments(values, 1);
if (values[0] <= 0)
throw ParseException("out of domain", token.position);
return _ParseFactorial(values[0].log());
} else if (strcasecmp("log", token.string.String()) == 0) {
_InitArguments(values, 1);
if (values[0] <= 0)
throw ParseException("out of domain", token.position);
return _ParseFactorial(values[0].log10());
} else if (strcasecmp("pow", token.string.String()) == 0) {
_InitArguments(values, 2);
return _ParseFactorial(values[0].pow(values[1]));
} else if (strcasecmp("sin", token.string.String()) == 0) {
_InitArguments(values, 1);
if (fDegreeMode)
values[0] = values[0] * MM_PI / 180;
return _ParseFactorial(values[0].sin());
} else if (strcasecmp("sinh", token.string.String()) == 0) {
_InitArguments(values, 1);
// This function always uses radians
return _ParseFactorial(values[0].sinh());
} else if (strcasecmp("sqrt", token.string.String()) == 0) {
_InitArguments(values, 1);
if (values[0] < 0)
throw ParseException("out of domain", token.position);
return _ParseFactorial(values[0].sqrt());
} else if (strcasecmp("tan", token.string.String()) == 0) {
_InitArguments(values, 1);
if (fDegreeMode)
values[0] = values[0] * MM_PI / 180;
MAPM divided_by_half_pi = values[0] / MM_HALF_PI;
if (divided_by_half_pi.is_integer() && divided_by_half_pi.is_odd())
throw ParseException("out of domain", token.position);
return _ParseFactorial(values[0].tan());
} else if (strcasecmp("tanh", token.string.String()) == 0) {
_InitArguments(values, 1);
// This function always uses radians
return _ParseFactorial(values[0].tanh());
}