diff --git a/Mobile/NUI/BasicCalculator/BasicCalculator.sln b/Mobile/NUI/BasicCalculator/BasicCalculator.sln new file mode 100755 index 00000000..dd3fbf30 --- /dev/null +++ b/Mobile/NUI/BasicCalculator/BasicCalculator.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31229.75 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicCalculator.Tizen.Mobile", "src\BasicCalculator\BasicCalculator.Tizen.Mobile\BasicCalculator.Tizen.Mobile.csproj", "{9757AA34-E55B-410B-A2F0-C041ADC75ED0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9757AA34-E55B-410B-A2F0-C041ADC75ED0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9757AA34-E55B-410B-A2F0-C041ADC75ED0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9757AA34-E55B-410B-A2F0-C041ADC75ED0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9757AA34-E55B-410B-A2F0-C041ADC75ED0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F98B7925-D0AD-4FBD-9041-9AC9757AD495} + EndGlobalSection +EndGlobal diff --git a/Mobile/NUI/BasicCalculator/LICENSE b/Mobile/NUI/BasicCalculator/LICENSE new file mode 100755 index 00000000..51a2cd23 --- /dev/null +++ b/Mobile/NUI/BasicCalculator/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2017 Samsung + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Mobile/NUI/BasicCalculator/README.md b/Mobile/NUI/BasicCalculator/README.md new file mode 100755 index 00000000..89123e19 --- /dev/null +++ b/Mobile/NUI/BasicCalculator/README.md @@ -0,0 +1,17 @@ +# BasicCalculator +BasicCalculator is a sample application which demonstrates how to create a calculator +with basic mathematical operations using NUI Xaml library. + +![Main page](./Screenshots/main.png) + +### Features +* Preview of the result +* Basic mathematical operations + +### Prerequisites + +* [Visual Studio](https://www.visualstudio.com/) - Buildtool, IDE +* [Visual Studio Tools for Tizen](https://docs.tizen.org/application/vstools/install) - Visual Studio plugin for Tizen .NET application development + +### Author +* Guowei Wang diff --git a/Mobile/NUI/BasicCalculator/Screenshots/main.png b/Mobile/NUI/BasicCalculator/Screenshots/main.png new file mode 100755 index 00000000..9d70132c Binary files /dev/null and b/Mobile/NUI/BasicCalculator/Screenshots/main.png differ diff --git a/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator.Tizen.Mobile.cs b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator.Tizen.Mobile.cs new file mode 100755 index 00000000..bdfd11d3 --- /dev/null +++ b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator.Tizen.Mobile.cs @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2017 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using BasicCalculator.Tizen.Mobile.Views; +using BasicCalculator.ViewModels; +using Tizen.NUI; +using Tizen.NUI.Binding; + +namespace BasicCalculator.Tizen.Mobile +{ + internal class Program : NUIApplication + { + #region methods + + private readonly string[] _keyList = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" }; + + /// + /// Loads NUI application. + /// + protected override void OnCreate() + { + base.OnCreate(); + + Window window = Window.Instance; + window.BackgroundColor = Color.Cyan; + window.KeyEvent += OnKeyEvent; + + MobileMainView page = new MobileMainView(); + page.PositionUsesPivotPoint = true; + page.ParentOrigin = ParentOrigin.Center; + page.PivotPoint = PivotPoint.Center; + page.Size = new Size(window.WindowSize); + window.Add(page); + } + + public void OnKeyEvent(object sender, Window.KeyEventArgs e) + { + if (e.Key.State == Key.StateType.Down && (e.Key.KeyPressedName == "XF86Back" || e.Key.KeyPressedName == "Escape")) + { + Exit(); + } + } + + /// + /// Main application entry point. + /// Initializes Tizen extensions. + /// + /// Application arguments. + private static void Main(string[] args) + { + var app = new Program(); + app.Run(args); + } + + #endregion + } +} diff --git a/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator.Tizen.Mobile.csproj b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator.Tizen.Mobile.csproj new file mode 100755 index 00000000..231216c0 --- /dev/null +++ b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator.Tizen.Mobile.csproj @@ -0,0 +1,35 @@ + + + + Exe + netcoreapp3.1 + 1 + + + + portable + + + None + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/BasicCalculator.csproj b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/BasicCalculator.csproj new file mode 100755 index 00000000..3ac35041 --- /dev/null +++ b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/BasicCalculator.csproj @@ -0,0 +1,12 @@ + + + + netcoreapp3.1 + + + + + + + + diff --git a/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/Models/MathExpression.cs b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/Models/MathExpression.cs new file mode 100755 index 00000000..af79fcdc --- /dev/null +++ b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/Models/MathExpression.cs @@ -0,0 +1,314 @@ +/* + * Copyright (c) 2017 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Globalization; +using System.Text; +using System.Text.RegularExpressions; + +namespace BasicCalculator.Models +{ + /// + /// Exception thrown when expression result length is exceeded. + /// + internal class MathExpressionResultLengthExceededException : Exception + { + + } + + /// + /// Exception thrown when expression evaluation fails. + /// + internal class MathExpressionEvaluationException : Exception + { + + } + + internal class MathExpression + { + #region fields + + /// + /// String holding whole current expression, which is to be calculated. + /// + private StringBuilder _expression; + + #endregion fields + + #region methods + + /// + /// Constructor setting initial expression, which shall be calculated with the + /// method. + /// + /// Expression to be calculated. + public MathExpression(string expression) + { + _expression = new StringBuilder(expression); + } + + /// + /// Retrieves the value to the left of the operator finding it's start index. + /// + /// Whole expression. + /// Position of the operator. + /// Returned start position of the value. + /// The value to the left of the operator. + private decimal GetLeftValue(StringBuilder expression, int operatorPosition, out int valueStart) + { + valueStart = operatorPosition - 1; + while (valueStart > 0 && (char.IsDigit(expression[valueStart - 1]) || expression[valueStart - 1] == '.' + || expression[valueStart - 1] == '-')) + { + --valueStart; + } + + if (!decimal.TryParse(expression.ToString(valueStart, operatorPosition - valueStart), out decimal value)) + { + throw new ArgumentException("Wrong value for left value of [" + expression.ToString(valueStart, + operatorPosition - valueStart + 1)); + } + + return value; + } + + /// + /// Retrieves the value to the right of the operator finding it's end index. + /// + /// Whole expression. + /// Position of the operator. + /// Returned end position of the value. + /// The value to the right of the operator. + private decimal GetRightValue(StringBuilder expression, int operatorPosition, out int valueEnd) + { + valueEnd = operatorPosition + 1; + while (valueEnd + 1 < expression.Length && (char.IsDigit(expression[valueEnd + 1]) + || expression[valueEnd + 1] == '.' || expression[valueEnd + 1] == '-')) + { + ++valueEnd; + } + + decimal value; + if (!decimal.TryParse(expression.ToString(operatorPosition + 1, valueEnd - operatorPosition), out value)) + { + throw new ArgumentException("Wrong right value for expression " + expression.ToString(operatorPosition, + valueEnd - operatorPosition + 1)); + } + + return value; + } + + /// + /// Evaluate string expression from _expression, starting at specified character. + /// + /// Position start for evaluate. + /// Next position after evaluated expression. + /// Thrown when there is invalid expression in parsed string. + /// Thrown on divide by 0. + /// + /// When the parser find "(" character it start itself recursively parsing from that character. When it finds + /// ")" it ends recursive instance and substitutes parsed expression together with brackets (if any) with it's + /// result. + /// + private int EvaluateSubExpression(int startPosition) + { + int endPosition = startPosition; + + // Expression operators order: () % ×/÷ +/- + + // () + for (; endPosition < _expression.Length; ++endPosition) + { + if (_expression[endPosition] == '(') + { + endPosition = EvaluateSubExpression(endPosition + 1); + } + + if (endPosition < _expression.Length && _expression[endPosition] == ')') + { + break; + } + } + + if (endPosition > _expression.Length) + { + endPosition = _expression.Length; + } + + StringBuilder thisExpression = new StringBuilder( + _expression.ToString(startPosition, endPosition - startPosition)); + + // % + for (int position = 0; position < thisExpression.Length; ++position) + { + if (thisExpression[position] == '%') + { + int percentStart; + string newValue = (GetLeftValue(thisExpression, position, out percentStart) / 100).ToString(); + thisExpression.Remove(percentStart, position - percentStart + 1); + thisExpression.Insert(percentStart, newValue); + position = percentStart; + } + } + + // "--" convert to positive + // End parsing bound is -2 as we don't accept "--" at the end of expression + for (int position = 0; position < thisExpression.Length - 2; ++position) + { + if (thisExpression[position] == '-' && thisExpression[position + 1] == '-') + { + thisExpression.Remove(position, 2); + if (position > 0 && char.IsDigit(thisExpression[position - 1])) + { + thisExpression.Insert(position, '+'); + } + } + } + + // "-" when should be treated as decrease we convert it to "+" + for (int position = 1; position < thisExpression.Length; ++position) + { + if (thisExpression[position] == '-' && char.IsDigit(thisExpression[position - 1])) + { + thisExpression.Insert(position, '+'); + } + } + + // ×/÷ + for (int position = 0; position < thisExpression.Length; ++position) + { + if (thisExpression[position] == '×') + { + decimal lValue, rValue; + lValue = GetLeftValue(thisExpression, position, out var leftValueStart); + rValue = GetRightValue(thisExpression, position, out var rightValueEnd); + thisExpression.Remove(leftValueStart, rightValueEnd - leftValueStart + 1); + thisExpression.Insert(leftValueStart, (lValue * rValue).ToString()); + position = leftValueStart; + } + else if (thisExpression[position] == '÷') + { + decimal lValue, rValue; + lValue = GetLeftValue(thisExpression, position, out var leftValueStart); + rValue = GetRightValue(thisExpression, position, out var rightValueEnd); + thisExpression.Remove(leftValueStart, rightValueEnd - leftValueStart + 1); + thisExpression.Insert(leftValueStart, (lValue / rValue).ToString()); + position = leftValueStart; + } + } + + // + + for (int position = 0; position < thisExpression.Length; ++position) + { + if (thisExpression[position] == '+') + { + decimal lValue, rValue; + lValue = GetLeftValue(thisExpression, position, out var leftValueStart); + rValue = GetRightValue(thisExpression, position, out var rightValueEnd); + thisExpression.Remove(leftValueStart, rightValueEnd - leftValueStart + 1); + thisExpression.Insert(leftValueStart, (lValue + rValue).ToString()); + position = leftValueStart; + } + } + + if (startPosition > 0 && _expression[startPosition - 1] == '(') + { + --startPosition; + } + + if (endPosition < _expression.Length && _expression[endPosition] == ')') + { + ++endPosition; + } + + _expression.Remove(startPosition, endPosition - startPosition); + _expression.Insert(startPosition, thisExpression); + + return startPosition + thisExpression.Length; + } + + /// + /// Returns string with formatted rounded to fit maximum length + /// ( parameter) + /// + /// Value to convert to string. + /// Maximum resulting string length. + /// Resulting string. + private string GetValueString(decimal value, int maxLength) + { + int dotIndex = value.ToString(CultureInfo.InvariantCulture).IndexOf('.'); + if (dotIndex == -1) + { + dotIndex = value.ToString(CultureInfo.InvariantCulture).Length; + } + + if (dotIndex > maxLength) + { + throw new MathExpressionResultLengthExceededException(); + } + + value = (dotIndex >= maxLength - 1) ? Math.Round(value) : Math.Round(value, maxLength - dotIndex - 1); + value /= 1.000000000000000000000000000000000M; + return value.ToString(CultureInfo.InvariantCulture); + } + + /// + /// Validates brackets count. + /// When opening and closing brackets count differs, does nothing. + /// Evaluates expression passed by constructor. + /// If successful evaluated result is returned as string with dots replaced with commas. + /// + /// Maximum returned result string length. + /// Result string or null if there are any expression errors. + public string Evaluate(int maxLength) + { + if (new Regex(Regex.Escape("(")).Matches(_expression.ToString()).Count - + new Regex(Regex.Escape(")")).Matches(_expression.ToString()).Count < 0) + { + throw new MathExpressionEvaluationException(); + } + + int position; + try + { + position = EvaluateSubExpression(0); + } + catch (Exception e) + { + if (e is ArgumentException || e is DivideByZeroException) + { + throw new MathExpressionEvaluationException(); + } + + throw; + } + + if (position < _expression.Length) + { + throw new MathExpressionEvaluationException(); + } + + if (!decimal.TryParse(_expression.ToString(), out decimal value)) + { + throw new MathExpressionEvaluationException(); + } + + return GetValueString(value, maxLength); + } + + #endregion methods + } +} diff --git a/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/ViewModels/CalculatorViewModel.cs b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/ViewModels/CalculatorViewModel.cs new file mode 100755 index 00000000..1e4ce4ea --- /dev/null +++ b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/ViewModels/CalculatorViewModel.cs @@ -0,0 +1,438 @@ +/* + * Copyright (c) 2017 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Windows.Input; +using BasicCalculator.Models; +using Tizen.NUI; +using Tizen.NUI.Binding; + +namespace BasicCalculator.ViewModels +{ + public class CalculatorViewModel : INotifyPropertyChanged + { + #region fields + + /// + /// Maximum length of the expression. + /// + private const int EXPRESSION_MAX_LENGTH = 24; + + /// + /// Maximum length of the expression's calculated value. + /// + private const int RESULT_MAX_LENGTH = 12; + + /// + /// Backing field for the property. + /// + private string _enteredExpression = "0"; + + /// + /// Backing field for the property. + /// + private string _currentResult = "0"; + + /// + /// Event fired on property changed. + /// + public event PropertyChangedEventHandler PropertyChanged; + + #endregion fields + + #region properties + + /// + /// Current result of entered expression. + /// + public string CurrentResult + { + get => _currentResult; + set + { + _currentResult = value; + OnPropertyChanged(); + } + } + + /// + /// Currently entered expression for calculation. + /// Sets expression new value if it has valid length. + /// Calls result recalculation when changed. + /// + public string EnteredExpression + { + get => _enteredExpression; + set + { + if (value.Length > EXPRESSION_MAX_LENGTH || value.Equals(_enteredExpression)) + { + return; + } + + _enteredExpression = value; + + // Update current result + MathExpression e = new MathExpression(_enteredExpression); + try + { + string result = e.Evaluate(RESULT_MAX_LENGTH); + CurrentResult = result; + } + catch (MathExpressionEvaluationException) + { + CurrentResult = "Expression error"; + } + catch (MathExpressionResultLengthExceededException) + { + CurrentResult = "Result length exceeded"; + } + catch (Exception) + { + CurrentResult = "Error"; + } + + OnPropertyChanged(); + } + } + + /// + /// Command for appending a character to the current expression. + /// + public ICommand AppendToExpressionCommand { get; set; } + + /// + /// Command for clearing current expression. + /// + public ICommand ClearInputCommand { get; set; } + + /// + /// Command for adding bracket to the current expression. + /// + public ICommand BracketCommand { get; set; } + + /// + /// Command to delete last character from the current expression. + /// + public ICommand DeleteCommand { get; set; } + + /// + /// Command for changing sign of the lastly entered value. + /// + public ICommand ChangeSignCommand { get; set; } + + /// + /// Command to evaluate current expression. + /// + public ICommand EvaluateExpressionCommand { get; set; } + + /// + /// Command for appending decimal point to the expression. + /// + public ICommand AppendDecimalPointCommand { get; set; } + + /// + /// Command for appending an operator to the current expression. + /// + public ICommand AppendOperatorCommand { get; set; } + + #endregion properties + + #region methods + + /// + /// Initializes view model. + /// Creates and assigns view model commands. + /// + public CalculatorViewModel() + { + AppendToExpressionCommand = new Command(AppendToExpression); + ClearInputCommand = new Command(ClearInput); + BracketCommand = new Command(AppendBracket); + DeleteCommand = new Command(DeleteLastCharacter); + ChangeSignCommand = new Command(ChangeSign); + EvaluateExpressionCommand = new Command(EvaluateExpression); + AppendDecimalPointCommand = new Command(AppendDecimalPoint); + AppendOperatorCommand = new Command(AppendOperator); + } + + /// + /// Invokes property changed with caller (property) name. + /// + /// Name of the property changed. + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + /// + /// Indicates if character passed in parameter is supported mathematic operator. + /// + /// Character + /// True if character is supported operator. + private bool IsOperator(char c) + { + return c == '+' || c == '-' || c == '×' || c == '÷'; + } + + /// + /// Clears current input (entered expression). + /// + private void ClearInput() + { + EnteredExpression = "0"; + } + + /// + /// Changes sign of the last value. + /// + private void ChangeSign() + { + char lastCharacter = EnteredExpression[EnteredExpression.Length - 1]; + + if (!char.IsDigit(lastCharacter)) + { + if (lastCharacter == '-' && EnteredExpression.Length > 1 + && EnteredExpression[EnteredExpression.Length - 2] == '(') + { + EnteredExpression = EnteredExpression.Remove(EnteredExpression.Length - 2, 2); + return; + } + + if (!IsOperator(lastCharacter)) + { + EnteredExpression += "×"; + } + + EnteredExpression += "(-"; + return; + } + + // Find last value start index + int lastValueStart = EnteredExpression.Length - 1; + while (lastValueStart > 0 + && (char.IsDigit(EnteredExpression[lastValueStart - 1]) || EnteredExpression[lastValueStart - 1] == '.')) + { + --lastValueStart; + } + + // If it is already negative, make it positive + if (lastValueStart >= 2 && EnteredExpression[lastValueStart - 2] == '(' + && EnteredExpression[lastValueStart - 1] == '-') + { + EnteredExpression = EnteredExpression.Remove(lastValueStart - 2, 2); + return; + } + + if (lastValueStart == 1 && EnteredExpression[0] == '-') + { + EnteredExpression = EnteredExpression.Remove(0, 1); + return; + } + + // Otherwise make it negative + EnteredExpression = EnteredExpression.Insert(lastValueStart, "(-"); + } + + /// + /// Compares number of the entered opening brackets to the closing brackets. + /// + /// 0 if the number equals, more than 0 if there are more opening brackets, + /// otherwise less than 0. + private int BracketBalance() + { + int balance = 0; + foreach (char t in EnteredExpression) + { + balance = t.Equals('(') ? balance + 1 : t.Equals(')') ? balance - 1 : balance; + } + + return balance; + } + + /// + /// Appends bracket to the current expression. + /// + private void AppendBracket() + { + char lastCharacter = EnteredExpression[EnteredExpression.Length - 1]; + + // remove last character if it's a '.' + if (lastCharacter == '.') + { + EnteredExpression = EnteredExpression.Remove(EnteredExpression.Length - 1); + lastCharacter = EnteredExpression[EnteredExpression.Length - 1]; + } + + if (EnteredExpression == "0") + { + EnteredExpression = "("; + } + else if (IsOperator(lastCharacter) || lastCharacter.Equals('(')) + { + EnteredExpression += "("; + } + else if (BracketBalance() > 0) + { + EnteredExpression += ")"; + } + else + { + EnteredExpression += "×("; + } + } + + /// + /// Deletes last character from the current expression. + /// + private void DeleteLastCharacter() + { + EnteredExpression = EnteredExpression.Remove(EnteredExpression.Length - 1); + if (EnteredExpression.Length == 0) + { + EnteredExpression = "0"; + } + } + + /// + /// Appends decimal point to the current expression. + /// Point is added only when last entered character is a number + /// and has no decimal point added already. + /// + private void AppendDecimalPoint() + { + int index = EnteredExpression.Length - 1; + if (!char.IsDigit(EnteredExpression[index])) + { + return; + } + + while (index > 0 && char.IsDigit(EnteredExpression[index])) + { + --index; + } + + if (!EnteredExpression[index].Equals('.')) + { + EnteredExpression += "."; + } + } + + /// + /// Appends and operator to the current expression. + /// Operator is added when last expression character is not an opening bracket. + /// If method is called with for '-' operator and expression has initial '0' value + /// then current expression is replaced with '-'. + /// If last expression character is a comma, it is removed before appending passed operator. + /// + /// The operator to be added. + private void AppendOperator(object operatorObject) + { + string operatorString = operatorObject as string; + char lastCharacter = EnteredExpression[EnteredExpression.Length - 1]; // we always have it + + // ignore if last character was '(' + if (lastCharacter.Equals('(') && !operatorString.Equals("-")) + { + return; + } + + // exchange if it's a starting '-' sign + if (operatorString.Equals("-") && EnteredExpression.Equals("0")) + { + EnteredExpression = operatorString; + return; + } + + // if operator pressed after other operator, we exchange it + if (IsOperator(lastCharacter) || lastCharacter.Equals('.')) + { + EnteredExpression = EnteredExpression.Remove(EnteredExpression.Length - 1) + operatorString; + return; + } + + EnteredExpression += operatorString; + } + + /// + /// Appends one character to currently entered expression. + /// When adding digit to expression with default value (0) replaces 0 with provided digit. + /// + /// String containing character to append. + public void AppendToExpression(object contentObject) + { + string contentToAppend = contentObject as string; + if (contentToAppend == null || contentToAppend.Length > 1) + { + return; + } + + if (!(char.IsDigit(contentToAppend[0]) || contentToAppend[0].Equals('%'))) + { + return; + } + + char lastCharacter = EnteredExpression[EnteredExpression.Length - 1]; + + // digits exchange "0" if it's the only current input + if (EnteredExpression.Equals("0") && char.IsDigit(contentToAppend[0])) + { + EnteredExpression = contentToAppend; + } + + // insert '%' only after digits and ')', otherwise ignore it + else if (contentToAppend.Equals("%")) + { + if (char.IsDigit(lastCharacter) || lastCharacter.Equals(')')) + { + EnteredExpression += contentToAppend; + } + } + else + { + if (lastCharacter.Equals('%')) + { + EnteredExpression += '×'; + } + + EnteredExpression += contentToAppend; + } + } + + /// + /// Evaluates current expression. + /// Updates expression with result value if calculated successfully. + /// + private void EvaluateExpression() + { + MathExpression e = new MathExpression(EnteredExpression); + + try + { + EnteredExpression = e.Evaluate(RESULT_MAX_LENGTH); + } + catch (Exception) + { + // do nothing + } + + } + + #endregion methods + } +} diff --git a/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/ViewModels/IKeyboardService.cs b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/ViewModels/IKeyboardService.cs new file mode 100755 index 00000000..b66543c1 --- /dev/null +++ b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/ViewModels/IKeyboardService.cs @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2017 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +using System; + +namespace BasicCalculator.ViewModels +{ + /// + /// Interface to maintain Keyboard. + /// + public interface IKeyboardService + { + /// + /// Receives keyboard events. + /// Sender. + /// Key events arguments. + void KeyEvent(object sender, EventArgs keyEventArgs); + + /// + /// Register for keyboard events for keys from the list. + /// + /// List of keys to register. + /// Delegate to call on key event. + void RegisterKeys(string[] keyList, KeyPressedDelegate keyPressedDelegate); + } +} diff --git a/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/ViewModels/KeyboardHandler.cs b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/ViewModels/KeyboardHandler.cs new file mode 100755 index 00000000..6372372d --- /dev/null +++ b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/ViewModels/KeyboardHandler.cs @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace BasicCalculator.ViewModels +{ + /// + /// Delegate to register key-pressed events. + /// + /// Pressed key name. + public delegate void KeyPressedDelegate(string keyName); + + /// + /// Class to handle key-pressed events. + /// + public class KeyboardHandler + { + private readonly CalculatorViewModel _viewModel; + + /// + /// Method called when a key is pressed. + /// + /// Pressed key name. + public void KeyPressed(string keyName) + { + _viewModel.AppendToExpression(keyName); + } + + /// + /// Constructor to ViewModel object reference. + /// + /// CalculatorViewModel object reference. + public KeyboardHandler(CalculatorViewModel viewModel) => _viewModel = viewModel; + } +} \ No newline at end of file diff --git a/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/Views/IViewResolver.cs b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/Views/IViewResolver.cs new file mode 100755 index 00000000..9b1449fb --- /dev/null +++ b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/BasicCalculator/Views/IViewResolver.cs @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using Tizen.NUI; +using Tizen.NUI.BaseComponents; +using Tizen.NUI.Components; + +namespace BasicCalculator.Views +{ + /// + /// View resolver class to obtain views for selected device. + /// + public interface IViewResolver + { + #region methods + + /// + /// Get root page view for this particular device. + /// + /// Root page view. + View GetRootPage(); + + #endregion methods + } +} diff --git a/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/Services/KeyboardService.cs b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/Services/KeyboardService.cs new file mode 100755 index 00000000..680ae5ad --- /dev/null +++ b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/Services/KeyboardService.cs @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2017 Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using BasicCalculator.Tizen.Mobile.Services; +using BasicCalculator.ViewModels; +using System; +using Tizen.NUI.Binding; + +namespace BasicCalculator.Tizen.Mobile.Services +{ + /// + /// Keyboard listener and event dispatcher. + /// + internal class KeyboardService : IKeyboardService + { + #region fields + + /// + /// Delegate used by to maintain + /// Event registered methods. + /// + private KeyPressedDelegate _keyPressedDelegate; + + #endregion fields + + #region methods + + /// + /// Event fired when any key is received by the application's MainWindow. + /// + /// Sending object's reference. + /// Key event arguments. + public void KeyEvent(object sender, EventArgs keyEventArgs) + { + //_keyPressedDelegate?.Invoke(keyEventArgs.KeyName); + } + + /// + /// Registers KeyUp Event for keys passed as list in the first argument to execute + /// delegate passed in the second argument. + /// + /// List of keys names. + /// Delegate to be executed on KeyUp event. + public void RegisterKeys(string[] keyList, KeyPressedDelegate keyPressedDelegate) + { + _keyPressedDelegate += keyPressedDelegate; + } + + #endregion methods + } +} diff --git a/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/Views/MobileMainView.xaml b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/Views/MobileMainView.xaml new file mode 100755 index 00000000..768583ec --- /dev/null +++ b/Mobile/NUI/BasicCalculator/src/BasicCalculator/BasicCalculator.Tizen.Mobile/Views/MobileMainView.xaml @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +