From c47d42d9459445d9e9869c49ebe66a0671a3a0a7 Mon Sep 17 00:00:00 2001
From: Kadah_Coba <none@none>
Date: Wed, 29 Jun 2011 23:40:20 -0700
Subject: [PATCH] STORM-1315 Ability to do simple math in numeric edit fields

---
 doc/contributions.txt           |   1 +
 indra/llmath/CMakeLists.txt     |   4 +
 indra/llmath/llcalc.cpp         | 150 ++++++++++++++++++++++++++++
 indra/llmath/llcalc.h           |  83 ++++++++++++++++
 indra/llmath/llcalcparser.cpp   |  46 +++++++++
 indra/llmath/llcalcparser.h     | 169 ++++++++++++++++++++++++++++++++
 indra/llui/lllineeditor.cpp     |  33 ++++++-
 indra/llui/lllineeditor.h       |   4 +
 indra/llui/llspinctrl.cpp       |  31 +++---
 indra/newview/llappviewer.cpp   |   5 +-
 indra/newview/llpanelface.cpp   |  11 +++
 indra/newview/llpanelobject.cpp |  51 +++++++++-
 12 files changed, 571 insertions(+), 17 deletions(-)
 create mode 100644 indra/llmath/llcalc.cpp
 create mode 100644 indra/llmath/llcalc.h
 create mode 100644 indra/llmath/llcalcparser.cpp
 create mode 100644 indra/llmath/llcalcparser.h

diff --git a/doc/contributions.txt b/doc/contributions.txt
index 729c73f7778..e9c9369f2c7 100644
--- a/doc/contributions.txt
+++ b/doc/contributions.txt
@@ -20,6 +20,7 @@ Aimee Trescothick
 	SNOW-570
 	SNOW-572
 	SNOW-575
+	STORM-1315
 	VWR-3321
 	VWR-3336
 	VWR-3903
diff --git a/indra/llmath/CMakeLists.txt b/indra/llmath/CMakeLists.txt
index 9dadad7dd34..cd100cdf9fd 100644
--- a/indra/llmath/CMakeLists.txt
+++ b/indra/llmath/CMakeLists.txt
@@ -12,6 +12,8 @@ include_directories(
 set(llmath_SOURCE_FILES
     llbbox.cpp
     llbboxlocal.cpp
+    llcalc.cpp
+    llcalcparser.cpp
     llcamera.cpp
     llcoordframe.cpp
     llline.cpp
@@ -46,6 +48,8 @@ set(llmath_HEADER_FILES
     coordframe.h
     llbbox.h
     llbboxlocal.h
+    llcalc.h
+    llcalcparser.h
     llcamera.h
     llcoord.h
     llcoordframe.h
diff --git a/indra/llmath/llcalc.cpp b/indra/llmath/llcalc.cpp
new file mode 100644
index 00000000000..f399463a47c
--- /dev/null
+++ b/indra/llmath/llcalc.cpp
@@ -0,0 +1,150 @@
+/*
+ *  LLCalc.cpp
+ *  SecondLife
+ *
+ *  Created by Aimee Walton on 28/09/2008.
+ *  Copyright 2008 Aimee Walton.
+ *
+ */
+
+#include "linden_common.h"
+
+#include "llcalc.h"
+
+#include "llcalcparser.h"
+#include "llmath.h"
+
+
+// Variable names for use in the build floater
+const char* LLCalc::X_POS = "PX";
+const char* LLCalc::Y_POS = "PY";
+const char* LLCalc::Z_POS = "PZ";
+const char* LLCalc::X_SCALE = "SX";
+const char* LLCalc::Y_SCALE = "SY";
+const char* LLCalc::Z_SCALE = "SZ";
+const char* LLCalc::X_ROT = "RX";
+const char* LLCalc::Y_ROT = "RY";
+const char* LLCalc::Z_ROT = "RZ";
+const char* LLCalc::HOLLOW = "HLW";
+const char* LLCalc::CUT_BEGIN = "CB";
+const char* LLCalc::CUT_END = "CE";
+const char* LLCalc::PATH_BEGIN = "PB";
+const char* LLCalc::PATH_END = "PE";
+const char* LLCalc::TWIST_BEGIN = "TB";
+const char* LLCalc::TWIST_END = "TE";
+const char* LLCalc::X_SHEAR = "SHX";
+const char* LLCalc::Y_SHEAR = "SHY";
+const char* LLCalc::X_TAPER = "TPX";
+const char* LLCalc::Y_TAPER = "TPY";
+const char* LLCalc::RADIUS_OFFSET = "ROF";
+const char* LLCalc::REVOLUTIONS = "REV";
+const char* LLCalc::SKEW = "SKW";
+const char* LLCalc::X_HOLE = "HLX";
+const char* LLCalc::Y_HOLE = "HLY";
+const char* LLCalc::TEX_U_SCALE = "TSU";
+const char* LLCalc::TEX_V_SCALE = "TSV";
+const char* LLCalc::TEX_U_OFFSET = "TOU";
+const char* LLCalc::TEX_V_OFFSET = "TOV";
+const char* LLCalc::TEX_ROTATION = "TROT";
+const char* LLCalc::TEX_TRANSPARENCY = "TRNS";
+const char* LLCalc::TEX_GLOW = "GLOW";
+
+
+LLCalc* LLCalc::sInstance = NULL;
+
+LLCalc::LLCalc() : mLastErrorPos(0)
+{
+//	mUserVariables = new calc_map_t;
+	mVariables = new calc_map_t;
+	mConstants = new calc_map_t;
+		
+	// Init table of constants
+	(*mConstants)["PI"] = F_PI;
+	(*mConstants)["TWO_PI"] = F_TWO_PI;
+	(*mConstants)["PI_BY_TWO"] = F_PI_BY_TWO;
+	(*mConstants)["SQRT2"] = F_SQRT2;
+	(*mConstants)["DEG_TO_RAD"] = DEG_TO_RAD;
+	(*mConstants)["RAD_TO_DEG"] = RAD_TO_DEG;
+	(*mConstants)["GRAVITY"] = GRAVITY;
+}
+
+LLCalc::~LLCalc()
+{
+	delete mConstants;
+	delete mVariables;
+//	delete mUserVariables;	
+}
+
+//static
+void LLCalc::cleanUp()
+{
+	delete sInstance;
+	sInstance = NULL;
+}
+
+//static
+LLCalc* LLCalc::getInstance()
+{
+    if (!sInstance)	sInstance = new LLCalc();
+	return sInstance;
+}
+
+void LLCalc::setVar(const std::string& name, const F32& value)
+{
+	(*mVariables)[name] = value;
+}
+
+void LLCalc::clearVar(const std::string& name)
+{
+	mVariables->erase(name);
+}
+
+void LLCalc::clearAllVariables()
+{
+	mVariables->clear();
+}
+
+/*
+void LLCalc::updateVariables(LLSD& vars)
+{
+	LLSD::map_iterator cIt = vars.beginMap();
+	for(; cIt != vars.endMap(); cIt++)
+	{
+		setVar(cIt->first, (F32)(LLSD::Real)cIt->second);
+	}
+}
+*/
+
+bool LLCalc::evalString(const std::string& expression, F32& result)
+{
+	std::string expr_upper = expression;
+	LLStringUtil::toUpper(expr_upper);
+	
+	LLCalcParser calc(result, mConstants, mVariables);
+
+	mLastErrorPos = 0;
+	std::string::iterator start = expr_upper.begin();
+ 	parse_info<std::string::iterator> info;
+	
+	try
+	{
+		info = parse(start, expr_upper.end(), calc, space_p);
+		lldebugs << "Math expression: " << expression << " = " << result << llendl;
+	}
+	catch(parser_error<std::string, std::string::iterator> &e)
+	{
+		mLastErrorPos = e.where - expr_upper.begin();
+		
+		llinfos << "Calc parser exception: " << e.descriptor << " at " << mLastErrorPos << " in expression: " << expression << llendl;
+		return false;
+	}
+	
+	if (!info.full)
+	{
+		mLastErrorPos = info.stop - expr_upper.begin();
+		llinfos << "Unhandled syntax error at " << mLastErrorPos << " in expression: " << expression << llendl;
+		return false;
+	}
+	
+	return true;
+}
diff --git a/indra/llmath/llcalc.h b/indra/llmath/llcalc.h
new file mode 100644
index 00000000000..23c83f623ef
--- /dev/null
+++ b/indra/llmath/llcalc.h
@@ -0,0 +1,83 @@
+/*
+ *  LLCalc.h
+ *  SecondLife
+ *
+ *  Created by Aimee Walton on 28/09/2008.
+ *  Copyright 2008 Aimee Walton.
+ *
+ */
+
+#ifndef LL_CALC_H
+#define LL_CALC_H
+
+#include <map>
+#include <string>
+
+class LLCalc
+{
+public:
+	LLCalc();
+	~LLCalc();
+
+	// Variable name constants
+	static const char* X_POS;
+	static const char* Y_POS;
+	static const char* Z_POS;
+	static const char* X_SCALE;
+	static const char* Y_SCALE;
+	static const char* Z_SCALE;
+	static const char* X_ROT;
+	static const char* Y_ROT;
+	static const char* Z_ROT;
+	static const char* HOLLOW;
+	static const char* CUT_BEGIN;
+	static const char* CUT_END;
+	static const char* PATH_BEGIN;
+	static const char* PATH_END;
+	static const char* TWIST_BEGIN;
+	static const char* TWIST_END;
+	static const char* X_SHEAR;
+	static const char* Y_SHEAR;
+	static const char* X_TAPER;
+	static const char* Y_TAPER;
+	static const char* RADIUS_OFFSET;
+	static const char* REVOLUTIONS;
+	static const char* SKEW;
+	static const char* X_HOLE;
+	static const char* Y_HOLE;
+	static const char* TEX_U_SCALE;
+	static const char* TEX_V_SCALE;
+	static const char* TEX_U_OFFSET;
+	static const char* TEX_V_OFFSET;
+	static const char* TEX_ROTATION;
+	static const char* TEX_TRANSPARENCY;
+	static const char* TEX_GLOW;
+
+	void	setVar(const std::string& name, const F32& value);
+	void	clearVar(const std::string& name);
+	void	clearAllVariables();
+//	void	updateVariables(LLSD& vars);
+
+	bool	evalString(const std::string& expression, F32& result);
+	std::string::size_type	getLastErrorPos()	{ return mLastErrorPos; }
+	
+	static LLCalc* getInstance();
+	static void cleanUp();
+
+	typedef	std::map<std::string, F32> calc_map_t;
+	
+private:
+	std::string::size_type	mLastErrorPos;
+	
+	calc_map_t*	mConstants;
+	calc_map_t*	mVariables;
+	
+	// *TODO: Add support for storing user defined variables, and stored functions.
+	//	Will need UI work, and a means to save them between sessions.
+//	calc_map_t* mUserVariables;
+	
+	// "There shall be only one"
+	static LLCalc*	sInstance;
+};
+
+#endif // LL_CALC_H
diff --git a/indra/llmath/llcalcparser.cpp b/indra/llmath/llcalcparser.cpp
new file mode 100644
index 00000000000..fd55376fa90
--- /dev/null
+++ b/indra/llmath/llcalcparser.cpp
@@ -0,0 +1,46 @@
+/*
+ *  LLCalcParser.cpp
+ *  SecondLife
+ *
+ *  Created by Aimee Walton on 28/09/2008.
+ *  Copyright 2008 Aimee Walton.
+ *
+ */
+
+#include "linden_common.h"
+
+#include "llcalcparser.h"
+using namespace boost::spirit::classic;
+
+F32 LLCalcParser::lookup(const std::string::iterator& start, const std::string::iterator& end) const
+{
+	LLCalc::calc_map_t::iterator iter;
+
+	std::string name(start, end);
+	
+	if (mConstants)
+	{
+		iter = mConstants->find(name);
+		if (iter != mConstants->end())
+		{
+			return (*iter).second;
+		}
+	}
+	else
+	{
+		// This should never happen!
+		throw_(end, std::string("Missing constants table"));
+	}
+	
+	if (mVariables)
+	{
+		iter = mVariables->find(name);
+		if (iter != mVariables->end())
+		{
+			return (*iter).second;
+		}
+	}
+	
+	throw_(end, std::string("Unknown symbol " + name));
+	return 0.f;
+}
diff --git a/indra/llmath/llcalcparser.h b/indra/llmath/llcalcparser.h
new file mode 100644
index 00000000000..a759a357103
--- /dev/null
+++ b/indra/llmath/llcalcparser.h
@@ -0,0 +1,169 @@
+/*
+ *  LLCalcParser.h
+ *  SecondLife
+ *
+ *  Created by Aimee Walton on 28/09/2008.
+ *  Copyright 2008 Aimee Walton.
+ *
+ */
+
+#ifndef LL_CALCPARSER_H
+#define LL_CALCPARSER_H
+
+#include <boost/spirit/include/classic_attribute.hpp>
+#include <boost/spirit/include/classic_core.hpp>
+#include <boost/spirit/include/classic_error_handling.hpp>
+#include <boost/spirit/include/classic_position_iterator.hpp>
+#include <boost/spirit/include/phoenix1_binders.hpp>
+#include <boost/spirit/include/classic_symbols.hpp>
+using namespace boost::spirit::classic;
+
+#include "llcalc.h"
+#include "llmath.h"
+
+struct LLCalcParser : grammar<LLCalcParser>
+{
+	LLCalcParser(F32& result, LLCalc::calc_map_t* constants, LLCalc::calc_map_t* vars) :
+		mResult(result), mConstants(constants), mVariables(vars) {};
+	
+	struct value_closure : closure<value_closure, F32>
+	{
+		member1 value;
+	};
+	
+	template <typename ScannerT>
+	struct definition
+	{
+		// Rule declarations
+		rule<ScannerT> statement, identifier;
+		rule<ScannerT, value_closure::context_t> expression, term,
+			power, 
+			unary_expr, 
+			factor, 
+			unary_func, 
+			binary_func,
+			group;
+
+		// start() should return the starting symbol
+		rule<ScannerT> const& start() const { return statement; }
+		
+		definition(LLCalcParser const& self)
+		{
+			using namespace phoenix;
+			
+			assertion<std::string> assert_domain("Domain error");
+//			assertion<std::string> assert_symbol("Unknown symbol");
+			assertion<std::string> assert_syntax("Syntax error");
+			
+			identifier =
+				lexeme_d[(alpha_p | '_') >> *(alnum_p | '_')]
+			;
+			
+			group =
+				'(' >> expression[group.value = arg1] >> assert_syntax(ch_p(')'))
+			;
+
+			unary_func =
+				((str_p("SIN") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_sin)(self,arg1)]) |
+				 (str_p("COS") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_cos)(self,arg1)]) |
+				 (str_p("TAN") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_tan)(self,arg1)]) |
+				 (str_p("ASIN") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_asin)(self,arg1)]) |
+				 (str_p("ACOS") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_acos)(self,arg1)]) |
+				 (str_p("ATAN") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_atan)(self,arg1)]) |
+				 (str_p("SQRT") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_sqrt)(self,arg1)]) |
+				 (str_p("LOG") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_log)(self,arg1)]) |
+				 (str_p("EXP") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_exp)(self,arg1)]) |
+				 (str_p("ABS") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_fabs)(self,arg1)])
+				) >> assert_syntax(ch_p(')'))
+			;
+			
+			binary_func =
+				((str_p("ATAN2") >> '(' >> expression[binary_func.value = arg1] >> ',' >>
+				  expression[binary_func.value = bind(&LLCalcParser::_atan2)(self, binary_func.value, arg1)]) |
+				 (str_p("MIN") >> '(' >> expression[binary_func.value = arg1] >> ',' >> 
+				  expression[binary_func.value = bind(&LLCalcParser::_min)(self, binary_func.value, arg1)]) |
+				 (str_p("MAX") >> '(' >> expression[binary_func.value = arg1] >> ',' >> 
+				  expression[binary_func.value = bind(&LLCalcParser::_max)(self, binary_func.value, arg1)])
+				) >> assert_syntax(ch_p(')'))
+			;
+			
+			// *TODO: Localisation of the decimal point?
+			// Problem, LLLineEditor::postvalidateFloat accepts a comma when appropriate
+			// for the current locale. However to do that here could clash with using
+			// the comma as a separator when passing arguments to functions.
+			factor =
+				(ureal_p[factor.value = arg1] |
+				 group[factor.value = arg1] |
+				 unary_func[factor.value = arg1] |
+				 binary_func[factor.value = arg1] |
+				 // Lookup throws an Unknown Symbol error if it is unknown, while this works fine,
+				 // would be "neater" to handle symbol lookup from here with an assertive parser.
+//				 constants_p[factor.value = arg1]|
+				 identifier[factor.value = bind(&LLCalcParser::lookup)(self, arg1, arg2)]
+				) >>
+				// Detect and throw math errors.
+				assert_domain(eps_p(bind(&LLCalcParser::checkNaN)(self, factor.value)))
+			;
+
+			unary_expr =
+				!ch_p('+') >> factor[unary_expr.value = arg1] |
+				'-' >> factor[unary_expr.value = -arg1]
+			;
+			
+			power =
+				unary_expr[power.value = arg1] >>
+				*('^' >> assert_syntax(unary_expr[power.value = bind(&powf)(power.value, arg1)]))
+			;
+			
+			term =
+				power[term.value = arg1] >>
+				*(('*' >> assert_syntax(power[term.value *= arg1])) |
+				  ('/' >> assert_syntax(power[term.value /= arg1]))
+				)
+			;
+			
+			expression =
+				assert_syntax(term[expression.value = arg1]) >>
+				*(('+' >> assert_syntax(term[expression.value += arg1])) |
+				  ('-' >> assert_syntax(term[expression.value -= arg1]))
+				)
+			;
+
+			statement =
+				!ch_p('=') >> ( expression )[var(self.mResult) = arg1] >> (end_p)
+			;
+		}
+	};
+	
+private:
+	// Member functions for semantic actions
+	F32	lookup(const std::string::iterator&, const std::string::iterator&) const;
+	F32 _min(const F32& a, const F32& b) const { return llmin(a, b); }
+	F32 _max(const F32& a, const F32& b) const { return llmax(a, b); }
+	
+	bool checkNaN(const F32& a) const { return !llisnan(a); }
+	
+	//FIX* non ambigious function fix making SIN() work for calc -Cryogenic Blitz
+	F32 _sin(const F32& a) const { return sin(DEG_TO_RAD * a); }
+	F32 _cos(const F32& a) const { return cos(DEG_TO_RAD * a); }
+	F32 _tan(const F32& a) const { return tan(DEG_TO_RAD * a); }
+	F32 _asin(const F32& a) const { return asin(a * RAD_TO_DEG); }
+	F32 _acos(const F32& a) const { return acos(a * RAD_TO_DEG); }
+	F32 _atan(const F32& a) const { return atan(a * RAD_TO_DEG); }
+	F32 _sqrt(const F32& a) const { return sqrt(a); }
+	F32 _log(const F32& a) const { return log(a); }
+	F32 _exp(const F32& a) const { return exp(a); }
+	F32 _fabs(const F32& a) const { return fabs(a) * RAD_TO_DEG; }
+
+	F32 _atan2(const F32& a,const F32& b) const { return atan2(a,b); }
+
+
+
+	LLCalc::calc_map_t* mConstants;
+	LLCalc::calc_map_t* mVariables;
+//	LLCalc::calc_map_t* mUserVariables;
+	
+	F32&		mResult;
+};
+
+#endif // LL_CALCPARSER_H
diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp
index 0196080d900..64969856da4 100644
--- a/indra/llui/lllineeditor.cpp
+++ b/indra/llui/lllineeditor.cpp
@@ -37,6 +37,7 @@
 #include "llgl.h"
 #include "lltimer.h"
 
+#include "llcalc.h"
 //#include "llclipboard.h"
 #include "llcontrol.h"
 #include "llbutton.h"
@@ -132,6 +133,7 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p)
 	mIgnoreTab( p.ignore_tab ),
 	mDrawAsterixes( p.is_password ),
 	mSelectAllonFocusReceived( p.select_on_focus ),
+	mSelectAllonCommit( TRUE ),
 	mPassDelete(FALSE),
 	mReadOnly(FALSE),
 	mBgImage( p.background_image ),
@@ -228,7 +230,10 @@ void LLLineEditor::onCommit()
 
 	setControlValue(getValue());
 	LLUICtrl::onCommit();
-	selectAll();
+
+	// Selection on commit needs to be turned off when evaluating maths
+	// expressions, to allow indication of the error position
+	if (mSelectAllonCommit) selectAll();
 }
 
 // Returns TRUE if user changed value at all
@@ -1995,6 +2000,32 @@ BOOL LLLineEditor::postvalidateFloat(const std::string &str)
 	return success;
 }
 
+BOOL LLLineEditor::evaluateFloat()
+{
+	bool success;
+	F32 result = 0.f;
+	std::string expr = getText();
+	LLStringUtil::toUpper(expr);
+
+	success = LLCalc::getInstance()->evalString(expr, result);
+
+	if (!success)
+	{
+		// Move the cursor to near the error on failure
+		setCursor(LLCalc::getInstance()->getLastErrorPos());
+		// *TODO: Translated error message indicating the type of error? Select error text?
+	}
+	else
+	{
+		// Replace the expression with the result
+		std::string result_str = llformat("%f",result);
+		setText(result_str);
+		selectAll();
+	}
+
+	return success;
+}
+
 void LLLineEditor::onMouseCaptureLost()
 {
 	endSelection();
diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h
index fe191e59714..987abc038cf 100644
--- a/indra/llui/lllineeditor.h
+++ b/indra/llui/lllineeditor.h
@@ -220,6 +220,7 @@ class LLLineEditor
 	void			deleteSelection();
 
 	void			setSelectAllonFocusReceived(BOOL b);
+	void			setSelectAllonCommit(BOOL b) { mSelectAllonCommit = b; }
 	
 	typedef boost::function<void (LLLineEditor* caller, void* user_data)> callback_t;
 	void			setKeystrokeCallback(callback_t callback, void* user_data);
@@ -234,6 +235,8 @@ class LLLineEditor
 	void			setPrevalidate( LLTextValidate::validate_func_t func );
 	static BOOL		postvalidateFloat(const std::string &str);
 
+	BOOL			evaluateFloat();
+
 	// line history support:
 	void			setEnableLineHistory( BOOL enabled ) { mHaveHistory = enabled; } // switches line history on or off 
 	void			updateHistory(); // stores current line in history
@@ -330,6 +333,7 @@ class LLLineEditor
 	BOOL		mDrawAsterixes;
 
 	BOOL		mSelectAllonFocusReceived;
+	BOOL		mSelectAllonCommit;
 	BOOL		mPassDelete;
 
 	BOOL		mReadOnly;
diff --git a/indra/llui/llspinctrl.cpp b/indra/llui/llspinctrl.cpp
index 15a7438ec95..b76d6049533 100644
--- a/indra/llui/llspinctrl.cpp
+++ b/indra/llui/llspinctrl.cpp
@@ -44,7 +44,7 @@
 #include "llresmgr.h"
 #include "lluictrlfactory.h"
 
-const U32 MAX_STRING_LENGTH = 32;
+const U32 MAX_STRING_LENGTH = 255;
 
 static LLDefaultChildRegistry::Register<LLSpinCtrl> r2("spinner");
 
@@ -124,14 +124,15 @@ LLSpinCtrl::LLSpinCtrl(const LLSpinCtrl::Params& p)
 	params.max_length.bytes(MAX_STRING_LENGTH);
 	params.commit_callback.function((boost::bind(&LLSpinCtrl::onEditorCommit, this, _2)));
 	
-	if( mPrecision>0 )//should accept float numbers
-	{
-		params.prevalidate_callback(&LLTextValidate::validateFloat);
-	}
-	else //should accept int numbers
-	{
-		params.prevalidate_callback(&LLTextValidate::validateInt);
-	}
+	//allow entering of any chars for LLCalc, proper input will be evaluated on commit
+	//if( mPrecision>0 )//should accept float numbers
+	//{
+	//	params.prevalidate_callback(&LLTextValidate::validateFloat);
+	//}
+	//else //should accept int numbers
+	//{
+	//	params.prevalidate_callback(&LLTextValidate::validateInt);
+	//}
 	
 	params.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM);
 	mEditor = LLUICtrlFactory::create<LLLineEditor> (params);
@@ -140,6 +141,7 @@ LLSpinCtrl::LLSpinCtrl(const LLSpinCtrl::Params& p)
 	// than when it doesn't.  Instead, if you always have to double click to select all the text, 
 	// it's easier to understand
 	//mEditor->setSelectAllonFocusReceived(TRUE);
+	mEditor->setSelectAllonCommit(FALSE);
 	addChild(mEditor);
 
 	updateEditor();
@@ -304,9 +306,10 @@ void LLSpinCtrl::onEditorCommit( const LLSD& data )
 {
 	BOOL success = FALSE;
 	
-	std::string text = mEditor->getText();
-	if( LLLineEditor::postvalidateFloat( text ) )
+	if( mEditor->evaluateFloat() )
 	{
+		std::string text = mEditor->getText();
+
 		LLLocale locale(LLLocale::USER_LOCALE);
 		F32 val = (F32) atof(text.c_str());
 
@@ -327,7 +330,11 @@ void LLSpinCtrl::onEditorCommit( const LLSD& data )
 	}
 	updateEditor();
 
-	if( !success )
+	if( success )
+	{
+		updateEditor();
+	}
+	else
 	{
 		reportInvalidData();		
 	}
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index d2582d524d1..fa841074fd2 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -56,6 +56,7 @@
 #include "llallocator.h"
 #include "llares.h" 
 #include "llcurl.h"
+#include "llcalc.h"
 #include "lltexturestats.h"
 #include "lltexturestats.h"
 #include "llviewerwindow.h"
@@ -1543,7 +1544,9 @@ bool LLAppViewer::cleanup()
 	// Note: this is where gLocalSpeakerMgr and gActiveSpeakerMgr used to be deleted.
 
 	LLWorldMap::getInstance()->reset(); // release any images
-	
+
+	LLCalc::cleanUp();
+
 	llinfos << "Global stuff deleted" << llendflush;
 
 	if (gAudiop)
diff --git a/indra/newview/llpanelface.cpp b/indra/newview/llpanelface.cpp
index 07c7f359896..de5522c8fb6 100644
--- a/indra/newview/llpanelface.cpp
+++ b/indra/newview/llpanelface.cpp
@@ -30,6 +30,7 @@
 #include "llpanelface.h"
  
 // library includes
+#include "llcalc.h"
 #include "llerror.h"
 #include "llfocusmgr.h"
 #include "llrect.h"
@@ -926,6 +927,16 @@ void LLPanelFace::getState()
 				getChildView("button apply")->setEnabled(enabled);
 			}
 		}
+
+		// Set variable values for numeric expressions
+		LLCalc* calcp = LLCalc::getInstance();
+		calcp->setVar(LLCalc::TEX_U_SCALE, childGetValue("TexScaleU").asReal());
+		calcp->setVar(LLCalc::TEX_V_SCALE, childGetValue("TexScaleV").asReal());
+		calcp->setVar(LLCalc::TEX_U_OFFSET, childGetValue("TexOffsetU").asReal());
+		calcp->setVar(LLCalc::TEX_V_OFFSET, childGetValue("TexOffsetV").asReal());
+		calcp->setVar(LLCalc::TEX_ROTATION, childGetValue("TexRot").asReal());
+		calcp->setVar(LLCalc::TEX_TRANSPARENCY, childGetValue("ColorTrans").asReal());
+		calcp->setVar(LLCalc::TEX_GLOW, childGetValue("glow").asReal());
 	}
 	else
 	{
diff --git a/indra/newview/llpanelobject.cpp b/indra/newview/llpanelobject.cpp
index 52917ff20b3..c222bbb1912 100644
--- a/indra/newview/llpanelobject.cpp
+++ b/indra/newview/llpanelobject.cpp
@@ -41,6 +41,7 @@
 // project includes
 #include "llagent.h"
 #include "llbutton.h"
+#include "llcalc.h"
 #include "llcheckboxctrl.h"
 #include "llcolorswatch.h"
 #include "llcombobox.h"
@@ -318,6 +319,8 @@ void LLPanelObject::getState( )
 		}
 	}
 
+	LLCalc* calcp = LLCalc::getInstance();
+
 	LLVOVolume *volobjp = NULL;
 	if ( objectp && (objectp->getPCode() == LL_PCODE_VOLUME))
 	{
@@ -334,6 +337,7 @@ void LLPanelObject::getState( )
 
 		// Disable all text input fields
 		clearCtrls();
+		calcp->clearAllVariables();
 		return;
 	}
 
@@ -360,12 +364,18 @@ void LLPanelObject::getState( )
 		mCtrlPosX->set( vec.mV[VX] );
 		mCtrlPosY->set( vec.mV[VY] );
 		mCtrlPosZ->set( vec.mV[VZ] );
+		calcp->setVar(LLCalc::X_POS, vec.mV[VX]);
+		calcp->setVar(LLCalc::Y_POS, vec.mV[VY]);
+		calcp->setVar(LLCalc::Z_POS, vec.mV[VZ]);
 	}
 	else
 	{
 		mCtrlPosX->clear();
 		mCtrlPosY->clear();
 		mCtrlPosZ->clear();
+		calcp->clearVar(LLCalc::X_POS);
+		calcp->clearVar(LLCalc::Y_POS);
+		calcp->clearVar(LLCalc::Z_POS);
 	}
 
 
@@ -380,12 +390,18 @@ void LLPanelObject::getState( )
 		mCtrlScaleX->set( vec.mV[VX] );
 		mCtrlScaleY->set( vec.mV[VY] );
 		mCtrlScaleZ->set( vec.mV[VZ] );
+		calcp->setVar(LLCalc::X_SCALE, vec.mV[VX]);
+		calcp->setVar(LLCalc::Y_SCALE, vec.mV[VY]);
+		calcp->setVar(LLCalc::Z_SCALE, vec.mV[VZ]);
 	}
 	else
 	{
 		mCtrlScaleX->clear();
 		mCtrlScaleY->clear();
 		mCtrlScaleZ->clear();
+		calcp->setVar(LLCalc::X_SCALE, 0.f);
+		calcp->setVar(LLCalc::Y_SCALE, 0.f);
+		calcp->setVar(LLCalc::Z_SCALE, 0.f);
 	}
 
 	mLabelSize->setEnabled( enable_scale );
@@ -405,12 +421,18 @@ void LLPanelObject::getState( )
 		mCtrlRotX->set( mCurEulerDegrees.mV[VX] );
 		mCtrlRotY->set( mCurEulerDegrees.mV[VY] );
 		mCtrlRotZ->set( mCurEulerDegrees.mV[VZ] );
+		calcp->setVar(LLCalc::X_ROT, mCurEulerDegrees.mV[VX]);
+		calcp->setVar(LLCalc::Y_ROT, mCurEulerDegrees.mV[VY]);
+		calcp->setVar(LLCalc::Z_ROT, mCurEulerDegrees.mV[VZ]);
 	}
 	else
 	{
 		mCtrlRotX->clear();
 		mCtrlRotY->clear();
 		mCtrlRotZ->clear();
+		calcp->clearVar(LLCalc::X_ROT);
+		calcp->clearVar(LLCalc::Y_ROT);
+		calcp->clearVar(LLCalc::Z_ROT);
 	}
 
 	mLabelRotation->setEnabled( enable_rotate );
@@ -625,9 +647,9 @@ void LLPanelObject::getState( )
 		F32 end_t	= volume_params.getEndT();
 
 		// Hollowness
-		F32 hollow = volume_params.getHollow();
-		mSpinHollow->set( 100.f * hollow );
-
+		F32 hollow = 100.f * volume_params.getHollow();
+		mSpinHollow->set( hollow );
+		calcp->setVar(LLCalc::HOLLOW, hollow);
 		// All hollow objects allow a shape to be selected.
 		if (hollow > 0.f)
 		{
@@ -679,6 +701,10 @@ void LLPanelObject::getState( )
 		mSpinCutEnd		->set( cut_end );
 		mCtrlPathBegin	->set( adv_cut_begin );
 		mCtrlPathEnd	->set( adv_cut_end );
+		calcp->setVar(LLCalc::CUT_BEGIN, cut_begin);
+		calcp->setVar(LLCalc::CUT_END, cut_end);
+		calcp->setVar(LLCalc::PATH_BEGIN, adv_cut_begin);
+		calcp->setVar(LLCalc::PATH_END, adv_cut_end);
 
 		// Twist
 		F32 twist		= volume_params.getTwist();
@@ -697,18 +723,24 @@ void LLPanelObject::getState( )
 
 		mSpinTwist		->set( twist );
 		mSpinTwistBegin	->set( twist_begin );
+		calcp->setVar(LLCalc::TWIST_END, twist);
+		calcp->setVar(LLCalc::TWIST_BEGIN, twist_begin);
 
 		// Shear
 		F32 shear_x = volume_params.getShearX();
 		F32 shear_y = volume_params.getShearY();
 		mSpinShearX->set( shear_x );
 		mSpinShearY->set( shear_y );
+		calcp->setVar(LLCalc::X_SHEAR, shear_x);
+		calcp->setVar(LLCalc::Y_SHEAR, shear_y);
 
 		// Taper
 		F32 taper_x	= volume_params.getTaperX();
 		F32 taper_y = volume_params.getTaperY();
 		mSpinTaperX->set( taper_x );
 		mSpinTaperY->set( taper_y );
+		calcp->setVar(LLCalc::X_TAPER, taper_x);
+		calcp->setVar(LLCalc::Y_TAPER, taper_y);
 
 		// Radius offset.
 		F32 radius_offset = volume_params.getRadiusOffset();
@@ -738,10 +770,12 @@ void LLPanelObject::getState( )
 			}
 		}
 		mSpinRadiusOffset->set( radius_offset);
+		calcp->setVar(LLCalc::RADIUS_OFFSET, radius_offset);
 
 		// Revolutions
 		F32 revolutions = volume_params.getRevolutions();
 		mSpinRevolutions->set( revolutions );
+		calcp->setVar(LLCalc::REVOLUTIONS, revolutions);
 		
 		// Skew
 		F32 skew	= volume_params.getSkew();
@@ -766,6 +800,7 @@ void LLPanelObject::getState( )
 			}
 		}
 		mSpinSkew->set( skew );
+		calcp->setVar(LLCalc::SKEW, skew);
 	}
 	
 	// Compute control visibility, label names, and twist range.
@@ -869,6 +904,8 @@ void LLPanelObject::getState( )
 	case MI_RING:
 		mSpinScaleX->set( scale_x );
 		mSpinScaleY->set( scale_y );
+		calcp->setVar(LLCalc::X_HOLE, scale_x);
+		calcp->setVar(LLCalc::Y_HOLE, scale_y);
 		mSpinScaleX->setMinValue(OBJECT_MIN_HOLE_SIZE);
 		mSpinScaleX->setMaxValue(OBJECT_MAX_HOLE_SIZE_X);
 		mSpinScaleY->setMinValue(OBJECT_MIN_HOLE_SIZE);
@@ -883,6 +920,14 @@ void LLPanelObject::getState( )
 			mSpinScaleX->setMaxValue(1.f);
 			mSpinScaleY->setMinValue(-1.f);
 			mSpinScaleY->setMaxValue(1.f);
+
+			// Torus' Hole Size is Box/Cyl/Prism's Taper
+			calcp->setVar(LLCalc::X_TAPER, 1.f - scale_x);
+			calcp->setVar(LLCalc::Y_TAPER, 1.f - scale_y);
+
+			// Box/Cyl/Prism have no hole size
+			calcp->setVar(LLCalc::X_HOLE, 0.f);
+			calcp->setVar(LLCalc::Y_HOLE, 0.f);
 		}
 		break;
 	}
-- 
GitLab