From 66708e6ad543465543536b736c99dc7bcd580889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Wed, 22 Mar 2017 01:49:29 +0100 Subject: [PATCH 001/121] Work on changes to get working setup.py --- MANIFEST | 3 +- requirements.txt | 3 +- setup.py | 85 +++++++++++++------ src/Makefile | 31 +++++-- src/calculator/console.py | 27 ++++++ src/{ => calculator}/main.py | 11 ++- src/{ => calculator}/standard_deviation.py | 0 src/calculator/ui/app/calculator_app.py | 9 +- src/calculator/ui/types/__init__.py | 1 + src/console.py | 21 ----- .../calculator/core/standard_deviation.py | 2 +- 11 files changed, 126 insertions(+), 67 deletions(-) create mode 100644 src/calculator/console.py rename src/{ => calculator}/main.py (80%) rename src/{ => calculator}/standard_deviation.py (100%) create mode 100644 src/calculator/ui/types/__init__.py delete mode 100644 src/console.py diff --git a/MANIFEST b/MANIFEST index e0bc060..aab08b1 100644 --- a/MANIFEST +++ b/MANIFEST @@ -1,2 +1,3 @@ recursive-include src/calculator/ui/ *.qml -src/Makefile \ No newline at end of file +src/Makefile +README.md \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 671d5c4..c27923e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ PyQt5==5.7.1 termcolor doxyqml -# on Debian + Nvidia you need to install also package PyOpenGL \ No newline at end of file +# on Debian + Nvidia you need to install also package PyOpenGL +PyOpenGL \ No newline at end of file diff --git a/setup.py b/setup.py index 6ac18db..cff4dbb 100644 --- a/setup.py +++ b/setup.py @@ -1,37 +1,66 @@ # coding=utf-8 -from distutils.core import setup +from distutils import core +from distutils.command.install import install +from unittest.mock import patch from setuptools import find_packages def install_requires(): with open('requirements.txt') as f: - return f.readlines() - - -setup( - name='MathCalculator', - version='0.1', - license='GNU GENERAL PUBLIC LICENSE Version 3', - long_description=open('README.md').read(), - url='https://github.com/thejoeejoee/IVS-VUT-BIT-2016-2017', - classifiers=[ - 'Development Status :: 3 - Alpha', - 'Environment :: X11 Applications :: Qt', - 'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3 :: Only', - 'Topic :: Scientific/Engineering :: Mathematics', - 'Topic :: Utilities' - ], - author='Josef Kolar, Son Hai Nguyen, Martin Omacht, Robert Navratil', - author_email='xkolar71@stud.fit.vutbr.cz, xnguye16@stud.fit.vutbr.cz,' - 'xomach00@stud.fit.vutbr.cz, xnavra61@stud.fit.vutbr.cz', - keywords='calculator expression mathematics', - packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), - install_requires=install_requires(), - package_data={'': ['Makefile']}, -) + return [line.strip().split('#')[0] for line in f.readlines() if line.strip()] + + +class CalculatorInstallation(install): + def run(self): + super(CalculatorInstallation, self).run() + + with patch('sys.argv', ['', '-o', 'src/calculator/ui/resources.py', 'src/calculator/ui/qml.qrc']): + from PyQt5.pyrcc_main import main + main() + + +def setup(): + core.setup( + name='calculator', + version='0.1', + license='GNU GENERAL PUBLIC LICENSE Version 3', + # long_description=open('./README.md').read(), + url='https://github.com/thejoeejoee/IVS-VUT-BIT-2016-2017', + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Environment :: X11 Applications :: Qt', + 'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3 :: Only', + 'Topic :: Scientific/Engineering :: Mathematics', + 'Topic :: Utilities' + ], + author='Josef Kolar, Son Hai Nguyen, Martin Omacht, Robert Navratil', + author_email='xkolar71@stud.fit.vutbr.cz, xnguye16@stud.fit.vutbr.cz,' + 'xomach00@stud.fit.vutbr.cz, xnavra61@stud.fit.vutbr.cz', + keywords='calculator expression mathematics', + packages=find_packages('src', exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), + install_requires=install_requires() + [ + 'PyOpenGL #;"Debian" in platform_version or "Ubuntu" in platform_version' + ], + package_dir={'': 'src'}, + entry_points={ + 'console_scripts': [ + 'calculator-console=calculator.console:main', + 'calculator-app=calculator.main:main', + ] + }, + # include_package_data=True, + cmdclass=dict( + install=CalculatorInstallation + ), + test_suite='tests', + ) + + +if __name__ == '__main__': + setup() diff --git a/src/Makefile b/src/Makefile index bf12fd4..18b56e3 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,15 +1,28 @@ -VENV=../.venv REQUIREMENTS=../requirements.txt -VENV_ACTIVATE=$(VENV)/bin/activate -PYTHON="$(VENV)/bin/python" -PIP="$(VENV)/bin/pip" -PYRCC5="$(VENV)/bin/pyrcc5" -install: install-python venv +VENV=true + +ifdef VENV + VENV=../.venv + VENV_ACTIVATE=$(VENV)/bin/activate + PYTHON="$(VENV)/bin/python" + PIP="$(VENV)/bin/pip" + PYRCC5="$(VENV)/bin/pyrcc5" +else + PYTHON="python3" + PIP="pip3" + PYRCC5="pyrcc5" +endif + + +install: install-python venv install-calculator install-python: - apt-get install python3.5 python3.5-venv python3-pip + apt-get update && apt-get -y install python3.5 python3-pip python3-venv && pip3 install --upgrade pip + +install-calculator: + $(PYTHON) ../setup.py install compile-qt-rcc: venv $(PYRCC5) -o calculator/ui/resources.py calculator/ui/qml.qrc @@ -18,9 +31,11 @@ test: venv $(PYTHON) test.py || true run: venv compile-qt-rcc - $(PYTHON) main.py + $(PYTHON) calculator/main.py venv: $(REQUIREMENTS) +ifdef VENV test -d $(VENV) || pyvenv $(VENV) $(PIP) install -Ur ../requirements.txt touch $(VENV_ACTIVATE) +endif diff --git a/src/calculator/console.py b/src/calculator/console.py new file mode 100644 index 0000000..c5a7ef5 --- /dev/null +++ b/src/calculator/console.py @@ -0,0 +1,27 @@ +# coding=utf-8 +from pprint import pformat + +from calculator.core.calculator import Calculator + +calculator = Calculator() + +try: + import readline +except ImportError: + pass + + +def main(): + while True: + user_input = input('>>> ').strip() + if not user_input: + continue + try: + result, variables = calculator.process(user_input) + print(" === {}\n === {}\n".format(result, pformat(dict(variables)))) + except Exception as e: + print(e, repr(e)) + + +if __name__ == '__main__': + main() diff --git a/src/main.py b/src/calculator/main.py similarity index 80% rename from src/main.py rename to src/calculator/main.py index 50fb298..5a24cf3 100755 --- a/src/main.py +++ b/src/calculator/main.py @@ -4,18 +4,23 @@ import sys from fileinput import input -from standard_deviation import main as sd_main +from calculator.standard_deviation import main as sd_main sys._excepthook = sys.excepthook def exception_hook(exctype, value, traceback): sys._excepthook(exctype, value, traceback) sys.excepthook = exception_hook -if __name__ == "__main__": + +def main(): if len(sys.argv) > 1 and (sys.argv[1] == '-sd' or sys.argv[1] == '--standard-deviation'): exit(sd_main(input(files=sys.argv[2] if len(sys.argv) > 2 else '-'))) - from calculator.ui.app import CalculatorApp + from .ui.app import CalculatorApp app = CalculatorApp(sys.argv) sys.exit(app.run()) + + +if __name__ == "__main__": + main() diff --git a/src/standard_deviation.py b/src/calculator/standard_deviation.py similarity index 100% rename from src/standard_deviation.py rename to src/calculator/standard_deviation.py diff --git a/src/calculator/ui/app/calculator_app.py b/src/calculator/ui/app/calculator_app.py index f5fee07..c053295 100644 --- a/src/calculator/ui/app/calculator_app.py +++ b/src/calculator/ui/app/calculator_app.py @@ -7,19 +7,20 @@ from PyQt5.QtCore import QUrl, QLocale from PyQt5.QtGui import QIcon from PyQt5.QtGui import QPixmap -from PyQt5.QtQml import QQmlApplicationEngine, qmlRegisterSingletonType, qmlRegisterType +from PyQt5.QtQml import QQmlApplicationEngine, qmlRegisterSingletonType from PyQt5.QtWidgets import QApplication +from calculator.settings import Expansion, ICON_SIZES from calculator.ui.adapter import UIAdapter from calculator.ui.types.core import Sides -from calculator.ui.types.syntaxhighlight import ExpSyntaxHighlighter from calculator.ui.types.qmlwrapper.utils import TypeRegister -from calculator.settings import Expansion, ICON_SIZES +from calculator.ui.types.syntaxhighlight import ExpSyntaxHighlighter if platform.system() == "Linux": # Needed for platform.linux_distribution, which is not available on Windows and OSX # For Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826 platform_identifier = platform.platform() - if 'Ubuntu' in platform_identifier or 'Debian' in platform_identifier: # Just in case it also happens on Debian, so it can be added + # Just in case it also happens on Debian, so it can be added + if 'Ubuntu' in platform_identifier or 'Debian' in platform_identifier: # noinspection PyUnresolvedReferences from OpenGL import GL diff --git a/src/calculator/ui/types/__init__.py b/src/calculator/ui/types/__init__.py new file mode 100644 index 0000000..9bad579 --- /dev/null +++ b/src/calculator/ui/types/__init__.py @@ -0,0 +1 @@ +# coding=utf-8 diff --git a/src/console.py b/src/console.py deleted file mode 100644 index dc1b271..0000000 --- a/src/console.py +++ /dev/null @@ -1,21 +0,0 @@ -# coding=utf-8 -from pprint import pformat - -from calculator.core.calculator import Calculator - -calculator = Calculator() - -try: - import readline -except ImportError: - pass - -while True: - user_input = input('>>> ').strip() - if not user_input: - continue - try: - result, variables = calculator.process(user_input) - print(" === {}\n === {}\n".format(result, pformat(dict(variables)))) - except Exception as e: - print(e, repr(e)) diff --git a/src/tests/calculator/core/standard_deviation.py b/src/tests/calculator/core/standard_deviation.py index 325cab4..1092057 100644 --- a/src/tests/calculator/core/standard_deviation.py +++ b/src/tests/calculator/core/standard_deviation.py @@ -3,7 +3,7 @@ from statistics import mean as stats_mean, stdev from unittest.case import TestCase -from standard_deviation import mean as my_mean, standard_deviation +from calculator.standard_deviation import mean as my_mean, standard_deviation class StandardDeviationTest(TestCase): From c260309e19ef7275856ace92fa252a9e764e7cb1 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 22 Mar 2017 02:12:30 +0100 Subject: [PATCH 002/121] New ResultSystemDisplay with implemented conversion --- src/calculator/exceptions.py | 4 ++ src/calculator/settings.py | 1 + src/calculator/ui/adapter.py | 13 +++- src/calculator/ui/assets/styles/UIStyles.qml | 9 +++ src/calculator/ui/qml.qrc | 1 + src/calculator/ui/qml/main.qml | 20 +++++- .../ui/qml/visualization/ResultDisplay.qml | 7 +-- .../qml/visualization/ResultSystemDisplay.qml | 61 +++++++++++++++++++ src/calculator/utils/number_formatter.py | 13 ++++ 9 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 src/calculator/ui/qml/visualization/ResultSystemDisplay.qml diff --git a/src/calculator/exceptions.py b/src/calculator/exceptions.py index 39f2e43..a30fce1 100644 --- a/src/calculator/exceptions.py +++ b/src/calculator/exceptions.py @@ -24,6 +24,10 @@ class VariableError(Exception): Exception for signalizing of invalid definition for calculator variable. """ +class UnsupportedBaseError(Exception): + """ + Raised when trying convert number into unsupported base + """ class VariableRemoveRestrictError(VariableError): """ diff --git a/src/calculator/settings.py b/src/calculator/settings.py index 6160bf7..05373ba 100644 --- a/src/calculator/settings.py +++ b/src/calculator/settings.py @@ -5,6 +5,7 @@ from PyQt5.QtQml import QQmlEngine, QJSEngine ICON_SIZES = (16, 24, 32, 48, 256) +SUPPORTED_BASES = (10, 2, 8, 16) class BuiltinFunction(object): ABS = 'abs' diff --git a/src/calculator/ui/adapter.py b/src/calculator/ui/adapter.py index c92df19..d0e969e 100644 --- a/src/calculator/ui/adapter.py +++ b/src/calculator/ui/adapter.py @@ -7,7 +7,7 @@ from PyQt5.QtQml import QJSEngine, QQmlEngine from calculator.core.calculator import Calculator -from calculator.exceptions import MathError, VariableError +from calculator.exceptions import MathError, VariableError, UnsupportedBaseError from calculator.typing import Variable, NumericValue from calculator.utils.number_formatter import NumberFormatter from calculator.utils.translate import translate @@ -28,6 +28,16 @@ class UIAdapter(QObject): func_identifiers_types = [{"identifier": func, "type": Expression.ExpressionTypes.Function} for func in BUILTIN_FUNCTIONS] + @pyqtSlot(float, int, result=str) + def convertToBase(self, value: NumericValue, base: int) -> str: + try: + v = self._formatter.format_in_base(value, base) + return v + except UnsupportedBaseError as e: + return translate("Adapter", "Unsupported base.") + except ValueError as e: + return "-" + @pyqtSlot(str) def process(self, expression: str) -> None: try: @@ -38,6 +48,7 @@ def process(self, expression: str) -> None: self.identifiersTypesChanged.emit(self.identifiersTypes) self.processed.emit(QVariant({ "result": None if result is None else self._formatter.format(result, 16), + "unformattedResult": result, "variables": { key: dict( value=self._formatter.format(value), diff --git a/src/calculator/ui/assets/styles/UIStyles.qml b/src/calculator/ui/assets/styles/UIStyles.qml index fd37e63..7990cab 100644 --- a/src/calculator/ui/assets/styles/UIStyles.qml +++ b/src/calculator/ui/assets/styles/UIStyles.qml @@ -1,6 +1,7 @@ pragma Singleton import QtQuick 2.0 import QtQuick.Controls.Styles 1.4 +// TODO allow import Expression 1.0 QtObject { @@ -122,9 +123,17 @@ QtObject { property var typeColors: ({}) property font font: Qt.font({family: "Roboto Light"}) + // TODO allow Component.onCompleted: { typeColors[Expression.Function] = "#EF4223" typeColors[Expression.Variable] = "#C1C0C0" } } + + property QtObject resultSystemDisplay: QtObject { + property color color: "#F2F2F2" + property color baseTextColor: "#ED1D3D" + property color valueTextColor: "#3D3D3D" + property font font: Qt.font({family: "Roboto Light"}) + } } diff --git a/src/calculator/ui/qml.qrc b/src/calculator/ui/qml.qrc index aaec22e..0f2afbd 100644 --- a/src/calculator/ui/qml.qrc +++ b/src/calculator/ui/qml.qrc @@ -47,5 +47,6 @@ assets/icons/256x256.png qml/controls/DropDown.qml qml/controls/Completer.qml + qml/visualization/ResultSystemDisplay.qml diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index f01b61e..35d6a79 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -147,6 +147,7 @@ ApplicationWindow { ResultDisplay { id: resultDisplay + result: "0" color: StyleSettings.resultDisplay.backgroundColor textColor: StyleSettings.resultDisplay.textColor font.family: StyleSettings.resultDisplay.font.family @@ -154,7 +155,22 @@ ApplicationWindow { anchors.top: parent.top anchors.left: parent.left anchors.right: variablePanel.left + anchors.bottom: resultSystemDisplay.top + } + + ResultSystemDisplay { + id: resultSystemDisplay + + value: 0 + bases: { "DEC": 10, "BIN": 2, "HEX": 16, "OCT": 8 } + height: 100 + baseTextColor: StyleSettings.resultSystemDisplay.baseTextColor + valueTextColor: StyleSettings.resultSystemDisplay.valueTextColor + font: StyleSettings.resultSystemDisplay.font + anchors.bottom: functionPanel.top + anchors.left: parent.left + anchors.right: variablePanel.left } Control.CalculateButton { @@ -284,8 +300,10 @@ ApplicationWindow { } function handleResult(data) { - if(typeof data["result"] !== "undefined") + if(typeof data["result"] !== "undefined") { resultDisplay.result = data["result"] + resultSystemDisplay.value = data["unformattedResult"] + } else expInput.text = "" diff --git a/src/calculator/ui/qml/visualization/ResultDisplay.qml b/src/calculator/ui/qml/visualization/ResultDisplay.qml index c6d1b26..17369d9 100644 --- a/src/calculator/ui/qml/visualization/ResultDisplay.qml +++ b/src/calculator/ui/qml/visualization/ResultDisplay.qml @@ -11,11 +11,10 @@ Rectangle { id: text font.family: component.font.family - font.pixelSize: parent.height / 3.8 + font.pixelSize: parent.height / 1.9 anchors.right: parent.right - anchors.rightMargin: font.pixelSize / 1.8 - anchors.verticalCenter: parent.verticalCenter - anchors.verticalCenterOffset: -parent.height / 20 + anchors.rightMargin: font.pixelSize / 3.4 + anchors.bottom: parent.bottom } } diff --git a/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml b/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml new file mode 100644 index 0000000..22aedd0 --- /dev/null +++ b/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml @@ -0,0 +1,61 @@ +import QtQuick 2.7 +import Calculator 1.0 + +Rectangle { + id: component + + property var bases + property real value: 0 + property font font + property color baseTextColor + property color valueTextColor + readonly property int margin: 10 + + Column { + id: container + + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.topMargin: component.margin + anchors.bottomMargin: component.margin + anchors.leftMargin: component.margin * 2 + + Repeater { + model: Object.keys(component.bases) + Item { + width: container.width + height: container.height / Object.keys(component.bases).length + + Text { + id: baseText + + text: modelData + color: component.baseTextColor + + font.pixelSize: parent.height * 0.9 + font.family: component.font.family + + anchors.top: parent.top + anchors.left: parent.left + } + + AnimatedText { + text: Calculator.convertToBase(component.value, component.bases[modelData]) + color: component.valueTextColor + font: baseText.font + + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: component.margin * 3 + fm.advanceWidth("DEC") // some constant to measure font width + + FontMetrics { + id: fm + font: baseText.font + } + } + } + } + } +} diff --git a/src/calculator/utils/number_formatter.py b/src/calculator/utils/number_formatter.py index 18e7036..a9066e7 100644 --- a/src/calculator/utils/number_formatter.py +++ b/src/calculator/utils/number_formatter.py @@ -2,12 +2,16 @@ from decimal import Decimal from calculator.typing import NumericValue +from calculator.settings import SUPPORTED_BASES + +from calculator.exceptions import UnsupportedBaseError class NumberFormatter(object): DEFAULT_CHARACTERS_LIMIT = 8 EXP_FORMAT = '{value} ×10{exp}' + BASE_CONVERTERS = {2: bin, 8: oct, 16: hex, 10: lambda x: str(x)} _EXP_DIVIDER = 'e' @@ -23,3 +27,12 @@ def format(cls, value: NumericValue, characters_limit: int = DEFAULT_CHARACTERS_ value=value, exp=exp.lstrip('+') ) + + @classmethod + def format_in_base(cls, value: int, base: int) -> str: + if abs(value - int(value)) > 0: + raise ValueError() + if not base in SUPPORTED_BASES: + raise UnsupportedBaseError("Base {} is not supported.".format(base)) + + return cls.BASE_CONVERTERS[base](int(value)) \ No newline at end of file From f62629b76b9f8992927d18353d7b1c21fd1db849 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 22 Mar 2017 02:32:31 +0100 Subject: [PATCH 003/121] Fix DropDown did not do anything when item was chosen by mouse --- src/calculator/ui/qml/controls/DropDown.qml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/calculator/ui/qml/controls/DropDown.qml b/src/calculator/ui/qml/controls/DropDown.qml index 521b56e..0062067 100644 --- a/src/calculator/ui/qml/controls/DropDown.qml +++ b/src/calculator/ui/qml/controls/DropDown.qml @@ -151,10 +151,7 @@ Item { anchors.fill: parent hoverEnabled: true onEntered: component.currentItemIndex = index - onClicked: { - component.hide() - component.currentItem = modelData - } + onClicked: component.chooseCurrent() } } } From d03bb78b1fda80d120f3f118ae27285b47a0d392 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 22 Mar 2017 13:27:17 +0100 Subject: [PATCH 004/121] Refactor generalize error dialog into PopUp --- src/calculator/ui/qml.qrc | 2 +- src/calculator/ui/qml/main.qml | 3 ++- .../visualization/{Error.qml => PopUp.qml} | 21 ++++++++++--------- 3 files changed, 14 insertions(+), 12 deletions(-) rename src/calculator/ui/qml/visualization/{Error.qml => PopUp.qml} (85%) diff --git a/src/calculator/ui/qml.qrc b/src/calculator/ui/qml.qrc index 0f2afbd..56d2c6c 100644 --- a/src/calculator/ui/qml.qrc +++ b/src/calculator/ui/qml.qrc @@ -37,7 +37,7 @@ qml/controls/CalculateButton.qml assets/images/equal.svg qml/visualization/AnimatedText.qml - qml/visualization/Error.qml + qml/visualization/PopUp.qml qml/controls/DefaultButton.qml ../translations/cs.qm assets/icons/16x16.png diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index 35d6a79..ef03d84 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -187,9 +187,10 @@ ApplicationWindow { onClicked: Calculator.process(expInput.text) } - Error { + PopUp { id: error + title: qsTr("Error") maskColor: StyleSettings.errorDialog.maskColor dialogColor: StyleSettings.errorDialog.color textColor: StyleSettings.errorDialog.textColor diff --git a/src/calculator/ui/qml/visualization/Error.qml b/src/calculator/ui/qml/visualization/PopUp.qml similarity index 85% rename from src/calculator/ui/qml/visualization/Error.qml rename to src/calculator/ui/qml/visualization/PopUp.qml index abdcf96..7ff9003 100644 --- a/src/calculator/ui/qml/visualization/Error.qml +++ b/src/calculator/ui/qml/visualization/PopUp.qml @@ -6,14 +6,15 @@ Item { signal hide() signal hidden() - signal show(string error) + signal show(string msg) signal hideAnimation() signal showAnimation() property alias maskColor: mask.color property alias dialogColor: dialog.color - property alias textColor: errorText.color - property alias errorMessage: errorMsg.text + property alias textColor: title.color + property alias message: message.text + property alias title: title.text property font font visible: false @@ -22,7 +23,7 @@ Item { Keys.onPressed: component.hide() onShow: { - component.errorMessage = error + component.message = msg component.showAnimation() } @@ -58,7 +59,7 @@ Item { anchors.verticalCenter: parent.verticalCenter Text { - id: errorText + id: title text: qsTr("Error") @@ -72,21 +73,21 @@ Item { } Text { - id: errorMsg + id: message - color: errorText.color + color: title.color font.pixelSize: parent.height / 7 font.family: component.font.family - anchors.left: errorText.left - anchors.top: errorText.bottom + anchors.left: title.left + anchors.top: title.bottom anchors.topMargin: parent.height / 25 } Controls.DefaultButton { text: qsTr("Ok") - color: errorText.color + color: title.color backgroundColor: component.dialogColor font.family: component.font.family From f4e86924a52c99c1520e06d20e08162ebd355cd2 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 22 Mar 2017 13:27:58 +0100 Subject: [PATCH 005/121] New PopUp info --- src/calculator/ui/assets/styles/UIStyles.qml | 7 +++++++ src/calculator/ui/easteregg/Game.qml | 4 ++-- src/calculator/ui/qml/main.qml | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/calculator/ui/assets/styles/UIStyles.qml b/src/calculator/ui/assets/styles/UIStyles.qml index 7990cab..e4fad72 100644 --- a/src/calculator/ui/assets/styles/UIStyles.qml +++ b/src/calculator/ui/assets/styles/UIStyles.qml @@ -115,6 +115,13 @@ QtObject { property font font: Qt.font({family: "Roboto Light"}) } + property QtObject infoDialog: QtObject { + property color maskColor: "black" + property color color: "#FFCE00" + property color textColor: "white" + property font font: Qt.font({family: "Roboto Light"}) + } + property QtObject completer: QtObject { property color color: "#2A2A2A" property color hoverColor: "#ED1946" diff --git a/src/calculator/ui/easteregg/Game.qml b/src/calculator/ui/easteregg/Game.qml index a4c4412..714a90a 100644 --- a/src/calculator/ui/easteregg/Game.qml +++ b/src/calculator/ui/easteregg/Game.qml @@ -145,8 +145,8 @@ ApplicationWindow { gameWindow.enemyScore += 1 if(gameWindow.enemyScore == gameWindow.maxScore) - gameWindow.gameOver("YouAreLooser") + gameWindow.gameOver(qsTr("You are looser.")) if(gameWindow.playerScore == gameWindow.maxScore) - gameWindow.gameOver("AIIsNoob") + gameWindow.gameOver(qsTr("AI is noob.")) } } diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index ef03d84..b3db782 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -201,6 +201,20 @@ ApplicationWindow { onHidden: expInput.focus = true } + PopUp { + id: info + + title: qsTr("Info") + maskColor: StyleSettings.infoDialog.maskColor + dialogColor: StyleSettings.infoDialog.color + textColor: StyleSettings.infoDialog.textColor + font: StyleSettings.infoDialog.font + + anchors.fill: parent + + onHidden: expInput.focus = true + } + Component.onCompleted: { Calculator.processed.connect(handleResult) Calculator.error.connect(error.show) From d563d38f4dc828923c34e713a7eef4ef04107687 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 22 Mar 2017 13:28:15 +0100 Subject: [PATCH 006/121] Connect info dialog with game msg --- src/calculator/ui/qml/main.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index b3db782..f0175ce 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -28,7 +28,7 @@ ApplicationWindow { Game { id: game - onGameOver: console.log(msg) + onGameOver: info.show(msg) } ExpSyntaxHighlighter { From 1d796004752b5717fded58946c9b847df54a638a Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 22 Mar 2017 13:28:29 +0100 Subject: [PATCH 007/121] REmove default title in PopUp --- src/calculator/ui/qml/visualization/PopUp.qml | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/calculator/ui/qml/visualization/PopUp.qml b/src/calculator/ui/qml/visualization/PopUp.qml index 7ff9003..4a8746b 100644 --- a/src/calculator/ui/qml/visualization/PopUp.qml +++ b/src/calculator/ui/qml/visualization/PopUp.qml @@ -61,8 +61,6 @@ Item { Text { id: title - text: qsTr("Error") - font.pixelSize: parent.height / 3 font.family: component.font.family From 377af9d56cdf679f20c43bf1c560d071c655612b Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 22 Mar 2017 13:28:47 +0100 Subject: [PATCH 008/121] Cleanings calculator app --- src/calculator/ui/app/calculator_app.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/calculator/ui/app/calculator_app.py b/src/calculator/ui/app/calculator_app.py index 2b0327e..f1852b4 100644 --- a/src/calculator/ui/app/calculator_app.py +++ b/src/calculator/ui/app/calculator_app.py @@ -35,13 +35,10 @@ def __init__(self, argv: List[str]): self._translator.load("".join((":/translations/", QLocale().system().name(), ".qsm"))) self.installTranslator(self._translator) - print(QPixmap(":/assets/images/icon.png").width()) icon = QIcon() for size in ICON_SIZES: - print(":/assets/icons/{}x{}.png".format(size, size)) icon.addFile(":/assets/icons/{}x{}.png".format(size, size), QSize(size, size)) - #icon.addPixmap(QPixmap(":/assets/icons/{}x{}.png".format(size, size))) self.setWindowIcon(icon) From dbfc8a54ab03f4f00fc27bd623caeb670c41e031 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 22 Mar 2017 14:05:34 +0100 Subject: [PATCH 009/121] New CountDown - add countdown at starting easter egg --- src/calculator/ui/assets/styles/UIStyles.qml | 5 ++ src/calculator/ui/qml.qrc | 1 + src/calculator/ui/qml/main.qml | 21 +++++-- .../ui/qml/visualization/CountDown.qml | 62 +++++++++++++++++++ 4 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 src/calculator/ui/qml/visualization/CountDown.qml diff --git a/src/calculator/ui/assets/styles/UIStyles.qml b/src/calculator/ui/assets/styles/UIStyles.qml index e4fad72..fddb9dc 100644 --- a/src/calculator/ui/assets/styles/UIStyles.qml +++ b/src/calculator/ui/assets/styles/UIStyles.qml @@ -137,6 +137,11 @@ QtObject { } } + property QtObject countDown: QtObject { + property var textColors: {0: "#ED1869", 1: "#F2BC1F", 2: "#39BFC1", 3: "#672980"} + property font font: Qt.font({family: "Roboto Light"}) + } + property QtObject resultSystemDisplay: QtObject { property color color: "#F2F2F2" property color baseTextColor: "#ED1D3D" diff --git a/src/calculator/ui/qml.qrc b/src/calculator/ui/qml.qrc index 56d2c6c..a5d95e1 100644 --- a/src/calculator/ui/qml.qrc +++ b/src/calculator/ui/qml.qrc @@ -48,5 +48,6 @@ qml/controls/DropDown.qml qml/controls/Completer.qml qml/visualization/ResultSystemDisplay.qml + qml/visualization/CountDown.qml diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index f0175ce..438a6fb 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -126,10 +126,8 @@ ApplicationWindow { Calculator.process(expInput.text) } onTextChanged: { - if(text.search("nyan") != -1) { - expInput.text = "" - game.run() - } + if(text.search("nyan") != -1) + countDown.start(3) completeText() } @@ -215,6 +213,21 @@ ApplicationWindow { onHidden: expInput.focus = true } + CountDown { + id: countDown + + anchors.fill: parent + font.family: StyleSettings.countDown.font + color: StyleSettings.countDown.textColors[count + 1] + + + onTriggered: { + expInput.focus = true + expInput.text = "" + game.run() + } + } + Component.onCompleted: { Calculator.processed.connect(handleResult) Calculator.error.connect(error.show) diff --git a/src/calculator/ui/qml/visualization/CountDown.qml b/src/calculator/ui/qml/visualization/CountDown.qml new file mode 100644 index 0000000..8a770bc --- /dev/null +++ b/src/calculator/ui/qml/visualization/CountDown.qml @@ -0,0 +1,62 @@ +import QtQuick 2.0 + +Item { + id: component + + signal start(int seconds) + signal triggered() + + readonly property alias count: timer.count + property alias color: text.color + property font font + + focus: visible + visible: opacity + opacity: 0 + + onStart: { + component.opacity = 1 + timer.count = seconds + timer.start() + } + + Behavior on opacity { + NumberAnimation { duration: 200 } + } + + MouseArea { + anchors.fill: parent + } + + Text { + id: text + + font.pixelSize: parent.height / 1.2 + font.family: component.font.family + + anchors.centerIn: parent + } + + Timer { + id: timer + + property int count + + interval: 1000 + running: false + repeat: true + triggeredOnStart: true + + onTriggered: { + if(!timer.count) { + timer.stop() + component.opacity = 0 + component.triggered() + } + + else + text.text = timer.count.toString() + timer.count-- + } + } +} From a1f7fee3f21455f2fe095c7e3acfd35b3a6a366e Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 22 Mar 2017 14:06:07 +0100 Subject: [PATCH 010/121] Fix rotate nyan cat after start --- src/calculator/ui/easteregg/Game.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calculator/ui/easteregg/Game.qml b/src/calculator/ui/easteregg/Game.qml index 714a90a..fa5a624 100644 --- a/src/calculator/ui/easteregg/Game.qml +++ b/src/calculator/ui/easteregg/Game.qml @@ -122,6 +122,7 @@ ApplicationWindow { function startGame() { nyan.vector = Qt.point(Math.cos(Math.PI / 3), -Math.sin(Math.PI / 3)) + nyan.rotateNyanCat() nyan.x = gameWindow.width / 2 - nyan.width / 2 nyan.y = gameWindow.height / 2 - nyan.height / 2 gameWindow.playerScore = 0 From e6b84f8ee2b830dac97b56e9d41c22cba4b41bbf Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 22 Mar 2017 14:10:14 +0100 Subject: [PATCH 011/121] Update translations --- src/calculator/translations/cs.qm | Bin 823 -> 1122 bytes src/calculator/translations/cs.ts | 51 ++++++++++++++++++++++++------ 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/calculator/translations/cs.qm b/src/calculator/translations/cs.qm index 060bd10f79b16d27099f068f8389409f9a4a2442..3c462e8b886b610ec9f1ad09cfd95f6d0494566b 100644 GIT binary patch delta 437 zcmYjNy-UMD6#pg7M_W-*911!d{R09z=wK-nhZ51!r9%;ggkEV%n~O=Rh=Xe<5i;rE zCJKVo$yNLZ9K=acbQD~~U0+ft<+ywA{XX7%+@1EBKAAge0qPczT7arNQXF98)TY>% z^5zsEU8V-@8vwuR(oY4D9rgVBf%t;@l6@mQ&?<*Jgum)vVUhIZj8i|RdN#A0H;6~} z{%N1;3x`b4DFO;uLkkD6VS(KSXA&=>0uB!XjpX$y+auP+2beU8Zv@15hBf5^J$ge@ z#3~~lkJ0GP)ZC!wdBP8QgV|QV4V_TE)UdpeM^$-yC5IABFtn*3qke*p4Fhb{W1?g- zCt$7+cH&mbR+mRta$z-x^<--7>?F>3D}QYMbY1kA<#W~%BH;d*e^H)StHP^!(Yw4E mW#s&_(zP5{ivJ@`8@XsgjCN2=w<+S)hoU}Znk4-xvhoiQVO!<^ delta 133 zcmaFFv7K##h@S@o18Xz`1LFk-pP5NOdO7oktaS_w3}0C1tv Adapter - + + Unsupported base. + Nepodporovaná číselná soustava. + + + Math error occured. Matematická chyba. - + Error in defining variable. Chyba při definování proměnné. - + Result is too big. Výsledek je příliš velký. - + Expression contains syntax error. Výraz obsahuje syntaktickou chybu. @@ -27,20 +32,18 @@ Error - Error - Chyba + Chyba - Ok - Ok + Ok ExpressionInput - + Enter expression... Zadejte výraz... @@ -52,13 +55,41 @@ Easter egg Easter egg + + + You are looser. + Jste looser. + + + + AI is noob. + AI je noob. + + + + PopUp + + + Ok + Ok + main - + Barbie Calculator Barbie Calculator + + + Error + Chyba + + + + Info + Info + From 0110b5533613d5852fbfac7eba9eadc4458b6676 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 22 Mar 2017 14:18:05 +0100 Subject: [PATCH 012/121] Update ui design --- plan/ui-design.svg | 641 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 591 insertions(+), 50 deletions(-) diff --git a/plan/ui-design.svg b/plan/ui-design.svg index 5b8b21a..93ca9a6 100644 --- a/plan/ui-design.svg +++ b/plan/ui-design.svg @@ -4,8 +4,8 @@ - - Zadejte vstup... + + Zadejte vstup... @@ -26,85 +26,515 @@ - Barbie Calculator + + + + + + + + + + + + + + + + + + - 645,74540 + + + + + + + + + + + - pow + + + + + - root + + + + + + - fact + + + + + + - rand + + + + + + - log + + + + + - e + + + - pi - - pow - - root - - fact - - rand - - log - - - e - - - pi + + + + - BIN 1010 1010101... - DEC 103 - OCT 403 - HEX 4FA - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - Ans + + + + + - 3*(343)+44 - 54,4 + + + + + + + + + + + + + + + + + + - DF - 3*(343)+44 - 54,4 + + + + + + + + + + + + + + + + + + + + + + - XY - 3*(343)+44 - 5454,4 + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - FOO - 3*(343)+44 - 54,4 + + + + + + + + + + + + + + + + + + + + + + + @@ -131,16 +561,127 @@ - =1 - =0 + + + + + + + + + + + + + Error + Naskytla se nějaká chyba. + + + Ok + + + + + + + + + + + + + + + + + + + + + + + - 3*(343)+44 + + + + + + + + + + + + From b98986a73be99633263a8e2a496c9e5561a9ac18 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 22 Mar 2017 21:01:06 +0100 Subject: [PATCH 013/121] Finish issue #23 - create mockup --- mockup/hidden-features.svg | 691 +++++++++++++++++++++++ mockup/shown-features-countdown.svg | 698 ++++++++++++++++++++++++ mockup/shown-features.svg | 812 ++++++++++++++++++++++++++++ 3 files changed, 2201 insertions(+) create mode 100644 mockup/hidden-features.svg create mode 100644 mockup/shown-features-countdown.svg create mode 100644 mockup/shown-features.svg diff --git a/mockup/hidden-features.svg b/mockup/hidden-features.svg new file mode 100644 index 0000000..3537542 --- /dev/null +++ b/mockup/hidden-features.svg @@ -0,0 +1,691 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Error + + Naskytla se nějaká chyba. + + + Ok + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Graph + + + + + + + + diff --git a/mockup/shown-features-countdown.svg b/mockup/shown-features-countdown.svg new file mode 100644 index 0000000..9a9692d --- /dev/null +++ b/mockup/shown-features-countdown.svg @@ -0,0 +1,698 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Error + + Naskytla se nějaká chyba. + + + Ok + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mockup/shown-features.svg b/mockup/shown-features.svg new file mode 100644 index 0000000..a0ab208 --- /dev/null +++ b/mockup/shown-features.svg @@ -0,0 +1,812 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Error + + Naskytla se nějaká chyba. + + + Ok + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 3faa2bbdb103efeafd6dc03dac10de5319a34c7d Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 22 Mar 2017 22:17:30 +0100 Subject: [PATCH 014/121] Typos --- src/calculator/ui/easteregg/Game.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/calculator/ui/easteregg/Game.qml b/src/calculator/ui/easteregg/Game.qml index fa5a624..3a21e1c 100644 --- a/src/calculator/ui/easteregg/Game.qml +++ b/src/calculator/ui/easteregg/Game.qml @@ -13,10 +13,9 @@ ApplicationWindow { signal run() signal gameOver(string msg) - property bool running: false property int playerScore: 0 property int enemyScore: 0 - property int maxScore: 3 + readonly property int maxScore: 3 property size playerSize: Qt.size(10, 100) width: 1500 From fc96336ccf6fd9cb4928cd6640dca71a2cf5cbe7 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 22 Mar 2017 22:18:14 +0100 Subject: [PATCH 015/121] First stage of adding documentation --- src/calculator/ui/easteregg/Game.qml | 25 ++++++++ .../animations/ParticleGenerator.qml | 8 +++ .../ui/easteregg/animations/RainbowTail.qml | 7 +++ .../ui/easteregg/entities/Player.qml | 8 +++ src/calculator/ui/easteregg/entities/Wall.qml | 3 + .../ui/easteregg/entities/WallSystem.qml | 10 ++++ .../easteregg/logic/collision/BoxCollider.qml | 9 +++ .../logic/collision/CollisionSystem.qml | 19 ++++++ .../ui/easteregg/logic/extendedmath.js | 20 +++++++ .../ui/easteregg/visualization/Score.qml | 6 ++ .../ui/qml/containers/FunctionsPanel.qml | 9 +++ src/calculator/ui/qml/containers/Stack.qml | 3 + .../ui/qml/containers/VariablesPanel.qml | 58 +++++++++++++++++++ .../ui/qml/controls/CalculateButton.qml | 4 ++ src/calculator/ui/qml/controls/Clickable.qml | 15 +++++ src/calculator/ui/qml/controls/Completer.qml | 13 +++++ .../ui/qml/controls/DefaultButton.qml | 7 +++ src/calculator/ui/qml/controls/DropDown.qml | 37 ++++++++++++ .../ui/qml/controls/ExpressionInput.qml | 5 ++ src/calculator/ui/qml/controls/TextButton.qml | 7 +++ .../ui/qml/controls/VariableSetButton.qml | 8 +++ src/calculator/ui/qml/item/VariableItem.qml | 5 ++ 22 files changed, 286 insertions(+) diff --git a/src/calculator/ui/easteregg/Game.qml b/src/calculator/ui/easteregg/Game.qml index 3a21e1c..42b2509 100644 --- a/src/calculator/ui/easteregg/Game.qml +++ b/src/calculator/ui/easteregg/Game.qml @@ -7,15 +7,29 @@ import "entities" as Entities import "visualization" as Visualization import "logic/collision" as Collision +/** + Standalone window with game(pong) + */ ApplicationWindow { id: gameWindow + /** + Used as function to start game + */ signal run() + /** + Emits when some of players loose + @param msg Message to player + */ signal gameOver(string msg) + /// Score of player property int playerScore: 0 + /// Score of other player property int enemyScore: 0 + /// Max score which player need to archieve to win readonly property int maxScore: 3 + /// Size of player rebound area property size playerSize: Qt.size(10, 100) width: 1500 @@ -119,6 +133,9 @@ ApplicationWindow { Component.onCompleted: collisionSystem.registerPair(nyan, ai) } + /** + Resets component to begin game + */ function startGame() { nyan.vector = Qt.point(Math.cos(Math.PI / 3), -Math.sin(Math.PI / 3)) nyan.rotateNyanCat() @@ -130,11 +147,19 @@ ApplicationWindow { frameTimer.running = true } + /** + Sets component to end game + */ function endGame() { frameTimer.running = false gameWindow.visible = false } + /** + React to collision of ball to increment score + @param obj Ball reference + @param side Side of collision to determinate who has point + */ function handleCollisionWithWall(obj, side) { if(!obj.isVoid) return diff --git a/src/calculator/ui/easteregg/animations/ParticleGenerator.qml b/src/calculator/ui/easteregg/animations/ParticleGenerator.qml index 8c542ae..5ab9ac4 100644 --- a/src/calculator/ui/easteregg/animations/ParticleGenerator.qml +++ b/src/calculator/ui/easteregg/animations/ParticleGenerator.qml @@ -1,14 +1,22 @@ import QtQuick 2.0 import QtQuick.Particles 2.0 +/** + Component which generates particles + */ Item { id: component + /// Expose emitter component property alias emitter: emitter + /// String name to identify group of particles which are the same property string groupName + /// Parent of particles, determinates particle coord system and visibility property Item particleParent + /// Stores reference to global particle system property Item particleSystem + /// Property which store component of particle, which will be delegated property Component particle: Rectangle { width: component.width height: width diff --git a/src/calculator/ui/easteregg/animations/RainbowTail.qml b/src/calculator/ui/easteregg/animations/RainbowTail.qml index a888cfe..2b0f95a 100644 --- a/src/calculator/ui/easteregg/animations/RainbowTail.qml +++ b/src/calculator/ui/easteregg/animations/RainbowTail.qml @@ -1,12 +1,19 @@ import QtQuick 2.0 import QtQuick.Particles 2.0 +/** + Component which generates rainbow like particles + */ Item { id: rainbowTail + /// Angle of particles, determinates angle of their trajectory property real generationAngle: 0 + /// Size of particle in pixels property size particleSize: Qt.size(10, 10) + /// Reference to root item, so particle could not be affected by any relatice coord system property Item rootItem + /// Reference to item to which particle emitters attached property Item containerItem clip: false diff --git a/src/calculator/ui/easteregg/entities/Player.qml b/src/calculator/ui/easteregg/entities/Player.qml index b38b858..837fc7a 100644 --- a/src/calculator/ui/easteregg/entities/Player.qml +++ b/src/calculator/ui/easteregg/entities/Player.qml @@ -2,14 +2,22 @@ import QtQuick 2.0 import "../logic/extendedmath.js" as EMath import "../logic/collision" as Collision +/** + This entitiy is used as player(rebound area) + */ Collision.BoxCollider { id: component + /// Position to which player will move property real wantedPosition: 0 + /// Step of movement in pixels per frame property real step color: "orange" + /** + Set Player new position, need to be called every frame + */ function frameMove() { var posDifference = wantedPosition - component.y - component.height / 2 diff --git a/src/calculator/ui/easteregg/entities/Wall.qml b/src/calculator/ui/easteregg/entities/Wall.qml index e68719d..aa175c3 100644 --- a/src/calculator/ui/easteregg/entities/Wall.qml +++ b/src/calculator/ui/easteregg/entities/Wall.qml @@ -3,6 +3,9 @@ import QtQuick.Controls 2.0 import Sides 1.0 import "../logic/collision" as Collision +/** + This entity works as rebound area, but is static + */ Collision.BoxCollider { id: component diff --git a/src/calculator/ui/easteregg/entities/WallSystem.qml b/src/calculator/ui/easteregg/entities/WallSystem.qml index 64917fe..8fc8622 100644 --- a/src/calculator/ui/easteregg/entities/WallSystem.qml +++ b/src/calculator/ui/easteregg/entities/WallSystem.qml @@ -2,13 +2,23 @@ import QtQuick 2.0 import Sides 1.0 import "../logic/collision" +/** + System of walls to border all playground edges + */ Item { id: component + /** + Emits collision with some wall in system + @param side Side of collision related to wall + */ signal voidCollided(int side) + /// Reference to global CollisionSystem to determinate collision property CollisionSystem collisionSystem + /// Width of walls in pixels property real wallWidth: 10 + /// Color of walls property color wallColor Wall { diff --git a/src/calculator/ui/easteregg/logic/collision/BoxCollider.qml b/src/calculator/ui/easteregg/logic/collision/BoxCollider.qml index 0aebc45..f8adf7b 100644 --- a/src/calculator/ui/easteregg/logic/collision/BoxCollider.qml +++ b/src/calculator/ui/easteregg/logic/collision/BoxCollider.qml @@ -1,9 +1,18 @@ import QtQuick 2.0 +/** + Base type for Entity + */ Rectangle { id: component + /** + Emits after collision with other BoxCollider + @param obj Object with which current item collided + @param side Side of collision relative to current item + */ signal collided(var obj, int side) + /// Defines whether is BoxCollider as void border property bool isVoid: false } diff --git a/src/calculator/ui/easteregg/logic/collision/CollisionSystem.qml b/src/calculator/ui/easteregg/logic/collision/CollisionSystem.qml index 9482cb3..ce2996f 100644 --- a/src/calculator/ui/easteregg/logic/collision/CollisionSystem.qml +++ b/src/calculator/ui/easteregg/logic/collision/CollisionSystem.qml @@ -2,9 +2,18 @@ import QtQuick 2.0 import Sides 1.0 import "../extendedmath.js" as EMath +/** + System to determinate collision of registered objects + */ QtObject { id: system + /** + Checks whether collision between two objects happened + @param bc1 First object + @param bc2 Second object + @return Side of collision referenced to first object, if collision did not happened return -1 + */ function checkCollision(bc1, bc2) { var r1 = bc1.mapToItem(null, 0, 0, bc1.width, bc1.height) var r2 = bc2.mapToItem(null, 0, 0, bc2.width, bc2.height) @@ -34,6 +43,11 @@ QtObject { return Sides.Right } + /** + Checks for collisions and if collision occure then emits signals of objects + @param bc1 First object + @param bc2 Second object + */ function watchCollision(bc1, bc2) { var result = system.checkCollision(bc1, bc2) @@ -43,6 +57,11 @@ QtObject { } } + /** + Register objects to system + @param bc1 First object + @param bc2 Second object + */ function registerPair(bc1, bc2) { bc1.xChanged.connect((function() {system.watchCollision(bc1, bc2)})) bc1.yChanged.connect((function() {system.watchCollision(bc1, bc2)})) diff --git a/src/calculator/ui/easteregg/logic/extendedmath.js b/src/calculator/ui/easteregg/logic/extendedmath.js index 119adce..953d893 100644 --- a/src/calculator/ui/easteregg/logic/extendedmath.js +++ b/src/calculator/ui/easteregg/logic/extendedmath.js @@ -1,17 +1,37 @@ .pragma library +/** + Return sign of number as positive or negative 1, if number is zero then it returns 0 + @param number Number which sign will be returned + @return Return -1 or 1 in case if number is not zero else returns 0 + */ function sgn(number) { if(number === 0) return 0 return Math.abs(number) / number } +/** + Checks whether is in interval + @param number Number which will be tested + @param start Bottom border of interval + @param end Top border of interval + @return True if number is in interval else returns false + */ function numberInInterval(number, start, end) { if(number >= start && number <= end) return true return false } +/** + Checks whether two intervals intersects + @param start1 Bottom border of first interval + @param end1 Top border of first interval + @param start2 Bottom border of second interval + @param end2 Top border of second interval + @return True if intervals intersects else returns false + */ function intervalHasIntersection(start1, end1, start2, end2) { if(numberInInterval(start1, start2, end2) || numberInInterval(end1, start2, end2)) return true diff --git a/src/calculator/ui/easteregg/visualization/Score.qml b/src/calculator/ui/easteregg/visualization/Score.qml index 48ebdf3..8d7f2dc 100644 --- a/src/calculator/ui/easteregg/visualization/Score.qml +++ b/src/calculator/ui/easteregg/visualization/Score.qml @@ -1,10 +1,16 @@ import QtQuick 2.0 +/** + Display score in format X:Y + */ Item { id: component + /// Score of player property int playerScore: 0 + /// Score of other player property int enemyScore: 0 + /// Color of score text property alias textColor: text.color Text { diff --git a/src/calculator/ui/qml/containers/FunctionsPanel.qml b/src/calculator/ui/qml/containers/FunctionsPanel.qml index 8fbaa67..0d095eb 100644 --- a/src/calculator/ui/qml/containers/FunctionsPanel.qml +++ b/src/calculator/ui/qml/containers/FunctionsPanel.qml @@ -1,15 +1,24 @@ import QtQuick 2.0 import "../controls" as Control +/** + Panel of function buttons + */ Item { id: component + /// Emits after clicking on function button, so function identifier could be expanded signal expandRequest(string func) + /// List of functions to be delegated property var items + /// Number of columns in which will be button displayed property alias columns: grid.columns + /// Background color of buttons property color backgroundColor + /// Text color of labels on buttons property color textColor + /// Text color of labels on buttons, when hovered property color hoverTextColor Grid { diff --git a/src/calculator/ui/qml/containers/Stack.qml b/src/calculator/ui/qml/containers/Stack.qml index 6ffff8b..2010e3e 100644 --- a/src/calculator/ui/qml/containers/Stack.qml +++ b/src/calculator/ui/qml/containers/Stack.qml @@ -1,5 +1,8 @@ import QtQuick 2.0 +/** + Container item with addition and move animation + */ Column { id: component diff --git a/src/calculator/ui/qml/containers/VariablesPanel.qml b/src/calculator/ui/qml/containers/VariablesPanel.qml index a28e043..aa906b2 100644 --- a/src/calculator/ui/qml/containers/VariablesPanel.qml +++ b/src/calculator/ui/qml/containers/VariablesPanel.qml @@ -2,33 +2,70 @@ import QtQuick 2.0 import "../managers" import "../visualization" +/** + Interactive panel which display all variables with their attributes + */ Item { id: component + /** + Emit after clicking on set button of variable + @param identifier Identifier of variable + @param value Value requested to be set to value + */ signal setVariableRequest(string identifier, real value) + /** + Emits after clicking on delete button of variable + @param identifier Identifier of variable + */ signal deleteVariableRequest(string identifier) + /** + Emits after clicking on certain part, it requests to some string to be expanded into user input + @param data String which is requested to expansion + */ signal expandRequest(string data) + /** + Emits after clicking on certain part, it requests to some string to overwrite user input + @param data String which is requested to overwrite + */ signal overwriteRequest(string data) + /// Height if single variable item property real itemHeight: 0 + /// Text color of expression and value of variable property color textColor + /// Text color of identifier and value of variable property color identifierTextColor + /// Background color of single variable panel property color color: backgroundColor + /// Background of panel property alias backgroundColor: background.color + /// Color of scrollbar property alias scrollBarColor: scrollBar.color + /// Background color of expression while hovering property color expressionHoverColor + /// Bakcground color of Ans variable property alias ansColor: ansItem.color + /// Text color of identifier and value of Ans variable property alias ansTextColor: ansItem.textColor + /// Text color of identifier and value of Ans variable property alias ansIdentifierTextColor: ansItem.identifierTextColor + /// Background color of Ans variable expression while hovering property alias ansExpressionHoverColor: ansItem.expressionHoverColor + /// Background color of dots(area to slide variable options) property color dotsBackgroundColor + /// Background color of variable remove button property color removeButtonColor + /// Background color of variable setters property color settersColor + /// Background color of variable setters when hovering property color settersHoveredColor + /// Text color of variable setters property color settersTextColor + /// Font of panel property font font clip: true @@ -122,6 +159,9 @@ Item { } } + /** + Create once component to be able later instance that component + */ function _initComponent() { var itemComponent = Qt.createComponent("qrc:/qml/item/VariableItem.qml") manager.itemComponent = itemComponent @@ -132,6 +172,12 @@ Item { }); } + /** + Use create or modify variable according to if variable exists + @param identifier Identifier of variable + @param expression Expression of variable + @param value Value of variable + */ function handleVariableAction(identifier, expression, value) { if(manager.findVariable(identifier) === null) manager.addVariable(identifier, expression, value) @@ -139,11 +185,23 @@ Item { manager.setVariable(identifier, expression, value) } + /** + Create new variable in panel + @param identifier Identifier of variable + @param expression Expression of variable + @param value Value of variable + */ function createVariable(identifier, expression, value) { manager.addVariable(identifier, expression, value) } + /** + Modifies variable(set expression or value) + @param identifier Identifier of variable + @param expression New expression of variable + @param value New value of variable + */ function modifyVariable(identifier, expression, value) { manager.setVariable(identifier, expression, value) } diff --git a/src/calculator/ui/qml/controls/CalculateButton.qml b/src/calculator/ui/qml/controls/CalculateButton.qml index e4192fd..9ce560e 100644 --- a/src/calculator/ui/qml/controls/CalculateButton.qml +++ b/src/calculator/ui/qml/controls/CalculateButton.qml @@ -1,6 +1,10 @@ import QtQuick 2.0 +/** + Button to confirm calculation + */ Clickable { + /// Bakcground color of button property alias color: background.color Rectangle { diff --git a/src/calculator/ui/qml/controls/Clickable.qml b/src/calculator/ui/qml/controls/Clickable.qml index 02f7cfa..148897c 100644 --- a/src/calculator/ui/qml/controls/Clickable.qml +++ b/src/calculator/ui/qml/controls/Clickable.qml @@ -1,13 +1,28 @@ import QtQuick 2.0 +/** + Base component for all mouse interactive components + */ Item { id: component + /** + Emits when clicked on component + @param pos Position of click + */ signal clicked(point pos) + /** + Emits when mouse entered area of component + */ signal entered() + /** + Emits when mouse leaved area of component + */ signal exited() + /// If set to true hover is enabled else disable hover property alias hoverEnabled: mouseArea.hoverEnabled + /// Holds whether component is hovered readonly property alias hovered: mouseArea.containsMouse MouseArea { diff --git a/src/calculator/ui/qml/controls/Completer.qml b/src/calculator/ui/qml/controls/Completer.qml index 26d5152..0534436 100644 --- a/src/calculator/ui/qml/controls/Completer.qml +++ b/src/calculator/ui/qml/controls/Completer.qml @@ -2,14 +2,23 @@ import QtQuick 2.0 import QtQuick.Controls 2.0 import StyleSettings 1.0 +/** + Component which offer suggestion to text inputs + */ DropDown { id: component + /// Background color of completer property color color + /// List of suggestions property var constantModel + /// Reference to targeted text input property var target + /// Holds current word, so only matching words could be suggested property string currentText + /// Background color of suggestion item when hovered property color hoverColor + /// Text color of suggestions property color textColor scrollbarWidth: 3 @@ -84,6 +93,10 @@ DropDown { component.target.Keys.downPressed.connect(component.moveDown) } + /** + Manage completer actions based on key pressed. Cannot steal key event from text input, so connect it to target event + @param event Passed key event + */ function handleOtherKeys(event) { if(event.key == Qt.Key_Space && (event.modifiers & Qt.ControlModifier)) { component.show() diff --git a/src/calculator/ui/qml/controls/DefaultButton.qml b/src/calculator/ui/qml/controls/DefaultButton.qml index d146fab..5bd6ca8 100644 --- a/src/calculator/ui/qml/controls/DefaultButton.qml +++ b/src/calculator/ui/qml/controls/DefaultButton.qml @@ -1,11 +1,18 @@ import QtQuick 2.0 +/** + Regular styled button + */ Clickable { id: component + /// Color of button property color color + /// Color of background which is behind button property color backgroundColor + /// Label of button property alias text: buttonText.text + /// Font of button property font font hoverEnabled: true diff --git a/src/calculator/ui/qml/controls/DropDown.qml b/src/calculator/ui/qml/controls/DropDown.qml index 0062067..50106f1 100644 --- a/src/calculator/ui/qml/controls/DropDown.qml +++ b/src/calculator/ui/qml/controls/DropDown.qml @@ -3,29 +3,66 @@ import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Window 2.0 +/** + Base dropdown menu, to select one item + */ Item { id: component + /** + Used as function to hide dropdown + */ signal hide() + /** + Used as function to show dropdown + */ signal show() + /** + Used as function to move upward item selection of item + */ signal moveUp() + /** + Used as function to move downward item selection of item + */ signal moveDown() + /** + Used as function to choose selected item + */ signal chooseCurrent() + /** + Emit when item was chosen + */ signal itemChoosed() + /** + Signal to start show animation + */ signal showAnimation() + /** + Signal to start hide animation + */ signal hideAnimation() + /// Holds chosen item property var currentItem: model[0] + /// Holds seleted item index property int currentItemIndex: 0 + /// Number of visible items without scroll property int visibleItemCount: 4 + /// Color of scrollbar property alias scrollBarColor: scrollbar.color + /// Width of scrollbar property alias scrollbarWidth: scrollbar.width + /// Holds whether component is shown readonly property bool dropMenuVisible: dropMenu.visible + /// Height of single selection item property int itemHeight + /// List of labels to be generation into items in dropdown property var model + /// Component which will be loaded to represent background property Component dropDownMenuBackground + /// Component which will be loaded to represent single dropdown item property Component menuItem clip: true diff --git a/src/calculator/ui/qml/controls/ExpressionInput.qml b/src/calculator/ui/qml/controls/ExpressionInput.qml index a4d85f2..f416ba7 100644 --- a/src/calculator/ui/qml/controls/ExpressionInput.qml +++ b/src/calculator/ui/qml/controls/ExpressionInput.qml @@ -2,10 +2,15 @@ import QtQuick 2.0 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 +/** + Specialized input for calculation expression + */ TextArea { id: expInput + /// Emits after expression was confirmed with enter key signal confirmed() + /// Text color of placeholder property alias placeholderTextColor: placeholderText.color focus: true diff --git a/src/calculator/ui/qml/controls/TextButton.qml b/src/calculator/ui/qml/controls/TextButton.qml index 19ed67b..a395585 100644 --- a/src/calculator/ui/qml/controls/TextButton.qml +++ b/src/calculator/ui/qml/controls/TextButton.qml @@ -1,12 +1,19 @@ import QtQuick 2.0 import "../controls" as Controls +/** + Button with label and simple animation + */ Controls.Clickable { id: component + /// Label of button property alias buttonText: innerText.text; + /// Background color property color color: "black" + /// Text color property color textColor: "black" + /// text color when hovered property color hoverTextColor: "white" hoverEnabled: true diff --git a/src/calculator/ui/qml/controls/VariableSetButton.qml b/src/calculator/ui/qml/controls/VariableSetButton.qml index 57faa02..e86252e 100644 --- a/src/calculator/ui/qml/controls/VariableSetButton.qml +++ b/src/calculator/ui/qml/controls/VariableSetButton.qml @@ -1,13 +1,21 @@ import QtQuick 2.0 import "../controls" as Controls +/** + Specialized button to set value to variable + */ Controls.Clickable { id: component + /// Value which will be set to variable property int value: 0 + /// Background color property color color + /// Background color when hovered property color hoverColor + /// Text color property color textColor + /// Font of text property font font hoverEnabled: true diff --git a/src/calculator/ui/qml/item/VariableItem.qml b/src/calculator/ui/qml/item/VariableItem.qml index 9969c75..308d5f1 100644 --- a/src/calculator/ui/qml/item/VariableItem.qml +++ b/src/calculator/ui/qml/item/VariableItem.qml @@ -3,9 +3,14 @@ import QtQuick 2.0 import "../visualization" as Visualization import "../menu" as Menu +/** + Single item which represents variable + */ Item { id: component + /** + */ signal valueSetRequest(string identifier, int value) signal deleteRequest(string identifier) signal expandRequest(string data) From 10168d174639ebefa244807658709681bca75383 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 22 Mar 2017 23:04:51 +0100 Subject: [PATCH 016/121] Finish documentation --- src/calculator/ui/qml/item/VariableItem.qml | 28 +++++++++++ src/calculator/ui/qml/loaders/FontsLoader.qml | 3 ++ src/calculator/ui/qml/main.qml | 23 ++++++++- .../ui/qml/managers/VariablesManager.qml | 50 +++++++++++++++++++ .../ui/qml/menu/VariableOptions.qml | 18 +++++++ .../ui/qml/visualization/AnimatedText.qml | 6 +++ .../ui/qml/visualization/CountDown.qml | 13 +++++ src/calculator/ui/qml/visualization/PopUp.qml | 27 +++++++++- .../ui/qml/visualization/ResultDisplay.qml | 6 +++ .../qml/visualization/ResultSystemDisplay.qml | 9 ++++ .../ui/qml/visualization/VariableDisplay.qml | 15 ++++++ 11 files changed, 196 insertions(+), 2 deletions(-) diff --git a/src/calculator/ui/qml/item/VariableItem.qml b/src/calculator/ui/qml/item/VariableItem.qml index 308d5f1..5b1bf77 100644 --- a/src/calculator/ui/qml/item/VariableItem.qml +++ b/src/calculator/ui/qml/item/VariableItem.qml @@ -10,25 +10,53 @@ Item { id: component /** + Emits to request variable set value + @param idendifier Identifier of variable + @param value Reqested new value of variable */ signal valueSetRequest(string identifier, int value) + /** + Emits to request variable deletion + @param idendifier Identifier of variable + */ signal deleteRequest(string identifier) + /** + Emits request to expand text by variable data + @param data String requested to be expanded + */ signal expandRequest(string data) + /** + Emits request to overwrite text by variable data + @param data String requested to be expanded + */ signal overwriteRequest(string data) + /// Background color property alias color: content.color + /// Text color property alias textColor: content.textColor + /// Text color of identifier property alias identifierTextColor: content.identifierTextColor + /// Variable identifier property alias variableIdentifier: content.variableIdentifier + /// Variable expression property alias variableExpression: content.variableExpression + /// Background color of expression when hovered property alias expressionHoverColor: content.expressionHoverColor + /// Variable value property alias variableValue: content.variableValue + /// Used font property alias font: content.font + /// Background color of dots(area to slide variable options) property alias dotsBackgroundColor: optionsMenu.dotsBackgroundColor + /// Background color of variable remove button property alias removeButtonColor: optionsMenu.removeButtonColor + /// Background color of variable setters property alias settersColor: optionsMenu.settersColor + /// Background color of variable setters when hovering property alias settersHoveredColor: optionsMenu.settersHoveredColor + /// Text color of variable setters property alias settersTextColor: optionsMenu.settersTextColor diff --git a/src/calculator/ui/qml/loaders/FontsLoader.qml b/src/calculator/ui/qml/loaders/FontsLoader.qml index 0795931..44ccb53 100644 --- a/src/calculator/ui/qml/loaders/FontsLoader.qml +++ b/src/calculator/ui/qml/loaders/FontsLoader.qml @@ -1,5 +1,8 @@ import QtQuick 2.0 +/** + Component which loads all fonts + */ Item { // loading font FontLoader { diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index 438a6fb..27f6f1d 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -19,6 +19,8 @@ ApplicationWindow { id: mainWindow width: 1101 + height: width * (522 / 1101) + minimumHeight: width * (522 / 1101) maximumHeight: minimumHeight @@ -161,7 +163,7 @@ ApplicationWindow { value: 0 bases: { "DEC": 10, "BIN": 2, "HEX": 16, "OCT": 8 } - height: 100 + height: parent.height / 5.1 baseTextColor: StyleSettings.resultSystemDisplay.baseTextColor valueTextColor: StyleSettings.resultSystemDisplay.valueTextColor font: StyleSettings.resultSystemDisplay.font @@ -265,6 +267,10 @@ ApplicationWindow { } } + /** + According to cursor in text it determinates current word which is edited + @return Current word + */ function currentWord() { var result = "" var startIndex = expInput.cursorPosition - 1 @@ -290,6 +296,9 @@ ApplicationWindow { return result } + /** + Show suggestion box with filtered suggestions + */ function completeText() { var lastChar = expInput.text.slice(-1) if(Calculator.expressionSplitters.indexOf(lastChar) != -1) @@ -303,10 +312,18 @@ ApplicationWindow { completer.currentTextChanged(completer.currentText) } + /** + Overwrite current expression by new expression + @param newExpression New expression + */ function overwriteExpression(newExpression) { expInput.text = newExpression } + /** + Expand expression into current expresion + @param expressionKey Key of builtin expression or dynamic expression + */ function expandExpression(expansionKey) { var expansionData = Calculator.expressionsExpansion[expansionKey] var selectedStart = expInput.selectionStart @@ -327,6 +344,10 @@ ApplicationWindow { expInput.insert(selectedStart, expansion) } + /** + After calculation handles variable and display synchronization + @param data Data with result and variables + */ function handleResult(data) { if(typeof data["result"] !== "undefined") { resultDisplay.result = data["result"] diff --git a/src/calculator/ui/qml/managers/VariablesManager.qml b/src/calculator/ui/qml/managers/VariablesManager.qml index ecfb9bf..87cbd48 100644 --- a/src/calculator/ui/qml/managers/VariablesManager.qml +++ b/src/calculator/ui/qml/managers/VariablesManager.qml @@ -1,20 +1,49 @@ import QtQuick 2.0 +/** + Manager of dynamic manipulation with Variable items + */ QtObject { id: manager + /** + Emit after creation of new object + @param object Reference to new object + */ signal newItem(var object) + /** + Emit after deletion of new object + @param identifier Identifier of deleted variable + */ signal deleteItem(string identifier) + /** + Emit after variable set, so other system could synchronize + @param identifer Identifier of variable + @param value New value of variable + */ signal setItem(string identifier, real value) + /// Component from which objects will be created property var itemComponent + /// Parent of new objects property Item componentsParent + /// List of existing objects property var _variableItems: [] + /** + Register item in system + @param variableItem Item to be registered + */ function registerVariable(variableItem) { manager._variableItems.push(variableItem) } + /** + Create and register new variable item according to data + @param identifier Identifier of variable + @param expression Expression of variable + @param value Value of variable + */ function addVariable(identifier, expression, value) { var object = manager.itemComponent.createObject(manager.componentsParent, { "variableIdentifier": identifier, @@ -28,6 +57,12 @@ QtObject { manager.newItem(object) } + /** + Sets value or expression to variable item + @param identifier Identifier of variable + @param expression New expression of variable + @param value New value of variable + */ function setVariable(identifier, expression, value) { var variable = manager.findVariable(identifier) if(variable == null) { @@ -39,6 +74,11 @@ QtObject { variable.variableValue = value } + /** + Finds variable item in system + @param variableIdentifier Identifier of variable + @return Item if is found else return null + */ function findVariable(variableIndetifier) { var object @@ -53,6 +93,10 @@ QtObject { return null } + /** + Delete variable item + @param variableIdentifier Identifier of variable + */ function handleDeleteRequest(variableIndetifier) { var object = manager.findVariable(variableIndetifier) @@ -60,6 +104,12 @@ QtObject { manager.deleteItem(variableIndetifier) } + /** + Sets value or expression to variable item and emit change + @param identifier Identifier of variable + @param expression New expression of variable + @param value New value of variable + */ function handleSetRequest(variableIndetifier, value) { var object = manager.findVariable(variableIndetifier) diff --git a/src/calculator/ui/qml/menu/VariableOptions.qml b/src/calculator/ui/qml/menu/VariableOptions.qml index 3a61483..144f7df 100644 --- a/src/calculator/ui/qml/menu/VariableOptions.qml +++ b/src/calculator/ui/qml/menu/VariableOptions.qml @@ -1,19 +1,37 @@ import QtQuick 2.0 import "../controls" as Controls +/** + Menu with variable actions + */ Item { id: component + /** + Emits after clicking on setter + @param value Value requested to be set + */ signal valueSetRequest(int value) + /** + Emits after clicking on delete button + */ signal deleteRequest() + /// Holds current width of menu(dynamically changes) readonly property alias menuWidth: dots.width + /// Sets true if menu is running show animation readonly property alias animationRunning: xAnimation.running + /// Background color of dots(area to slide up menu) property alias dotsBackgroundColor: dotsBackground.color + /// Background color of remove button property alias removeButtonColor: removeButtonBackground.color + /// Background color of setters property color settersColor + /// Background color of setters when hovered property color settersHoveredColor + /// Text color of setters property color settersTextColor + /// Font of component property font font state: "hidden" diff --git a/src/calculator/ui/qml/visualization/AnimatedText.qml b/src/calculator/ui/qml/visualization/AnimatedText.qml index ce09448..8af8ebc 100644 --- a/src/calculator/ui/qml/visualization/AnimatedText.qml +++ b/src/calculator/ui/qml/visualization/AnimatedText.qml @@ -1,10 +1,16 @@ import QtQuick 2.7 +/** + Animate change of text + */ Item { id: component + /// Text to be displayed property string text: "" + /// Font of text property font font: Qt.font() + /// Text color property color color: "black" width: baseText.width diff --git a/src/calculator/ui/qml/visualization/CountDown.qml b/src/calculator/ui/qml/visualization/CountDown.qml index 8a770bc..2c9d199 100644 --- a/src/calculator/ui/qml/visualization/CountDown.qml +++ b/src/calculator/ui/qml/visualization/CountDown.qml @@ -1,13 +1,26 @@ import QtQuick 2.0 +/** + Simple countdown display only number of remaining time + */ Item { id: component + /** + Start countdown and display component + @param seconds Count of seconds to be counted down + */ signal start(int seconds) + /** + Emits after time runs out + */ signal triggered() + /// Holds current remaining time in seconds readonly property alias count: timer.count + /// Text color of numbers property alias color: text.color + /// Font of text property font font focus: visible diff --git a/src/calculator/ui/qml/visualization/PopUp.qml b/src/calculator/ui/qml/visualization/PopUp.qml index 4a8746b..1008ef5 100644 --- a/src/calculator/ui/qml/visualization/PopUp.qml +++ b/src/calculator/ui/qml/visualization/PopUp.qml @@ -1,20 +1,45 @@ import QtQuick 2.0 import "../controls" as Controls +/** + Base for modal PopUp dialogs + */ Item { id: component + /** + Used as function to hide component + */ signal hide() + /** + Emits after component is hidden + */ signal hidden() + /** + Used as function to show component + @param msg Message to be displayed + */ signal show(string msg) - signal hideAnimation() + /** + Signal to start show animation + */ signal showAnimation() + /** + Signal to start hide animation + */ + signal hideAnimation() + /// Background color of modal property alias maskColor: mask.color + /// Background color of dialog property alias dialogColor: dialog.color + /// Text color property alias textColor: title.color + /// Text to be displayed in dialog property alias message: message.text + /// Title of dialog property alias title: title.text + /// Used font property font font visible: false diff --git a/src/calculator/ui/qml/visualization/ResultDisplay.qml b/src/calculator/ui/qml/visualization/ResultDisplay.qml index 17369d9..657da14 100644 --- a/src/calculator/ui/qml/visualization/ResultDisplay.qml +++ b/src/calculator/ui/qml/visualization/ResultDisplay.qml @@ -1,10 +1,16 @@ import QtQuick 2.0 +/** + Specialized component to show calculation result + */ Rectangle { id: component + /// Result to be displayed property alias result: text.text + /// Text color property alias textColor: text.color + /// Used font property font font AnimatedText { diff --git a/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml b/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml index 22aedd0..5e3f65e 100644 --- a/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml +++ b/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml @@ -1,14 +1,23 @@ import QtQuick 2.7 import Calculator 1.0 +/** + Display result of calculation in different bases + */ Rectangle { id: component + /// List of bases to be displayed property var bases + /// Value which will be converted to different bases property real value: 0 + /// Used font property font font + /// Text color of base name property color baseTextColor + /// Text color of converted value property color valueTextColor + /// Margin of text readonly property int margin: 10 Column { diff --git a/src/calculator/ui/qml/visualization/VariableDisplay.qml b/src/calculator/ui/qml/visualization/VariableDisplay.qml index a611afc..3c306af 100644 --- a/src/calculator/ui/qml/visualization/VariableDisplay.qml +++ b/src/calculator/ui/qml/visualization/VariableDisplay.qml @@ -3,15 +3,30 @@ import QtQuick 2.0 Rectangle { id: component + /** + Emits request to expand text by variable data + @param data String requested to be expanded + */ signal expandRequest(string data) + /** + Emits request to overwrite text by variable data + @param data String requested to be expanded + */ signal overwriteRequest(string data) + /// Text color property color textColor + /// Text color of identifier property color identifierTextColor + /// Background color of expression when hovered property color expressionHoverColor + /// Variable identifier property string variableIdentifier: "" + /// Variable expression property string variableExpression: "" + /// Variable value property string variableValue: "0" + /// Used font property font font QtObject { From 1c7eb2cd58c1f52b9558a733d279f809f848a62f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Thu, 23 Mar 2017 08:28:10 +0100 Subject: [PATCH 017/121] Improves in setup.py, main.py and whole installation process --- MANIFEST | 3 - MANIFEST.in | 3 + setup.py | 30 +++----- src/Makefile | 63 ++++++++-------- src/calculator/__init__.py | 3 + src/calculator/{typing.py => _typing.py} | 0 src/calculator/core/calculator/calculator.py | 2 +- src/calculator/core/math/math.py | 4 +- src/calculator/core/solver/solver.py | 5 +- src/calculator/main.py | 73 +++++++++++++++++-- src/calculator/standard_deviation.py | 2 +- src/calculator/ui/adapter.py | 2 +- src/calculator/ui/qml.qrc | 4 +- src/calculator/{ => ui}/translations/cs.qm | Bin src/calculator/{ => ui}/translations/cs.ts | 0 src/calculator/utils/number_formatter.py | 2 +- 16 files changed, 126 insertions(+), 70 deletions(-) delete mode 100644 MANIFEST create mode 100644 MANIFEST.in rename src/calculator/{typing.py => _typing.py} (100%) rename src/calculator/{ => ui}/translations/cs.qm (100%) rename src/calculator/{ => ui}/translations/cs.ts (100%) diff --git a/MANIFEST b/MANIFEST deleted file mode 100644 index aab08b1..0000000 --- a/MANIFEST +++ /dev/null @@ -1,3 +0,0 @@ -recursive-include src/calculator/ui/ *.qml -src/Makefile -README.md \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..114782b --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +graft src/calculator/ui +include src/Makefile +include README.md \ No newline at end of file diff --git a/setup.py b/setup.py index cff4dbb..e7c4776 100644 --- a/setup.py +++ b/setup.py @@ -2,32 +2,24 @@ from distutils import core -from distutils.command.install import install -from unittest.mock import patch +from os.path import abspath, dirname, join from setuptools import find_packages +base_path = abspath(dirname(__file__)) + def install_requires(): - with open('requirements.txt') as f: + with open(join(base_path, 'requirements.txt')) as f: return [line.strip().split('#')[0] for line in f.readlines() if line.strip()] -class CalculatorInstallation(install): - def run(self): - super(CalculatorInstallation, self).run() - - with patch('sys.argv', ['', '-o', 'src/calculator/ui/resources.py', 'src/calculator/ui/qml.qrc']): - from PyQt5.pyrcc_main import main - main() - - def setup(): core.setup( name='calculator', - version='0.1', + version='0.2', license='GNU GENERAL PUBLIC LICENSE Version 3', - # long_description=open('./README.md').read(), + long_description=open(join(base_path, './README.md')).read(), url='https://github.com/thejoeejoee/IVS-VUT-BIT-2016-2017', classifiers=[ 'Development Status :: 3 - Alpha', @@ -47,17 +39,17 @@ def setup(): install_requires=install_requires() + [ 'PyOpenGL #;"Debian" in platform_version or "Ubuntu" in platform_version' ], - package_dir={'': 'src'}, + package_dir={'': 'src/'}, + # package_data={ + # '': ['**/*.{file_type}'.format(file_type=file_type) for file_type in file_types] + # }, entry_points={ 'console_scripts': [ 'calculator-console=calculator.console:main', 'calculator-app=calculator.main:main', ] }, - # include_package_data=True, - cmdclass=dict( - install=CalculatorInstallation - ), + include_package_data=True, test_suite='tests', ) diff --git a/src/Makefile b/src/Makefile index 18b56e3..7f3ee2a 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,41 +1,46 @@ - REQUIREMENTS=../requirements.txt -VENV=true - -ifdef VENV - VENV=../.venv - VENV_ACTIVATE=$(VENV)/bin/activate - PYTHON="$(VENV)/bin/python" - PIP="$(VENV)/bin/pip" - PYRCC5="$(VENV)/bin/pyrcc5" +ifdef ENVIROMENT + ENVIROMENT:=$(shell echo $(ENVIROMENT) | tr a-z A-Z) else - PYTHON="python3" - PIP="pip3" - PYRCC5="pyrcc5" + ENVIROMENT=$(shell if [ "${VIRTUAL_ENV}" != "" ] > /dev/null; then echo LOCAL; else echo GLOBAL; fi) endif +GLOBAL_PYTHON=python3 +GLOBAL_PIP=pip3 +GLOBAL_PYRCC5=pyrcc5 -install: install-python venv install-calculator +VENV_PATH=../.venv +LOCAL_PYTHON=$(VENV_PATH)/bin/$(GLOBAL_PYTHON) +LOCAL_PIP=$(VENV_PATH)/bin/$(GLOBAL_PIP) +LOCAL_PYRCC5=$(VENV_PATH)/bin/$(GLOBAL_PYRCC5) -install-python: - apt-get update && apt-get -y install python3.5 python3-pip python3-venv && pip3 install --upgrade pip +PYTHON=$(ENVIROMENT)_PYTHON -install-calculator: - $(PYTHON) ../setup.py install +all: run -compile-qt-rcc: venv - $(PYRCC5) -o calculator/ui/resources.py calculator/ui/qml.qrc +LOCAL: + @touch $(VENV_PATH) || pyvenv $(VENV_PATH) + $(info ***** Using LOCAL enviroment with python $(abspath $($(PYTHON))). *****) -test: venv - $(PYTHON) test.py || true +GLOBAL: + $(info ***** Using SYSTEM enviroment with python $(shell which $($(PYTHON))). *****) -run: venv compile-qt-rcc - $(PYTHON) calculator/main.py +# .PHONY: install $(ENVIROMENT) install-python install-calculator test run -venv: $(REQUIREMENTS) -ifdef VENV - test -d $(VENV) || pyvenv $(VENV) - $(PIP) install -Ur ../requirements.txt - touch $(VENV_ACTIVATE) -endif +install: install-python $(ENVIROMENT) install-calculator + +install-python: + @if ! dpkg -s python3.5 > /dev/null && dpkg -s python3-pip > /dev/null; then\ + apt-get update && apt-get -y install python3.5 python3-pip python3-venv;\ + fi + pip3 install --upgrade pip > /dev/null + +install-calculator: $(ENVIROMENT) + $($(PYTHON)) ../setup.py install + +test: $(ENVIROMENT) + $($(PYTHON)) test.py || true + +run: $(ENVIROMENT) + $($(PYTHON)) calculator/main.py diff --git a/src/calculator/__init__.py b/src/calculator/__init__.py index 9bad579..2f06d87 100644 --- a/src/calculator/__init__.py +++ b/src/calculator/__init__.py @@ -1 +1,4 @@ # coding=utf-8 + +# noinspection PyProtectedMember +from calculator._typing import * diff --git a/src/calculator/typing.py b/src/calculator/_typing.py similarity index 100% rename from src/calculator/typing.py rename to src/calculator/_typing.py diff --git a/src/calculator/core/calculator/calculator.py b/src/calculator/core/calculator/calculator.py index f950ba1..825031f 100644 --- a/src/calculator/core/calculator/calculator.py +++ b/src/calculator/core/calculator/calculator.py @@ -4,7 +4,7 @@ from calculator.core.solver.solver import Solver from calculator.exceptions import VariableError, VariableRemoveRestrictError -from calculator.typing import NumericValue, Variable +from calculator import NumericValue, Variable from calculator.utils import OrderedDefaultDict diff --git a/src/calculator/core/math/math.py b/src/calculator/core/math/math.py index 8b0a818..cc83475 100644 --- a/src/calculator/core/math/math.py +++ b/src/calculator/core/math/math.py @@ -4,8 +4,8 @@ import random from calculator.exceptions import MathError -from calculator.typing import BinaryNumericFunction -from calculator.typing import NumericValue +from calculator._typing import BinaryNumericFunction +from calculator._typing import NumericValue class Math(object): diff --git a/src/calculator/core/solver/solver.py b/src/calculator/core/solver/solver.py index 0864ce9..e825f56 100644 --- a/src/calculator/core/solver/solver.py +++ b/src/calculator/core/solver/solver.py @@ -2,13 +2,10 @@ from ast import BinOp, Add, Num, Sub, Div, Mult, Call, AST, UnaryOp, USub, Name, Pow, FloorDiv, Mod from typing import Dict, Union, Type, Set, Optional +from calculator import BinaryNumericFunction, NumericFunction, NumericValue, Variable from calculator.core.math import Math from calculator.core.parser import Parser from calculator.settings import BuiltinFunction -from calculator.typing import BinaryNumericFunction -from calculator.typing import NumericFunction -from calculator.typing import NumericValue -from calculator.typing import Variable from calculator.utils import method_single_dispatch diff --git a/src/calculator/main.py b/src/calculator/main.py index 5a24cf3..9e63eaf 100755 --- a/src/calculator/main.py +++ b/src/calculator/main.py @@ -3,24 +3,81 @@ import sys from fileinput import input +from glob import iglob +from itertools import chain +from os import W_OK, access +from os.path import exists, getmtime, abspath, dirname, join -from calculator.standard_deviation import main as sd_main +# definitive solution for calculator imports - due running from non standard working directories +base_path = abspath(dirname(__file__)) +sys.path.insert(0, join(base_path, '..')) -sys._excepthook = sys.excepthook -def exception_hook(exctype, value, traceback): - sys._excepthook(exctype, value, traceback) -sys.excepthook = exception_hook +QRC_FILE = abspath(join(base_path, './ui/qml.qrc')) +RESOURCES_FILE = abspath(join(base_path, './ui/resources.py')) def main(): - if len(sys.argv) > 1 and (sys.argv[1] == '-sd' or sys.argv[1] == '--standard-deviation'): - exit(sd_main(input(files=sys.argv[2] if len(sys.argv) > 2 else '-'))) + if len(sys.argv) > 1 and sys.argv[1] in {'-sd', '--standard-deviation'}: + from calculator.standard_deviation import main as sd_main + sys.exit(sd_main( + # file from first parameter if given else std input + input(files=sys.argv[2] if len(sys.argv) > 2 else '-') + )) - from .ui.app import CalculatorApp + if not update_qrc(): + print('Application cannot be started due problems with resources file.') + sys.exit(1) + + # local import due to resources.py + from calculator.ui.app import CalculatorApp app = CalculatorApp(sys.argv) sys.exit(app.run()) +def update_qrc(): + """ + Conditionally updates the qrc file from QML and dependent resources. + :return: True, if file was successfully updated or update is not required, else None + """ + from PyQt5.pyrcc_main import processResourceFile + file_types = 'qml qrc js ttf otf svg qm png'.split() + + last_modification = max( + map( + getmtime, + chain( + *( + file_ for file_ in ( + iglob(join( + abspath(dirname(__file__)), + '{base_path}/**/*.{file_type}'.format( + file_type=file_type, + base_path=base_path) + ), recursive=True + ) for file_type in file_types) + ) + ) + ) + ) + + if exists(RESOURCES_FILE) and (last_modification - getmtime(RESOURCES_FILE)) < 2: + return True + + print('Change in UI files detected, recompiling resources.py...', file=sys.stderr) + if not exists(RESOURCES_FILE) or not access(RESOURCES_FILE, W_OK): + print('Resources file {} not exists or is not writable, please call with write permissions.'.format( + RESOURCES_FILE + ), file=sys.stderr) + return + + if processResourceFile([QRC_FILE], RESOURCES_FILE, False): + print('Resources.py successfully recompiled.', file=sys.stderr) + return True + + print('Problem with compiling resources.py ({}).'.format(repr(e)), file=sys.stderr) + + + if __name__ == "__main__": main() diff --git a/src/calculator/standard_deviation.py b/src/calculator/standard_deviation.py index fc1276c..9601736 100644 --- a/src/calculator/standard_deviation.py +++ b/src/calculator/standard_deviation.py @@ -4,8 +4,8 @@ from typing import List from typing import Sequence +from calculator import NumericValue from calculator.core.math import Math -from calculator.typing import NumericValue def mean(values: Sequence[NumericValue]) -> float: diff --git a/src/calculator/ui/adapter.py b/src/calculator/ui/adapter.py index 612958f..b314488 100644 --- a/src/calculator/ui/adapter.py +++ b/src/calculator/ui/adapter.py @@ -4,10 +4,10 @@ from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty, QVariant from PyQt5.QtQml import QJSEngine, QQmlEngine +from calculator import Variable, NumericValue from calculator.core.calculator import Calculator from calculator.exceptions import MathError, VariableError from calculator.settings import BUILTIN_FUNCTIONS, EXPRESSION_EXPANSIONS, HIGHLIGHT_RULES -from calculator.typing import Variable, NumericValue from calculator.utils.number_formatter import NumberFormatter from calculator.utils.translate import translate diff --git a/src/calculator/ui/qml.qrc b/src/calculator/ui/qml.qrc index 3cd01dd..55f9ab0 100644 --- a/src/calculator/ui/qml.qrc +++ b/src/calculator/ui/qml.qrc @@ -39,7 +39,9 @@ qml/visualization/AnimatedText.qml qml/visualization/Error.qml qml/controls/DefaultButton.qml - ../translations/cs.qm + + translations/cs.qm + assets/icons/16x16.png assets/icons/24x24.png assets/icons/32x32.png diff --git a/src/calculator/translations/cs.qm b/src/calculator/ui/translations/cs.qm similarity index 100% rename from src/calculator/translations/cs.qm rename to src/calculator/ui/translations/cs.qm diff --git a/src/calculator/translations/cs.ts b/src/calculator/ui/translations/cs.ts similarity index 100% rename from src/calculator/translations/cs.ts rename to src/calculator/ui/translations/cs.ts diff --git a/src/calculator/utils/number_formatter.py b/src/calculator/utils/number_formatter.py index 18e7036..0fbea24 100644 --- a/src/calculator/utils/number_formatter.py +++ b/src/calculator/utils/number_formatter.py @@ -1,7 +1,7 @@ # coding=utf-8 from decimal import Decimal -from calculator.typing import NumericValue +from calculator._typing import NumericValue class NumberFormatter(object): From c458018e13a4a9b76f50fee6be636980a93203d7 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Sat, 25 Mar 2017 00:36:36 +0100 Subject: [PATCH 018/121] Finish issue #41 - Add flickable to text in result system display --- src/calculator/ui/assets/styles/UIStyles.qml | 1 + src/calculator/ui/qml/main.qml | 1 + .../qml/visualization/ResultSystemDisplay.qml | 44 ++++++++++++++++--- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/calculator/ui/assets/styles/UIStyles.qml b/src/calculator/ui/assets/styles/UIStyles.qml index fddb9dc..2f70f98 100644 --- a/src/calculator/ui/assets/styles/UIStyles.qml +++ b/src/calculator/ui/assets/styles/UIStyles.qml @@ -146,6 +146,7 @@ QtObject { property color color: "#F2F2F2" property color baseTextColor: "#ED1D3D" property color valueTextColor: "#3D3D3D" + property color scrollbarColor: "#ED1D3D" property font font: Qt.font({family: "Roboto Light"}) } } diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index 27f6f1d..6762d8f 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -164,6 +164,7 @@ ApplicationWindow { value: 0 bases: { "DEC": 10, "BIN": 2, "HEX": 16, "OCT": 8 } height: parent.height / 5.1 + scrollbarColor: StyleSettings.resultSystemDisplay.scrollbarColor baseTextColor: StyleSettings.resultSystemDisplay.baseTextColor valueTextColor: StyleSettings.resultSystemDisplay.valueTextColor font: StyleSettings.resultSystemDisplay.font diff --git a/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml b/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml index 5e3f65e..e9fbf6e 100644 --- a/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml +++ b/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml @@ -17,6 +17,8 @@ Rectangle { property color baseTextColor /// Text color of converted value property color valueTextColor + /// Color of value scrollbar + property color scrollbarColor /// Margin of text readonly property int margin: 10 @@ -50,17 +52,47 @@ Rectangle { anchors.left: parent.left } - AnimatedText { - text: Calculator.convertToBase(component.value, component.bases[modelData]) - color: component.valueTextColor + FontMetrics { + id: fm font: baseText.font + } + + Rectangle { + color: component.scrollbarColor + opacity: (flick.visibleArea.widthRatio != 1 && flick.moving) + + x: flick.width * flick.visibleArea.xPosition + flick.x + width: flick.width * flick.visibleArea.widthRatio + height: 2 + + anchors.top: flick.bottom + anchors.topMargin: height + + Behavior on opacity { + NumberAnimation { duration: 200 } + } + } + + Flickable { + id: flick + + boundsBehavior: Flickable.StopAtBounds + flickableDirection: Flickable.HorizontalFlick + clip: true + + width: parent.width - anchors.leftMargin + height: parent.height + + contentHeight: valueText.height + contentWidth: valueText.width - anchors.top: parent.top anchors.left: parent.left anchors.leftMargin: component.margin * 3 + fm.advanceWidth("DEC") // some constant to measure font width - FontMetrics { - id: fm + AnimatedText { + id: valueText + text: Calculator.convertToBase(component.value, component.bases[modelData]) + color: component.valueTextColor font: baseText.font } } From 02d41417ed426c6df7967151d013673a3d60e386 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Thu, 30 Mar 2017 23:52:20 +0200 Subject: [PATCH 019/121] Fixes after merge of app-installer --- src/calculator.pro | 2 +- src/calculator/ui/adapter.py | 1 - src/calculator/ui/app/calculator_app.py | 4 ---- src/calculator/ui/translations/cs.ts | 26 ++++++++++++------------- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/calculator.pro b/src/calculator.pro index 72dc782..f1d42c8 100644 --- a/src/calculator.pro +++ b/src/calculator.pro @@ -1,6 +1,6 @@ SOURCES = $$files(*.py, true) $$files(*.qml, true) -TRANSLATIONS = calculator/translations/cs.ts +TRANSLATIONS = calculator/ui/translations/cs.ts CODECFORTR = UTF-8 diff --git a/src/calculator/ui/adapter.py b/src/calculator/ui/adapter.py index aa12d15..840102a 100644 --- a/src/calculator/ui/adapter.py +++ b/src/calculator/ui/adapter.py @@ -9,7 +9,6 @@ from calculator import Variable, NumericValue from calculator.core.calculator import Calculator from calculator.exceptions import MathError, VariableError, UnsupportedBaseError -from calculator.settings import BUILTIN_FUNCTIONS, EXPRESSION_EXPANSIONS, HIGHLIGHT_RULES from calculator.utils.number_formatter import NumberFormatter from calculator.utils.translate import translate from calculator.settings import (BUILTIN_FUNCTIONS, EXPRESSION_EXPANSIONS, HIGHLIGHT_RULES, EXPRESSION_SPLITTERS, diff --git a/src/calculator/ui/app/calculator_app.py b/src/calculator/ui/app/calculator_app.py index dc018e0..1c4395c 100644 --- a/src/calculator/ui/app/calculator_app.py +++ b/src/calculator/ui/app/calculator_app.py @@ -35,14 +35,10 @@ def __init__(self, argv: List[str]): self._translator = QTranslator() self._translator.load("".join((":/translations/", QLocale().system().name(), ".qsm"))) self.installTranslator(self._translator) - - print(QPixmap(":/assets/images/icon.png").width()) icon = QIcon() for size in ICON_SIZES: - print(":/assets/icons/{}x{}.png".format(size, size)) icon.addFile(":/assets/icons/{}x{}.png".format(size, size), QSize(size, size)) - #icon.addPixmap(QPixmap(":/assets/icons/{}x{}.png".format(size, size))) self.setWindowIcon(icon) diff --git a/src/calculator/ui/translations/cs.ts b/src/calculator/ui/translations/cs.ts index 1ad9e41..f4544fe 100644 --- a/src/calculator/ui/translations/cs.ts +++ b/src/calculator/ui/translations/cs.ts @@ -4,27 +4,27 @@ Adapter - + Unsupported base. Nepodporovaná číselná soustava. - + Math error occured. Matematická chyba. - + Error in defining variable. Chyba při definování proměnné. - + Result is too big. Výsledek je příliš velký. - + Expression contains syntax error. Výraz obsahuje syntaktickou chybu. @@ -43,7 +43,7 @@ ExpressionInput - + Enter expression... Zadejte výraz... @@ -51,17 +51,17 @@ Game - + Easter egg Easter egg - + You are looser. Jste looser. - + AI is noob. AI je noob. @@ -69,7 +69,7 @@ PopUp - + Ok Ok @@ -77,17 +77,17 @@ main - + Barbie Calculator Barbie Calculator - + Error Chyba - + Info Info From 737de87cdb7bac8f5af9009b20d723d6eae3f924 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Fri, 31 Mar 2017 02:50:53 +0200 Subject: [PATCH 020/121] Create simple diplaying signature of function --- src/calculator/settings.py | 16 ++++----- src/calculator/ui/adapter.py | 15 ++++++++ src/calculator/ui/assets/styles/UIStyles.qml | 6 ++++ src/calculator/ui/qml.qrc | 3 +- src/calculator/ui/qml/main.qml | 31 ++++++++++++---- .../FunctionSignatureDisplay.qml | 36 +++++++++++++++++++ src/calculator/utils/number_formatter.py | 27 +++++++++++++- 7 files changed, 117 insertions(+), 17 deletions(-) create mode 100644 src/calculator/ui/qml/visualization/FunctionSignatureDisplay.qml diff --git a/src/calculator/settings.py b/src/calculator/settings.py index 05373ba..c89bf1e 100644 --- a/src/calculator/settings.py +++ b/src/calculator/settings.py @@ -60,12 +60,12 @@ def singletonProvider(engine: QQmlEngine, script_engine: QJSEngine) -> QObject: EXPRESSION_EXPANSIONS = ( - (BuiltinFunction.ABS, 'abs(', Expansion.ExpansionType.BracketsPack), - (BuiltinFunction.FACT, 'fact(', Expansion.ExpansionType.BracketsPack), - (BuiltinFunction.LOG, 'log(', Expansion.ExpansionType.BracketsPack), - (BuiltinFunction.LN, 'ln(', Expansion.ExpansionType.BracketsPack), - (BuiltinFunction.ROOT, 'root(', Expansion.ExpansionType.BracketsPack), - (BuiltinFunction.POW, 'pow(', Expansion.ExpansionType.BracketsPack), - (BuiltinFunction.SQRT, 'sqrt(', Expansion.ExpansionType.BracketsPack), - (BuiltinFunction.RAND, 'rand(', Expansion.ExpansionType.BracketsPack) + (BuiltinFunction.ABS, 'abs', Expansion.ExpansionType.BracketsPack), + (BuiltinFunction.FACT, 'fact', Expansion.ExpansionType.BracketsPack), + (BuiltinFunction.LOG, 'log', Expansion.ExpansionType.BracketsPack), + (BuiltinFunction.LN, 'ln', Expansion.ExpansionType.BracketsPack), + (BuiltinFunction.ROOT, 'root', Expansion.ExpansionType.BracketsPack), + (BuiltinFunction.POW, 'pow', Expansion.ExpansionType.BracketsPack), + (BuiltinFunction.SQRT, 'sqrt', Expansion.ExpansionType.BracketsPack), + (BuiltinFunction.RAND, 'rand', Expansion.ExpansionType.BracketsPack) ) diff --git a/src/calculator/ui/adapter.py b/src/calculator/ui/adapter.py index 840102a..524045c 100644 --- a/src/calculator/ui/adapter.py +++ b/src/calculator/ui/adapter.py @@ -1,12 +1,15 @@ # coding=utf-8 import re +import inspect from typing import Dict, Tuple, Set from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty, QVariant from PyQt5.QtQml import QJSEngine, QQmlEngine +from math import fabs from calculator import Variable, NumericValue +from calculator.core.solver import Solver from calculator.core.calculator import Calculator from calculator.exceptions import MathError, VariableError, UnsupportedBaseError from calculator.utils.number_formatter import NumberFormatter @@ -23,6 +26,7 @@ class UIAdapter(QObject): identifiersTypesChanged = pyqtSignal(QVariant) processed = pyqtSignal(QVariant) error = pyqtSignal(str) + _variables = dict() # type: Dict[str, Variable] _formatter = NumberFormatter func_identifiers_types = [{"identifier": func, "type": Expression.ExpressionTypes.Function} @@ -104,6 +108,17 @@ def setVariableValue(self, variable: str, value: NumericValue): } })) + @pyqtSlot(str, result=str) + def checkFunctionExpansion(self, expansion: str): + if expansion in BUILTIN_FUNCTIONS: + func = Solver.builtin_functions[expansion] + + try: + return self._formatter.format_function_args_spec(expansion, inspect.signature(func)) + except ValueError: + print("No signature") + return "" + @pyqtSlot(str) def removeVariable(self, variable_identifier: str) -> None: print(variable_identifier) diff --git a/src/calculator/ui/assets/styles/UIStyles.qml b/src/calculator/ui/assets/styles/UIStyles.qml index 2f70f98..d4f4575 100644 --- a/src/calculator/ui/assets/styles/UIStyles.qml +++ b/src/calculator/ui/assets/styles/UIStyles.qml @@ -149,4 +149,10 @@ QtObject { property color scrollbarColor: "#ED1D3D" property font font: Qt.font({family: "Roboto Light"}) } + + property QtObject functionSignatureDisplay: QtObject { + property color color: "black" + property color textColor: "white" + property font font: Qt.font({family: "Roboto Light"}) + } } diff --git a/src/calculator/ui/qml.qrc b/src/calculator/ui/qml.qrc index 73820c7..5dfa635 100644 --- a/src/calculator/ui/qml.qrc +++ b/src/calculator/ui/qml.qrc @@ -39,9 +39,7 @@ qml/visualization/AnimatedText.qml qml/visualization/PopUp.qml qml/controls/DefaultButton.qml - translations/cs.qm - assets/icons/16x16.png assets/icons/24x24.png assets/icons/32x32.png @@ -51,5 +49,6 @@ qml/controls/Completer.qml qml/visualization/ResultSystemDisplay.qml qml/visualization/CountDown.qml + qml/visualization/FunctionSignatureDisplay.qml diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index 6762d8f..e28ae4a 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -236,6 +236,19 @@ ApplicationWindow { Calculator.error.connect(error.show) } + FunctionSignatureDisplay { + id: functionSignature + + color: StyleSettings.functionSignatureDisplay.color + textColor: StyleSettings.functionSignatureDisplay.textColor + font: StyleSettings.functionSignatureDisplay.font + opacity: 0.8 + + x: completer.calcTextInfoPos(width) + y: expInput.cursorRectangle.y - height + expInput.y + height: completer.itemHeight * 1.2 + } + Control.Completer { id: completer @@ -249,7 +262,7 @@ ApplicationWindow { width: parent.width * 0.18 itemHeight: width / 8 - x: calcPos() + x: calcTextInfoPos(width) y: expInput.cursorRectangle.y + expInput.cursorRectangle.height + expInput.y onItemChoosed: { @@ -260,12 +273,13 @@ ApplicationWindow { expandExpression(currentItem["identifier"]) } - function calcPos() { - if(expInput.cursorRectangle.x + completer.width + fmExpInput.advanceWidth(" ") < expInput.width) + function calcTextInfoPos(infoWidth) { + if(expInput.cursorRectangle.x + infoWidth + fmExpInput.advanceWidth(" ") < expInput.width) return expInput.cursorRectangle.x + expInput.x else - return expInput.x + expInput.width - completer.width - expInput.textMargin + return expInput.x + expInput.width - infoWidth - expInput.textMargin } + } /** @@ -338,8 +352,13 @@ ApplicationWindow { expInput.remove(selectedStart, selectedEnd) - if(expansionType == Expansion.BracketsPack) - expInput.insert(selectedStart, expansion + selectedText + ")") + if(expansionType == Expansion.BracketsPack) { + expInput.insert(selectedStart, expansion + "(" + selectedText + ")") + var funcSignature = Calculator.checkFunctionExpansion(expansion) + if(funcSignature != "") { + functionSignature.text = funcSignature + } + } else expInput.insert(selectedStart, expansion) diff --git a/src/calculator/ui/qml/visualization/FunctionSignatureDisplay.qml b/src/calculator/ui/qml/visualization/FunctionSignatureDisplay.qml new file mode 100644 index 0000000..04aa818 --- /dev/null +++ b/src/calculator/ui/qml/visualization/FunctionSignatureDisplay.qml @@ -0,0 +1,36 @@ +import QtQuick 2.0 + +/** + Component to display function signature + */ +Rectangle { + id: component + + /** + Used as function to display component + @param signature Signature to display + */ + signal show(string signature) + /** + Used as function to hide component + */ + signal hide() + + /// Contains function signature in string + property alias text: text.text + /// Text color display + property alias textColor: text.color + /// Used font + property font font + + width: text.width + height * 0.2 + + Text { + id: text + + font.family: component.font.family + font.pixelSize: parent.height * 0.7 + + anchors.centerIn: parent + } +} diff --git a/src/calculator/utils/number_formatter.py b/src/calculator/utils/number_formatter.py index 5a1509e..47aa908 100644 --- a/src/calculator/utils/number_formatter.py +++ b/src/calculator/utils/number_formatter.py @@ -1,6 +1,10 @@ # coding=utf-8 +import re + from decimal import Decimal +from inspect import Signature + from calculator import NumericValue from calculator.settings import SUPPORTED_BASES @@ -35,4 +39,25 @@ def format_in_base(cls, value: int, base: int) -> str: if not base in SUPPORTED_BASES: raise UnsupportedBaseError("Base {} is not supported.".format(base)) - return cls.BASE_CONVERTERS[base](int(value)) \ No newline at end of file + return cls.BASE_CONVERTERS[base](int(value)) + + @staticmethod + def format_function_args_spec(func_identifier: str, func_signature: Signature) -> str: + raw_args = [(arg_identifier, func_signature.parameters[arg_identifier].annotation) + for arg_identifier in func_signature.parameters.keys()] + formatted_args_list = list() + + for arg_identifier, arg_annotation in raw_args: + arg_annotation = repr(arg_annotation) + + if " Date: Sat, 1 Apr 2017 03:42:56 +0200 Subject: [PATCH 021/121] Rename folder from syntaxhighlight to expression --- .../ui/types/{syntaxhighlight => expression}/__init__.py | 0 .../{syntaxhighlight => expression}/exp_syntax_highlighter.py | 2 +- .../types/{syntaxhighlight => expression}/syntax_highlighter.py | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename src/calculator/ui/types/{syntaxhighlight => expression}/__init__.py (100%) rename src/calculator/ui/types/{syntaxhighlight => expression}/exp_syntax_highlighter.py (96%) rename src/calculator/ui/types/{syntaxhighlight => expression}/syntax_highlighter.py (100%) diff --git a/src/calculator/ui/types/syntaxhighlight/__init__.py b/src/calculator/ui/types/expression/__init__.py similarity index 100% rename from src/calculator/ui/types/syntaxhighlight/__init__.py rename to src/calculator/ui/types/expression/__init__.py diff --git a/src/calculator/ui/types/syntaxhighlight/exp_syntax_highlighter.py b/src/calculator/ui/types/expression/exp_syntax_highlighter.py similarity index 96% rename from src/calculator/ui/types/syntaxhighlight/exp_syntax_highlighter.py rename to src/calculator/ui/types/expression/exp_syntax_highlighter.py index b49e9ea..68ef37d 100644 --- a/src/calculator/ui/types/syntaxhighlight/exp_syntax_highlighter.py +++ b/src/calculator/ui/types/expression/exp_syntax_highlighter.py @@ -7,7 +7,7 @@ from PyQt5.QtQml import QJSValue from PyQt5.QtQuick import QQuickItem -from calculator.ui.types.syntaxhighlight import SyntaxHighlighter, HighlightRule +from calculator.ui.types.expression import SyntaxHighlighter, HighlightRule class ExpSyntaxHighlighter(QObject): diff --git a/src/calculator/ui/types/syntaxhighlight/syntax_highlighter.py b/src/calculator/ui/types/expression/syntax_highlighter.py similarity index 100% rename from src/calculator/ui/types/syntaxhighlight/syntax_highlighter.py rename to src/calculator/ui/types/expression/syntax_highlighter.py From c5a860034b45efe1ff87b59d483f8ecea4a52489 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Sat, 1 Apr 2017 03:44:03 +0200 Subject: [PATCH 022/121] New ExpAnalyzer & move currentWord method from QML to ExpAnalyzer --- src/calculator/ui/app/calculator_app.py | 3 +- src/calculator/ui/qml/controls/Completer.qml | 12 +--- src/calculator/ui/qml/main.qml | 43 +++--------- .../ui/types/expression/__init__.py | 3 +- .../ui/types/expression/exp_analyzer.py | 68 +++++++++++++++++++ 5 files changed, 82 insertions(+), 47 deletions(-) create mode 100644 src/calculator/ui/types/expression/exp_analyzer.py diff --git a/src/calculator/ui/app/calculator_app.py b/src/calculator/ui/app/calculator_app.py index 1c4395c..108e0be 100644 --- a/src/calculator/ui/app/calculator_app.py +++ b/src/calculator/ui/app/calculator_app.py @@ -12,7 +12,7 @@ from calculator.ui.adapter import UIAdapter from calculator.ui.types.core import Sides -from calculator.ui.types.syntaxhighlight import ExpSyntaxHighlighter +from calculator.ui.types.expression import ExpSyntaxHighlighter, ExpAnalyzer from calculator.ui.types.qmlwrapper.utils import TypeRegister from calculator.settings import Expansion, ICON_SIZES, Expression @@ -50,6 +50,7 @@ def registerTypes(): qmlRegisterSingletonType(Expression, "Expression", 1, 0 ,"Expression", Expression.singletonProvider) qmlRegisterSingletonType(UIAdapter, "Calculator", 1, 0, "Calculator", UIAdapter.singletonProvider) TypeRegister.register_type(ExpSyntaxHighlighter) + TypeRegister.register_type(ExpAnalyzer) def run(self) -> int: CalculatorApp.registerTypes() diff --git a/src/calculator/ui/qml/controls/Completer.qml b/src/calculator/ui/qml/controls/Completer.qml index 0534436..4c0a97b 100644 --- a/src/calculator/ui/qml/controls/Completer.qml +++ b/src/calculator/ui/qml/controls/Completer.qml @@ -32,20 +32,10 @@ DropDown { onCurrentTextChanged: { var newModel = [] - if(currentText.search(/^[ ]*$/) != -1) { - component.model = constantModel - return - } - for(var key in component.constantModel) { var value = component.constantModel[key] - var currentTextWithoutSpace = currentText.replace(new RegExp("[ ]*(?=\\S+)", 'g'), "") - - // contains ")" which need to be escaped - if(currentTextWithoutSpace.search(/\)/) !== -1) - currentTextWithoutSpace = currentText.replace(/\)/g, "\\)") - if(value["identifier"].search("^" + currentTextWithoutSpace) !== -1) + if(value["identifier"].search("^" + currentText) !== -1) newModel.push(value) } diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index e28ae4a..509a4fb 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -2,6 +2,7 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 // TODO allow import ExpSyntaxHighlighter 1.0 +import ExpAnalyzer 1.0 import Sides 1.0 import Calculator 1.0 import Expansion 1.0 @@ -33,6 +34,11 @@ ApplicationWindow { onGameOver: info.show(msg) } + ExpAnalyzer { + id: exa + target: expInput + } + ExpSyntaxHighlighter { id: esh target: expInput @@ -282,48 +288,17 @@ ApplicationWindow { } - /** - According to cursor in text it determinates current word which is edited - @return Current word - */ - function currentWord() { - var result = "" - var startIndex = expInput.cursorPosition - 1 - var regExp = new RegExp(Calculator.expressionSplittersRegExp) - - if(expInput.cursorPosition == 0) - return - - while(expInput.text[startIndex]) { - if(expInput.text[startIndex].match(regExp)) - break; - --startIndex - } - startIndex++; - - while(expInput.text[startIndex]) { - if(expInput.text[startIndex].match(regExp)) - break - result += expInput.text[startIndex] - startIndex++ - } - - return result - } - /** Show suggestion box with filtered suggestions */ function completeText() { var lastChar = expInput.text.slice(-1) + var currentWord = exa.currentWord() + if(Calculator.expressionSplitters.indexOf(lastChar) != -1) completer.show() - if(typeof currentWord() != "undefined") - completer.currentText = currentWord() - else - completer.currentText = "" - + completer.currentText = currentWord completer.currentTextChanged(completer.currentText) } diff --git a/src/calculator/ui/types/expression/__init__.py b/src/calculator/ui/types/expression/__init__.py index 4194e72..e32c5f1 100644 --- a/src/calculator/ui/types/expression/__init__.py +++ b/src/calculator/ui/types/expression/__init__.py @@ -1,5 +1,6 @@ # coding=utf-8 from .syntax_highlighter import SyntaxHighlighter, HighlightRule from .exp_syntax_highlighter import ExpSyntaxHighlighter +from .exp_analyzer import ExpAnalyzer -__all_ = ("SyntaxHighlighter", "HighlightRule", "ExpSyntaxHighlighter") \ No newline at end of file +__all_ = ("SyntaxHighlighter", "HighlightRule", "ExpSyntaxHighlighter", "ExpAnalyzer") \ No newline at end of file diff --git a/src/calculator/ui/types/expression/exp_analyzer.py b/src/calculator/ui/types/expression/exp_analyzer.py new file mode 100644 index 0000000..44a21a6 --- /dev/null +++ b/src/calculator/ui/types/expression/exp_analyzer.py @@ -0,0 +1,68 @@ +# coding=utf-8 +import re + +from typing import Optional + +from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot +from PyQt5.QtQuick import QQuickItem + +from calculator.settings import EXPRESSION_SPLITTERS + + +class ExpAnalyzer(QObject): + targetChanged = pyqtSignal(QQuickItem) + + def __init__(self, parent: Optional[QObject] = None) -> None: + super().__init__(parent) + + self._target = None + + def _get_content(self): + return self._target.property("text") + + def _get_cursor(self): + return self._target.property("cursorPosition") + + def _get_selection(self): + return (self._target.property("selectionStart"), + self._target.property("selectionEnd"), + self._target.property("selectedText")) + + @pyqtSlot(result=str) + def currentWord(self): + """ + According to cursor in text it determines current word which is edited + :return: Current word + """ + + content = self._get_content() + cursor = self._get_cursor() + word_start = 0 + word_end = len(content) + + if self._get_cursor() == 0: + return "" + + splitter_positions = list() + for splitter in EXPRESSION_SPLITTERS: + splitter_positions.extend([i for i, letter in enumerate(content) if letter == splitter]) + + left_splitters_pos = list(filter(lambda x: x <= cursor, splitter_positions)) + right_splitters_pos = list(filter(lambda x: x > cursor, splitter_positions)) + + if left_splitters_pos: + word_start = min(left_splitters_pos, key=lambda x: abs(x - cursor)) + 1 + if right_splitters_pos: + word_end = min(right_splitters_pos, key=lambda x: abs(x - cursor)) + + return re.escape(content[word_start: word_end:].lstrip().rstrip()) + + @pyqtProperty(QQuickItem) + def target(self) -> QQuickItem: + return self._target + + @target.setter + def target(self, v: QQuickItem) -> None: + if self._target != v: + self._target = v + self.targetChanged.emit(v) \ No newline at end of file From 7fcac7dc6f1e3daf520919217e913b4e5db19f64 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Sat, 1 Apr 2017 03:45:16 +0200 Subject: [PATCH 023/121] Refactor use strip instead of lstrip and rstrip --- src/calculator/ui/types/expression/exp_analyzer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calculator/ui/types/expression/exp_analyzer.py b/src/calculator/ui/types/expression/exp_analyzer.py index 44a21a6..598e7a8 100644 --- a/src/calculator/ui/types/expression/exp_analyzer.py +++ b/src/calculator/ui/types/expression/exp_analyzer.py @@ -55,7 +55,7 @@ def currentWord(self): if right_splitters_pos: word_end = min(right_splitters_pos, key=lambda x: abs(x - cursor)) - return re.escape(content[word_start: word_end:].lstrip().rstrip()) + return re.escape(content[word_start: word_end:].strip()) @pyqtProperty(QQuickItem) def target(self) -> QQuickItem: From f767f5db0ac68ce36b559925a919bac023b2d091 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Sun, 2 Apr 2017 01:32:58 +0200 Subject: [PATCH 024/121] Refactor use set instead of tuple in settings expression splitters --- src/calculator/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calculator/settings.py b/src/calculator/settings.py index c89bf1e..7c29680 100644 --- a/src/calculator/settings.py +++ b/src/calculator/settings.py @@ -18,7 +18,7 @@ class BuiltinFunction(object): RAND = 'rand' -EXPRESSION_SPLITTERS = ("+", "-", "(", "*", "/") +EXPRESSION_SPLITTERS = {"+", "-", "(", "*", "/"} class Expression(QObject): class ExpressionTypes(IntEnum): Function = 0 From 406bd7e1e43f8b1794be164ea5e3f0b8fa0512ab Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Sun, 2 Apr 2017 01:35:09 +0200 Subject: [PATCH 025/121] Add system to get current function signature & move all expression analysis methods from Adapter to ExpAnalyzer --- src/calculator/ui/adapter.py | 29 +--- src/calculator/ui/qml/main.qml | 41 +++--- .../ui/types/expression/exp_analyzer.py | 136 +++++++++++++++++- src/calculator/utils/number_formatter.py | 3 +- 4 files changed, 153 insertions(+), 56 deletions(-) diff --git a/src/calculator/ui/adapter.py b/src/calculator/ui/adapter.py index 524045c..2384f75 100644 --- a/src/calculator/ui/adapter.py +++ b/src/calculator/ui/adapter.py @@ -1,21 +1,15 @@ # coding=utf-8 -import re -import inspect - from typing import Dict, Tuple, Set from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty, QVariant from PyQt5.QtQml import QJSEngine, QQmlEngine -from math import fabs from calculator import Variable, NumericValue -from calculator.core.solver import Solver from calculator.core.calculator import Calculator from calculator.exceptions import MathError, VariableError, UnsupportedBaseError from calculator.utils.number_formatter import NumberFormatter from calculator.utils.translate import translate -from calculator.settings import (BUILTIN_FUNCTIONS, EXPRESSION_EXPANSIONS, HIGHLIGHT_RULES, EXPRESSION_SPLITTERS, - Expression) +from calculator.settings import (BUILTIN_FUNCTIONS, HIGHLIGHT_RULES, EXPRESSION_SPLITTERS, Expression) class UIAdapter(QObject): @@ -108,17 +102,6 @@ def setVariableValue(self, variable: str, value: NumericValue): } })) - @pyqtSlot(str, result=str) - def checkFunctionExpansion(self, expansion: str): - if expansion in BUILTIN_FUNCTIONS: - func = Solver.builtin_functions[expansion] - - try: - return self._formatter.format_function_args_spec(expansion, inspect.signature(func)) - except ValueError: - print("No signature") - return "" - @pyqtSlot(str) def removeVariable(self, variable_identifier: str) -> None: print(variable_identifier) @@ -135,11 +118,6 @@ def highlightRules(self) -> QVariant: def builtinFunctions(self) -> QVariant: return QVariant(list(BUILTIN_FUNCTIONS)) - @pyqtProperty(QVariant) - def expressionsExpansion(self) -> QVariant: - return QVariant({expression: dict(expansion=expansion, expansionType=expansion_type) - for expression, expansion, expansion_type in EXPRESSION_EXPANSIONS}) - @staticmethod def singletonProvider(engine: QQmlEngine, script_engine: QJSEngine) -> QObject: adapter = UIAdapter() @@ -170,11 +148,6 @@ def _commit_new_variables_state(self, variables: Dict[str, Variable]) -> Tuple[S def expressionSplitters(self) -> QVariant: return QVariant(list(EXPRESSION_SPLITTERS)) - @pyqtProperty(str) - def expressionSplittersRegExp(self) -> str: - result = re.escape("".join(EXPRESSION_SPLITTERS)) - return "".join(("[", result ,"]")) - @pyqtProperty(QVariant, notify=identifiersTypesChanged) def variables(self) -> QVariant: return QVariant(list(self._calculator.variables.keys())) diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index 509a4fb..931fb04 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -138,16 +138,22 @@ ApplicationWindow { countDown.start(3) completeText() + showFunctionSignature() } onSelectedTextChanged: { if(selectedText.length) { completer.model = completer.constantModel completer.currentText = "" } - else + else { completeText() + showFunctionSignature() + } + } + onCursorPositionChanged: { + completeText() + showFunctionSignature() } - onCursorPositionChanged: completeText() } ResultDisplay { @@ -302,6 +308,13 @@ ApplicationWindow { completer.currentTextChanged(completer.currentText) } + function showFunctionSignature() { + var currentFunction = exa.currentFunction() + var funcSignature = exa.currentFunctionSignature() + + functionSignature.text = funcSignature + } + /** Overwrite current expression by new expression @param newExpression New expression @@ -315,28 +328,8 @@ ApplicationWindow { @param expressionKey Key of builtin expression or dynamic expression */ function expandExpression(expansionKey) { - var expansionData = Calculator.expressionsExpansion[expansionKey] - var selectedStart = expInput.selectionStart - var selectedText = expInput.selectedText - var selectedEnd = expInput.selectionEnd - var expansion, expansionType - - // if not found, then it is not in Settings, so use normal expansion - expansion = (typeof expansionData === "undefined") ?expansionKey :expansionData["expansion"] - expansionType = (typeof expansionData === "undefined") ?Expansion.Normal :expansionData["expansionType"] - - expInput.remove(selectedStart, selectedEnd) - - if(expansionType == Expansion.BracketsPack) { - expInput.insert(selectedStart, expansion + "(" + selectedText + ")") - var funcSignature = Calculator.checkFunctionExpansion(expansion) - if(funcSignature != "") { - functionSignature.text = funcSignature - } - } - - else - expInput.insert(selectedStart, expansion) + expInput.remove(expInput.selectionStart, expInput.selectionEnd) + expInput.insert(expInput.selectionStart, exa.expandExpression(expansionKey)) } /** diff --git a/src/calculator/ui/types/expression/exp_analyzer.py b/src/calculator/ui/types/expression/exp_analyzer.py index 598e7a8..58bd4bd 100644 --- a/src/calculator/ui/types/expression/exp_analyzer.py +++ b/src/calculator/ui/types/expression/exp_analyzer.py @@ -1,12 +1,15 @@ # coding=utf-8 import re +import inspect -from typing import Optional +from typing import Optional, Tuple, List from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot from PyQt5.QtQuick import QQuickItem -from calculator.settings import EXPRESSION_SPLITTERS +from calculator.core.solver import Solver +from calculator.settings import EXPRESSION_SPLITTERS, EXPRESSION_EXPANSIONS, Expansion, BUILTIN_FUNCTIONS +from calculator.utils.number_formatter import NumberFormatter class ExpAnalyzer(QObject): @@ -29,7 +32,134 @@ def _get_selection(self): self._target.property("selectedText")) @pyqtSlot(result=str) - def currentWord(self): + def currentFunctionSignature(self) -> str: + """ + :return: Return signature of current function if cursor is not in body of any function then + it returns empty string + """ + func_identifier = self.currentFunction() + + if func_identifier in BUILTIN_FUNCTIONS: + func = Solver.builtin_functions[func_identifier] + + try: + return NumberFormatter.format_function_args_spec(func_identifier, inspect.signature(func)) + except ValueError: + print("No signature") + return "" + + @pyqtSlot(result=str) + def currentFunction(self) -> str: + """ + :return: Identifier of function in which is cursor is located if cursor is not in body of + funnction then it return empty string + """ + + cursor = self._get_cursor() + content = self._get_content() + functions = [] + mutable_content = list(content) + + for func in self._functions_list(content): + start, end = self._function_body_borders(func, "".join(mutable_content)) + functions.append((func, start, end)) + + for i in range(start - len(func) -1, start): + mutable_content[i] = "_" + + current_functions = [] # functions can be nested, so then display the most nested function + + for func, start, end in functions: + if start <= cursor <= end + 1: + current_functions.append((func, end - start + 1)) + + if current_functions: + return min(current_functions, key=lambda t: t[1])[0] + return "" + + def _function_body_borders(self, func: str, expr: str) -> Tuple[int, int]: + """ + :param func: Function identifier + :param expr: Expression where it will be finding + :return: Start and end index of body of function + """ + brackets_count = {"(": 1, ")": 0} # 1 for function + match = re.search('{} *\('.format(func), expr) + + for i in range(match.end(), len(expr)): + current_char = expr[i] + + if current_char in "()": + brackets_count[current_char] += 1 + + if brackets_count["("] == brackets_count[")"]: + return match.end(), i - 1 + return match.end(), len(expr) + + def _functions_list(self, expr: str) -> List[str]: + """ + Return list of all functions identifiers including desirable duplication + :param expr: Input string expression + :return: List of functions identifiers + """ + result = [] + + for splitter in EXPRESSION_SPLITTERS - {"("}: + expr = expr.replace(splitter, ".") + splitted_expressions = [ s.strip() for s in expr.split(".")] + + for e in splitted_expressions: + result.extend([match.group() for match in re.finditer('(?!(\(|\)| |\.)).+?(?=\()', e)]) + + return result + + @pyqtSlot(str, result=int) + def expansionType(self, expansion: str) -> int: + """ + Return type of expansion. + :param expansion: String to expand + :return: Type of expansion + """ + + try: + return {func: expansion_type + for func, _, expansion_type in EXPRESSION_EXPANSIONS}[expansion] + except KeyError: + return Expansion.ExpansionType.Normal + + @pyqtSlot(str, result=str) + def getExpansion(self, expansion: str) -> str: + """ + Return Whole expansion if expansion is in settings else return inputted expansion. + :param expansion: Expansion key + :return: Whole expansion + """ + + for func, func_expansion, _ in EXPRESSION_EXPANSIONS: + if func == expansion: + return func_expansion + return expansion + + @pyqtSlot(str, result=str) + def expandExpression(self, expansion: str) -> str: + """ + :param expansion: String to expand + :return: Expanded expansion + """ + + _, _, selected_text = self._get_selection() + + expansion_type = self.expansionType(expansion) + expansion = self.getExpansion(expansion) + + if expansion_type == Expansion.ExpansionType.BracketsPack: + return "{}({})".format(expansion, selected_text) + else: + return expansion + + + @pyqtSlot(result=str) + def currentWord(self) -> str: """ According to cursor in text it determines current word which is edited :return: Current word diff --git a/src/calculator/utils/number_formatter.py b/src/calculator/utils/number_formatter.py index 47aa908..178d455 100644 --- a/src/calculator/utils/number_formatter.py +++ b/src/calculator/utils/number_formatter.py @@ -60,4 +60,5 @@ def format_function_args_spec(func_identifier: str, func_signature: Signature) - arg_annotation ) for arg_identifier, arg_annotation in formatted_args_list])) - return formatted_args \ No newline at end of file + + return formatted_args From 624318ccd1fd479802a78e55a0a9560d2c2d520a Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Sun, 2 Apr 2017 01:48:33 +0200 Subject: [PATCH 026/121] Refactor NumberFormatter to Formatter, format -> format_number, format_in_base -> format_number_in_base --- src/calculator/ui/adapter.py | 12 ++++++------ src/calculator/ui/types/expression/exp_analyzer.py | 4 ++-- .../utils/{number_formatter.py => formatter.py} | 6 +++--- src/tests/calculator/utils/number_formatter.py | 6 +++--- 4 files changed, 14 insertions(+), 14 deletions(-) rename src/calculator/utils/{number_formatter.py => formatter.py} (91%) diff --git a/src/calculator/ui/adapter.py b/src/calculator/ui/adapter.py index 2384f75..040c33c 100644 --- a/src/calculator/ui/adapter.py +++ b/src/calculator/ui/adapter.py @@ -7,7 +7,7 @@ from calculator import Variable, NumericValue from calculator.core.calculator import Calculator from calculator.exceptions import MathError, VariableError, UnsupportedBaseError -from calculator.utils.number_formatter import NumberFormatter +from calculator.utils.formatter import Formatter from calculator.utils.translate import translate from calculator.settings import (BUILTIN_FUNCTIONS, HIGHLIGHT_RULES, EXPRESSION_SPLITTERS, Expression) @@ -22,14 +22,14 @@ class UIAdapter(QObject): error = pyqtSignal(str) _variables = dict() # type: Dict[str, Variable] - _formatter = NumberFormatter + _formatter = Formatter func_identifiers_types = [{"identifier": func, "type": Expression.ExpressionTypes.Function} for func in BUILTIN_FUNCTIONS] @pyqtSlot(float, int, result=str) def convertToBase(self, value: NumericValue, base: int) -> str: try: - v = self._formatter.format_in_base(value, base) + v = self._formatter.format_number_in_base(value, base) return v except UnsupportedBaseError as e: return translate("Adapter", "Unsupported base.") @@ -45,11 +45,11 @@ def process(self, expression: str) -> None: self.identifiersTypesChanged.emit(self.identifiersTypes) self.processed.emit(QVariant({ - "result": None if result is None else self._formatter.format(result, 16), + "result": None if result is None else self._formatter.format_number(result, 16), "unformattedResult": result, "variables": { key: dict( - value=self._formatter.format(value), + value=self._formatter.format_number(value), expression=self._format_source_expression( variable=key, source_expression=expression @@ -87,7 +87,7 @@ def setVariableValue(self, variable: str, value: NumericValue): "result": None, "variables": { key: dict( - value=self._formatter.format(value), + value=self._formatter.format_number(value), expression=self._format_source_expression( variable=key, source_expression=expression diff --git a/src/calculator/ui/types/expression/exp_analyzer.py b/src/calculator/ui/types/expression/exp_analyzer.py index 58bd4bd..9b33570 100644 --- a/src/calculator/ui/types/expression/exp_analyzer.py +++ b/src/calculator/ui/types/expression/exp_analyzer.py @@ -9,7 +9,7 @@ from calculator.core.solver import Solver from calculator.settings import EXPRESSION_SPLITTERS, EXPRESSION_EXPANSIONS, Expansion, BUILTIN_FUNCTIONS -from calculator.utils.number_formatter import NumberFormatter +from calculator.utils.formatter import Formatter class ExpAnalyzer(QObject): @@ -43,7 +43,7 @@ def currentFunctionSignature(self) -> str: func = Solver.builtin_functions[func_identifier] try: - return NumberFormatter.format_function_args_spec(func_identifier, inspect.signature(func)) + return Formatter.format_function_args_spec(func_identifier, inspect.signature(func)) except ValueError: print("No signature") return "" diff --git a/src/calculator/utils/number_formatter.py b/src/calculator/utils/formatter.py similarity index 91% rename from src/calculator/utils/number_formatter.py rename to src/calculator/utils/formatter.py index 178d455..4147925 100644 --- a/src/calculator/utils/number_formatter.py +++ b/src/calculator/utils/formatter.py @@ -11,7 +11,7 @@ from calculator.exceptions import UnsupportedBaseError -class NumberFormatter(object): +class Formatter(object): DEFAULT_CHARACTERS_LIMIT = 8 EXP_FORMAT = '{value} ×10{exp}' @@ -20,7 +20,7 @@ class NumberFormatter(object): _EXP_DIVIDER = 'e' @classmethod - def format(cls, value: NumericValue, characters_limit: int = DEFAULT_CHARACTERS_LIMIT) -> str: + def format_number(cls, value: NumericValue, characters_limit: int = DEFAULT_CHARACTERS_LIMIT) -> str: stringed = str(value) if cls._EXP_DIVIDER not in stringed and not (len(stringed) > characters_limit): @@ -33,7 +33,7 @@ def format(cls, value: NumericValue, characters_limit: int = DEFAULT_CHARACTERS_ ) @classmethod - def format_in_base(cls, value: int, base: int) -> str: + def format_number_in_base(cls, value: int, base: int) -> str: if abs(value - int(value)) > 0: raise ValueError() if not base in SUPPORTED_BASES: diff --git a/src/tests/calculator/utils/number_formatter.py b/src/tests/calculator/utils/number_formatter.py index 317a4a0..6d3e8c6 100644 --- a/src/tests/calculator/utils/number_formatter.py +++ b/src/tests/calculator/utils/number_formatter.py @@ -1,12 +1,12 @@ # coding=utf-8 from unittest.case import TestCase -from calculator.utils.number_formatter import NumberFormatter +from calculator.utils.formatter import Formatter class TestNumberFormatter(TestCase): def setUp(self): - self.formatter = NumberFormatter() + self.formatter = Formatter() def test_simple(self): self.skipTest('Formatted number in HTML cannot be actually tested.') @@ -20,7 +20,7 @@ def test_simple(self): ) for value, formatted in cases: self.assertEqual( - self.formatter.format(value=value), + self.formatter.format_number(value=value), formatted, 'Formatted value.' ) From f96d5bb16bbb28d771d51eeb79645f68d6d3b3ab Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Sun, 2 Apr 2017 01:50:32 +0200 Subject: [PATCH 027/121] Fix bring PopUp in front --- src/calculator/ui/qml/main.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index 931fb04..97da907 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -208,6 +208,7 @@ ApplicationWindow { dialogColor: StyleSettings.errorDialog.color textColor: StyleSettings.errorDialog.textColor font: StyleSettings.errorDialog.font + z: 2 anchors.fill: parent @@ -222,6 +223,7 @@ ApplicationWindow { dialogColor: StyleSettings.infoDialog.color textColor: StyleSettings.infoDialog.textColor font: StyleSettings.infoDialog.font + z: 2 anchors.fill: parent From 357badde7f09a9f324eb3ec9a5e18b935187669e Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Sun, 2 Apr 2017 02:00:15 +0200 Subject: [PATCH 028/121] Add hide and show to FunctionSignatureDisplay --- src/calculator/ui/qml/main.qml | 4 ++-- .../visualization/FunctionSignatureDisplay.qml | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index 97da907..ed58d60 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -256,7 +256,7 @@ ApplicationWindow { color: StyleSettings.functionSignatureDisplay.color textColor: StyleSettings.functionSignatureDisplay.textColor font: StyleSettings.functionSignatureDisplay.font - opacity: 0.8 + constantOpacity: 0.8 x: completer.calcTextInfoPos(width) y: expInput.cursorRectangle.y - height + expInput.y @@ -314,7 +314,7 @@ ApplicationWindow { var currentFunction = exa.currentFunction() var funcSignature = exa.currentFunctionSignature() - functionSignature.text = funcSignature + functionSignature.show(funcSignature) } /** diff --git a/src/calculator/ui/qml/visualization/FunctionSignatureDisplay.qml b/src/calculator/ui/qml/visualization/FunctionSignatureDisplay.qml index 04aa818..45c42a9 100644 --- a/src/calculator/ui/qml/visualization/FunctionSignatureDisplay.qml +++ b/src/calculator/ui/qml/visualization/FunctionSignatureDisplay.qml @@ -20,11 +20,28 @@ Rectangle { property alias text: text.text /// Text color display property alias textColor: text.color + property real constantOpacity /// Used font property font font + opacity: constantOpacity width: text.width + height * 0.2 + onShow: { + text.text = signature + + if(signature == "") + component.hide() + else + component.opacity = component.constantOpacity + } + + onHide: component.opacity = 0 + + Behavior on opacity { + NumberAnimation { duration: 250 } + } + Text { id: text From 4067d8627bb854136bf25e99306e56d386f25a25 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Sun, 2 Apr 2017 02:01:30 +0200 Subject: [PATCH 029/121] Remove useless code main.qml --- src/calculator/ui/qml/main.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index ed58d60..2f822bb 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -311,7 +311,6 @@ ApplicationWindow { } function showFunctionSignature() { - var currentFunction = exa.currentFunction() var funcSignature = exa.currentFunctionSignature() functionSignature.show(funcSignature) From 10e979e69ccca0c60752a298e718abf2c8d3ecb2 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Sun, 2 Apr 2017 02:02:09 +0200 Subject: [PATCH 030/121] Change styles of Completer to match signature display --- src/calculator/ui/assets/styles/UIStyles.qml | 3 +-- src/calculator/ui/qml/main.qml | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calculator/ui/assets/styles/UIStyles.qml b/src/calculator/ui/assets/styles/UIStyles.qml index d4f4575..8bf0d6b 100644 --- a/src/calculator/ui/assets/styles/UIStyles.qml +++ b/src/calculator/ui/assets/styles/UIStyles.qml @@ -123,14 +123,13 @@ QtObject { } property QtObject completer: QtObject { - property color color: "#2A2A2A" + property color color: "black" property color hoverColor: "#ED1946" property color textColor: "#C1C0C0" property color scrollBarColor: "#9F9F9F" property var typeColors: ({}) property font font: Qt.font({family: "Roboto Light"}) - // TODO allow Component.onCompleted: { typeColors[Expression.Function] = "#EF4223" typeColors[Expression.Variable] = "#C1C0C0" diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index 2f822bb..cef5444 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -269,6 +269,7 @@ ApplicationWindow { target: expInput constantModel: Calculator.identifiersTypes + opacity: 0.8 color: StyleSettings.completer.color hoverColor: StyleSettings.completer.hoverColor textColor: StyleSettings.completer.textColor From 93f7281c36f6cfdf3d81fcbf56860a7d059ee1a0 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Sun, 2 Apr 2017 02:09:15 +0200 Subject: [PATCH 031/121] Fix add signature to abs and pow --- src/calculator/core/math/math.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/calculator/core/math/math.py b/src/calculator/core/math/math.py index cc83475..86f50e3 100644 --- a/src/calculator/core/math/math.py +++ b/src/calculator/core/math/math.py @@ -16,8 +16,14 @@ class Math(object): add = operator.add # type: BinaryNumericFunction subtract = operator.sub # type: BinaryNumericFunction multiple = operator.mul # type: BinaryNumericFunction - abs = math.fabs # type: BinaryNumericFunction - pow = math.pow # type: BinaryNumericFunction + + @staticmethod + def abs(x: NumericValue) -> NumericValue: + return math.fabs(x) + + @staticmethod + def pow(x: NumericValue, y: NumericValue) -> NumericValue: + return math.pow(x, y) @staticmethod def divide(a: NumericValue, b: NumericValue) -> NumericValue: From 40b60c0a97034380a08835e61cc9b01078316c90 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Sun, 2 Apr 2017 02:15:52 +0200 Subject: [PATCH 032/121] Fix handle TypeError and NotImplementedError during process --- src/calculator/ui/adapter.py | 4 ++++ src/calculator/ui/translations/cs.qm | Bin 1122 -> 1373 bytes src/calculator/ui/translations/cs.ts | 26 ++++++++++++++++++-------- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/calculator/ui/adapter.py b/src/calculator/ui/adapter.py index 040c33c..2a61890 100644 --- a/src/calculator/ui/adapter.py +++ b/src/calculator/ui/adapter.py @@ -72,6 +72,10 @@ def process(self, expression: str) -> None: self.error.emit(translate("Adapter", "Error in defining variable.")) except OverflowError: self.error.emit(translate("Adapter", "Result is too big.")) + except TypeError: + self.error.emit(translate("Adapter", "Parameters count does not match function.")) + except NotImplemented: + self.error.emit(translate("Adapter", "Function is not defined.")) @pyqtSlot(str, int) def setVariableValue(self, variable: str, value: NumericValue): diff --git a/src/calculator/ui/translations/cs.qm b/src/calculator/ui/translations/cs.qm index 3c462e8b886b610ec9f1ad09cfd95f6d0494566b..b7d549f92d9b64b3d1b3f7736ded8b976b9fcf6f 100644 GIT binary patch delta 355 zcmaFFahGd?NPPtZ18Xz`0}DF?E6aHxJ)1#I+>U{P;R}P$%p@Scm{G}-kAZ<{67zRu|i&ci9$+hT4r8q%H#~j)Or_& z0ET=mg7y4epdoB&#$75{nXZQ%h2d niWQRcOY?ws<)=a{%}p#x&QM5$Iz^8i=rDH2l+A}3=Q9BSzJ^v= delta 146 zcmcc1^@wAFNPPwa18Xz`1G5YRE6aHxy@A1JW)hI@U{vztV_;xh&b%RO9RmZy7gnQB zehdsunXL0x9|FqTu-@jn2NW-6^Q(>nir-;>RbmHJ@6MSS*$3ncaTY4+0p%IE4qPq= o@|R`;HC<%}3fM3>Gh{GSG9)o1GAK-3{Cx5o#^B8-n5HrT0Oo`v_W%F@ diff --git a/src/calculator/ui/translations/cs.ts b/src/calculator/ui/translations/cs.ts index f4544fe..d32e6a2 100644 --- a/src/calculator/ui/translations/cs.ts +++ b/src/calculator/ui/translations/cs.ts @@ -4,27 +4,37 @@ Adapter - + Unsupported base. Nepodporovaná číselná soustava. - + Math error occured. Matematická chyba. - + Error in defining variable. Chyba při definování proměnné. - + Result is too big. Výsledek je příliš velký. - + + Parameters count does not match function. + Počet parametrů neodpovídá funkci. + + + + Function is not defined. + Funkce není definována. + + + Expression contains syntax error. Výraz obsahuje syntaktickou chybu. @@ -77,17 +87,17 @@ main - + Barbie Calculator Barbie Calculator - + Error Chyba - + Info Info From e96a39ac4c96f55b72cd0e41089e7cc9ef6ddc93 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Sun, 2 Apr 2017 03:16:26 +0200 Subject: [PATCH 033/121] Fix add background to graph - mockup --- mockup/shown-features.svg | 1452 ++++++++++++++++++------------------- 1 file changed, 723 insertions(+), 729 deletions(-) diff --git a/mockup/shown-features.svg b/mockup/shown-features.svg index a0ab208..8b9b9ce 100644 --- a/mockup/shown-features.svg +++ b/mockup/shown-features.svg @@ -2,811 +2,805 @@ + viewBox="0 0 180 147.5" enable-background="new 0 0 180 147.5" xml:space="preserve"> + - - - - - - - - - - - - - + + + + + + + + + + + + + - + - + - + - - - - + + + + - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - + + + - - - - - - - - - - + + + + + + + + + + - - - - + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - + + + + - - - - + + + + - - - + + + - - - - - - - + + + + + + + - - + + - - + + - - + + - - - - + + + + - - + + - Error + Error - Naskytla se nějaká chyba. - + Naskytla se nějaká chyba. + - Ok + Ok - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - - - + + + + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 8c6ca1252417e4c27ec6c3d30ec52fa3f7dd11c5 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Sun, 2 Apr 2017 03:32:14 +0200 Subject: [PATCH 034/121] Fix issue #50 - Completer not showing before ) --- src/calculator/ui/types/expression/exp_analyzer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calculator/ui/types/expression/exp_analyzer.py b/src/calculator/ui/types/expression/exp_analyzer.py index 9b33570..bdb6c20 100644 --- a/src/calculator/ui/types/expression/exp_analyzer.py +++ b/src/calculator/ui/types/expression/exp_analyzer.py @@ -165,7 +165,7 @@ def currentWord(self) -> str: :return: Current word """ - content = self._get_content() + content = self._get_content().replace(")", "") cursor = self._get_cursor() word_start = 0 word_end = len(content) From adb77761b3197ab85586dcc758bc860a4ba61ae5 Mon Sep 17 00:00:00 2001 From: Meigurad Date: Sun, 2 Apr 2017 03:36:09 +0200 Subject: [PATCH 035/121] Updating user documentation --- doc/doc.md | 196 ++++++++++++++++++++++++++++++++++++++++++++--- doc/empty.png | Bin 0 -> 26279 bytes doc/func1.png | Bin 0 -> 10394 bytes doc/func2.png | Bin 0 -> 8957 bytes doc/system1.png | Bin 0 -> 4500 bytes doc/system2.png | Bin 0 -> 6845 bytes doc/variable.png | Bin 0 -> 3992 bytes 7 files changed, 186 insertions(+), 10 deletions(-) create mode 100644 doc/empty.png create mode 100644 doc/func1.png create mode 100644 doc/func2.png create mode 100644 doc/system1.png create mode 100644 doc/system2.png create mode 100644 doc/variable.png diff --git a/doc/doc.md b/doc/doc.md index 57be7bc..f8deab7 100644 --- a/doc/doc.md +++ b/doc/doc.md @@ -1,19 +1,42 @@ + # IVS-VUT-BIT-2016-2017 Documentation ## Table of contents +* [Introduction](#introduction) +* [Usage](#usage) + * [Installation](#installation) + * [Uninstallation](#uninstallation) * [Input syntax](#input-syntax) - * [Numbers](#numbers) + * [Number systems](#number-systems) * [Operators](#operators) * [Functions](#functinos) +* [Getting started](#getting-started) + * [Components](#components) + * [Step by step](#step-by-step) + +## Introduction + +This application represents classic calculator with special functions. Calculator can work with hugh nun numbers, because his core is written in [Python](https://www.python.org/). + +## Usage /todo + +### Installation + +### Uninstallation ## Input syntax -### Numbers + +### Number systems ??? + +Calculator can compute with classic number systems, such as decimal, hexa-decimal, octal and binary. #### Decimal -Examples: +Decimal mode is set implicitly. + +Examples of decimal numbers: `156` @@ -27,7 +50,7 @@ Examples: Requires calculator to be switched to hexadecimal mode. -Valid characters: `0-9 A-F` +Valid characters: `0-9 A-F` (lower case `a-f` are not valid) Examples: @@ -129,7 +152,7 @@ or `[base] ** [exponent]` -or +or `pow([base], [exponent])` @@ -145,10 +168,16 @@ Examples: #### Factorial -Syntax: +Factorial works only with integer numbers. + +Syntax: `[number]!` +or + +`fact([number])` + Example: `6!` @@ -157,22 +186,104 @@ Example: `(21 / 3)!` +`fact(6)` + +`fact(fact(6))` + +#### Absolute value + +Syntax: + +`|[number]|` + +or + +`abs([number])` + +Examples: + +`|-6|` + +`||-12 + 4| + 5|` + +`abs(-6)` + +`abs(abs(-12 + 4) + 5)` + ### Functions -#### Square root +#### Abs Syntax: -`sqrt([number])` +`abs([number])` Examples: -`sqrt(8)` +`|-6|` -`sqrt(96 + 42)` +`||-12 + 4| + 5|` + +`abs(-6)` + +`abs(abs(-12 + 4) + 5)` + +#### Fact + +Syntax: + +`fact([number])` + +Example: + +`fact(6)` + +`fact(fact(6))` + +`fact(27 / 3)` + +#### Ln + +Ln is native logarithm (with Euler number base) + +Syntax: + +`ln([number])` + +Example: + +`ln(5)` + +`ln(5 + 2)` + +#### Log + +Syntax: + +`log([number], [log-base])` + +Example: + +`log(2, 2)` + +`log(54 + 8, 15)` + +#### Pow + +Syntax: + +`pow([number], [nth-power])` + +Example: + +`pow(5, 2)` + +#### Rand /todo #### Root +Root is computing real root of number + Syntax: `root([number], [nth-root]` @@ -182,3 +293,68 @@ Examples: `3 + 6` `-96 + 42` + +#### Square root + +Syntax: + +`sqrt([number])` + +Examples: + +`sqrt(8)` + +`sqrt(96 + 42)` + +## Getting started + +First of all you will start the Barbie calculator application. After that you will see working field: + +[No value set calculator](empty.png) + +*This main frame can be resized only with implicit proportions*. + +### Components + +In this subsection will be described main parts of calculator. + +#### Number systems converter + +If result is _integer type_, this result will be converted and showed in 4 different number systems (decimal, hexa-decimal, octal, binary). + +[Number systems bar with zero](system1.png) [Number systems bar with numbers](system2.png) + +#### Functions & expressions + +There is a function bar with build-in functions like *rand()* or *pow()* and expression field where you can write expressions to compute. + +[No expression](func1.png) [Functions + numbers](func2.png) + +#### Variables + +Barbie calculator also knows computing with variables, so you can set your own variables and use them in calculations. + +Be aware, because variable names are **case sensitive** + +[Variable bar](variable.png) + +#### Results + +There are 2 result windows one with actual result and one with whole result ??? /todo + +### Step by step /todo + + + + + + + + + + + + + + + diff --git a/doc/empty.png b/doc/empty.png new file mode 100644 index 0000000000000000000000000000000000000000..224ac69e97b9c0aa72fcfa43c0427c0022870512 GIT binary patch literal 26279 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 qx)%e~OZ|np{~kN%k3QW-t^D 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 B3MK#m literal 0 HcmV?d00001 diff --git a/doc/system1.png b/doc/system1.png new file mode 100644 index 0000000000000000000000000000000000000000..ba58a4f372f7e3d578afa800b7bce8e556f7c705 GIT binary patch literal 4500 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 literal 0 HcmV?d00001 diff --git a/doc/variable.png b/doc/variable.png new file mode 100644 index 0000000000000000000000000000000000000000..2e97de3c9652d79f4ea0f76784da4515ea78a5b1 GIT binary patch literal 3992 zcmeHKX;4#X77cBXD7!SGfQGmS5JE(RAhKy#&8D)1B>|B&kY$2S)zti(`8ieda~OhuQchW^-}6mx$J=AZ67~ANa4roeyB&TS9Sz3X$nq2><SDYNWokAQ- zQ?{$S7`5=|8RE&BEsP7FxxtUL{KNfsx?>$Sdv#~`K zq$KZ3w_hwfDsaZK_-S-&I+}-hb+z7xWGR^D^Pb{HdQ-ixbFNiM*WDV{YaLg9B~_yV zenx8X_f3vNJMM(zq`7L44izi@d|T$zcP|FmE4I@UHo}?Ze##d&5BG)#5%cIV>!{g5XwnTpicGr&mu*-Z2&9a!da6?)lqA& z(Yc<$q$6^Cf~O9fAXo7j|LU$)e8`dY%CJnm9k83tGx{x)E_ukPx9?cQy&Zzscc^oc z*2*5ulS8*sh=o@*`c-TX5DnO!!>vj0-L>Tjdx6hYgRQH+d}7XwdQeHNWZYCks8T~2 zs1YHboE5(>zb2eM7|azf+)5b@K(t-+>Gr+MSdFmQlyqjJoI;HU*@MI=Mw#;L6E?ef z)7_oNmphBIyFz#Q5mCU#zsWc8(@G8|usKJ8pWOZXA2U(D%@?O~D(Lf+Kx6J>xUl3b zY=^_fybG1>-Jckhm5@5xZ>Uv0+>|;vq)NuxLIf)E4_$TyUG;PPZ^-m-9PIY~>rACl zMRT(axl`*K*%pZL)1}K>fi47-jIf38Ol=w{^I@ojf9M|^+&(n1#5-C!9I=zR$QC(xDeJQVeZBjy)ou8`m$}8v92QD?n2G{Jaa&ViMcls2_f1RY>{8i+o zcOo%>f?F6Mg!z;s2`Jh;*vQ`fSkcm(OxOf3T;kNrOOmd>t9|H8e4kaiWB5{kzb{eb zk4s8Hd(*$idTSG{~54O#w)bNzJzgGkq0_#}8OJ%KQRIF!2WPU)SQ6Q*kM-1ZEi;eluM7H)D(o{*BBWaOW1t zB#(+sqGUhZpFvm(m6<-wU{%ky-PbH@ti8Ni-E?7?mf#H=gMIirD(^`sJe4jh&+v)S zo9~%*K+I)1$J_#9>!6U30lD%*A!qL9us#i~ z^mwn7H`X)0sN3r_n$t&U(b&vJ+vUG<25_sa{eQUX&*>x#K-&q~gUh?2W&0>ej+BWu zjviQOIrisnRXnFW)J4bp0rd9~t1?Bwn#Xkm)+H{Ry+6gZEJz#sf*$7esgT#Xi0CP16C8B|dv?cgg}Gk)$`robY=ky3k z9d@XBKG;0kFIawt+&=-_yIsg#{TkUi%PtGBVk|$NpruVvRI2@~N@KgnsW80f`zsuq zK-LJwD6b!LrCD(=ky~BWm$w~j1_@EOTh+2*m2GAW@JbUeW=2`lpfl+X=lm_WgMdCDUjT)aGGVWK-dY@zWu@VT~*kf?6wEa^x5 zu3R#CED9lowj?72HZSRYnTz@@U5}5o>K#5?Q&La>A$QJ9YdmnB9(h15&M%y;LCA+r zk+q664oxFZ>kYM#4|su+mc+qU+me^kBX>|!Aj3qmn%#1}z5U%O)*M%Cd4m!^sFE?` z@AF^}aO57}HkV!yGTwDePr0ceBy?TX(P#D&?8hw%vOzhYs>ipF7Ml)NXth>5a>kr& zdUye1L7O7Y#PFLDg35VNgZ)eckJ*gSANllUDOMe(ciFc2Gp7xELk})qJo{bI3b)t= zip|H+HuZC_Ux&@fJTa@P=^yZ_qPl3sJcrEhG6{urz#r|yzmT;xy|tpd+>IcfD3-MB zt1kLc?Au{4w>Dm&&Sk29WRjt*S_d!5LS!2p6Lc|H;Mm5GB!{i|kPVW57qig1DFM z*YvNb)T*FaMHzC^!THiRU-$FgK+rEPVQLMZZ@6K=^LkvW>`wL^E&y4yJgJ9z%So>f zmE30;7E;?x{@Izd{F)kG0rOmEu|JcaUYvLIqSeaQY}g%HH>T`T@pf1_o=?6NV}w_h zORL;dLa_$G=g+kd7t2Js;@z^MI#X^AhS@N>0`~CA#`_2VwZkS+h>A_Mp6VbZUs4h+lxrUA~5h*M;H|KxuFjY``2 literal 0 HcmV?d00001 From 7ee86a65c11754f53c80bbcbe25759d165cad5cb Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Sun, 2 Apr 2017 14:08:34 +0200 Subject: [PATCH 036/121] Fix weird expansion, do not expand brackets always - #51 --- src/calculator/ui/adapter.py | 1 - src/calculator/ui/qml/main.qml | 16 +++--- .../ui/types/expression/exp_analyzer.py | 49 +++++++++++++++---- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/calculator/ui/adapter.py b/src/calculator/ui/adapter.py index 2a61890..c6ebcd7 100644 --- a/src/calculator/ui/adapter.py +++ b/src/calculator/ui/adapter.py @@ -108,7 +108,6 @@ def setVariableValue(self, variable: str, value: NumericValue): @pyqtSlot(str) def removeVariable(self, variable_identifier: str) -> None: - print(variable_identifier) self._calculator.remove_variable(variable_identifier) self._variables = self._calculator.variables.copy() diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index cef5444..e51b02d 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -281,10 +281,6 @@ ApplicationWindow { y: expInput.cursorRectangle.y + expInput.cursorRectangle.height + expInput.y onItemChoosed: { - var end = expInput.cursorPosition - var start = end - currentText.length - - expInput.remove(start, end) expandExpression(currentItem["identifier"]) } @@ -330,8 +326,16 @@ ApplicationWindow { @param expressionKey Key of builtin expression or dynamic expression */ function expandExpression(expansionKey) { - expInput.remove(expInput.selectionStart, expInput.selectionEnd) - expInput.insert(expInput.selectionStart, exa.expandExpression(expansionKey)) + var expansion = exa.expandExpression(expansionKey) + + if(expInput.selectedText == "") { + var borders = exa.currentWordBorders() + expInput.remove(borders["start"], borders["end"]) + } + else + expInput.remove(expInput.selectionStart, expInput.selectionEnd) + + expInput.insert(expInput.selectionStart, expansion) } /** diff --git a/src/calculator/ui/types/expression/exp_analyzer.py b/src/calculator/ui/types/expression/exp_analyzer.py index bdb6c20..7fb9c0a 100644 --- a/src/calculator/ui/types/expression/exp_analyzer.py +++ b/src/calculator/ui/types/expression/exp_analyzer.py @@ -2,9 +2,9 @@ import re import inspect -from typing import Optional, Tuple, List +from typing import Optional, Tuple, List, Dict -from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot +from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot, QVariant from PyQt5.QtQuick import QQuickItem from calculator.core.solver import Solver @@ -153,16 +153,34 @@ def expandExpression(self, expansion: str) -> str: expansion = self.getExpansion(expansion) if expansion_type == Expansion.ExpansionType.BracketsPack: - return "{}({})".format(expansion, selected_text) + has_open_bracket = False + borders = self._currentWordBorders() + content = self._get_content() + + for i in range(borders["end"], len(content)): + current_char = content[i] + + if current_char == "(": + has_open_bracket = True + if not current_char.isspace(): + break + + if selected_text: + return "{}({})".format(expansion, selected_text) + elif has_open_bracket: + return expansion + else: + return "{}(".format(expansion) else: return expansion + @pyqtSlot(result=QVariant) + def currentWordBorders(self) -> QVariant: + return QVariant(self._currentWordBorders()) - @pyqtSlot(result=str) - def currentWord(self) -> str: + def _currentWordBorders(self) -> Dict[str, int]: """ - According to cursor in text it determines current word which is edited - :return: Current word + :return: Current word start and end """ content = self._get_content().replace(")", "") @@ -171,7 +189,7 @@ def currentWord(self) -> str: word_end = len(content) if self._get_cursor() == 0: - return "" + return {"start": -1, "end": -1} splitter_positions = list() for splitter in EXPRESSION_SPLITTERS: @@ -185,7 +203,20 @@ def currentWord(self) -> str: if right_splitters_pos: word_end = min(right_splitters_pos, key=lambda x: abs(x - cursor)) - return re.escape(content[word_start: word_end:].strip()) + return {"start": word_start, "end": word_end} + + @pyqtSlot(result=str) + def currentWord(self) -> str: + """ + According to cursor in text it determines current word which is edited + :return: Current word + """ + content = self._get_content().replace(")", "") + borders = self._currentWordBorders() + + if borders["start"] != -1 and borders["end"] != -1: + return re.escape(content[borders["start"]:borders["end"]:].strip()) + return "" @pyqtProperty(QQuickItem) def target(self) -> QQuickItem: From c2ded211ec5a8e083998a1a0d0ed76fe59cef634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Sun, 2 Apr 2017 15:16:42 +0200 Subject: [PATCH 037/121] Add syntax restrict for aug assignment [#49] --- src/calculator/core/parser/parser.py | 4 +-- .../core/parser/transform/__init__.py | 3 ++- .../parser/transform/aug_assign_restrict.py | 16 ++++++++++++ .../parser/transform/aug_assign_restrict.py | 26 +++++++++++++++++++ 4 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 src/calculator/core/parser/transform/aug_assign_restrict.py create mode 100644 src/tests/calculator/core/parser/transform/aug_assign_restrict.py diff --git a/src/calculator/core/parser/parser.py b/src/calculator/core/parser/parser.py index 580eb79..b2caf17 100644 --- a/src/calculator/core/parser/parser.py +++ b/src/calculator/core/parser/parser.py @@ -4,7 +4,7 @@ from calculator.core.parser.preprocessor import AbsoluteValuePreprocessor from calculator.core.parser.preprocessor import FactorialPreprocessor -from calculator.core.parser.transform import ComplexRestrictTransform +from calculator.core.parser.transform import ComplexRestrictTransform, AugAssignRestrictTransform from calculator.exceptions import ParserSyntaxError, SyntaxRestrictError @@ -17,9 +17,9 @@ class Parser(object): AbsoluteValuePreprocessor, FactorialPreprocessor, ) - # TODO solve problem with hexadecimals literals like '4A' DEFAULT_TRANSFORMS = ( ComplexRestrictTransform, + AugAssignRestrictTransform ) _transforms = () diff --git a/src/calculator/core/parser/transform/__init__.py b/src/calculator/core/parser/transform/__init__.py index d5878ec..ee76e74 100644 --- a/src/calculator/core/parser/transform/__init__.py +++ b/src/calculator/core/parser/transform/__init__.py @@ -1,5 +1,6 @@ # coding=utf-8 +from .aug_assign_restrict import AugAssignRestrictTransform from .complex_restrict import ComplexRestrictTransform -__all__ = ('ComplexRestrictTransform', ) +__all__ = ('ComplexRestrictTransform', 'AugAssignRestrictTransform') diff --git a/src/calculator/core/parser/transform/aug_assign_restrict.py b/src/calculator/core/parser/transform/aug_assign_restrict.py new file mode 100644 index 0000000..24cc592 --- /dev/null +++ b/src/calculator/core/parser/transform/aug_assign_restrict.py @@ -0,0 +1,16 @@ +# coding=utf-8 +from ast import AugAssign, NodeTransformer + +from calculator.exceptions import SyntaxRestrictError + + +class AugAssignRestrictTransform(NodeTransformer): + """ + Restrict for aug assignments like a += 5. + """ + + def visit_AugAssign(self, assign: AugAssign) -> None: + """ + :param assign: AugAssign node of found assign in processed AST + """ + raise SyntaxRestrictError('Aug assign is not actually supported.') diff --git a/src/tests/calculator/core/parser/transform/aug_assign_restrict.py b/src/tests/calculator/core/parser/transform/aug_assign_restrict.py new file mode 100644 index 0000000..cd03ea1 --- /dev/null +++ b/src/tests/calculator/core/parser/transform/aug_assign_restrict.py @@ -0,0 +1,26 @@ +# coding=utf-8 + +from ast import Num, AugAssign, Name, Add, Assign +from unittest import TestCase + +from calculator.core.parser.transform.aug_assign_restrict import AugAssignRestrictTransform +from calculator.exceptions import SyntaxRestrictError + + +class AugAssignRestrictTransformTest(TestCase): + def setUp(self): + self.transform = AugAssignRestrictTransform() + + def test_restrict_aug_assign(self): + with self.assertRaises(SyntaxRestrictError): + self.transform.visit( + AugAssign(target=Name(), op=Add(), value=Num()) + ) + + def test_no_restrict_assign(self): + try: + self.transform.visit( + Assign(targets=(Name(),), value=Num()) + ) + except Exception as e: + self.fail(str(e)) From b36c9c1f7338f60c79f550319fbb6e38822b915e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Sun, 2 Apr 2017 16:24:48 +0200 Subject: [PATCH 038/121] Fix stripped variable value value from source expression [#52] --- src/calculator/settings.py | 5 ++- src/calculator/ui/adapter.py | 4 +- src/calculator/utils/formatter.py | 65 ++++++++++++++++++++----------- 3 files changed, 48 insertions(+), 26 deletions(-) diff --git a/src/calculator/settings.py b/src/calculator/settings.py index 7c29680..9228863 100644 --- a/src/calculator/settings.py +++ b/src/calculator/settings.py @@ -7,6 +7,7 @@ ICON_SIZES = (16, 24, 32, 48, 256) SUPPORTED_BASES = (10, 2, 8, 16) + class BuiltinFunction(object): ABS = 'abs' FACT = 'fact' @@ -19,6 +20,8 @@ class BuiltinFunction(object): EXPRESSION_SPLITTERS = {"+", "-", "(", "*", "/"} + + class Expression(QObject): class ExpressionTypes(IntEnum): Function = 0 @@ -30,6 +33,7 @@ class ExpressionTypes(IntEnum): def singletonProvider(engine: QQmlEngine, script_engine: QJSEngine) -> QObject: return Expression() + class Expansion(QObject): class ExpansionType(IntEnum): Normal = 0 @@ -58,7 +62,6 @@ def singletonProvider(engine: QQmlEngine, script_engine: QJSEngine) -> QObject: (("(n)(y)(a)(n)",), "#ED1869 #F2BC1F #39BFC1 #672980".split()), ) - EXPRESSION_EXPANSIONS = ( (BuiltinFunction.ABS, 'abs', Expansion.ExpansionType.BracketsPack), (BuiltinFunction.FACT, 'fact', Expansion.ExpansionType.BracketsPack), diff --git a/src/calculator/ui/adapter.py b/src/calculator/ui/adapter.py index c6ebcd7..202f11b 100644 --- a/src/calculator/ui/adapter.py +++ b/src/calculator/ui/adapter.py @@ -7,9 +7,9 @@ from calculator import Variable, NumericValue from calculator.core.calculator import Calculator from calculator.exceptions import MathError, VariableError, UnsupportedBaseError +from calculator.settings import (BUILTIN_FUNCTIONS, HIGHLIGHT_RULES, EXPRESSION_SPLITTERS, Expression) from calculator.utils.formatter import Formatter from calculator.utils.translate import translate -from calculator.settings import (BUILTIN_FUNCTIONS, HIGHLIGHT_RULES, EXPRESSION_SPLITTERS, Expression) class UIAdapter(QObject): @@ -170,4 +170,4 @@ def _format_source_expression(variable: str, source_expression: str) -> str: :param source_expression: source expression for variable :return: striped source expression """ - return source_expression.lstrip(variable).lstrip() + return source_expression.replace(variable, '', 1).lstrip() diff --git a/src/calculator/utils/formatter.py b/src/calculator/utils/formatter.py index 4147925..276632f 100644 --- a/src/calculator/utils/formatter.py +++ b/src/calculator/utils/formatter.py @@ -1,21 +1,24 @@ # coding=utf-8 import re - from decimal import Decimal - from inspect import Signature from calculator import NumericValue -from calculator.settings import SUPPORTED_BASES - from calculator.exceptions import UnsupportedBaseError +from calculator.settings import SUPPORTED_BASES class Formatter(object): DEFAULT_CHARACTERS_LIMIT = 8 EXP_FORMAT = '{value} ×10{exp}' - BASE_CONVERTERS = {2: bin, 8: oct, 16: hex, 10: lambda x: str(x)} + BASE_CONVERTERS = { + 2: bin, + 8: oct, + 16: hex, + 10: str + } + CLASS_TO_TYPE_REGEX = re.compile(r"(?<=').+(?=')", re.VERBOSE) _EXP_DIVIDER = 'e' @@ -34,31 +37,47 @@ def format_number(cls, value: NumericValue, characters_limit: int = DEFAULT_CHAR @classmethod def format_number_in_base(cls, value: int, base: int) -> str: - if abs(value - int(value)) > 0: - raise ValueError() - if not base in SUPPORTED_BASES: + """ + Formats the given number to string in given base. + :param value: value, to be formatted + :param base: base of formatted value + :return: formatted value in wanted base + """ + if abs(value - int(value)): + raise ValueError('Only integer values can be formatted into other bases.') + if base not in SUPPORTED_BASES: raise UnsupportedBaseError("Base {} is not supported.".format(base)) - return cls.BASE_CONVERTERS[base](int(value)) + return cls.BASE_CONVERTERS.get(base, str)(int(value)) - @staticmethod - def format_function_args_spec(func_identifier: str, func_signature: Signature) -> str: + @classmethod + def format_function_args_spec(cls, func_identifier: str, func_signature: Signature) -> str: + """ + From function name and signature formats informal string to represent parameter types of given function. + :param func_identifier: function name + :param func_signature: Signature object + :return: formatted string + """ raw_args = [(arg_identifier, func_signature.parameters[arg_identifier].annotation) for arg_identifier in func_signature.parameters.keys()] - formatted_args_list = list() + formatted_args_list = list() for arg_identifier, arg_annotation in raw_args: arg_annotation = repr(arg_annotation) - if " Date: Sun, 2 Apr 2017 23:41:49 +0200 Subject: [PATCH 039/121] Fix wrong expansion after closing bracket --- src/calculator/ui/types/expression/exp_analyzer.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/calculator/ui/types/expression/exp_analyzer.py b/src/calculator/ui/types/expression/exp_analyzer.py index 7fb9c0a..ecae280 100644 --- a/src/calculator/ui/types/expression/exp_analyzer.py +++ b/src/calculator/ui/types/expression/exp_analyzer.py @@ -183,7 +183,7 @@ def _currentWordBorders(self) -> Dict[str, int]: :return: Current word start and end """ - content = self._get_content().replace(")", "") + content = self._get_content() cursor = self._get_cursor() word_start = 0 word_end = len(content) @@ -203,6 +203,12 @@ def _currentWordBorders(self) -> Dict[str, int]: if right_splitters_pos: word_end = min(right_splitters_pos, key=lambda x: abs(x - cursor)) + word = content[word_start:word_end:].strip() + + if word: + if word[-1] == ")" and self._get_cursor() != word_end: + word_end -= 1 + return {"start": word_start, "end": word_end} @pyqtSlot(result=str) @@ -211,11 +217,13 @@ def currentWord(self) -> str: According to cursor in text it determines current word which is edited :return: Current word """ - content = self._get_content().replace(")", "") + content = self._get_content() borders = self._currentWordBorders() if borders["start"] != -1 and borders["end"] != -1: - return re.escape(content[borders["start"]:borders["end"]:].strip()) + word = content[borders["start"]:borders["end"]:].strip() + + return re.escape(word) return "" @pyqtProperty(QQuickItem) From ee3aa3161f2c6a6793e5f48f90f3a1706d64e650 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Mon, 3 Apr 2017 00:15:34 +0200 Subject: [PATCH 040/121] Fix expansion throw error, fi content was empty - #55 --- src/calculator/ui/types/expression/exp_analyzer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/calculator/ui/types/expression/exp_analyzer.py b/src/calculator/ui/types/expression/exp_analyzer.py index ecae280..149bfba 100644 --- a/src/calculator/ui/types/expression/exp_analyzer.py +++ b/src/calculator/ui/types/expression/exp_analyzer.py @@ -157,6 +157,9 @@ def expandExpression(self, expansion: str) -> str: borders = self._currentWordBorders() content = self._get_content() + if borders["end"] == -1: + return "{}(".format(expansion) + for i in range(borders["end"], len(content)): current_char = content[i] From 615e62f49b218ebd464514b98cdf6c143cafc8d9 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Mon, 3 Apr 2017 00:17:31 +0200 Subject: [PATCH 041/121] Fix expansion deleted selected text - #56 --- src/calculator/ui/types/expression/exp_analyzer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calculator/ui/types/expression/exp_analyzer.py b/src/calculator/ui/types/expression/exp_analyzer.py index 149bfba..5c06fdd 100644 --- a/src/calculator/ui/types/expression/exp_analyzer.py +++ b/src/calculator/ui/types/expression/exp_analyzer.py @@ -157,7 +157,7 @@ def expandExpression(self, expansion: str) -> str: borders = self._currentWordBorders() content = self._get_content() - if borders["end"] == -1: + if borders["end"] == -1 and not selected_text: return "{}(".format(expansion) for i in range(borders["end"], len(content)): From 2c307b7bb2e9b1915418d4ba40f44027a1caac53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Mon, 3 Apr 2017 13:24:27 +0200 Subject: [PATCH 042/121] Fix condition in resources.py compiling --- src/calculator/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calculator/main.py b/src/calculator/main.py index 9e63eaf..e5ffe33 100755 --- a/src/calculator/main.py +++ b/src/calculator/main.py @@ -65,7 +65,7 @@ def update_qrc(): return True print('Change in UI files detected, recompiling resources.py...', file=sys.stderr) - if not exists(RESOURCES_FILE) or not access(RESOURCES_FILE, W_OK): + if exists(RESOURCES_FILE) and not access(RESOURCES_FILE, W_OK): print('Resources file {} not exists or is not writable, please call with write permissions.'.format( RESOURCES_FILE ), file=sys.stderr) @@ -75,7 +75,7 @@ def update_qrc(): print('Resources.py successfully recompiled.', file=sys.stderr) return True - print('Problem with compiling resources.py ({}).'.format(repr(e)), file=sys.stderr) + print('Problem with compiling resources.py.', file=sys.stderr) From 79ff37ea45d39971609d2363c60654c4d9b91b7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Mon, 3 Apr 2017 13:51:56 +0200 Subject: [PATCH 043/121] Add preprocessor that converts ^ to ** [#57] --- .../core/parser/preprocessor/caret_power.py | 12 +++++++++ src/calculator/ui/adapter.py | 2 +- .../core/parser/preprocessor/caret_power.py | 27 +++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 src/calculator/core/parser/preprocessor/caret_power.py create mode 100644 src/tests/calculator/core/parser/preprocessor/caret_power.py diff --git a/src/calculator/core/parser/preprocessor/caret_power.py b/src/calculator/core/parser/preprocessor/caret_power.py new file mode 100644 index 0000000..a1409a9 --- /dev/null +++ b/src/calculator/core/parser/preprocessor/caret_power.py @@ -0,0 +1,12 @@ +# coding=utf-8 + + +class CaretPowerPreprocessor(object): + """ + Preprocessor, which converts all carets as ^ (as xor) to ** (as power). + """ + CARET_SIGN = '^' + POWER_SIGN = '**' + + def __call__(self, expression: str) -> str: + return expression.replace(self.CARET_SIGN, self.POWER_SIGN) diff --git a/src/calculator/ui/adapter.py b/src/calculator/ui/adapter.py index 202f11b..cad7425 100644 --- a/src/calculator/ui/adapter.py +++ b/src/calculator/ui/adapter.py @@ -74,7 +74,7 @@ def process(self, expression: str) -> None: self.error.emit(translate("Adapter", "Result is too big.")) except TypeError: self.error.emit(translate("Adapter", "Parameters count does not match function.")) - except NotImplemented: + except NotImplementedError: self.error.emit(translate("Adapter", "Function is not defined.")) @pyqtSlot(str, int) diff --git a/src/tests/calculator/core/parser/preprocessor/caret_power.py b/src/tests/calculator/core/parser/preprocessor/caret_power.py new file mode 100644 index 0000000..f603086 --- /dev/null +++ b/src/tests/calculator/core/parser/preprocessor/caret_power.py @@ -0,0 +1,27 @@ +# coding=utf-8 +from unittest import TestCase + +from calculator.core.parser.preprocessor.caret_power import CaretPowerPreprocessor + + +class CaretPowerPreprocessorTest(TestCase): + def setUp(self): + self.preprocessor = CaretPowerPreprocessor() + + def test_simple(self): + self.assertEqual( + self.preprocessor('2 ^ 4'), + '2 ** 4' + ) + + def test_advanced(self): + self.assertEqual( + self.preprocessor('(2 ^ 5) ^ 6'), + '(2 ** 5) ** 6' + ) + + def test_power_unchanged(self): + self.assertEqual( + self.preprocessor('2 ** 9'), + '2 ** 9' + ) From 37bfa74bff38efc39c68e087faf53e24e2296b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Mon, 3 Apr 2017 15:32:52 +0200 Subject: [PATCH 044/121] Refactor of incorrect function call signalizing --- src/calculator/core/solver/solver.py | 11 ++++- src/calculator/exceptions.py | 8 +++ src/calculator/ui/adapter.py | 6 +-- src/calculator/utils/formatter.py | 37 ++++++++------ .../calculator/core/solver/solver_resolve.py | 49 +++++++++++++++++++ 5 files changed, 92 insertions(+), 19 deletions(-) diff --git a/src/calculator/core/solver/solver.py b/src/calculator/core/solver/solver.py index e825f56..ecfd301 100644 --- a/src/calculator/core/solver/solver.py +++ b/src/calculator/core/solver/solver.py @@ -1,10 +1,12 @@ # coding=utf-8 from ast import BinOp, Add, Num, Sub, Div, Mult, Call, AST, UnaryOp, USub, Name, Pow, FloorDiv, Mod +from inspect import Signature from typing import Dict, Union, Type, Set, Optional from calculator import BinaryNumericFunction, NumericFunction, NumericValue, Variable from calculator.core.math import Math from calculator.core.parser import Parser +from calculator.exceptions import InvalidFunctionCallError from calculator.settings import BuiltinFunction from calculator.utils import method_single_dispatch @@ -114,7 +116,14 @@ def _(self, call: Call) -> NumericValue: if not callable(function): raise NotImplementedError(call.func.id) - return function(*map(self._resolve, call.args)) + signature = Signature.from_callable(function) + params = tuple(map(self._resolve, call.args)) + try: + signature.bind(*params) + except TypeError as e: + raise InvalidFunctionCallError('Given parameters does not correspond to function signature.') from e + + return function(*params) @_resolve.register(Num) def _(self, num: Num) -> NumericValue: diff --git a/src/calculator/exceptions.py b/src/calculator/exceptions.py index a30fce1..897856f 100644 --- a/src/calculator/exceptions.py +++ b/src/calculator/exceptions.py @@ -24,11 +24,13 @@ class VariableError(Exception): Exception for signalizing of invalid definition for calculator variable. """ + class UnsupportedBaseError(Exception): """ Raised when trying convert number into unsupported base """ + class VariableRemoveRestrictError(VariableError): """ Raised from Calculator, if there are any depending variables to variable to remove. @@ -37,3 +39,9 @@ class VariableRemoveRestrictError(VariableError): def __init__(self, dependencies, *args, **kwargs): self._dependencies = dependencies super().__init__(*args, **kwargs) + + +class InvalidFunctionCallError(TypeError): + """ + Raised to signalize of call function with incorrect count of parameters. + """ diff --git a/src/calculator/ui/adapter.py b/src/calculator/ui/adapter.py index cad7425..1b5e248 100644 --- a/src/calculator/ui/adapter.py +++ b/src/calculator/ui/adapter.py @@ -6,7 +6,7 @@ from calculator import Variable, NumericValue from calculator.core.calculator import Calculator -from calculator.exceptions import MathError, VariableError, UnsupportedBaseError +from calculator.exceptions import MathError, VariableError, UnsupportedBaseError, InvalidFunctionCallError from calculator.settings import (BUILTIN_FUNCTIONS, HIGHLIGHT_RULES, EXPRESSION_SPLITTERS, Expression) from calculator.utils.formatter import Formatter from calculator.utils.translate import translate @@ -72,7 +72,7 @@ def process(self, expression: str) -> None: self.error.emit(translate("Adapter", "Error in defining variable.")) except OverflowError: self.error.emit(translate("Adapter", "Result is too big.")) - except TypeError: + except InvalidFunctionCallError: self.error.emit(translate("Adapter", "Parameters count does not match function.")) except NotImplementedError: self.error.emit(translate("Adapter", "Function is not defined.")) @@ -155,7 +155,7 @@ def expressionSplitters(self) -> QVariant: def variables(self) -> QVariant: return QVariant(list(self._calculator.variables.keys())) - #TODO notify + # TODO notify @pyqtProperty(QVariant, notify=identifiersTypesChanged) def identifiersTypes(self): return [{"identifier": var_identifier, "type": Expression.ExpressionTypes.Variable} diff --git a/src/calculator/utils/formatter.py b/src/calculator/utils/formatter.py index 276632f..6c84f9b 100644 --- a/src/calculator/utils/formatter.py +++ b/src/calculator/utils/formatter.py @@ -1,7 +1,7 @@ # coding=utf-8 import re from decimal import Decimal -from inspect import Signature +from inspect import Signature, Parameter from calculator import NumericValue from calculator.exceptions import UnsupportedBaseError @@ -58,26 +58,33 @@ def format_function_args_spec(cls, func_identifier: str, func_signature: Signatu :param func_signature: Signature object :return: formatted string """ - raw_args = [(arg_identifier, func_signature.parameters[arg_identifier].annotation) - for arg_identifier in func_signature.parameters.keys()] + raw_args = [ + (arg_identifier, + func_signature.parameters[arg_identifier].default, + func_signature.parameters[arg_identifier].annotation, + ) + for arg_identifier in func_signature.parameters.keys() + ] - formatted_args_list = list() - for arg_identifier, arg_annotation in raw_args: - arg_annotation = repr(arg_annotation) + formatted_args = list() + for identifier, default, annotation in raw_args: + annotation = repr(annotation) - formatted_args_list.append(( - arg_identifier, - cls.CLASS_TO_TYPE_REGEX.search(arg_annotation).group(0) - if " Date: Mon, 3 Apr 2017 15:32:13 +0200 Subject: [PATCH 045/121] Fix when result was too big, then it would not display in variables and base widget --- src/calculator/ui/adapter.py | 7 +++---- src/calculator/utils/formatter.py | 4 +++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/calculator/ui/adapter.py b/src/calculator/ui/adapter.py index 1b5e248..7f1736e 100644 --- a/src/calculator/ui/adapter.py +++ b/src/calculator/ui/adapter.py @@ -27,10 +27,9 @@ class UIAdapter(QObject): for func in BUILTIN_FUNCTIONS] @pyqtSlot(float, int, result=str) - def convertToBase(self, value: NumericValue, base: int) -> str: + def convertToBase(self, value: str, base: int) -> str: try: - v = self._formatter.format_number_in_base(value, base) - return v + return self._formatter.format_number_in_base(value, base) except UnsupportedBaseError as e: return translate("Adapter", "Unsupported base.") except ValueError as e: @@ -46,7 +45,7 @@ def process(self, expression: str) -> None: self.identifiersTypesChanged.emit(self.identifiersTypes) self.processed.emit(QVariant({ "result": None if result is None else self._formatter.format_number(result, 16), - "unformattedResult": result, + "unformattedResult": str(result), "variables": { key: dict( value=self._formatter.format_number(value), diff --git a/src/calculator/utils/formatter.py b/src/calculator/utils/formatter.py index 6c84f9b..983462b 100644 --- a/src/calculator/utils/formatter.py +++ b/src/calculator/utils/formatter.py @@ -36,13 +36,15 @@ def format_number(cls, value: NumericValue, characters_limit: int = DEFAULT_CHAR ) @classmethod - def format_number_in_base(cls, value: int, base: int) -> str: + def format_number_in_base(cls, value: str, base: int) -> str: """ Formats the given number to string in given base. :param value: value, to be formatted :param base: base of formatted value :return: formatted value in wanted base """ + value = float(value) + if abs(value - int(value)): raise ValueError('Only integer values can be formatted into other bases.') if base not in SUPPORTED_BASES: From 2e122bfe1e6e3a707673b775fb8e9123979d7081 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Mon, 3 Apr 2017 15:32:42 +0200 Subject: [PATCH 046/121] Refactor expandExpression in ExpAnalyzer --- .../ui/types/expression/exp_analyzer.py | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/calculator/ui/types/expression/exp_analyzer.py b/src/calculator/ui/types/expression/exp_analyzer.py index 5c06fdd..d8a3671 100644 --- a/src/calculator/ui/types/expression/exp_analyzer.py +++ b/src/calculator/ui/types/expression/exp_analyzer.py @@ -152,30 +152,27 @@ def expandExpression(self, expansion: str) -> str: expansion_type = self.expansionType(expansion) expansion = self.getExpansion(expansion) - if expansion_type == Expansion.ExpansionType.BracketsPack: - has_open_bracket = False - borders = self._currentWordBorders() - content = self._get_content() + if expansion_type != Expansion.ExpansionType.BracketsPack: + return expansion - if borders["end"] == -1 and not selected_text: - return "{}(".format(expansion) + borders = self._currentWordBorders() + content = self._get_content() - for i in range(borders["end"], len(content)): - current_char = content[i] + if borders["end"] == -1 and not selected_text: + return "{}(".format(expansion) - if current_char == "(": - has_open_bracket = True - if not current_char.isspace(): - break + for i in range(borders["end"], len(content)): + current_char = content[i] - if selected_text: - return "{}({})".format(expansion, selected_text) - elif has_open_bracket: + if current_char == "(": return expansion - else: - return "{}(".format(expansion) + if not current_char.isspace(): + break + + if selected_text: + return "{}({})".format(expansion, selected_text) else: - return expansion + return "{}(".format(expansion) @pyqtSlot(result=QVariant) def currentWordBorders(self) -> QVariant: From 7b1838f25f29f21ba1a7dc338de5fbaf2bb34605 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Mon, 3 Apr 2017 15:34:01 +0200 Subject: [PATCH 047/121] Cleanings --- src/calculator/ui/adapter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/calculator/ui/adapter.py b/src/calculator/ui/adapter.py index 7f1736e..1e46d3b 100644 --- a/src/calculator/ui/adapter.py +++ b/src/calculator/ui/adapter.py @@ -154,7 +154,6 @@ def expressionSplitters(self) -> QVariant: def variables(self) -> QVariant: return QVariant(list(self._calculator.variables.keys())) - # TODO notify @pyqtProperty(QVariant, notify=identifiersTypesChanged) def identifiersTypes(self): return [{"identifier": var_identifier, "type": Expression.ExpressionTypes.Variable} From 92840df0a25a14f33d77ac8b25bc50bfc5823c7c Mon Sep 17 00:00:00 2001 From: Meigurad Date: Mon, 3 Apr 2017 22:57:20 +0200 Subject: [PATCH 048/121] Updating documentation Rewriting to czech and completing few chapters --- doc/complete.png | Bin 0 -> 27036 bytes doc/debugging.png | Bin 117029 -> 0 bytes doc/doc.md | 341 ++++++++++++-------------------------------- doc/enfunc1.png | Bin 0 -> 35745 bytes doc/enfunc2.png | Bin 0 -> 36771 bytes doc/func2.png | Bin 8957 -> 36851 bytes doc/long_result.png | Bin 0 -> 60292 bytes doc/many_vars.png | Bin 0 -> 50550 bytes 8 files changed, 92 insertions(+), 249 deletions(-) create mode 100644 doc/complete.png delete mode 100644 doc/debugging.png create mode 100644 doc/enfunc1.png create mode 100644 doc/enfunc2.png create mode 100644 doc/long_result.png create mode 100644 doc/many_vars.png diff --git a/doc/complete.png b/doc/complete.png new file mode 100644 index 0000000000000000000000000000000000000000..e09ce917c07ef35471566e20b5b62a99ee67ac71 GIT binary patch literal 27036 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 literal 0 HcmV?d00001 diff --git a/doc/debugging.png b/doc/debugging.png deleted file mode 100644 index 388cbeb828acdc08cfa8eda4c3c8d72ee9e7ccbe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 117029 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ⅆg`kY=s@J4pQF4IOBJgSh(tnG%k5Pdahe<%b@r; zIWd7S0Xh&g&s@}hyKKI-c^j@8n&*7xK>OjC5tW*1f=1n=G$>BT)AZ)9r3@c_p+Nt< zRDi>wJJam=WJ?IyT7IBu@jy7EEF@&`!(hxiTlZ>eGis&TZ66UaQm}Y7HHQ25bHzzMcEI2vMWsfwE%oOVM*V3sjHuk4Y^X$o zrR!HmqJ|#~0Dv+*qa-4-*wL2cPi=;$5SZa12WYnqix;4`_{C2-sC{9E8M;H!Qq$ae zL{Ehcq#(av0*d9U!oQI{gVfsD*)1WmnkKB^2q+$4YGbpoO8xIpw`AG#f_KQ5*T;m0 z^h4ejgl!rgXhHFbCcmg<;yHL3ove;ewhtZ%HJexwJlp5Ab#}au;dm-DB2-jn%-2qH zIDHe&(L{qFt6?M(67g3uh{ zKZ$r<6uE5v+wJ9eUFYhHK8EJ--!=JcdDx-pHg^iUQlhu&qKl)L_3V4^rA1^p0`o+p zc*an;WyFZ9izhq8B5I1pFjcT0x4PBcSIT{U<&!J(6fLLl*z|rUnIYu)_v>-jctybb z0E;JFA8)jNlgG*X^cSJbwJy2-_Dhy;!!OxU?8tN`;~FYPSO@}uoOj|Z}0woEc0DQlVu>@#D5xuStrBcG+p=qKDXpz*TMLEtpBmp zTO-1VuNpkhx3@9=J-IBb)yMxFq#pAe{_o54qMPu4yT&flacdxcnC#!J2T9oAgMq=a z$7cwYfAQG`UGV}8{p1t_4 znvs%1Oh9mTy$4mz5lyT28xC#7rrwBDeh#m#SMa}`w@fV;k`D<9;Yh09NMDh8dE`*B z!1u7QX7gI0_j6|uAFmSkyZxZG^UGljFR#>@Gqu#hrq$kmdZ>tolJeKwToYoO^xsyo zy6?y@alY>`@*#)Cr=(aThUqhONVMqg-u1`=@W-(qus0}$8_50VJB)d~g`;>(6xH_Z zkKW&_g8816A$4_^hlv6QQBuD)A1-FF?4GM+M$10_?NtAe5c}meooqnqj5ly|kgKY% zb0ZRG8t!1%n1sU-N~gpIXZIZ3$B(yP{@X?V^y7!MB!d{#9?J-E^CtX_^mD;Kn{s-L(` zA9A4p?G~Ed`W`F$ZS8Qn(Z-NUxR?rjoQha??LGD8e|b`Sryr0Vq(8Md99pALu!Oy& zzw9e4Tj=ooq`wf<9Bz2w=HleUoj8y=r{ifOdfY8TZ7wCuQx0|_)4}r+J5O`%*|8$Q zhM~VA{^ttmJWDV#+=H2GCT6I&s#p2X;BwXLXA*NbZ@v|iTbv^y@}g>|*|(4OEG(1` zjwIjt4M>l&yhM9NPGW#pzjt!53z}J8O%aOId8h0rbiOc2iy2>>8)+{ZpH;CkTIhTQ zQUl(ATjFEVNUPwJj|Cq#dM4kNDIOE7c-llH7S*GFOBWL)$DshN$Fi#14pLbJ$k^@n>Cx?I`;l>(GM!5Be&I0+(X97pbLZ<(5{xb-G%3Jw{Mxsa;M-j-S=h7_IL9@j{=9+VFa z9X^$S9(D{u2r&&EPy3)P4K-Eu?k7Z-Ye#vYrKc&>$L>xXe0M9o{prV;uN>=Q?z~-9 zWB{LPH`FPBKtA23_bbQPP0ihE!yHS?)dCfxjfEe`MJrojxrY)^9}$;{+31G{L07Q1 zZ(w_FFv;=41mPsYqd+WKr8V1;2qR&>z2w}_?yO0F;mSZ|4kSn{8YfnCEKT=PPk*?| z_VfN*7hh1a9|~n=wH35d(}5sG*5gYjBs4oF?uMF=bx~tS$Yd;s0C#-<>buveHK&Zk zXrzK4Is0-v<#ZN&f!bG_Yp?59bxKfIX=!FiNG+ek@m3o_`;qAnx2wcaR+%a8;_j(C!Q9pL__y(gCo>3qLue8XE#{Qpe6=t`}|c-T%;_g9hg^}K?j1J`G*KJU%NZZ zw_>V2JuqPXH~nR)-cA>>jqdVBiQrC86dKCPict(|I1%*`wp}xEYnw5_&a8r{Ep3FlSg

%1

'.arg(section["title"]) + if(typeof section["subtitle"] != "undefined") + content += '

%1

'.arg(section["subtitle"]) + if(typeof section["content"] != "undefined") + content += "

%1

".arg(section["content"]) + } + text.text += "%1%2".arg(text.style).arg(content) + } + } + } + + Rectangle { //scrollbar + y: flick.visibleArea.yPosition * flick.height + // TODO + width: 4 + height: flick.visibleArea.heightRatio * flick.height + + color: "red" + + anchors.right: parent.right + } +} From 6b449f9a2c68caa913e04d23e6c05e6b45d467ba Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Tue, 11 Apr 2017 17:14:29 +0200 Subject: [PATCH 102/121] Style and connect with menu bar of main window Help window - #70 --- src/calculator/ui/assets/styles/UIStyles.qml | 8 ++++ src/calculator/ui/qml/main.qml | 15 +++++-- src/calculator/ui/qml/windows/Help.qml | 41 ++++++++++++++------ 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/src/calculator/ui/assets/styles/UIStyles.qml b/src/calculator/ui/assets/styles/UIStyles.qml index b33980c..e2ef56d 100644 --- a/src/calculator/ui/assets/styles/UIStyles.qml +++ b/src/calculator/ui/assets/styles/UIStyles.qml @@ -187,4 +187,12 @@ QtObject { property color titleColor: "#ED1946" property font font: Qt.font({family: "AbeeZee"}) } + + property QtObject helpWindow: QtObject { + property color textColor: "gray" + property color titleColor: "#ED1946" + property color subtitleColor: "#2C2C2C" + property color scrollBarColor: "#2C2C2C" + property font font: Qt.font({family: "AbeeZee"}) + } } diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index 959bf97..89cbcc0 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -58,9 +58,10 @@ ApplicationWindow { menuItemHeight: 19 onItemChoosed: { - if(item == "About") { + if(item == "About") aboutWindow.show() - } + if(item == "Help") + helpWindow.show() } } @@ -363,7 +364,15 @@ ApplicationWindow { } Windows.Help { - width: 300 + id: helpWindow + + textColor: StyleSettings.helpWindow.textColor + titleColor: StyleSettings.helpWindow.titleColor + subtitleColor: StyleSettings.helpWindow.subtitleColor + scrollBarColor: StyleSettings.helpWindow.scrollBarColor + textFont.family: StyleSettings.helpWindow.font.family + + width: 500 height: 600 } diff --git a/src/calculator/ui/qml/windows/Help.qml b/src/calculator/ui/qml/windows/Help.qml index e52cbd1..9c87744 100644 --- a/src/calculator/ui/qml/windows/Help.qml +++ b/src/calculator/ui/qml/windows/Help.qml @@ -2,8 +2,25 @@ import QtQuick 2.5 import QtQuick.Controls 2.0 import "../../assets/contents/help.js" as HelpContent +/** + Component to display help content. + */ ApplicationWindow { - visible: true + id: window + + /// Color of content text + property alias textColor: text.color + /// Text color of title + property color titleColor + /// Text color of subtitle + property color subtitleColor + /// Used font + property font textFont + /// Color of scrollbar + property alias scrollBarColor: scrollBar.color + + title: qsTr("Help") + visible: false Flickable { id: flick @@ -33,17 +50,18 @@ ApplicationWindow { font-size: %5px; } ' - .arg("red") - .arg("blue") - .arg(15) - .arg(10) - .arg(25) - .arg(35) + .arg(titleColor) + .arg(subtitleColor) + .arg(15) // body margin + .arg(10) // text margins + .arg(15) // text font size + .arg(20) // title font size width: parent.width - 7 textFormat: Text.RichText wrapMode: TextEdit.WordWrap text: '' + font.family: window.font.family Component.onCompleted: { var content = "" @@ -63,14 +81,13 @@ ApplicationWindow { } } - Rectangle { //scrollbar + Rectangle { + id: scrollBar + y: flick.visibleArea.yPosition * flick.height - // TODO - width: 4 + width: 3 height: flick.visibleArea.heightRatio * flick.height - color: "red" - anchors.right: parent.right } } From 24ef87d87bdbc223a7c2dc7fdec7c485cf88e472 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Tue, 11 Apr 2017 19:28:57 +0200 Subject: [PATCH 103/121] Add hiding of scrollbar in help window --- src/calculator/ui/qml/windows/Help.qml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/calculator/ui/qml/windows/Help.qml b/src/calculator/ui/qml/windows/Help.qml index 9c87744..2214fe6 100644 --- a/src/calculator/ui/qml/windows/Help.qml +++ b/src/calculator/ui/qml/windows/Help.qml @@ -84,10 +84,16 @@ ApplicationWindow { Rectangle { id: scrollBar + opacity: (flick.visibleArea.heightRatio === 1) ?0 :1 + y: flick.visibleArea.yPosition * flick.height width: 3 height: flick.visibleArea.heightRatio * flick.height anchors.right: parent.right + + Behavior on opacity { + NumberAnimation { duration: 400 } + } } } From 7400f9c3982af717e8d6956fdf45829ca1c33c7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Tue, 11 Apr 2017 20:31:40 +0200 Subject: [PATCH 104/121] Fix adapter remove dependent variable, fix formatter --- src/calculator/ui/adapter.py | 20 ++++++++++++++------ src/calculator/utils/formatter.py | 10 +++++----- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/calculator/ui/adapter.py b/src/calculator/ui/adapter.py index 904aecd..07b42d4 100644 --- a/src/calculator/ui/adapter.py +++ b/src/calculator/ui/adapter.py @@ -6,7 +6,8 @@ from calculator import Variable, NumericValue from calculator.core.calculator import Calculator -from calculator.exceptions import MathError, VariableError, UnsupportedBaseError, InvalidFunctionCallError +from calculator.exceptions import MathError, VariableError, UnsupportedBaseError, InvalidFunctionCallError, \ + VariableRemoveRestrictError from calculator.settings import (BUILTIN_FUNCTIONS, HIGHLIGHT_RULES, EXPRESSION_SPLITTERS, Expression) from calculator.utils.formatter import Formatter from calculator.utils.translate import translate @@ -46,11 +47,11 @@ def process(self, expression: str) -> None: self.identifiersTypesChanged.emit(self.identifiersTypes) self.processed.emit(QVariant({ - "result": None if result is None else self._formatter.format_number(result, 16), + "result": None if result is None else self._formatter.format_number(result, characters_limit=12), "unformattedResult": str(result), "variables": { key: dict( - value=self._formatter.format_number(value), + value=self._formatter.format_number(value, characters_limit=8), expression=self._format_source_expression( variable=key, source_expression=expression @@ -111,10 +112,17 @@ def setVariableValue(self, variable: str, value: NumericValue): } })) - @pyqtSlot(str) - def removeVariable(self, variable_identifier: str) -> None: - self._calculator.remove_variable(variable_identifier) + @pyqtSlot(str, result=bool) + def removeVariable(self, variable_identifier: str) -> bool: + try: + self._calculator.remove_variable(variable_identifier) + except VariableRemoveRestrictError as e: + # TODO: deps! + self.error.emit(translate("Adapter", "#TODO")) + return False + self._variables = self._calculator.variables.copy() + return True @pyqtProperty(QVariant) def highlightRules(self) -> QVariant: diff --git a/src/calculator/utils/formatter.py b/src/calculator/utils/formatter.py index 7ec204b..ceb4136 100644 --- a/src/calculator/utils/formatter.py +++ b/src/calculator/utils/formatter.py @@ -9,8 +9,6 @@ class Formatter(object): - DEFAULT_CHARACTERS_LIMIT = 8 - EXP_FORMAT = '{value}×10{exp}' BASE_CONVERTERS = { 2: bin, @@ -23,13 +21,15 @@ class Formatter(object): _EXP_DIVIDER = 'e' @classmethod - def format_number(cls, value: NumericValue, characters_limit: int = DEFAULT_CHARACTERS_LIMIT) -> str: + def format_number(cls, value: NumericValue, characters_limit: int) -> str: stringed = str(value) - if cls._EXP_DIVIDER not in stringed and not (len(stringed) > characters_limit): + if not (cls._EXP_DIVIDER in stringed or len(stringed) > characters_limit): return stringed[:characters_limit] - value, exp = '{:.2e}'.format(Decimal.from_float(value)).split(cls._EXP_DIVIDER) + value, exp = ('{:.%de}' % (max((characters_limit - 4, 2)))).format( + Decimal.from_float(value), + ).split(cls._EXP_DIVIDER) # type: str, str return cls.EXP_FORMAT.format( value=value, exp=exp.lstrip('+') From 6178d06c23f11372892ae5017626ac9e5fd18fbb Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Tue, 11 Apr 2017 20:55:52 +0200 Subject: [PATCH 105/121] Fix do not delete variable if it is dependency of other variable --- .../ui/qml/containers/VariablesPanel.qml | 8 +++++++ src/calculator/ui/qml/item/VariableItem.qml | 14 +++++++---- src/calculator/ui/qml/main.qml | 5 +++- .../ui/qml/managers/VariablesManager.qml | 24 ++++++++++++++++++- 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/calculator/ui/qml/containers/VariablesPanel.qml b/src/calculator/ui/qml/containers/VariablesPanel.qml index 0e7ca33..557b318 100644 --- a/src/calculator/ui/qml/containers/VariablesPanel.qml +++ b/src/calculator/ui/qml/containers/VariablesPanel.qml @@ -220,4 +220,12 @@ Item { function modifyVariable(identifier, expression, value) { manager.setVariable(identifier, expression, value) } + + /** + Delete variable item + @param variableIdentifier Identifier of variable + */ + function deleteVariable(variableIdentifier) { + manager.deleteVariable(variableIdentifier) + } } diff --git a/src/calculator/ui/qml/item/VariableItem.qml b/src/calculator/ui/qml/item/VariableItem.qml index fc1ed6c..a7337b8 100644 --- a/src/calculator/ui/qml/item/VariableItem.qml +++ b/src/calculator/ui/qml/item/VariableItem.qml @@ -30,6 +30,10 @@ Item { @param data String requested to be expanded */ signal overwriteRequest(string data) + /** + Used as function to delete item + */ + signal deleteItem() /// Background color property alias color: content.color @@ -90,9 +94,11 @@ Item { anchors.leftMargin: -optionsMenu.menuWidth onValueSetRequest: component.valueSetRequest(component.variableIdentifier, value) - onDeleteRequest: SequentialAnimation { - NumberAnimation { target: component; property: "opacity"; from: 1; to: 0; duration: 200 } - ScriptAction { script: component.deleteRequest(component.variableIdentifier) } - } + onDeleteRequest: component.deleteRequest(component.variableIdentifier) + } + + onDeleteItem: SequentialAnimation { + NumberAnimation { target: component; property: "opacity"; from: 1; to: 0; duration: 200 } + ScriptAction { script: component.destroy() } } } diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index 89cbcc0..fe2bb42 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -128,7 +128,10 @@ ApplicationWindow { anchors.top: parent.top anchors.right: parent.right - onDeleteVariableRequest: Calculator.removeVariable(identifier) + onDeleteVariableRequest: { + if(Calculator.removeVariable(identifier)) + variablePanel.deleteVariable(identifier) + } onSetVariableRequest: Calculator.setVariableValue(identifier, value) onExpandRequest: forceExpandExpression(data) onOverwriteRequest: overwriteExpression(data) diff --git a/src/calculator/ui/qml/managers/VariablesManager.qml b/src/calculator/ui/qml/managers/VariablesManager.qml index 87cbd48..08a1b27 100644 --- a/src/calculator/ui/qml/managers/VariablesManager.qml +++ b/src/calculator/ui/qml/managers/VariablesManager.qml @@ -74,6 +74,19 @@ QtObject { variable.variableValue = value } + /** + Unregister variable item from manager + @param variableIdentifier Identifier of variable + */ + function _unregisterVariable(variableIdentifier) { + for(var key in manager._variableItems) { + + if(manager._variableItems[key].variableIdentifier == variableIdentifier){ + manager._variableItems.splice(key, 1); + } + } + } + /** Finds variable item in system @param variableIdentifier Identifier of variable @@ -97,10 +110,19 @@ QtObject { Delete variable item @param variableIdentifier Identifier of variable */ + function deleteVariable(variableIdentifier) { + var object = findVariable(variableIdentifier) + _unregisterVariable(variableIdentifier) + object.deleteItem() + } + + /** + Request deletion of variable item + @param variableIdentifier Identifier of variable + */ function handleDeleteRequest(variableIndetifier) { var object = manager.findVariable(variableIndetifier) - object.destroy() manager.deleteItem(variableIndetifier) } From 6a279bc01fd264717139b94351c333b86ac49d1b Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Tue, 11 Apr 2017 21:54:53 +0200 Subject: [PATCH 106/121] New AppWindow - attempt to resize window in ratio --- src/calculator/ui/app/calculator_app.py | 2 ++ src/calculator/ui/qml/main.qml | 17 ++++++++----- src/calculator/ui/types/window/__init__.py | 4 +++ src/calculator/ui/types/window/app_window.py | 26 ++++++++++++++++++++ 4 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 src/calculator/ui/types/window/__init__.py create mode 100644 src/calculator/ui/types/window/app_window.py diff --git a/src/calculator/ui/app/calculator_app.py b/src/calculator/ui/app/calculator_app.py index 0eed78b..945a076 100644 --- a/src/calculator/ui/app/calculator_app.py +++ b/src/calculator/ui/app/calculator_app.py @@ -14,6 +14,7 @@ from calculator.ui.types.core import Sides from calculator.ui.types.expression import ExpSyntaxHighlighter, ExpAnalyzer from calculator.ui.types.qmlwrapper.utils import TypeRegister +from calculator.ui.types.window import AppWindow from calculator.settings import Expansion, ICON_SIZES, Expression if platform.system() == "Linux": # Needed for platform.linux_distribution, which is not available on Windows and OSX @@ -71,6 +72,7 @@ def registerTypes(): qmlRegisterSingletonType(Expression, "Expression", 1, 0 ,"Expression", Expression.singletonProvider) qmlRegisterSingletonType(UIAdapter, "Calculator", 1, 0, "Calculator", UIAdapter.singletonProvider) TypeRegister.register_type(ExpSyntaxHighlighter) + TypeRegister.register_type(AppWindow) TypeRegister.register_type(ExpAnalyzer) def run(self) -> int: diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index fe2bb42..1c284ce 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -8,6 +8,7 @@ import Calculator 1.0 import Expansion 1.0 import Expression 1.0 import StyleSettings 1.0 +import AppWindow 1.0 import "controls" as Control import "menu" @@ -18,21 +19,25 @@ import "windows" as Windows import "containers" import "visualization" -ApplicationWindow { +AppWindow { id: mainWindow - width: 1101 - height: width * (522 / 1101) + //width: 1101 + //height: 522//width * (522 / 1101) - minimumHeight: width * (522 / 1101) - maximumHeight: minimumHeight + //minimumHeight: width * (522 / 1101) + //maximumHeight: minimumHeight - title: qsTr("Barbie Calculator") + //title: qsTr("Barbie Calculator") visible: true Item { id: root anchors.fill: parent + Component.onCompleted: { + console.log("width", width) + console.log("height", height) + } } AppMenuBar { diff --git a/src/calculator/ui/types/window/__init__.py b/src/calculator/ui/types/window/__init__.py new file mode 100644 index 0000000..f77ed5e --- /dev/null +++ b/src/calculator/ui/types/window/__init__.py @@ -0,0 +1,4 @@ +# coding=utf-8 +from .app_window import AppWindow + +__all__= ("AppWindow") \ No newline at end of file diff --git a/src/calculator/ui/types/window/app_window.py b/src/calculator/ui/types/window/app_window.py new file mode 100644 index 0000000..528486b --- /dev/null +++ b/src/calculator/ui/types/window/app_window.py @@ -0,0 +1,26 @@ +# coding=utf-8 +from PyQt5.QtCore import QSize +from PyQt5.QtCore import qDebug +from PyQt5.QtGui import QResizeEvent +from PyQt5.QtGui import QWindow +from PyQt5.QtQuick import QQuickWindow + + +class AppWindow(QQuickWindow): + def __init__(self, parent: QWindow = None) -> None: + super().__init__(parent) + + self.setTitle("Barbie Calculator") + self.setWidth(1101) + self.setHeight(522) + + + def resizeEvent(self, e: QResizeEvent) -> None: + ee = QResizeEvent(QSize(e.size().width(), 522), e.oldSize()) + new_width = e.size().width() + new_height = (new_width / 1101) * 522 + + self.setMinimumHeight(new_height) + self.setMaximumHeight(new_height) + self.contentItem().setProperty("width", new_width) + self.contentItem().setProperty("height", new_height) \ No newline at end of file From e2f0b0aae5280f86a865afbe056e4d25144129b5 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 12 Apr 2017 00:02:13 +0200 Subject: [PATCH 107/121] Fix order of displaying bases - #76 --- src/calculator/ui/qml/main.qml | 1 + .../ui/qml/visualization/ResultSystemDisplay.qml | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index 1c284ce..a91416e 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -233,6 +233,7 @@ AppWindow { id: resultSystemDisplay value: 0 + basesList: ["BIN", "OCT", "DEC", "HEX"] bases: { "DEC": 10, "BIN": 2, "HEX": 16, "OCT": 8 } height: parent.height / 5.1 scrollbarColor: StyleSettings.resultSystemDisplay.scrollbarColor diff --git a/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml b/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml index 7eaf6c8..f27d68d 100644 --- a/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml +++ b/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml @@ -7,7 +7,9 @@ import Calculator 1.0 Rectangle { id: component - /// List of bases to be displayed + /// Ordered list of bases to be displayed + property var basesList + /// Dict of bases with number base to be displayed property var bases /// Value which will be converted to different bases property real value: 0 @@ -36,10 +38,10 @@ Rectangle { anchors.leftMargin: component.margin * 2 Repeater { - model: Object.keys(component.bases) + model: component.basesList Item { width: container.width - height: container.height / Object.keys(component.bases).length + height: container.height / component.basesList.length Text { id: baseText From 57e9b56ec9c8973a24cbcb638db0c015e7800905 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 12 Apr 2017 00:15:23 +0200 Subject: [PATCH 108/121] Change content of prompter in flickable text from "< Slide" to "<<<" --- .../visualization/FlickableAnimatedText.qml | 47 ++++++------------- 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/src/calculator/ui/qml/visualization/FlickableAnimatedText.qml b/src/calculator/ui/qml/visualization/FlickableAnimatedText.qml index db997ee..44ea812 100644 --- a/src/calculator/ui/qml/visualization/FlickableAnimatedText.qml +++ b/src/calculator/ui/qml/visualization/FlickableAnimatedText.qml @@ -39,16 +39,16 @@ Item { id: prompter color: (component.theme == "dark") ?"black" :"white" - opacity: (prompter.width > 0 && flick.visibleArea.widthRatio != 1) ?0.8 :0 + opacity: (!flick.contentX && flick.visibleArea.widthRatio != 1) ?0.8 :0 visible: opacity + clip: true - width: height * 3 - flick.contentX + width: height * 2.5 height: fontMetrics.height * 0.8 radius: 3 anchors.verticalCenter: flick.verticalCenter anchors.right: flick.right - anchors.rightMargin: flick.contentX Behavior on opacity { NumberAnimation { duration: 200 } @@ -59,41 +59,24 @@ Item { font: component.font } - Item { + Row { height: parent.height - width: arrow.paintedWidth + slideText.anchors.leftMargin + fontMetrics.advanceWidth(slideText.text) + width: height * 0.6 * 4 * (2/5.) anchors.centerIn: parent - Image { - id: arrow - - source: (component.theme == "dark") ?"qrc:/assets/images/arrow_left_light.svg" - :"qrc:/assets/images/arrow_left_dark.svg" - fillMode: Image.PreserveAspectFit - - //width: height - height: parent.height * 0.6 - - //sourceSize.width: width - sourceSize.height: height - - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - } - - Text { - id: slideText + Repeater { + model: 3 - text: qsTr("Slide") - color: (component.theme == "dark") ?"white" - :"black" + Image { + source: (component.theme == "dark") ?"qrc:/assets/images/arrow_left_light.svg" + :"qrc:/assets/images/arrow_left_dark.svg" + fillMode: Image.PreserveAspectFit - font.family: "Roboto Light" - font.pixelSize: parent.height * 0.9 + height: parent.height * 0.6 + sourceSize.height: height - anchors.verticalCenter: parent.verticalCenter - anchors.left: arrow.right - anchors.leftMargin: height / 10 + anchors.verticalCenter: parent.verticalCenter + } } } } From 1a5d1e0ce8039373eb2147f30424c6ca9488845a Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 12 Apr 2017 01:02:16 +0200 Subject: [PATCH 109/121] Add content to help - #70 --- src/calculator/ui/assets/contents/help.js | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/calculator/ui/assets/contents/help.js b/src/calculator/ui/assets/contents/help.js index adafaf9..5285206 100644 --- a/src/calculator/ui/assets/contents/help.js +++ b/src/calculator/ui/assets/contents/help.js @@ -1,10 +1,25 @@ var content = [ { - "title": qsTr(""), - "content": qsTr("") + "title": qsTr("Barbie Calculator"), + "content": qsTr("Manual on how to use Barbie Calculator.") }, { - "subtitle": qsTr(""), - "content": qsTr("") + "subtitle": qsTr("Getting started"), + "content": qsTr("First we need to enter expression, which we want to calculate into text input. Then we press the button with equal sign or press Enter/Return key. Congratulations you have done your very first calculation using Barbie Calculator.") + }, + { + "title": qsTr("Advance") + }, + { + "subtitle": qsTr("Using completer"), + "content": qsTr("To show completer press Ctrl+Space. Completer will be shown automatically every time after entering operator. To choose item you want to complete click on the item or use arrows to navigate to item and then pres key Enter/Return.") + }, + { + "subtitle": qsTr("Variables intro"), + "content": qsTr("Barbie Calculator also provide support of custom variables. To create variable 'a' simply enter assignment of 'a' as 'a=' and then you add expression. If variable depends on another variable then it's value will be updated every time any of the dependent variables change.") + }, + { + "subtitle": qsTr("Variables manipulation"), + "content": qsTr("To enter into input expression from which variable was calculated click on the expression of variable in variable panel. If you click on variable in panel it will enter it's identifier and after right-click it's value. To delete or set value hover menu header(three dots) to show menu where you can choose desired action.") } ] From e1e8a038d9adb241be593fe0693901039922ebbb Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 12 Apr 2017 01:23:08 +0200 Subject: [PATCH 110/121] Refactor move version outside of translation function - About --- src/calculator/ui/qml/windows/About.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calculator/ui/qml/windows/About.qml b/src/calculator/ui/qml/windows/About.qml index 8649f00..f7a1f87 100644 --- a/src/calculator/ui/qml/windows/About.qml +++ b/src/calculator/ui/qml/windows/About.qml @@ -83,7 +83,7 @@ Item { Text { id: title - text: qsTr("Barbie calculator 0.1") + text: qsTr("Barbie calculator") + " " + "0.1" font.pixelSize: 25 font.family: component.font.family From 4d8e7aa78bdb9d0edb732d727e5ec54065f01c2f Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 12 Apr 2017 01:23:26 +0200 Subject: [PATCH 111/121] Typo --- src/calculator/ui/assets/contents/help.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/calculator/ui/assets/contents/help.js b/src/calculator/ui/assets/contents/help.js index 5285206..1bad936 100644 --- a/src/calculator/ui/assets/contents/help.js +++ b/src/calculator/ui/assets/contents/help.js @@ -1,11 +1,11 @@ var content = [ { - "title": qsTr("Barbie Calculator"), + "title": qsTr("Barbie calculator"), "content": qsTr("Manual on how to use Barbie Calculator.") }, { "subtitle": qsTr("Getting started"), - "content": qsTr("First we need to enter expression, which we want to calculate into text input. Then we press the button with equal sign or press Enter/Return key. Congratulations you have done your very first calculation using Barbie Calculator.") + "content": qsTr("First we need to enter expression, which we want to calculate into text input. Then we press the button with equal sign or press Enter/Return key. Congratulations you have done your very first calculation using Barbie calculator.") }, { "title": qsTr("Advance") @@ -16,7 +16,7 @@ var content = [ }, { "subtitle": qsTr("Variables intro"), - "content": qsTr("Barbie Calculator also provide support of custom variables. To create variable 'a' simply enter assignment of 'a' as 'a=' and then you add expression. If variable depends on another variable then it's value will be updated every time any of the dependent variables change.") + "content": qsTr("Barbie calculator also provide support of custom variables. To create variable 'a' simply enter assignment of 'a' as 'a=' and then you add expression. If variable depends on another variable then it's value will be updated every time any of the dependent variables change.") }, { "subtitle": qsTr("Variables manipulation"), From e1795487434a4d53e10a3a999b1a9f367c148fcd Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 12 Apr 2017 01:24:37 +0200 Subject: [PATCH 112/121] Fix app did not show help and about, when app was translated, so literals did not match --- src/calculator/ui/qml/main.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index a91416e..c69b53c 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -63,9 +63,9 @@ AppWindow { menuItemHeight: 19 onItemChoosed: { - if(item == "About") + if(item == qsTr("About")) aboutWindow.show() - if(item == "Help") + if(item == qsTr("Help")) helpWindow.show() } } From d23646166219e9d58fe1ef6c6da4d598e0b08de8 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 12 Apr 2017 01:24:56 +0200 Subject: [PATCH 113/121] Update translations --- src/calculator/ui/translations/cs.qm | Bin 1528 -> 5865 bytes src/calculator/ui/translations/cs.ts | 134 ++++++++++++++++++++++++--- 2 files changed, 122 insertions(+), 12 deletions(-) diff --git a/src/calculator/ui/translations/cs.qm b/src/calculator/ui/translations/cs.qm index af52b7301a8470f1aabc0cd0f007a82b546e4c75..3f29a29b645a7cc047bb465edb3d49968157576f 100644 GIT binary patch literal 5865 zcmbtYU5K1l89uX_B+aJzYquImk&{rmjjhvCypY-kH|r*DZPE~vU=)gH<~uXlC3YaCnMuT=9Z z4I93xl=|b`(O-Uqbywz&zl`gy@6NR!{FhP-KWl#Z_6NA$-+bv5<{x^w`J*r4Jv2Z0 z_p|t&+xf-~jH?&?b=Mbn{%-ycu=AB&XRp72@qG8Y-~TP-&F%hkc0#Eg?L7~zLH@n} z*b`oU1^RF7xpJ(9`A6p8|IIq)pP2vWC(lCOjlH{H!}=Zj_r3l$_O)R4{rV?=#q$UI z|MbyMmD=~c5Sw~;2WCE@E;Wp@s-yZUQbuVNt3++6vD}Z<1n(2vU&9=Zx8On;g#U%5 z;ZP@Gub-tlv8heQHfZg}z|N(%%d-ZhA5$l>CQ)q_V!ehIqlWm+@oQA3oJvZ0hfbQL z9a`Nn!%jXlnM*baTTqu^P6pFlRo(zXi~0AER+yy;$4uZT6S1HJ6)s^aw?>g zlG+XqTz0uOiPb~r0Ii$U9)#03d~E=6P#dMn=GW{n7G=B<8=LBRH7F;{JwFhbJLlqy ze*8Xl4h9UM-XUHg47yp)M&*u8)UhJ5Ur7A_QN3Bnn)~M683KOwi08qg%HRSOg%V(Y z8@a`#Ah$3k1TNOC=d=o*Z_8Um7?E$IsyPRvvbG`^dV&x`KCISyv|WUTMfDg)S;+x3 z(W+dSRD)rdrW*oiSdS_X@n3K#LCmk`zPOrz$2ZU5SK~^Z4rAPJfV&u$a4!VN*`(Lg z*yWhF*;3ZqHCRE;OR<`uO4)08j!-yv_6+w$P$p8*r-XjLQxC!Pmfthu;&&(*AoZR> zr9wYPXUZ*w{uvs==QIfZGRn6-IagVYZm!)iAeuJw)Ht zwmOdk>38hdCKH{7Ba5{Ynt=7-UrMA+g&^R1-$XqRtYrZ<-WLF0Qvp)+7LtXb9tlVn z3Fp(9v{+9*hNlXQCrI`TMUqaK#M6Y}TFz%O`=q~$H|(EoN-9t;3pU3ZG%%P$d@Cq3 z0>+U#*zly#$=<+g$yn-B9i%?aqWx@>Mo3+Uan5D)M{O*kj46pXl)@P-l9Aegl)B^r zR@F zX0L+v$&JW;W-g{m4=G92&a(_Dx*oFaxssb93ZWNiw6}iF3hUwV^ES(qNDu6!rI%gQ zOH9Uo9?fAYh0r%+#Tm7{fj?PuVJOsa?;61$`*C_C5pHU?2*mHx8V( zSw<0QX4tySQrL6S=tc0~1|r2cdAOthb3ha;!Dswz43_bB(*dRyfyFZ>qE}#k->uWV zd1{M2ESKk+**(m?rM?aeNLvn?i+;Q6ZE~p{9>A)KE9!>yMY{7@RngQ2-lVk(vhep^#r?F z1JT1b0hOl;d?FoqVhW*${v%TGj5a`Y<&cThg^$>H-WE)#?BUKS?M@`;F==zNKC~7j84>XiEXiq zYw&toFzTA@%VWA;d<<9-9q6gG!i}X11*!;K2vXEoO?P>o z_8?R64xk3qGfjTrQfC-;1}qWMRE13>u5t-Z%jJD+B0Fr6F3FT(=&Wr_}gedI=u zdcv6C_b3ayA^4PBY`Sd6<(tJ9yr7C#h3f-5#JQ1xmRd3=`L^$XP#W}}>%KLCO&-bm zi6w$%>2cP0%37E4br83p8up7Lcb~mGm^ck_Fw{oQ$FsHHcvWh@9`8@JVN# z9}0A2#$gZ5A@5~I*@%*-$;`S=)w4ON9+pD|SujKnFuL33V(Fs;7lo=z56v>|=QlGn z{y(47O5AZ=@ LSEKRo=3MhXae_|> delta 200 zcmaE<`-8jQW=D#|srPfHo>i3DpRjQ6T_*+xh6V-()@TL>mRSs}Ea!o2Jq9&#JD~U% z2A`QpKt3Czk|!Sn1G6fV;>mLi42)r{MxXqE{O7FmRv!ZD+r@gD>mE>jF`Hj?9FQNy z{;CA1pUIswGqMk;ZUSeak{(c=f$M + + About + + + Barbie calculator + Barbie calculator + + + + From revision + Z revize + + + + Created by: + Vytvořeno: + + + + All rights reserved. + Všechna práva vyhrazena. + + Adapter - + Unsupported base. Nepodporovaná číselná soustava. @@ -13,41 +36,46 @@ Matematická chyba. - + Math error occurred. Matematická chyba. - + Error in defining variable. Chyba při definování proměnné. - + Result is too big. Výsledek je příliš velký. - + Given parameters does not match to function {}. Předané parametry neodpovídají funkci {}. - + This construct is not supported. Tato konstrukce není podporovaná. + + + #TODO + + Parameters count does not match function. Počet parametrů neodpovídá funkci. - + Function is not defined. Funkce není definována. - + Expression contains syntax error. Výraz obsahuje syntaktickou chybu. @@ -89,6 +117,14 @@ AI je noob. + + Help + + + Help + Nápověda + + PopUp @@ -97,20 +133,94 @@ Ok + + help + + Barbie Calculator + Barbie Calculator + + + + Manual on how to use Barbie Calculator. + Manuál jak používat Barbie Calculator. + + + + Getting started + Začínáme + + + + Barbie calculator + Barbie calculator + + + + First we need to enter expression, which we want to calculate into text input. Then we press the button with equal sign or press Enter/Return key. Congratulations you have done your very first calculation using Barbie calculator. + Nejdříve musíme zadat výraz, který chceme vypočítat, do textového vstupu. Následně stiskneme tlačítko se symbolem rovnítka nebo zmáčkneme klávesu Enter. Gratulujeme provedli jste svůj první výpočet pomocí Barbie calculatoru. + + + + Advance + Pokročilé + + + + Using completer + Použití našeptávače + + + + To show completer press Ctrl+Space. Completer will be shown automatically every time after entering operator. To choose item you want to complete click on the item or use arrows to navigate to item and then pres key Enter/Return. + K zobrazení našeptávače stiskněte Ctrl+Mezerník. Našeptávač se zobrazí automaticky pokaždém zadání operátoru. Můžeme vybrat položku kliknutím nebo posunout se pomocí šipek a poté stisknout klávesu Enter. + + + + Variables intro + Začínáme s proměnnými + + + + Barbie calculator also provide support of custom variables. To create variable 'a' simply enter assignment of 'a' as 'a=' and then you add expression. If variable depends on another variable then it's value will be updated every time any of the dependent variables change. + Barbie calculator také podporuje tvorbu vlastních proměnných. Abychom vytvořili proměnnou 'a', jednoduše zadáme přiřazení 'a' tedy 'a=' a poté doplníme výraz. Pokud proměnná závisí na jiných proměnných, pak bude její hodnota přepočítá, ppokaždé když hodnota proměnných, na kterých je závislá, změní. + + + + Variables manipulation + Manipulace s proměnnými + + + + To enter into input expression from which variable was calculated click on the expression of variable in variable panel. If you click on variable in panel it will enter it's identifier and after right-click it's value. To delete or set value hover menu header(three dots) to show menu where you can choose desired action. + K vložení výrazu, ze kterého byla proměnná spočítána, musíme kliknout na výraz proměnné v panelu proměnných. Pokud klikneme na proměnnou v panelu, tak vložíme její identifikátor a pravým kliknutím její hodnotu. Abychom nastavili nebo vymazali proměnnou, najedeme na hlavičku menu(tři tečky), tím zobrazíme menu, kde vybereme danou akci. + + main - Barbie Calculator - Barbie Calculator + Barbie Calculator + + + + + + Help + Nápověda + + + + + About + O programu - + Error Chyba - + Info Info From d7d937e2a094dcbea55bab36d8c3ccd030f0815c Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 12 Apr 2017 01:25:49 +0200 Subject: [PATCH 114/121] Fix smallthings which did not affected app --- src/calculator/ui/app/calculator_app.py | 2 +- src/calculator/ui/types/expression/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calculator/ui/app/calculator_app.py b/src/calculator/ui/app/calculator_app.py index 945a076..22ca51e 100644 --- a/src/calculator/ui/app/calculator_app.py +++ b/src/calculator/ui/app/calculator_app.py @@ -55,7 +55,7 @@ def __init__(self, argv: List[str]): super().__init__(argv) self._translator = QTranslator() - self._translator.load("".join((":/translations/", QLocale().system().name(), ".qsm"))) + self._translator.load("".join((":/translations/", QLocale().system().name(), ".qm"))) self.installTranslator(self._translator) icon = QIcon() diff --git a/src/calculator/ui/types/expression/__init__.py b/src/calculator/ui/types/expression/__init__.py index e32c5f1..fe927dc 100644 --- a/src/calculator/ui/types/expression/__init__.py +++ b/src/calculator/ui/types/expression/__init__.py @@ -3,4 +3,4 @@ from .exp_syntax_highlighter import ExpSyntaxHighlighter from .exp_analyzer import ExpAnalyzer -__all_ = ("SyntaxHighlighter", "HighlightRule", "ExpSyntaxHighlighter", "ExpAnalyzer") \ No newline at end of file +__all__ = ("SyntaxHighlighter", "HighlightRule", "ExpSyntaxHighlighter", "ExpAnalyzer") \ No newline at end of file From 1c89abe0db6742fe5b9d13edb7ea9baa28ce3104 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 12 Apr 2017 01:26:22 +0200 Subject: [PATCH 115/121] Add colored print of qt console --- src/calculator/ui/app/calculator_app.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/calculator/ui/app/calculator_app.py b/src/calculator/ui/app/calculator_app.py index 22ca51e..a28d30b 100644 --- a/src/calculator/ui/app/calculator_app.py +++ b/src/calculator/ui/app/calculator_app.py @@ -2,6 +2,8 @@ import platform from typing import List +from termcolor import colored + from PyQt5.QtCore import (QSize, QtFatalMsg, QtCriticalMsg, QtWarningMsg, QtInfoMsg, qInstallMessageHandler, QtDebugMsg) from PyQt5.QtCore import QTranslator @@ -39,14 +41,22 @@ def qt_message_handler(mode, context, message): QtFatalMsg: "Fatal", QtDebugMsg: "Debug" } - mode = modes[mode] + + modes_colors = { + QtInfoMsg: "blue", + QtWarningMsg: "yellow", + QtCriticalMsg: "red", + QtFatalMsg: "red", + QtDebugMsg: "green" + } + + mode = colored(modes[mode], modes_colors[mode]) if context.file is None: print('{mode}: {msg}'.format(mode=mode, msg=message)) else: - print('{mode}: line: {line}, function: {func}, file: {file}'.format( - mode=mode, line=context.line, func=context.function, file=context.file)) - print('{msg}'.format(msg=message)) + print('{mode}: {msg}\t\t\t\tline: {line}, function: {func}, file: {file}'.format( + mode=mode, line=context.line, func=context.function, file=context.file, msg=message)) qInstallMessageHandler(qt_message_handler) From 1565d96ba352edc9f14a4452e76a7128b0512148 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 12 Apr 2017 01:42:09 +0200 Subject: [PATCH 116/121] Fix ResultSystemDisplay value responsivity --- src/calculator/ui/qml/visualization/ResultSystemDisplay.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml b/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml index f27d68d..fb38fc9 100644 --- a/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml +++ b/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml @@ -89,7 +89,7 @@ Rectangle { height: parent.height anchors.left: parent.left - anchors.leftMargin: component.margin * 3 + fm.advanceWidth("DEC") // some constant to measure font width + anchors.leftMargin: font.pixelSize * 3 // some constant to measure font width } } } From 56b9f9e566c7d4620544ccbf0a6e6623b177c6ff Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 12 Apr 2017 01:44:37 +0200 Subject: [PATCH 117/121] Finish custom window, which keeps size ratio --- src/calculator/ui/qml/main.qml | 14 +++------- src/calculator/ui/types/window/app_window.py | 29 ++++++++++++++------ 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index c69b53c..50c6fe7 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -22,22 +22,16 @@ import "visualization" AppWindow { id: mainWindow - //width: 1101 - //height: 522//width * (522 / 1101) + width: 1101 + ratio: 522/1101. + height: 522 - //minimumHeight: width * (522 / 1101) - //maximumHeight: minimumHeight - - //title: qsTr("Barbie Calculator") + title: qsTr("Barbie calculator") visible: true Item { id: root anchors.fill: parent - Component.onCompleted: { - console.log("width", width) - console.log("height", height) - } } AppMenuBar { diff --git a/src/calculator/ui/types/window/app_window.py b/src/calculator/ui/types/window/app_window.py index 528486b..478f584 100644 --- a/src/calculator/ui/types/window/app_window.py +++ b/src/calculator/ui/types/window/app_window.py @@ -1,5 +1,6 @@ # coding=utf-8 -from PyQt5.QtCore import QSize +from PyQt5.QtCore import QSize, QCoreApplication +from PyQt5.QtCore import pyqtProperty from PyQt5.QtCore import qDebug from PyQt5.QtGui import QResizeEvent from PyQt5.QtGui import QWindow @@ -10,17 +11,29 @@ class AppWindow(QQuickWindow): def __init__(self, parent: QWindow = None) -> None: super().__init__(parent) - self.setTitle("Barbie Calculator") - self.setWidth(1101) - self.setHeight(522) - + self.__ratio = 1 def resizeEvent(self, e: QResizeEvent) -> None: - ee = QResizeEvent(QSize(e.size().width(), 522), e.oldSize()) new_width = e.size().width() - new_height = (new_width / 1101) * 522 + new_height = new_width * self.__ratio self.setMinimumHeight(new_height) self.setMaximumHeight(new_height) self.contentItem().setProperty("width", new_width) - self.contentItem().setProperty("height", new_height) \ No newline at end of file + self.contentItem().setProperty("height", new_height) + + @pyqtProperty(str) + def title(self) -> str: + return self.title() + + @title.setter + def title(self, v: str) -> None: + self.setTitle(v) + + @pyqtProperty(float) + def ratio(self) -> float: + return self.__ratio + + @ratio.setter + def ratio(self, v: float) -> None: + self.__ratio = v \ No newline at end of file From 1d8011ffe60c391a29a14c072c95b188895ae47b Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 12 Apr 2017 01:45:08 +0200 Subject: [PATCH 118/121] Typo --- src/calculator/ui/assets/styles/UIStyles.qml | 2 +- src/calculator/ui/qml/main.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calculator/ui/assets/styles/UIStyles.qml b/src/calculator/ui/assets/styles/UIStyles.qml index e2ef56d..e4a7370 100644 --- a/src/calculator/ui/assets/styles/UIStyles.qml +++ b/src/calculator/ui/assets/styles/UIStyles.qml @@ -1,7 +1,7 @@ pragma Singleton import QtQuick 2.0 import QtQuick.Controls.Styles 1.4 -// TODO allow + import Expression 1.0 QtObject { diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index 50c6fe7..a9f8505 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -1,6 +1,6 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 -// TODO allow + import ExpSyntaxHighlighter 1.0 import ExpAnalyzer 1.0 import Sides 1.0 From 21a380f43b12f00001534721b4d128d6d4dd7c36 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 12 Apr 2017 01:47:08 +0200 Subject: [PATCH 119/121] Change width of help window --- src/calculator/ui/qml/main.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index a9f8505..89dd841 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -375,7 +375,7 @@ AppWindow { scrollBarColor: StyleSettings.helpWindow.scrollBarColor textFont.family: StyleSettings.helpWindow.font.family - width: 500 + width: 400 height: 600 } From 3fe5a596a44fcc7745a2ab6e4c083a9a4de38f42 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 12 Apr 2017 01:50:45 +0200 Subject: [PATCH 120/121] Refactor rename bin folder to dev --- {bin => dev}/qmldevengine/main.cpp | 0 {bin => dev}/qmldevengine/qmldevengine.pro | 0 {bin => dev}/qmldevengine/types/sides.cpp | 0 {bin => dev}/qmldevengine/types/sides.h | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {bin => dev}/qmldevengine/main.cpp (100%) rename {bin => dev}/qmldevengine/qmldevengine.pro (100%) rename {bin => dev}/qmldevengine/types/sides.cpp (100%) rename {bin => dev}/qmldevengine/types/sides.h (100%) diff --git a/bin/qmldevengine/main.cpp b/dev/qmldevengine/main.cpp similarity index 100% rename from bin/qmldevengine/main.cpp rename to dev/qmldevengine/main.cpp diff --git a/bin/qmldevengine/qmldevengine.pro b/dev/qmldevengine/qmldevengine.pro similarity index 100% rename from bin/qmldevengine/qmldevengine.pro rename to dev/qmldevengine/qmldevengine.pro diff --git a/bin/qmldevengine/types/sides.cpp b/dev/qmldevengine/types/sides.cpp similarity index 100% rename from bin/qmldevengine/types/sides.cpp rename to dev/qmldevengine/types/sides.cpp diff --git a/bin/qmldevengine/types/sides.h b/dev/qmldevengine/types/sides.h similarity index 100% rename from bin/qmldevengine/types/sides.h rename to dev/qmldevengine/types/sides.h From 9c282f15cf715e7c76446341e2f67b220f5570ff Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 12 Apr 2017 01:53:27 +0200 Subject: [PATCH 121/121] Set version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5a1aa3e..efd7dae 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ def setup(): core.setup( name='calculator', - version='0.2', + version='0.3', license='GNU GENERAL PUBLIC LICENSE Version 3', long_description=open(join(base_path, 'README.md')).read(), url='https://github.com/thejoeejoee/IVS-VUT-BIT-2016-2017',

+8Lk@IT3Y;73zyPGid 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[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 Wzj170DgS{q%1XVLtP(d4{=WbmxN;l- diff --git a/doc/doc.md b/doc/doc.md index f8deab7..9ebd168 100644 --- a/doc/doc.md +++ b/doc/doc.md @@ -3,204 +3,45 @@ ## Table of contents -* [Introduction](#introduction) -* [Usage](#usage) - * [Installation](#installation) - * [Uninstallation](#uninstallation) -* [Input syntax](#input-syntax) - * [Number systems](#number-systems) - * [Operators](#operators) - * [Functions](#functinos) -* [Getting started](#getting-started) - * [Components](#components) - * [Step by step](#step-by-step) +* [Úvod](#uvod) +* [Instalace](#instalace) +* [Odinstalace](#odinstalace) +* [Funkce](#funkce) + * [Absolutní hodnota](#abs-hodnota) + * [Faktorial](#faktorial) + * [Přirozený logaritmus](#n-logaritmus) + * [Obecný logaritmus](#logaritmus) + * [Mocnina](#mocnina) + * [Náhodné číslo](#rand-cislo) + * [Obecná odmocnina](#obecna-odmocnina) + * [Odmocnina](#odmocnina) +* [Tutorial](#tutorial) + * [Komponenty](#komponenty) + * [Přáce s kalkulačkou](#calc-work) ## Introduction This application represents classic calculator with special functions. Calculator can work with hugh nun numbers, because his core is written in [Python](https://www.python.org/). -## Usage /todo +## Instalace -### Installation +## Odinstalace -### Uninstallation +## Funkce -## Input syntax +Ve všech funkcích jdou použít klasické operátory (+, -, *, /) a i jiné funkce. +### Absolutní hodnota -### Number systems ??? +Zápis: -Calculator can compute with classic number systems, such as decimal, hexa-decimal, octal and binary. +`abs([číslo])` -#### Decimal +nebo -Decimal mode is set implicitly. +`|[číslo]|` -Examples of decimal numbers: - -`156` - -`-56` - -`42.666` - -`-0.135` - -#### Hexadecimal - -Requires calculator to be switched to hexadecimal mode. - -Valid characters: `0-9 A-F` (lower case `a-f` are not valid) - -Examples: - -`3A1F` - -`FFFF` - -`561` - -#### Octal - -Requires calculator to be switched to octal mode. - -Valid characters: `0-7` - -Examples: - -`37` - -`777` - -#### Binary - -Requires calculator to be switched to binary mode. - -Valid characters: `0-1` - -Examples: - -`101101` - -`1111` - -### Operators - -#### Add - -Syntax: - -`[left-operand] + [right-operand]` - -Requires two operands. - -Examples: - -`3 + 6` - -`-96 + 42` - -#### Substract - -Syntax: - -`[left-operand] - [right-operand]` - -Requires two operands. - -Examples: - -`3 - 6` - -`26489 - (153 - 145)` - -#### Multiply - -Syntax: - -`[left-operand] * [right-operand]` - -Requires two operands. - -Examples: - -`3 * 6` - -`-96 * 42` - -#### Divide - -Syntax: - -`[left-operand] / [right-operand]` - -Requires two operands. - -Examples: - -`3 / 6` - -`-96 / 42` - -#### Power of - -Syntax: - -`[base] ^ [exponent]` - -or - -`[base] ** [exponent]` - -or - -`pow([base], [exponent])` - -Requires two operands. - -Examples: - -`3 ^ 6` - -`-96 ** 42` - -`pow(2, 10)` - -#### Factorial - -Factorial works only with integer numbers. - -Syntax: - -`[number]!` - -or - -`fact([number])` - -Example: - -`6!` - -`4!!` - -`(21 / 3)!` - -`fact(6)` - -`fact(fact(6))` - -#### Absolute value - -Syntax: - -`|[number]|` - -or - -`abs([number])` - -Examples: +Příklady: `|-6|` @@ -210,147 +51,149 @@ Examples: `abs(abs(-12 + 4) + 5)` -### Functions - -#### Abs +### Faktorial -Syntax: +Zápis: -`abs([number])` +`fact([číslo])` -Examples: +nebo -`|-6|` +`[číslo]!` -`||-12 + 4| + 5|` +Příklady: -`abs(-6)` - -`abs(abs(-12 + 4) + 5)` - -#### Fact - -Syntax: - -`fact([number])` +`6!` -Example: +`6!!` `fact(6)` `fact(fact(6))` -`fact(27 / 3)` +### Přirozený logaritmus -#### Ln +Zápis: -Ln is native logarithm (with Euler number base) +`ln([číslo])` -Syntax: - -`ln([number])` - -Example: +Příklady: `ln(5)` `ln(5 + 2)` -#### Log +### Obecný logaritmus -Syntax: +Zápis: -`log([number], [log-base])` +`log([číslo], [základ logaritmu])` -Example: +Příklady: `log(2, 2)` `log(54 + 8, 15)` -#### Pow +### Mocnina + +Zápis: -Syntax: +`pow([mocněnec], [mocnitel])` -`pow([number], [nth-power])` +nebo -Example: +`[mocněnec]**[mocnitel]` + +Příklady: + +`5**2` `pow(5, 2)` -#### Rand /todo +### Náhodné číslo + +Zápis: -#### Root +`rand()` -Root is computing real root of number +### Obecná odmocnina -Syntax: +Zápis: -`root([number], [nth-root]` +`root([odmocněnec], [odmocnitel])` -Examples: +Příklady: -`3 + 6` +`root(3, 6)` -`-96 + 42` +`root(86, 15*2)` -#### Square root +### Odmocnina -Syntax: +Zápis: -`sqrt([number])` +`sqrt([odmocněnec])` -Examples: +Příklady: `sqrt(8)` `sqrt(96 + 42)` -## Getting started +## Tutorial -First of all you will start the Barbie calculator application. After that you will see working field: +V této kapitole bude popsána práce v Barbie Calculator, jeho funkce a užitečné vlastnosti, a dále také základní panely pro práci. -[No value set calculator](empty.png) +Zde na obrázku je Barbie Calculator po zapnutí -*This main frame can be resized only with implicit proportions*. +[Prázdná kalkulačka](empty.png) -### Components +*Okno programu má pevně nastavený poměr stran*. -In this subsection will be described main parts of calculator. +### Komponenty -#### Number systems converter +#### Převod do číselných soustav -If result is _integer type_, this result will be converted and showed in 4 different number systems (decimal, hexa-decimal, octal, binary). +Pokud je výsledek _celočíselný_, tak bude výsledek převeden a zobrazen ve 4 číselných soustavách (desítkové, šestnáctkové, osmičkové, dvojkové). -[Number systems bar with zero](system1.png) [Number systems bar with numbers](system2.png) +[Číselné soustavy po otevření](system1.png) [Číselné soustavy s převedeným číslem](system2.png) -#### Functions & expressions +#### Funkce a zápisové okno -There is a function bar with build-in functions like *rand()* or *pow()* and expression field where you can write expressions to compute. +Jednou z hlavních částí je panel s funkcemi a k němu navazující okno s výrazem k výpočtu. -[No expression](func1.png) [Functions + numbers](func2.png) +[Bez výrazu - prázdné](func1.png) [S funkcí a operací](func2.png) -#### Variables +#### Proměnné -Barbie calculator also knows computing with variables, so you can set your own variables and use them in calculations. +Barbie Calculator umí také používat proměnné, takže si můžete uložit výpočty do proměnných a dále je používat -Be aware, because variable names are **case sensitive** +Dávejte si ale pozor na to, že proměnné jsou **case sensitive**. -[Variable bar](variable.png) +[Panel proměnných](variable.png) -#### Results +### Práce s kalkulačkou -There are 2 result windows one with actual result and one with whole result ??? /todo +Na následujícím obrázku je ukázané tzv. dopňování kódu -### Step by step /todo +[Doplňování kódu](complete.png) +Dále také rozšíření výrazu do funkce. +Pokud výraz označíte a a kliknete na funkci, tak se celý výraz vloží do požadované funkce. +[Výraz před použitím funkce](enfunc1.png) +[Výraz po použití funkce](enfunc2.png) +Jak vidíte už jsou inicializované nějaké proměnné. V jejich nastavení je možný přepis na `1` nebo `0`, a nebo také proměnnou smazat. Pokud bude proměnnýc příliš, můžete se k nim dostat pomocí posuvníku (případně kolečka myši). +[Mnoho proměnných a jejich nastavení](many_vars.png) +Další ukázkou bude kombinace mnoha funkcí s vysokým výsledkem. Při vysokých (nebo nízkých) výsledcích se výsledek vypisuje ve formátu `[cifra].[2 cifry]` se zaokrouhlením na příslušné 2. desetinné místo. +[Posuvník u číselných soustav při vysoké hodnotě](long_result.png) diff --git a/doc/enfunc1.png b/doc/enfunc1.png new file mode 100644 index 0000000000000000000000000000000000000000..5a55b827b6edc23d2be54f3ff8d304d847a9e722 GIT binary patch literal 35745 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 literal 0 HcmV?d00001 diff --git a/doc/enfunc2.png b/doc/enfunc2.png new file mode 100644 index 0000000000000000000000000000000000000000..ab8f80ae32eca657322e4de47cf7073db75b89b8 GIT binary patch literal 36771 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 GpZ*uToEdfi literal 8957 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 B3MK#m diff --git a/doc/long_result.png b/doc/long_result.png new file mode 100644 index 0000000000000000000000000000000000000000..5d4247948ad84a5653e6fcede304d6ef9757b25c GIT binary patch literal 60292 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 U-tdkNgUW`eDr+cZpS$tk-_9Uwq5uE@ literal 0 HcmV?d00001 diff --git a/doc/many_vars.png b/doc/many_vars.png new file mode 100644 index 0000000000000000000000000000000000000000..51cc62bbbd3f9ebc2811b4de9baf6039bf556eb1 GIT binary patch literal 50550 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 Date: Mon, 3 Apr 2017 23:04:37 +0200 Subject: [PATCH 049/121] Updating documentation Repairing links to pictures --- doc/doc.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/doc/doc.md b/doc/doc.md index 9ebd168..222ffce 100644 --- a/doc/doc.md +++ b/doc/doc.md @@ -3,21 +3,21 @@ ## Table of contents -* [Úvod](#uvod) +* [Úvod](#úvod) * [Instalace](#instalace) * [Odinstalace](#odinstalace) * [Funkce](#funkce) - * [Absolutní hodnota](#abs-hodnota) + * [Absolutní hodnota](#absolutní-hodnota) * [Faktorial](#faktorial) - * [Přirozený logaritmus](#n-logaritmus) - * [Obecný logaritmus](#logaritmus) + * [Přirozený logaritmus](#přirozený-logaritmus) + * [Obecný logaritmus](#obecný-logaritmus) * [Mocnina](#mocnina) - * [Náhodné číslo](#rand-cislo) - * [Obecná odmocnina](#obecna-odmocnina) + * [Náhodné číslo](#nahodné-číslo) + * [Obecná odmocnina](#obecná-odmocnina) * [Odmocnina](#odmocnina) * [Tutorial](#tutorial) * [Komponenty](#komponenty) - * [Přáce s kalkulačkou](#calc-work) + * [Přáce s kalkulačkou](#práce-s-kalkulačkou) ## Introduction @@ -147,7 +147,7 @@ V této kapitole bude popsána práce v Barbie Calculator, jeho funkce a užite Zde na obrázku je Barbie Calculator po zapnutí -[Prázdná kalkulačka](empty.png) +![Prázdná kalkulačka](empty.png) *Okno programu má pevně nastavený poměr stran*. @@ -157,13 +157,13 @@ Zde na obrázku je Barbie Calculator po zapnutí Pokud je výsledek _celočíselný_, tak bude výsledek převeden a zobrazen ve 4 číselných soustavách (desítkové, šestnáctkové, osmičkové, dvojkové). -[Číselné soustavy po otevření](system1.png) [Číselné soustavy s převedeným číslem](system2.png) +![Číselné soustavy po otevření](system1.png) ![Číselné soustavy s převedeným číslem](system2.png) #### Funkce a zápisové okno Jednou z hlavních částí je panel s funkcemi a k němu navazující okno s výrazem k výpočtu. -[Bez výrazu - prázdné](func1.png) [S funkcí a operací](func2.png) +![Bez výrazu - prázdné](func1.png) ![S funkcí a operací](func2.png) #### Proměnné @@ -171,29 +171,29 @@ Barbie Calculator umí také používat proměnné, takže si můžete uložit v Dávejte si ale pozor na to, že proměnné jsou **case sensitive**. -[Panel proměnných](variable.png) +![Panel proměnných](variable.png) ### Práce s kalkulačkou Na následujícím obrázku je ukázané tzv. dopňování kódu -[Doplňování kódu](complete.png) +![Doplňování kódu](complete.png) Dále také rozšíření výrazu do funkce. Pokud výraz označíte a a kliknete na funkci, tak se celý výraz vloží do požadované funkce. -[Výraz před použitím funkce](enfunc1.png) +![Výraz před použitím funkce](enfunc1.png) -[Výraz po použití funkce](enfunc2.png) +![Výraz po použití funkce](enfunc2.png) Jak vidíte už jsou inicializované nějaké proměnné. V jejich nastavení je možný přepis na `1` nebo `0`, a nebo také proměnnou smazat. Pokud bude proměnnýc příliš, můžete se k nim dostat pomocí posuvníku (případně kolečka myši). -[Mnoho proměnných a jejich nastavení](many_vars.png) +![Mnoho proměnných a jejich nastavení](many_vars.png) Další ukázkou bude kombinace mnoha funkcí s vysokým výsledkem. Při vysokých (nebo nízkých) výsledcích se výsledek vypisuje ve formátu `[cifra].[2 cifry]` se zaokrouhlením na příslušné 2. desetinné místo. -[Posuvník u číselných soustav při vysoké hodnotě](long_result.png) +![Posuvník u číselných soustav při vysoké hodnotě](long_result.png) From 74028bdda61534e30adb952f817bd12b59ce3687 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Mon, 3 Apr 2017 23:17:22 +0200 Subject: [PATCH 050/121] Change variable image in doc --- doc/variable.png | Bin 3992 -> 6026 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/variable.png b/doc/variable.png index 2e97de3c9652d79f4ea0f76784da4515ea78a5b1..e3e81a7731cf7e5b2725223a240b8c2885cf344e 100644 GIT binary patch literal 6026 zcmcJT2UJsAmw+!piU!2;BnncLs$c@ri-e{~M^O+VROv|X5CSO82>7T10zssRbPQDj zNbg7qy@}M&duQ&$e`ft_X3d-V*P6*%NzT3ZoPF;uXYcRs5Dhg2+H(x&005v>d?^1I z0LXN}@ftM*{9Vq}tN{PWoE|I40=XU6=fQ(B<}#`>08kJPJv5>KkI&ja)NulU^R<6{ z$r|l4UjP7%MNwYniM#&N=z+nLzA>uR4Lkcku`p?~PWx-QOHs7OvQFbBHB{R_08Ri%*Kp&vdxb*|F}}qTUaM=%c(Cb=f3@XgQBKR$cxiR z&u0wbV~8-DNke)|73~aJuN;MUbaX6DPw&>y`7E!q5Ghgs);#uE928(z(1FRofQMm) zaDO=P?z$=J3<_WsYbmEL2c#XNTrN73`TpW2-JF8}&5wxpyC{H&)NE|&7=`%#j8p@% zYA5jIS2sW77T&|n>UpQxr=g7m{b&=~)GEN2>SH$6VhJ)u&f~^%M|WGp_%**K!v>xM__xUU%lXr##a0=g>md)2Z*4mjOu{QNi_kO&;g$}zPy^xYHvBK zgj2pd{Z$8{^jkSQSC}wab&sAe+V1AD{waN8;@_#)GFb5XG zKjuJXQURL*48z_}7No$dOX zKxFQk-GaV&u3%pbVS6`tYzO8-b{cGa(#8!^6?+-VUu(w(e~h`N?6rM1@X7gNN9ZDs z-Sgg?%?X$qDYv`&d-8?1sn9C1;f|>>SF;^2Y@vY49R*GyIAhBG6k1|M@T zuhgNH_0&kXleqNkFGsKHzf}^v2VbR2aZ*OT|A8$gqD=EDJRNJN=wY*Tr}6E3OfTW! zvSk{xN^E(0`KRp>n&lp0?ttF4+J1WXAiT8LPWp7KY$EE5aoK;S9XM_!&`I!Lys*_7 zeSj#O5j{a01@d$<j!);ACjINh7_VHFa|A0|EjBTT-CFexy&`cc$6iUKc@IuaYc1P6qFiz-NBIpDEu!Q=`COVEftrgF1eg8@=L{B}kDsP7wZ!vog3&W+7D)oQ zY^SoavTX9wXddnEzdSsraM8p-sydzaL~^f&jUh?Frp^0^TB6E*Yqy-YJsh5v?G|^> zYkRUbNIUzP%rHuC9x`f=exR{?Bolas!LYaHQSCn00MUS@f zjAd_{oe%L6VqMcjZp!DZ9Gkp6aHq61&mt<&Lz*MUP(q?`&gND^ za+XV@Zz24r5=@>dZrf`x;ag-Zg~ajG{p=b=qa3-VD#QH!xP( z;0etqv&s2mo$HC=_zDJw$~y>(TbbnVB8&~5)u`Qc7PjkgofyX>9rmQxuI3a4f$7q% zyn6q~yC(BKJ9j(l`_d|A5A#mqY)g%laMKD(rAw_hj21Ag;@qxS_2;RvF1olMV+X4@ zc-f8PlJQAZgfS7L=hUI(O?6;GqSNK+`jNk-C=e>fVLPOdF>uY72mgiTZuk2lP^)EU zndj}eUudaYD|BZ`Q=$gujPXqdpBsh+1)81-Hn}!P?BR{}rCc3p1dcR4m_DH;MD<9R ztJNFLVA2WSr)9aQUMphSv{NLeUuO5tbHeZd&i9kP2NyW;KIf2Hb}20AAj$1asho!_ z>=|#2zrX)Qwg+9Vu#$3IGuOQ0j}Gs{{MY19u5b4c(s^aRM=$#J7A;^aHMPzdbptO1 zCQyb&gT3kwO>J$T9(ClvMSs~XdOTlCcfQdOj=qk1SBzklEj=8P6r30JDp=Y`@r)vS zXjWZ;!{ej-fE|&Mm3%J(&L&vqTau@H-ED)$`3EFSU`Xf3y4A}ui;Hrs=`_;9Z z930t{r7bN=4AZt4*z3T1BEfEcC1T?tMdU^`1=HRwxR#JaYF`N~R~sxUCMJvJiKeDW z!840sQV_hWbF-unPdTPolsE_BVD}VQ@n^uKhBN@|MosQUI}~o^xSW|#6_E-Y;;4(F zh3$E{H0%=o&rZbQoSn)ien63%jUL_F&ma`tp-e0#W_A=1=Qz~$tY5={t_S^CwBC7(v_nC~(CT1oETlme>)SY+oE(m#Y|4DKAHDXF^M)L86XtzdR8_AaW<+B#%7 z&2nAg-{<3}tuv5`HzaRh64z=(tgiR-Rz;?HC?)ss#%^$ixWmEc` z!|Q?ug>T`&s{H<+Sf1Un;{X=@Z=KC|yVBYYS{zDxv#v~i9bbL-d%gA>m*Zki`bDT0 z_JPC|8}v|*=^+!02fN}n>4QA2`qC%eI%Y5TLvya|ZuFX~vtwg_Hkjpnp6q-ord_bh zoU_Ak^pCV@cD=;A(_{NSX8M=8(^AQ|zPJuS%dH#5j|+;H2D~&;y6E4Gv00V5R`2D( z=&YZ8CoW;EQAzd{$3Zlz-6kEJSQjLw2-na4J(K1&-@ylUwN=zfN0>5KYyTfF#uF~2 z%!(ZJ-kRhmt`)rzd0LV$cXrG0Bf)$A=uy~C8JI#q%U)%jEWY+-M{rdk!;xu88;(!1 z-a9fLPWF!Vs~~!AZrnG~LqM+lE+oEDr3D_=tr(kcBR=zJSj`LSH#>QwXi$_U4oLsalkn2L!;#oZbYO zPy+}5I)eR8jsJUg`_~uhtw0jLCm&G)NueOtR6_sIG@HzzQcIS|vy$B2%DVn#xYHft zI~)XF#6#cGWsji0+6>Bt7%iyzN$rn)3#aR|c|X5UizAyT2HDYq1VOd<=UJ7s;WvYQ zHtXxpU&fMpy$I4G>t#z+3md^Md$P~Y!pD*%qUBQMZ?Ul z?t;oOgUS)%8sTTVXQQuMS>#H1?l!4!g>tpL;fl|PT>A$|lV!YaWRH*h$yP9Qp}Ge| zOua=2P^$S~nDu`jFBO8q!daiSvQzSox^sn+omj>9Gt334=zIqg&F-(bmW4#}~U?)B(}D9w8Yi;Sp7z*_Avhud`BP zh3~ez2Zk1zF7p+hJ}bEn%O?Q`)azENS5P?ZV&oiNBQ32YCxBT*ljwmZVM zi=pl2YDeqVDD*}^@%Hj$E7I=H^;sgi7Mu)sl~K`H4Dy+2UkJw>woOz`>vQghyt*u9 zlpMF|gX0gSBx5(dj@oS>bY2+w{kuL&P$ZyKUqr57~X~E;uk&YLv<-n zjp_d@MG@&Sm@ugR{HgSdPDa*p&YPm!)^?3lLnQQ+Kv0jU?^_j3V&8F%@tD2=VNpht zR#ET0~WhM1$sNSqI#r{Vw*Ghlg=Oc*`1Hh%kJ)mU7(s6y2f{733!~4Ub(&3@E59)1B{iBH z2rj)7;0vync}}+p{MWWp4Ea0dSqnN@%0xXHhN2f$~^BnyAN!!p7 zd^CPB5^VZ$MC9$b<4jc*jYB#cVpq9`{jWCPKb*-Pwnr6B{&5gyH;QK;oo{@@ZMip| zkFRA?P4l=FKTE?+(zCUNlK|W4mT1A2vSEsiS6i3KBh?GW??6#vWtox-$wSra$*XyW zwVOcW${gg~le&Rex_rx8q`J>)aNz7kMgarl_YQ>qJpx>LBUSkp31!xqgx+19yuzae z!i-2o_2UcKSn9B`7Q#ReftE(3dpt3XE@Z53T$leDI`8gT6yW&Za_bgPEXb|4Fka&W z$o{os;)7y%wV;k}pi3%c5&8}1*0WdWu)Ehl2}Nyt5y^79mBuNkR+_Og<>eraE`_*ZyRY?BWftidweE_DFA4#3qKKzww z*KJ8%PfA1b0cr7uDXOj={t)imcF{Qzx;uz4W5@UjTnge~euS*!nI4x3gFIrMJb;0Q ziN0Yn&s?m9OEn@z9v*b+IP^$~*LMJVpHWBeh-?mEYHCGQ_cM5Kov8PF8!dEwGV%UZ zpL_A5TvBpQbL%k)H*mnc*CGhIOmzmnJD&TC6sSOGC7w@Tf(8e>(2PEow_kL1eP%Q) zS=Z%qn5wc_|${ha?kHPAM~7*Sc35_Ig3~qtwq0|6!`2qpj!G=!cv1QdPTs zsS85Uh%2x*h96P22VpS%%f%R6Vox6iE z8G8}aWXt}`13P*$hVMJouO%&R>~nzOsE%}Wpco&nTCK(Ii0M$4M|l{CEyp~^8-Lhl zWPVJ%Z>tS+MumSU=HZikLzhW)USij!okPxxMN|)b58k^)+#8O6mXBDx!?5%uf6G!m zcFj#Mo0l*iz?B7-3tL^#$jgj2Vri7aTt*B~VO4J8K|J%a!0a5P6gtOSBz)9BJa_1^ zP{b8+(9-Erpkr;d>-4naDK9~RgjNA@rfG=;aot~q64+J;-{rr*FhzAJKNVIpiPpYR z4lJK*aA@86U~aIft>z=#@6)QAIIj|^#bV5JMPfLtQlw8lbqFjKwkEL514z#Iwle*)%cG$9g{>zG&so}-MQvB1qPT{R;>#>E?3PJF z&bVys2oX*ln(mc6$T)~y>N{?b-)~h-fllOJG)vxSK1(2t4?4`h*o*gH^tya=Q=Hx{ z@e;EM6Y`6@rVCUeN`sDbp*41F<-MyD1?awQSDCrh{8{&JgW+y1)+MEeAu_P)QT7pG zldUAm1Zs} zTYcK81}BI&Y0V}U>U(2f)Y_c%j(_V8+pT{q3qFf{hzfI}&{2rTUMF0q{KcC8pa5s6 zVu*05DAJzoR2Hr~Xn|%Sh4$s>o3P5qvuzYt7BW8PIZuPUxakLGbx#;j%>)j>o%B!O z__r<5e;unq8mk6Z`GPD5yn+J&CD_`dDF=Y7&SU@|*l#ubznie9P!yZ!L*ZL-_5Vly h|CtKDUEt@0s@`cF?V73`4EE0f#rtaVxw3}-{|0VzRsaA1 literal 3992 zcmeHKX;4#X77cBXD7!SGfQGmS5JE(RAhKy#&8D)1B>|B&kY$2S)zti(`8ieda~OhuQchW^-}6mx$J=AZ67~ANa4roeyB&TS9Sz3X$nq2><SDYNWokAQ- zQ?{$S7`5=|8RE&BEsP7FxxtUL{KNfsx?>$Sdv#~`K zq$KZ3w_hwfDsaZK_-S-&I+}-hb+z7xWGR^D^Pb{HdQ-ixbFNiM*WDV{YaLg9B~_yV zenx8X_f3vNJMM(zq`7L44izi@d|T$zcP|FmE4I@UHo}?Ze##d&5BG)#5%cIV>!{g5XwnTpicGr&mu*-Z2&9a!da6?)lqA& z(Yc<$q$6^Cf~O9fAXo7j|LU$)e8`dY%CJnm9k83tGx{x)E_ukPx9?cQy&Zzscc^oc z*2*5ulS8*sh=o@*`c-TX5DnO!!>vj0-L>Tjdx6hYgRQH+d}7XwdQeHNWZYCks8T~2 zs1YHboE5(>zb2eM7|azf+)5b@K(t-+>Gr+MSdFmQlyqjJoI;HU*@MI=Mw#;L6E?ef z)7_oNmphBIyFz#Q5mCU#zsWc8(@G8|usKJ8pWOZXA2U(D%@?O~D(Lf+Kx6J>xUl3b zY=^_fybG1>-Jckhm5@5xZ>Uv0+>|;vq)NuxLIf)E4_$TyUG;PPZ^-m-9PIY~>rACl zMRT(axl`*K*%pZL)1}K>fi47-jIf38Ol=w{^I@ojf9M|^+&(n1#5-C!9I=zR$QC(xDeJQVeZBjy)ou8`m$}8v92QD?n2G{Jaa&ViMcls2_f1RY>{8i+o zcOo%>f?F6Mg!z;s2`Jh;*vQ`fSkcm(OxOf3T;kNrOOmd>t9|H8e4kaiWB5{kzb{eb zk4s8Hd(*$idTSG{~54O#w)bNzJzgGkq0_#}8OJ%KQRIF!2WPU)SQ6Q*kM-1ZEi;eluM7H)D(o{*BBWaOW1t zB#(+sqGUhZpFvm(m6<-wU{%ky-PbH@ti8Ni-E?7?mf#H=gMIirD(^`sJe4jh&+v)S zo9~%*K+I)1$J_#9>!6U30lD%*A!qL9us#i~ z^mwn7H`X)0sN3r_n$t&U(b&vJ+vUG<25_sa{eQUX&*>x#K-&q~gUh?2W&0>ej+BWu zjviQOIrisnRXnFW)J4bp0rd9~t1?Bwn#Xkm)+H{Ry+6gZEJz#sf*$7esgT#Xi0CP16C8B|dv?cgg}Gk)$`robY=ky3k z9d@XBKG;0kFIawt+&=-_yIsg#{TkUi%PtGBVk|$NpruVvRI2@~N@KgnsW80f`zsuq zK-LJwD6b!LrCD(=ky~BWm$w~j1_@EOTh+2*m2GAW@JbUeW=2`lpfl+X=lm_WgMdCDUjT)aGGVWK-dY@zWu@VT~*kf?6wEa^x5 zu3R#CED9lowj?72HZSRYnTz@@U5}5o>K#5?Q&La>A$QJ9YdmnB9(h15&M%y;LCA+r zk+q664oxFZ>kYM#4|su+mc+qU+me^kBX>|!Aj3qmn%#1}z5U%O)*M%Cd4m!^sFE?` z@AF^}aO57}HkV!yGTwDePr0ceBy?TX(P#D&?8hw%vOzhYs>ipF7Ml)NXth>5a>kr& zdUye1L7O7Y#PFLDg35VNgZ)eckJ*gSANllUDOMe(ciFc2Gp7xELk})qJo{bI3b)t= zip|H+HuZC_Ux&@fJTa@P=^yZ_qPl3sJcrEhG6{urz#r|yzmT;xy|tpd+>IcfD3-MB zt1kLc?Au{4w>Dm&&Sk29WRjt*S_d!5LS!2p6Lc|H;Mm5GB!{i|kPVW57qig1DFM z*YvNb)T*FaMHzC^!THiRU-$FgK+rEPVQLMZZ@6K=^LkvW>`wL^E&y4yJgJ9z%So>f zmE30;7E;?x{@Izd{F)kG0rOmEu|JcaUYvLIqSeaQY}g%HH>T`T@pf1_o=?6NV}w_h zORL;dLa_$G=g+kd7t2Js;@z^MI#X^AhS@N>0`~CA#`_2VwZkS+h>A_Mp6VbZUs4h+lxrUA~5h*M;H|KxuFjY``2 From 5185f3b20504627b16d44a6fe6daec62209d2b52 Mon Sep 17 00:00:00 2001 From: Meigurad Date: Mon, 3 Apr 2017 23:20:44 +0200 Subject: [PATCH 051/121] Updating documentation --- doc/doc.md | 6 +++--- doc/func2.png | Bin 36851 -> 10072 bytes 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/doc.md b/doc/doc.md index 222ffce..d8e13f3 100644 --- a/doc/doc.md +++ b/doc/doc.md @@ -17,11 +17,11 @@ * [Odmocnina](#odmocnina) * [Tutorial](#tutorial) * [Komponenty](#komponenty) - * [Přáce s kalkulačkou](#práce-s-kalkulačkou) + * [Práce s kalkulačkou](#práce-s-kalkulačkou) -## Introduction +## Úvod -This application represents classic calculator with special functions. Calculator can work with hugh nun numbers, because his core is written in [Python](https://www.python.org/). +Tato aplikace představuje klasickou kalkulačku se speciálními funkcemi. Jádro Barbie Calculatoru je napsáno v [Pythonu](https://www.python.org/). ## Instalace diff --git a/doc/func2.png b/doc/func2.png index f46fb94fd138c4c82155ad327e961e1641323fb5..6130c779e367916a02c9f147c77b150654584ec8 100644 GIT binary patch literal 10072 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 Q+=c_FDQPLf?>`Fu4@uQ>IsgCw literal 36851 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 GpZ*uToEdfi From db4eb4fe133082afcb646b8303007089201e021d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Mon, 3 Apr 2017 23:28:22 +0200 Subject: [PATCH 052/121] Fix factorial of variable with number [Closes #61] --- .../core/parser/preprocessor/factorial.py | 6 +++--- .../core/parser/preprocessor/factorial.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/calculator/core/parser/preprocessor/factorial.py b/src/calculator/core/parser/preprocessor/factorial.py index b4bec5d..fea4e8d 100644 --- a/src/calculator/core/parser/preprocessor/factorial.py +++ b/src/calculator/core/parser/preprocessor/factorial.py @@ -18,11 +18,11 @@ class FactorialPreprocessor(object): # position is reverted, because factorial is matched from !, which is at the end of wanted expression _MATH_EXPRESSION_REGEX = re.compile( r''' - [\dA-F.]+ # number or variable name + [\wA-F.]+ # number or variable name | - \)[\d\w+-/* ]+\(\w* # or function call with any arguments + \)[\w+-/* ]+\(\w* # or function call with any arguments | - \)[()\d\w+-/* ]+\( # or any expression + \)[()\w+-/* ]+\( # or any expression ''', re.VERBOSE ) diff --git a/src/tests/calculator/core/parser/preprocessor/factorial.py b/src/tests/calculator/core/parser/preprocessor/factorial.py index 04971e5..1e6636f 100644 --- a/src/tests/calculator/core/parser/preprocessor/factorial.py +++ b/src/tests/calculator/core/parser/preprocessor/factorial.py @@ -66,6 +66,20 @@ def test_factorized_function(self): 'Test for factorized of result from function call.' ) + def test_variable_factorial(self): + self.assertEqual( + self._format_factorial('foobar'), + self.preprocessor('foobar!'), + 'Test for factorized foo variable call.' + ) + + def test_advanced_variable_factorial(self): + self.assertEqual( + self._format_factorial('foobar56 + bar5'), + self.preprocessor('(foobar56 + bar5)!'), + 'Test for factorized foo variable call.' + ) + @staticmethod def _format_factorial(expr: str = '', pre: str = '', post: str = '') -> str: return '{}{}({}){}'.format( From 09e02353892094773a1286f068af9295c38eb304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Mon, 3 Apr 2017 23:57:38 +0200 Subject: [PATCH 053/121] Replace factorial by gamma function to signalize overflow errors [#64] --- src/calculator/core/math/math.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/calculator/core/math/math.py b/src/calculator/core/math/math.py index 86f50e3..da88efd 100644 --- a/src/calculator/core/math/math.py +++ b/src/calculator/core/math/math.py @@ -3,9 +3,9 @@ import operator import random -from calculator.exceptions import MathError from calculator._typing import BinaryNumericFunction from calculator._typing import NumericValue +from calculator.exceptions import MathError class Math(object): @@ -47,15 +47,18 @@ def modulo(a: NumericValue, b: NumericValue) -> NumericValue: raise MathError from e @staticmethod - def fact(n: int) -> int: + def fact(n: NumericValue) -> NumericValue: try: - return math.factorial(n) + return math.gamma(n + 1) except ValueError as e: raise MathError from e - @classmethod - def log(cls, x: NumericValue, base: NumericValue = 10) -> NumericValue: - return cls.ln(x, base) + @staticmethod + def log(x: NumericValue, base: NumericValue = 10) -> NumericValue: + try: + return math.log(x, base) + except ValueError as e: + raise MathError from e @staticmethod def root(x: NumericValue, y: NumericValue = 2) -> NumericValue: @@ -75,10 +78,6 @@ def rand() -> NumericValue: """ return random.random() - @staticmethod - def ln(x: NumericValue, base: NumericValue = math.e) -> NumericValue: - # TODO choosing more accurate functions - try: - return math.log(x, base) - except ValueError as e: - raise MathError from e + @classmethod + def ln(cls, x: NumericValue) -> NumericValue: + return cls.log(x, math.e) From 62d0625627054401f5a8066123f347250bfa9fae Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Tue, 4 Apr 2017 00:21:12 +0200 Subject: [PATCH 054/121] Change function expansion behaviour - [#65] --- src/calculator/ui/qml/main.qml | 13 +++++++++++++ src/calculator/ui/types/expression/exp_analyzer.py | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index e51b02d..eaacc03 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -327,15 +327,28 @@ ApplicationWindow { */ function expandExpression(expansionKey) { var expansion = exa.expandExpression(expansionKey) + var moveCursorInsideFunction = false if(expInput.selectedText == "") { var borders = exa.currentWordBorders() expInput.remove(borders["start"], borders["end"]) + moveCursorInsideFunction = true } + else expInput.remove(expInput.selectionStart, expInput.selectionEnd) expInput.insert(expInput.selectionStart, expansion) + + // if only expand empty function put cursor inside its body + // need to move to get know function signature + if(moveCursorInsideFunction) + expInput.cursorPosition--; + + // but if function does not take any paramaters put cursor on the end + // if contains "()" in signature => it's function that does not take any parameter + if(exa.currentFunctionSignature().indexOf("()") != -1) + expInput.cursorPosition++; } /** diff --git a/src/calculator/ui/types/expression/exp_analyzer.py b/src/calculator/ui/types/expression/exp_analyzer.py index d8a3671..7579c97 100644 --- a/src/calculator/ui/types/expression/exp_analyzer.py +++ b/src/calculator/ui/types/expression/exp_analyzer.py @@ -159,7 +159,7 @@ def expandExpression(self, expansion: str) -> str: content = self._get_content() if borders["end"] == -1 and not selected_text: - return "{}(".format(expansion) + return "{}()".format(expansion) for i in range(borders["end"], len(content)): current_char = content[i] @@ -172,7 +172,7 @@ def expandExpression(self, expansion: str) -> str: if selected_text: return "{}({})".format(expansion, selected_text) else: - return "{}(".format(expansion) + return "{}()".format(expansion) @pyqtSlot(result=QVariant) def currentWordBorders(self) -> QVariant: From 4688f69b7e58d9303d62e3b0b565d51edcff7636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Tue, 4 Apr 2017 00:44:06 +0200 Subject: [PATCH 055/121] Fix updating Ans as result of assign, fix numeric value label in function signatures --- src/calculator/_typing.py | 4 ++-- src/calculator/ui/adapter.py | 2 ++ src/calculator/utils/formatter.py | 10 ++++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/calculator/_typing.py b/src/calculator/_typing.py index caa9a4c..e6e777c 100644 --- a/src/calculator/_typing.py +++ b/src/calculator/_typing.py @@ -1,7 +1,7 @@ # coding=utf-8 -from typing import Union, Tuple, Callable, Set +from typing import Union, Tuple, Callable, Set, NewType -NumericValue = Union[int, float] # possible types of result of mathematical expression +NumericValue = NewType('NumericValue', Union[int, float]) # possible types of result of mathematical expression Variable = Tuple[ NumericValue, # actual value diff --git a/src/calculator/ui/adapter.py b/src/calculator/ui/adapter.py index 1e46d3b..17b550e 100644 --- a/src/calculator/ui/adapter.py +++ b/src/calculator/ui/adapter.py @@ -41,6 +41,8 @@ def process(self, expression: str) -> None: result, variables = self._calculator.process(expression) created_variables, modified_variables = self._commit_new_variables_state(variables=variables) + if Calculator.ANSWER_VARIABLE_NAME in modified_variables: + result, *_ = variables.get(Calculator.ANSWER_VARIABLE_NAME) self.identifiersTypesChanged.emit(self.identifiersTypes) self.processed.emit(QVariant({ diff --git a/src/calculator/utils/formatter.py b/src/calculator/utils/formatter.py index 983462b..1d45051 100644 --- a/src/calculator/utils/formatter.py +++ b/src/calculator/utils/formatter.py @@ -70,14 +70,16 @@ def format_function_args_spec(cls, func_identifier: str, func_signature: Signatu formatted_args = list() for identifier, default, annotation in raw_args: - annotation = repr(annotation) + annotation_repr = repr(annotation) formatted_args.append(( identifier, default, - cls.CLASS_TO_TYPE_REGEX.search(annotation).group(0) - if " Date: Tue, 4 Apr 2017 01:18:45 +0200 Subject: [PATCH 056/121] Add expansion around current word, when expand using function panel - [#65] --- src/calculator/ui/qml/main.qml | 22 ++++++++++++++++--- .../ui/types/expression/exp_analyzer.py | 8 +++++-- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index eaacc03..1082221 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -106,7 +106,7 @@ ApplicationWindow { anchors.left: parent.left anchors.bottom: parent.bottom - onExpandRequest: expandExpression(func) + onExpandRequest: expandExpressionAroundWord(func) } FontMetrics { @@ -321,13 +321,29 @@ ApplicationWindow { expInput.text = newExpression } + /** + Expand expression into current expresion¨around currend word if there isn't and text selected + @param expressionKey Key of builtin expression or dynamic expression + */ + function expandExpressionAroundWord(expansion) { + expansion = exa.expandExpression(expansion, true) + var borders = exa.currentWordBorders() + + if(expInput.selectedText == "") + expInput.remove(borders["start"], borders["end"]) + else + expInput.remove(expInput.selectionStart, expInput.selectionEnd) + + expInput.insert(expInput.selectionStart, expansion) + } + /** Expand expression into current expresion @param expressionKey Key of builtin expression or dynamic expression */ - function expandExpression(expansionKey) { - var expansion = exa.expandExpression(expansionKey) + function expandExpression(expansion) { var moveCursorInsideFunction = false + expansion = exa.expandExpression(expansion, false) if(expInput.selectedText == "") { var borders = exa.currentWordBorders() diff --git a/src/calculator/ui/types/expression/exp_analyzer.py b/src/calculator/ui/types/expression/exp_analyzer.py index 7579c97..ec444c0 100644 --- a/src/calculator/ui/types/expression/exp_analyzer.py +++ b/src/calculator/ui/types/expression/exp_analyzer.py @@ -140,14 +140,18 @@ def getExpansion(self, expansion: str) -> str: return func_expansion return expansion - @pyqtSlot(str, result=str) - def expandExpression(self, expansion: str) -> str: + + @pyqtSlot(str, bool, result=str) + def expandExpression(self, expansion: str, expand_around_current_word: bool) -> str: """ :param expansion: String to expand + :param expand_around_current_word: If set to True, then exand around current word :return: Expanded expansion """ _, _, selected_text = self._get_selection() + if not selected_text and expand_around_current_word: + selected_text = self.currentWord().replace("\\", "") expansion_type = self.expansionType(expansion) expansion = self.getExpansion(expansion) From ac37e614ce3802fa8503112965495d117a28a536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Tue, 4 Apr 2017 01:37:58 +0200 Subject: [PATCH 057/121] Fix formatting source expression in Answer var --- src/calculator/settings.py | 2 +- src/calculator/ui/adapter.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/calculator/settings.py b/src/calculator/settings.py index 9228863..1dec5eb 100644 --- a/src/calculator/settings.py +++ b/src/calculator/settings.py @@ -19,7 +19,7 @@ class BuiltinFunction(object): RAND = 'rand' -EXPRESSION_SPLITTERS = {"+", "-", "(", "*", "/"} +EXPRESSION_SPLITTERS = set("+-(*/,%") class Expression(QObject): diff --git a/src/calculator/ui/adapter.py b/src/calculator/ui/adapter.py index 17b550e..114a77d 100644 --- a/src/calculator/ui/adapter.py +++ b/src/calculator/ui/adapter.py @@ -170,4 +170,6 @@ def _format_source_expression(variable: str, source_expression: str) -> str: :param source_expression: source expression for variable :return: striped source expression """ - return source_expression.replace(variable, '', 1).lstrip() + return source_expression.replace(variable, '', 1).strip() \ + if '=' in source_expression else \ + source_expression.strip() From 341898fce8ced5519a58853d38e6dd809b48f882 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Tue, 4 Apr 2017 01:29:48 +0200 Subject: [PATCH 058/121] Fix set expression mousearea width to component width, fix expansion of expression --- .../ui/qml/visualization/VariableDisplay.qml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/calculator/ui/qml/visualization/VariableDisplay.qml b/src/calculator/ui/qml/visualization/VariableDisplay.qml index 3c306af..408abe0 100644 --- a/src/calculator/ui/qml/visualization/VariableDisplay.qml +++ b/src/calculator/ui/qml/visualization/VariableDisplay.qml @@ -96,10 +96,19 @@ Rectangle { anchors.top: parent.top MouseArea { - anchors.fill: parent hoverEnabled: true + width: component.width - onClicked: component.overwriteRequest(component.variableIdentifier + ' ' + component.variableExpression) + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + + onClicked: { + if(component.variableExpression.indexOf("=") != -1) + component.overwriteRequest(component.variableIdentifier + ' ' + component.variableExpression) + else + component.overwriteRequest(component.variableExpression) + } onContainsMouseChanged: { if(containsMouse) expressionBackground.color = component.expressionHoverColor From a0c8232c63e2d6add72a8a96d522b7319895e539 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Tue, 4 Apr 2017 01:43:00 +0200 Subject: [PATCH 059/121] Fix expansion in variable display --- src/calculator/ui/qml/main.qml | 16 +++++-- .../ui/qml/visualization/VariableDisplay.qml | 44 ++++++++++--------- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index 1082221..bbf8722 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -87,7 +87,7 @@ ApplicationWindow { onDeleteVariableRequest: Calculator.removeVariable(identifier) onSetVariableRequest: Calculator.setVariableValue(identifier, value) - onExpandRequest: expandExpression(data) + onExpandRequest: forceExpandExpression(data) onOverwriteRequest: overwriteExpression(data) } @@ -322,8 +322,16 @@ ApplicationWindow { } /** - Expand expression into current expresion¨around currend word if there isn't and text selected - @param expressionKey Key of builtin expression or dynamic expression + Expand expression into current expresion without any optimatizations + @param expression Inserting expansion + */ + function forceExpandExpression(expansion) { + expInput.insert(expInput.selectionStart, expansion) + } + + /** + Expand expression into current expresion around currend word if there isn't and text selected + @param expression Key of builtin expression or dynamic expression */ function expandExpressionAroundWord(expansion) { expansion = exa.expandExpression(expansion, true) @@ -339,7 +347,7 @@ ApplicationWindow { /** Expand expression into current expresion - @param expressionKey Key of builtin expression or dynamic expression + @param expression Key of builtin expression or dynamic expression */ function expandExpression(expansion) { var moveCursorInsideFunction = false diff --git a/src/calculator/ui/qml/visualization/VariableDisplay.qml b/src/calculator/ui/qml/visualization/VariableDisplay.qml index 408abe0..511ec8f 100644 --- a/src/calculator/ui/qml/visualization/VariableDisplay.qml +++ b/src/calculator/ui/qml/visualization/VariableDisplay.qml @@ -42,7 +42,7 @@ Rectangle { if(mouse.button == Qt.LeftButton) component.expandRequest(component.variableIdentifier) else - component.overwriteRequest(component.variableIdentifier + ' ' + component.variableExpression) + createExpansionRequest() } } @@ -94,27 +94,22 @@ Rectangle { anchors.left: parent.left anchors.top: parent.top + } + + MouseArea { + hoverEnabled: true + width: component.width + + anchors.left: expression.left + anchors.top: expression.top + anchors.bottom: expression.bottom - MouseArea { - hoverEnabled: true - width: component.width - - anchors.left: parent.left - anchors.top: parent.top - anchors.bottom: parent.bottom - - onClicked: { - if(component.variableExpression.indexOf("=") != -1) - component.overwriteRequest(component.variableIdentifier + ' ' + component.variableExpression) - else - component.overwriteRequest(component.variableExpression) - } - onContainsMouseChanged: { - if(containsMouse) - expressionBackground.color = component.expressionHoverColor - else - expressionBackground.color = "transparent" - } + onClicked: createExpansionRequest() + onContainsMouseChanged: { + if(containsMouse) + expressionBackground.color = component.expressionHoverColor + else + expressionBackground.color = "transparent" } } } @@ -131,4 +126,11 @@ Rectangle { anchors.rightMargin: internal.sideMargin anchors.bottom: leftSide.bottom } + + function createExpansionRequest() { + if(component.variableExpression.indexOf("=") != -1) + component.overwriteRequest(component.variableIdentifier + ' ' + component.variableExpression) + else + component.expandRequest(component.variableExpression) + } } From 876e27d48f480dd65fc4c413a0ccf7351fa88645 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Tue, 4 Apr 2017 02:07:31 +0200 Subject: [PATCH 060/121] New FlickableAnimatedText --- src/calculator/ui/qml.qrc | 1 + .../visualization/FlickableAnimatedText.qml | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 src/calculator/ui/qml/visualization/FlickableAnimatedText.qml diff --git a/src/calculator/ui/qml.qrc b/src/calculator/ui/qml.qrc index 5dfa635..0601782 100644 --- a/src/calculator/ui/qml.qrc +++ b/src/calculator/ui/qml.qrc @@ -50,5 +50,6 @@ qml/visualization/ResultSystemDisplay.qml qml/visualization/CountDown.qml qml/visualization/FunctionSignatureDisplay.qml + qml/visualization/FlickableAnimatedText.qml diff --git a/src/calculator/ui/qml/visualization/FlickableAnimatedText.qml b/src/calculator/ui/qml/visualization/FlickableAnimatedText.qml new file mode 100644 index 0000000..e4a212d --- /dev/null +++ b/src/calculator/ui/qml/visualization/FlickableAnimatedText.qml @@ -0,0 +1,26 @@ +import QtQuick 2.0 + +/** + Text with ability of flicking. + */ +Flickable { + id: component + + /// Displayed text + property alias text: text.text + /// Text Color + property alias color: text.color + /// Used font + property alias font: text.font + + boundsBehavior: Flickable.StopAtBounds + flickableDirection: Flickable.HorizontalFlick + clip: true + + contentHeight: text.height + contentWidth: text.width + + AnimatedText { + id: text + } +} From 6880a8aeaa639cb9cb77ea210e345afe67bac8d9 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Tue, 4 Apr 2017 02:08:07 +0200 Subject: [PATCH 061/121] Refactor use FlickableAnimatedText in ResultSystemDisplay --- .../qml/visualization/ResultSystemDisplay.qml | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml b/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml index e9fbf6e..294188a 100644 --- a/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml +++ b/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml @@ -73,28 +73,18 @@ Rectangle { } } - Flickable { + FlickableAnimatedText { id: flick - boundsBehavior: Flickable.StopAtBounds - flickableDirection: Flickable.HorizontalFlick - clip: true + text: Calculator.convertToBase(component.value, component.bases[modelData]) + color: component.valueTextColor + font: baseText.font width: parent.width - anchors.leftMargin height: parent.height - contentHeight: valueText.height - contentWidth: valueText.width - anchors.left: parent.left anchors.leftMargin: component.margin * 3 + fm.advanceWidth("DEC") // some constant to measure font width - - AnimatedText { - id: valueText - text: Calculator.convertToBase(component.value, component.bases[modelData]) - color: component.valueTextColor - font: baseText.font - } } } } From 6a4dac183ce569365c83ecc6502d98449abd3acf Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Tue, 4 Apr 2017 02:09:09 +0200 Subject: [PATCH 062/121] Change expression in variableItem is now flickable --- src/calculator/ui/assets/styles/UIStyles.qml | 2 + .../ui/qml/containers/VariablesPanel.qml | 5 ++ src/calculator/ui/qml/item/VariableItem.qml | 3 +- src/calculator/ui/qml/main.qml | 2 + .../ui/qml/visualization/VariableDisplay.qml | 76 ++++++++++++------- 5 files changed, 60 insertions(+), 28 deletions(-) diff --git a/src/calculator/ui/assets/styles/UIStyles.qml b/src/calculator/ui/assets/styles/UIStyles.qml index 8bf0d6b..7b61b85 100644 --- a/src/calculator/ui/assets/styles/UIStyles.qml +++ b/src/calculator/ui/assets/styles/UIStyles.qml @@ -77,10 +77,12 @@ QtObject { property color textColor: "white" property color identifierColor: "black" property color expressionHoverColor: "#AAAAAA" + property color scrollbarColor: "black" property font font: styles.variablesPanel.font } property QtObject variableItem: QtObject { + property color scrollbarColor: "#ED1D3D" property QtObject dots: QtObject { property color color: "#3D3D3D" } diff --git a/src/calculator/ui/qml/containers/VariablesPanel.qml b/src/calculator/ui/qml/containers/VariablesPanel.qml index aa906b2..d646c47 100644 --- a/src/calculator/ui/qml/containers/VariablesPanel.qml +++ b/src/calculator/ui/qml/containers/VariablesPanel.qml @@ -53,6 +53,8 @@ Item { property alias ansIdentifierTextColor: ansItem.identifierTextColor /// Background color of Ans variable expression while hovering property alias ansExpressionHoverColor: ansItem.expressionHoverColor + /// Color of value scrollbar Ans variable + property alias ansScrollbarColor: ansItem.scrollbarColor /// Background color of dots(area to slide variable options) property color dotsBackgroundColor @@ -64,6 +66,8 @@ Item { property color settersHoveredColor /// Text color of variable setters property color settersTextColor + /// Color of value scrollbar + property color itemScrollbarColor /// Font of panel property font font @@ -92,6 +96,7 @@ Item { object.settersColor = Qt.binding(function() { return component.settersColor }) object.settersHoveredColor = Qt.binding(function() { return component.settersHoveredColor }) object.settersTextColor = Qt.binding(function() { return component.settersTextColor }) + object.scrollbarColor = Qt.binding(function() { return component.itemScrollbarColor }) object.width = Qt.binding(function() { return component.width }) object.height = Qt.binding(function() { return component.itemHeight }) diff --git a/src/calculator/ui/qml/item/VariableItem.qml b/src/calculator/ui/qml/item/VariableItem.qml index 5b1bf77..00b868f 100644 --- a/src/calculator/ui/qml/item/VariableItem.qml +++ b/src/calculator/ui/qml/item/VariableItem.qml @@ -47,6 +47,8 @@ Item { property alias variableValue: content.variableValue /// Used font property alias font: content.font + /// Color of value scrollbar + property alias scrollbarColor: content.scrollbarColor /// Background color of dots(area to slide variable options) property alias dotsBackgroundColor: optionsMenu.dotsBackgroundColor @@ -59,7 +61,6 @@ Item { /// Text color of variable setters property alias settersTextColor: optionsMenu.settersTextColor - clip: true Visualization.VariableDisplay { diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index bbf8722..5a80424 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -69,12 +69,14 @@ ApplicationWindow { ansIdentifierTextColor: StyleSettings.ans.identifierColor ansColor: StyleSettings.ans.backgroundColor ansExpressionHoverColor: StyleSettings.ans.expressionHoverColor + ansScrollbarColor: StyleSettings.ans.scrollbarColor dotsBackgroundColor: StyleSettings.variableItem.dots.color removeButtonColor: StyleSettings.variableItem.removeButton.color settersColor: StyleSettings.variableItem.setters.color settersHoveredColor: StyleSettings.variableItem.setters.hoverColor settersTextColor: StyleSettings.variableItem.setters.textColor + itemScrollbarColor: StyleSettings.variableItem.scrollbarColor font.family: StyleSettings.variablesPanel.font.family diff --git a/src/calculator/ui/qml/visualization/VariableDisplay.qml b/src/calculator/ui/qml/visualization/VariableDisplay.qml index 511ec8f..72a906e 100644 --- a/src/calculator/ui/qml/visualization/VariableDisplay.qml +++ b/src/calculator/ui/qml/visualization/VariableDisplay.qml @@ -28,6 +28,8 @@ Rectangle { property string variableValue: "0" /// Used font property font font + /// Color of value scrollbar + property color scrollbarColor QtObject { id: internal @@ -60,6 +62,19 @@ Rectangle { } } + // value display + AnimatedText { + text: component.variableValue + color: component.textColor + + font.family: component.font.family + font.pixelSize: parent.height * 0.55 + + anchors.right: parent.right + anchors.rightMargin: internal.sideMargin + anchors.bottom: leftSide.bottom + } + // display expression and variable name Item { id: leftSide @@ -82,7 +97,23 @@ Rectangle { anchors.left: parent.left } - AnimatedText { + Rectangle { + color: component.scrollbarColor + opacity: (expression.visibleArea.widthRatio != 1 && expression.moving) + + x: expression.width * expression.visibleArea.xPosition + expression.x + width: expression.width * expression.visibleArea.widthRatio + height: 2 + z: 2 + + anchors.top: expression.bottom + + Behavior on opacity { + NumberAnimation { duration: 200 } + } + } + + FlickableAnimatedText { id: expression antialiasing: true @@ -92,41 +123,32 @@ Rectangle { font.family: component.font.family font.pixelSize: parent.height * 0.23 + width: component.width - internal.sideMargin * 2 + height: contentHeight + anchors.left: parent.left anchors.top: parent.top - } - MouseArea { - hoverEnabled: true - width: component.width + MouseArea { + hoverEnabled: true - anchors.left: expression.left - anchors.top: expression.top - anchors.bottom: expression.bottom + anchors.fill: parent - onClicked: createExpansionRequest() - onContainsMouseChanged: { - if(containsMouse) - expressionBackground.color = component.expressionHoverColor - else - expressionBackground.color = "transparent" + onClicked: { + createExpansionRequest() + mouse.accepted = false + } + + onContainsMouseChanged: { + if(containsMouse) + expressionBackground.color = component.expressionHoverColor + else + expressionBackground.color = "transparent" + } } } } - // value display - AnimatedText { - text: component.variableValue - color: component.textColor - - font.family: component.font.family - font.pixelSize: parent.height * 0.55 - - anchors.right: parent.right - anchors.rightMargin: internal.sideMargin - anchors.bottom: leftSide.bottom - } - function createExpansionRequest() { if(component.variableExpression.indexOf("=") != -1) component.overwriteRequest(component.variableIdentifier + ' ' + component.variableExpression) From bef8beb713bbf13463e26c43fa36b54b114afbed Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Tue, 4 Apr 2017 14:05:45 +0200 Subject: [PATCH 063/121] Fix moving cursor after expansion - #65 --- src/calculator/ui/qml/main.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index 5a80424..dab527e 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -353,6 +353,7 @@ ApplicationWindow { */ function expandExpression(expansion) { var moveCursorInsideFunction = false + var expansionType = exa.expansionType(expansion) expansion = exa.expandExpression(expansion, false) if(expInput.selectedText == "") { @@ -368,7 +369,7 @@ ApplicationWindow { // if only expand empty function put cursor inside its body // need to move to get know function signature - if(moveCursorInsideFunction) + if(moveCursorInsideFunction && expansionType == Expansion.BracketsPack) expInput.cursorPosition--; // but if function does not take any paramaters put cursor on the end From 7182ee1bc7c2f416e0a4ed55bffdfb962ecc0b23 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Tue, 4 Apr 2017 16:28:58 +0200 Subject: [PATCH 064/121] Fix overlapping ov Anis item - #67 --- src/calculator/ui/qml/containers/VariablesPanel.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/calculator/ui/qml/containers/VariablesPanel.qml b/src/calculator/ui/qml/containers/VariablesPanel.qml index d646c47..0235ec7 100644 --- a/src/calculator/ui/qml/containers/VariablesPanel.qml +++ b/src/calculator/ui/qml/containers/VariablesPanel.qml @@ -119,6 +119,7 @@ Item { variableValue: "0" height: component.itemHeight + z: 2 font.family: component.font.family @@ -155,9 +156,9 @@ Item { opacity: (flick.visibleArea.heightRatio === 1) ?0 :1 - y: flick.visibleArea.yPosition * component.height + y: flick.visibleArea.yPosition * flick.height + flick.y width: 3 - height: component.height * flick.visibleArea.heightRatio + height: flick.height * flick.visibleArea.heightRatio Behavior on opacity { NumberAnimation { duration: 400 } From ecd44fc478d21ac4b011bf7040f11985bc6b700a Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Tue, 4 Apr 2017 16:38:55 +0200 Subject: [PATCH 065/121] Fix show completer in abs(2+) after adding operator --- src/calculator/ui/qml/main.qml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index dab527e..fb60197 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -302,6 +302,9 @@ ApplicationWindow { var lastChar = expInput.text.slice(-1) var currentWord = exa.currentWord() + if(expInput.cursorPosition) + lastChar = expInput.text[expInput.cursorPosition - 1] + if(Calculator.expressionSplitters.indexOf(lastChar) != -1) completer.show() From b3267043970c5e9d3dc1bf8c60c3315fd78c436c Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Tue, 4 Apr 2017 17:33:22 +0200 Subject: [PATCH 066/121] Fix dropdown bug, scrolling out of area with items --- src/calculator/ui/qml/controls/DropDown.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calculator/ui/qml/controls/DropDown.qml b/src/calculator/ui/qml/controls/DropDown.qml index 50106f1..aa7bc25 100644 --- a/src/calculator/ui/qml/controls/DropDown.qml +++ b/src/calculator/ui/qml/controls/DropDown.qml @@ -187,8 +187,11 @@ Item { MouseArea { anchors.fill: parent hoverEnabled: true - onEntered: component.currentItemIndex = index onClicked: component.chooseCurrent() + onContainsMouseChanged: { + if(containsMouse) + component.currentItemIndex = index + } } } } From b983d5737bb77665e5fcc503400e8f69b6d533fb Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Tue, 4 Apr 2017 18:12:10 +0200 Subject: [PATCH 067/121] Fix moving outside of dropdown --- src/calculator/ui/qml/controls/DropDown.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calculator/ui/qml/controls/DropDown.qml b/src/calculator/ui/qml/controls/DropDown.qml index aa7bc25..8b6dad3 100644 --- a/src/calculator/ui/qml/controls/DropDown.qml +++ b/src/calculator/ui/qml/controls/DropDown.qml @@ -189,7 +189,7 @@ Item { hoverEnabled: true onClicked: component.chooseCurrent() onContainsMouseChanged: { - if(containsMouse) + if(containsMouse && flick.y == 0) component.currentItemIndex = index } } From 5a46fdf20f108bc0c234afe951bb351d656e84a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Tue, 4 Apr 2017 18:19:34 +0200 Subject: [PATCH 068/121] Refactor of function call - now is supported also keyword arguments --- src/calculator/core/solver/solver.py | 29 +++++++++++++++---- src/calculator/exceptions.py | 6 +++- src/calculator/ui/adapter.py | 18 +++++++----- .../calculator/core/solver/solver_resolve.py | 26 +++++++++++++---- 4 files changed, 60 insertions(+), 19 deletions(-) diff --git a/src/calculator/core/solver/solver.py b/src/calculator/core/solver/solver.py index ecfd301..6db679c 100644 --- a/src/calculator/core/solver/solver.py +++ b/src/calculator/core/solver/solver.py @@ -1,6 +1,7 @@ # coding=utf-8 from ast import BinOp, Add, Num, Sub, Div, Mult, Call, AST, UnaryOp, USub, Name, Pow, FloorDiv, Mod from inspect import Signature +from operator import attrgetter from typing import Dict, Union, Type, Set, Optional from calculator import BinaryNumericFunction, NumericFunction, NumericValue, Variable @@ -111,19 +112,35 @@ def _(self, call: Call) -> NumericValue: :return: result of the called function """ # TODO I am not sure, if call.func is always Name node with .id attribute - function = self.builtin_functions.get(call.func.id) + function_name = call.func.id + function = self.builtin_functions.get(function_name) if not callable(function): - raise NotImplementedError(call.func.id) + raise NameError(function_name) signature = Signature.from_callable(function) - params = tuple(map(self._resolve, call.args)) + + args = tuple(map(self._resolve, call.args)) + kwargs = dict(zip( + map(attrgetter('arg'), call.keywords), + map( + self._resolve, + map( + attrgetter('value'), + call.keywords + ) + ) + )) + try: - signature.bind(*params) + signature.bind(*args, **kwargs) except TypeError as e: - raise InvalidFunctionCallError('Given parameters does not correspond to function signature.') from e + raise InvalidFunctionCallError( + function_name, + 'Given parameters does not correspond to function signature.' + ) from e - return function(*params) + return function(*args, **kwargs) @_resolve.register(Num) def _(self, num: Num) -> NumericValue: diff --git a/src/calculator/exceptions.py b/src/calculator/exceptions.py index 897856f..dc2a5cd 100644 --- a/src/calculator/exceptions.py +++ b/src/calculator/exceptions.py @@ -37,7 +37,7 @@ class VariableRemoveRestrictError(VariableError): """ def __init__(self, dependencies, *args, **kwargs): - self._dependencies = dependencies + self.dependencies = dependencies super().__init__(*args, **kwargs) @@ -45,3 +45,7 @@ class InvalidFunctionCallError(TypeError): """ Raised to signalize of call function with incorrect count of parameters. """ + + def __init__(self, function_name, *args, **kwargs): + self.function_name = function_name + super().__init__(*args, **kwargs) diff --git a/src/calculator/ui/adapter.py b/src/calculator/ui/adapter.py index 114a77d..904aecd 100644 --- a/src/calculator/ui/adapter.py +++ b/src/calculator/ui/adapter.py @@ -65,18 +65,22 @@ def process(self, expression: str) -> None: } })) - except SyntaxError as e: + except SyntaxError: self.error.emit(translate("Adapter", "Expression contains syntax error.")) - except MathError as e: - self.error.emit(translate("Adapter", "Math error occured.")) - except VariableError as e: + except MathError: + self.error.emit(translate("Adapter", "Math error occurred.")) + except VariableError: self.error.emit(translate("Adapter", "Error in defining variable.")) except OverflowError: self.error.emit(translate("Adapter", "Result is too big.")) - except InvalidFunctionCallError: - self.error.emit(translate("Adapter", "Parameters count does not match function.")) - except NotImplementedError: + except InvalidFunctionCallError as e: + self.error.emit(translate("Adapter", "Given parameters does not match to function {}.").format( + e.function_name + )) + except NameError: self.error.emit(translate("Adapter", "Function is not defined.")) + except NotImplementedError: + self.error.emit(translate("Adapter", "This construct is not supported.")) @pyqtSlot(str, int) def setVariableValue(self, variable: str, value: NumericValue): diff --git a/src/tests/calculator/core/solver/solver_resolve.py b/src/tests/calculator/core/solver/solver_resolve.py index 4cbbc0a..afe9b46 100644 --- a/src/tests/calculator/core/solver/solver_resolve.py +++ b/src/tests/calculator/core/solver/solver_resolve.py @@ -1,5 +1,5 @@ # coding=utf-8 -from ast import Num, BinOp, AST, UnaryOp, USub, UAdd, Name, Call +from ast import Num, BinOp, AST, UnaryOp, USub, UAdd, Name, Call, keyword from operator import attrgetter from random import randint from unittest import TestCase @@ -94,7 +94,8 @@ def function(*args): func=Name( id=function.__name__, ), - args=args_to_call + args=args_to_call, + keywords=list() ) ), 'Resolve Call should return same value as mocked function.' @@ -123,7 +124,8 @@ def function(a, b=42): func=Name( id=function.__name__, ), - args=tuple() + args=tuple(), + keywords=list() ) ) self.assertFalse(called, 'Function should not to be called after invalid try.') @@ -134,7 +136,20 @@ def function(a, b=42): func=Name( id=function.__name__, ), - args=(Num(n=4), Num(n=9), Num(n=5)) + args=(Num(n=4), Num(n=9), Num(n=5)), + keywords=list() + ) + ) + self.assertFalse(called, 'Function should not to be called after invalid try.') + + with self.assertRaises(InvalidFunctionCallError, msg='Call with keyword param.'): + self.solver._resolve( + Call( + func=Name( + id=function.__name__, + ), + args=(Num(n=9),), + keywords=[keyword(arg='e', value=Num(n=9)), ] ) ) self.assertFalse(called, 'Function should not to be called after invalid try.') @@ -147,7 +162,8 @@ def function(a, b=42): func=Name( id=function.__name__, ), - args=args_to_call + args=args_to_call, + keywords=[] ) ), 'Resolve Call should return same value as mocked function.' From 6b21ef189122b0475e85bb9e481edc9b168f9e07 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Tue, 4 Apr 2017 18:35:50 +0200 Subject: [PATCH 069/121] Fix completer was not working in nested functions --- src/calculator/ui/types/expression/exp_analyzer.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/calculator/ui/types/expression/exp_analyzer.py b/src/calculator/ui/types/expression/exp_analyzer.py index ec444c0..e137e91 100644 --- a/src/calculator/ui/types/expression/exp_analyzer.py +++ b/src/calculator/ui/types/expression/exp_analyzer.py @@ -210,8 +210,13 @@ def _currentWordBorders(self) -> Dict[str, int]: word = content[word_start:word_end:].strip() if word: - if word[-1] == ")" and self._get_cursor() != word_end: - word_end -= 1 + if self._get_cursor() != word_end: + brackets_behind_cursor = re.search( + '\)*', + self._get_content()[self._get_cursor()::] + ).group() + + word_end -= len(brackets_behind_cursor) return {"start": word_start, "end": word_end} From 2467108d67dc395be7bf1e0ab7077d3a321bf1fd Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Tue, 4 Apr 2017 18:39:52 +0200 Subject: [PATCH 070/121] Update translations --- src/calculator/ui/translations/cs.qm | Bin 1373 -> 1528 bytes src/calculator/ui/translations/cs.ts | 33 +++++++++++++++++++-------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/calculator/ui/translations/cs.qm b/src/calculator/ui/translations/cs.qm index b7d549f92d9b64b3d1b3f7736ded8b976b9fcf6f..af52b7301a8470f1aabc0cd0f007a82b546e4c75 100644 GIT binary patch delta 398 zcmYLDu}T9$6r8){5|gNnA!4^iw6KWa2Z-1NL4#-z59p+63$_ye*(Za zX7*Qz$F%HpFAy2l2d{4cZP6J2ED(P&4o|M=?!tIXK9k>%6&lOL%keiw_j*3zl(xy& zPON8hq$7EGzd`y?D!xZP?W^bR=mUuB#}1_|obHyj);#945;$y_aPbNTkC3FP(N+=K zq@7DxkjJC9j`u(JD*X%<1f;IPf7@FB=LxB}eU^#j^U{P;R}P$%p@Scm{G}-kAZ<{67zZwDB^e5-MMe2V3i-*&rIRl( ziPpO?1Tf??@-n0{lrShT6fh(*6ajHAm|w)04;0G-vhx{IfO7c^WejhDtcO6+G=@^3 zOg2L@LnebB2gp`7Al3{>EK1BxElDjZR!Gh-%_~tz$xkg-$jdKL$W1Is&QM4z%}XxH X%+J$f2dZXwOqqO}DQoj#W>Y2rif2pI diff --git a/src/calculator/ui/translations/cs.ts b/src/calculator/ui/translations/cs.ts index d32e6a2..98e1bd1 100644 --- a/src/calculator/ui/translations/cs.ts +++ b/src/calculator/ui/translations/cs.ts @@ -4,37 +4,50 @@ Adapter - + Unsupported base. Nepodporovaná číselná soustava. - Math error occured. + Matematická chyba. + + + + Math error occurred. Matematická chyba. - + Error in defining variable. Chyba při definování proměnné. - + Result is too big. Výsledek je příliš velký. - + + Given parameters does not match to function {}. + Předané parametry neodpovídají funkci {}. + + + + This construct is not supported. + Tato konstrukce není podporovaná. + + Parameters count does not match function. - Počet parametrů neodpovídá funkci. + Počet parametrů neodpovídá funkci. - + Function is not defined. Funkce není definována. - + Expression contains syntax error. Výraz obsahuje syntaktickou chybu. @@ -92,12 +105,12 @@ Barbie Calculator - + Error Chyba - + Info Info From 290ad53197d236e0aac41d7bc0c2f81b6ce6b3b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Tue, 4 Apr 2017 19:36:36 +0200 Subject: [PATCH 071/121] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 469513f..35d09a3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # IVS-VUT-BIT-2016-2017 [![Build Status](https://travis-ci.com/thejoeejoee/IVS-VUT-BIT-2016-2017.svg?token=MqEeDyeLfZw3xFmAVUzV&branch=develop)](https://travis-ci.com/thejoeejoee/IVS-VUT-BIT-2016-2017) +[![Codeship](https://img.shields.io/codeship/a2ac7ad0-fb4b-0134-7062-02a6a40c3d5e.svg)]() [![codecov](https://img.shields.io/codecov/c/token/M5EwaVLlg7/github/thejoeejoee/IVS-VUT-BIT-2016-2017/develop.svg)](https://codecov.io/gh/thejoeejoee/IVS-VUT-BIT-2016-2017) [![License](https://img.shields.io/badge/license-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0.en.html) From df75cc23a79c580b7cbbad9a147da7f282782146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Tue, 4 Apr 2017 19:37:12 +0200 Subject: [PATCH 072/121] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 35d09a3..6e2aeb5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # IVS-VUT-BIT-2016-2017 [![Build Status](https://travis-ci.com/thejoeejoee/IVS-VUT-BIT-2016-2017.svg?token=MqEeDyeLfZw3xFmAVUzV&branch=develop)](https://travis-ci.com/thejoeejoee/IVS-VUT-BIT-2016-2017) -[![Codeship](https://img.shields.io/codeship/a2ac7ad0-fb4b-0134-7062-02a6a40c3d5e.svg)]() +[![Codeship](https://img.shields.io/codeship/a2ac7ad0-fb4b-0134-7062-02a6a40c3d5e.svg)](https://app.codeship.com/projects/211472) [![codecov](https://img.shields.io/codecov/c/token/M5EwaVLlg7/github/thejoeejoee/IVS-VUT-BIT-2016-2017/develop.svg)](https://codecov.io/gh/thejoeejoee/IVS-VUT-BIT-2016-2017) [![License](https://img.shields.io/badge/license-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0.en.html) From 89845d0400e8e278fa10a30134a9ded80a8498d1 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Wed, 5 Apr 2017 12:46:37 +0200 Subject: [PATCH 073/121] Add brackets wrapping of selected text - #69 --- src/calculator/ui/qml/main.qml | 8 ++++++++ src/calculator/ui/types/expression/exp_analyzer.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index fb60197..e0cb59a 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -156,6 +156,14 @@ ApplicationWindow { completeText() showFunctionSignature() } + + Keys.onPressed: { + if(expInput.selectedText != "" && event.text == "(") { + expInput.insert(expInput.selectionEnd, ")") + expInput.cursorPosition = expInput.selectionStart + expInput.deselect() + } + } } ResultDisplay { diff --git a/src/calculator/ui/types/expression/exp_analyzer.py b/src/calculator/ui/types/expression/exp_analyzer.py index e137e91..d0668db 100644 --- a/src/calculator/ui/types/expression/exp_analyzer.py +++ b/src/calculator/ui/types/expression/exp_analyzer.py @@ -84,7 +84,7 @@ def _function_body_borders(self, func: str, expr: str) -> Tuple[int, int]: :return: Start and end index of body of function """ brackets_count = {"(": 1, ")": 0} # 1 for function - match = re.search('{} *\('.format(func), expr) + match = re.search('{} *\('.format(re.escape(func)), expr) for i in range(match.end(), len(expr)): current_char = expr[i] From 31fc73267b7542a6baf93d01f4f96c542362ffa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Thu, 6 Apr 2017 02:27:24 +0200 Subject: [PATCH 074/121] Add test for invalid factorials --- src/calculator/core/math/math.py | 3 +++ src/tests/calculator/core/math/math.py | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/calculator/core/math/math.py b/src/calculator/core/math/math.py index da88efd..371ab19 100644 --- a/src/calculator/core/math/math.py +++ b/src/calculator/core/math/math.py @@ -48,6 +48,9 @@ def modulo(a: NumericValue, b: NumericValue) -> NumericValue: @staticmethod def fact(n: NumericValue) -> NumericValue: + if abs(int(n) - n) > 0: + raise MathError('Invalid parameter for factorial.') + try: return math.gamma(n + 1) except ValueError as e: diff --git a/src/tests/calculator/core/math/math.py b/src/tests/calculator/core/math/math.py index 9e85ec6..5a9b2a5 100644 --- a/src/tests/calculator/core/math/math.py +++ b/src/tests/calculator/core/math/math.py @@ -87,9 +87,20 @@ def test_fact(self): 6, 'Factorial of 3' ) + self.assertEqual( + self.math.fact(170), + 7.257415615307998e+306, + 'Maximum defined factorial' + ) with self.assertRaises(MathError, msg='Invalid factorial of -5.'): self.math.fact(-5) + with self.assertRaises(MathError, msg='Invalid factorial of 0.25.'): + self.math.fact(0.25) + + with self.assertRaises(MathError, msg='Invalid factorial of 5.25.'): + self.math.fact(5.25) + def test_root(self): self.assertEqual( self.math.root(16, 4), From dd8964da832cf277a0cf3a116e408a728623fa84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Thu, 6 Apr 2017 02:28:01 +0200 Subject: [PATCH 075/121] Change of formatted size of 10 in Formatter --- src/calculator/utils/formatter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calculator/utils/formatter.py b/src/calculator/utils/formatter.py index 1d45051..7ec204b 100644 --- a/src/calculator/utils/formatter.py +++ b/src/calculator/utils/formatter.py @@ -11,7 +11,7 @@ class Formatter(object): DEFAULT_CHARACTERS_LIMIT = 8 - EXP_FORMAT = '{value} ×10{exp}' + EXP_FORMAT = '{value}×10{exp}' BASE_CONVERTERS = { 2: bin, 8: oct, From 4d58b2511a7eccef9aceeedeff69056913e7c781 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Thu, 6 Apr 2017 03:11:09 +0200 Subject: [PATCH 076/121] Fix dropdown now hides after click outside dropdown --- src/calculator/ui/qml/controls/DropDown.qml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/calculator/ui/qml/controls/DropDown.qml b/src/calculator/ui/qml/controls/DropDown.qml index 8b6dad3..9f92592 100644 --- a/src/calculator/ui/qml/controls/DropDown.qml +++ b/src/calculator/ui/qml/controls/DropDown.qml @@ -135,6 +135,13 @@ Item { ScriptAction { script: { component.visible = false }} } + MouseArea { + parent: root + enabled: component.visible + anchors.fill: parent + onClicked: component.hide() + } + Rectangle { id: scrollbar From 8ab01af72a72bac55ab374885c640b2c376c2ba4 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Thu, 6 Apr 2017 03:12:22 +0200 Subject: [PATCH 077/121] Fix hover behaviour of item in dropdown --- src/calculator/ui/qml/controls/DropDown.qml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/calculator/ui/qml/controls/DropDown.qml b/src/calculator/ui/qml/controls/DropDown.qml index 9f92592..94257cc 100644 --- a/src/calculator/ui/qml/controls/DropDown.qml +++ b/src/calculator/ui/qml/controls/DropDown.qml @@ -192,10 +192,15 @@ Item { height: dropMenu.height / component.model.length MouseArea { + signal checkMousePos + anchors.fill: parent hoverEnabled: true onClicked: component.chooseCurrent() - onContainsMouseChanged: { + onMouseXChanged: checkMousePos() + onMouseYChanged: checkMousePos() + onContainsMouseChanged: checkMousePos() + onCheckMousePos: { if(containsMouse && flick.y == 0) component.currentItemIndex = index } From 3eecc990e6059560bf1a1ac7f13f246f2aade003 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Thu, 6 Apr 2017 03:13:28 +0200 Subject: [PATCH 078/121] New AppMenuBar with About and Help menu items --- src/calculator/ui/assets/styles/UIStyles.qml | 18 +++ src/calculator/ui/qml.qrc | 1 + src/calculator/ui/qml/main.qml | 33 +++++ src/calculator/ui/qml/menu/AppMenuBar.qml | 131 +++++++++++++++++++ 4 files changed, 183 insertions(+) create mode 100644 src/calculator/ui/qml/menu/AppMenuBar.qml diff --git a/src/calculator/ui/assets/styles/UIStyles.qml b/src/calculator/ui/assets/styles/UIStyles.qml index 7b61b85..62b3ad5 100644 --- a/src/calculator/ui/assets/styles/UIStyles.qml +++ b/src/calculator/ui/assets/styles/UIStyles.qml @@ -156,4 +156,22 @@ QtObject { property color textColor: "white" property font font: Qt.font({family: "Roboto Light"}) } + + property QtObject appMenuBar: QtObject { + property color color: "#2C2C2C" + property font font: Qt.font({family: "Roboto Light"}) + + property QtObject title: QtObject { + property color color: "white" + property color activeColor: "#ED1946" + } + + property QtObject item: QtObject { + property color backgroundColor: "white" + property color borderColor: "lightGray" + property color hoverColor: "lightGray" + property color textColor: "#3D3D3D" + property color hoverTextColor: "white" + } + } } diff --git a/src/calculator/ui/qml.qrc b/src/calculator/ui/qml.qrc index 0601782..41452b2 100644 --- a/src/calculator/ui/qml.qrc +++ b/src/calculator/ui/qml.qrc @@ -51,5 +51,6 @@ qml/visualization/CountDown.qml qml/visualization/FunctionSignatureDisplay.qml qml/visualization/FlickableAnimatedText.qml + qml/menu/AppMenuBar.qml diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index e0cb59a..4f50c9c 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -10,6 +10,7 @@ import Expression 1.0 import StyleSettings 1.0 import "controls" as Control +import "menu" import "../easteregg" import "loaders" as Loaders @@ -28,6 +29,38 @@ ApplicationWindow { title: qsTr("Barbie Calculator") visible: true + Item { + id: root + anchors.fill: parent + } + + AppMenuBar { + color: StyleSettings.appMenuBar.color + font: StyleSettings.appMenuBar.font + + title: qsTr("Help") + titleColor: StyleSettings.appMenuBar.title.color + titleActiveColor: StyleSettings.appMenuBar.title.activeColor + + items: [qsTr("Help"), qsTr("About")] + + itemBackgroundColor: StyleSettings.appMenuBar.item.backgroundColor + itemBorderColor: StyleSettings.appMenuBar.item.borderColor + itemHoverColor: StyleSettings.appMenuBar.item.hoverColor + itemTextColor: StyleSettings.appMenuBar.item.textColor + itemHoverTextColor: StyleSettings.appMenuBar.item.hoverTextColor + + z: 3 + width: 200 + height: 24 + menuWidth: 100 + menuItemHeight: 19 + + onItemChoosed: { + console.log(item) + } + } + Game { id: game diff --git a/src/calculator/ui/qml/menu/AppMenuBar.qml b/src/calculator/ui/qml/menu/AppMenuBar.qml new file mode 100644 index 0000000..bc5ae04 --- /dev/null +++ b/src/calculator/ui/qml/menu/AppMenuBar.qml @@ -0,0 +1,131 @@ +import QtQuick 2.0 +import "../controls" + +/** + Component which has same functionality as MenuBar, but it does not affect window + container size. + */ +Canvas { + id: component + + /** + Emits after clicking on item in menu + @param item Label of item + */ + signal itemChoosed(string item) + + /// Background color of bar + property color color + /// Text margins of title + property int titleMargins: 10 + /// Title text + property alias title: label.text + /// Text color of title + property alias titleColor: label.color + /// Text color of title, when it's active + property color titleActiveColor + /// List of items in menu + property alias items: menu.model + /// Width of menu + property alias menuWidth: menu.width + /// Height of single item in menu + property alias menuItemHeight: menu.itemHeight + /// Background color of item in menu + property color itemBackgroundColor + /// Border color of item in menu + property color itemBorderColor + /// Hover color of item in menu + property color itemHoverColor + /// Text color of item in menu + property color itemTextColor + /// Hover text color of item in menu + property color itemHoverTextColor + /// Used font + property font font + + onColorChanged: component.requestPaint() + + onPaint: { + var ctx = getContext("2d") + ctx.clearRect(0, 0, component.width, component.height) + + ctx.fillStyle = component.color + + // draw shape + //|------------/ + //|-----------/ + + ctx.beginPath() + ctx.moveTo(0, 0) + ctx.lineTo(component.width, 0) + ctx.lineTo(component.width - component.height, component.height) + ctx.lineTo(component.width, component.height) + ctx.lineTo(0, component.height) + ctx.lineTo(0, 0) + + ctx.fill() + } + + Rectangle { + id: title + + color: (menu.visible) ?component.titleActiveColor :"transparent" + width: label.width + 2 * component.titleMargins + height: component.height + + Behavior on color { + ColorAnimation { duration: 200 } + } + + Text { + id: label + + font.pixelSize: component.height * 0.8 + font.family: component.font.family + + anchors.centerIn: parent + } + } + + Item { + y: component.height + + DropDown { + id: menu + + onItemChoosed: component.itemChoosed(menu.currentItem) + + dropDownMenuBackground: Rectangle { + color: component.itemBackgroundColor + border.color: component.itemBorderColor + anchors.fill: parent + } + + menuItem: Item { + Rectangle { // hover overlay + opacity: 0.8 + color: (hovered) ?component.itemHoverColor :"transparent" + + anchors.fill: parent + } + + Text { + color: (hovered) ?component.itemHoverTextColor :component.itemTextColor + text: itemData + + font.family: component.font.family + font.pixelSize: parent.height * 0.8 + + anchors.leftMargin: menu.itemHeight / 2 + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + } + } + } + } + + MouseArea { + anchors.fill: parent + onClicked: menu.show() + } +} From ae33ad27b2c0cfc8b70aab30e5e5020bd447192c Mon Sep 17 00:00:00 2001 From: EnkeyMC Date: Thu, 6 Apr 2017 12:59:53 +0200 Subject: [PATCH 079/121] Added exception for variable name error --- src/calculator/exceptions.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/calculator/exceptions.py b/src/calculator/exceptions.py index dc2a5cd..8b5153a 100644 --- a/src/calculator/exceptions.py +++ b/src/calculator/exceptions.py @@ -25,6 +25,12 @@ class VariableError(Exception): """ +class VariableNameError(VariableError): + """ + Raised when variable has an invalid name. + """ + + class UnsupportedBaseError(Exception): """ Raised when trying convert number into unsupported base From bbd02809cf1ee74b97e166ebfc6625f6a03015a7 Mon Sep 17 00:00:00 2001 From: EnkeyMC Date: Thu, 6 Apr 2017 13:00:23 +0200 Subject: [PATCH 080/121] Added constant for variable name limit --- src/calculator/core/calculator/calculator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calculator/core/calculator/calculator.py b/src/calculator/core/calculator/calculator.py index 825031f..5f2926d 100644 --- a/src/calculator/core/calculator/calculator.py +++ b/src/calculator/core/calculator/calculator.py @@ -15,6 +15,7 @@ class Calculator(object): ANSWER_VARIABLE_NAME = 'Ans' DEFAULT_VARIABLE_TYPE = int + MAX_VARIABLE_NAME_LEN = 10 def __init__(self): super().__init__() From 442d4ca91797c9240601edb03b4568fd9f37a69c Mon Sep 17 00:00:00 2001 From: EnkeyMC Date: Thu, 6 Apr 2017 13:01:10 +0200 Subject: [PATCH 081/121] Added tests for variable names --- .../calculator/core/calculator/calculator.py | 58 ++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/tests/calculator/core/calculator/calculator.py b/src/tests/calculator/core/calculator/calculator.py index 380d3a8..ef38a15 100644 --- a/src/tests/calculator/core/calculator/calculator.py +++ b/src/tests/calculator/core/calculator/calculator.py @@ -2,7 +2,7 @@ from unittest.case import TestCase from calculator.core.calculator import Calculator -from calculator.exceptions import VariableError, VariableRemoveRestrictError +from calculator.exceptions import VariableError, VariableRemoveRestrictError, VariableNameError class CalculatorTest(TestCase): @@ -207,3 +207,59 @@ def test_variable_process(self): variables, 'Variables after variable process.' ) + + def test_too_long_variable_name(self): + var_name = 'a' * (self.calculator.MAX_VARIABLE_NAME_LEN + 1) + msg = 'Error when creating variable with name longer than {}'.format(self.calculator.MAX_VARIABLE_NAME_LEN) + with self.assertRaises(VariableNameError, msg=msg): + self.calculator.process("{} = 42".format(var_name)) + + self.assertDictEqual( + self.calculator.variables, + { + Calculator.ANSWER_VARIABLE_NAME: self._default_variable_definition + }, + 'No new variables should be created.' + ) + + def test_too_long_variable_name_in_expr(self): + var_name = 'a' * (self.calculator.MAX_VARIABLE_NAME_LEN + 1) + msg = 'Error when creating variable with name longer ' \ + 'than {} in expression with default value.'.format(self.calculator.MAX_VARIABLE_NAME_LEN) + with self.assertRaises(VariableNameError, msg=msg): + self.calculator.process("5 + {}".format(var_name)) + + self.assertDictEqual( + self.calculator.variables, + { + Calculator.ANSWER_VARIABLE_NAME: self._default_variable_definition + }, + 'No new variables should be created.' + ) + + def test_variable_with_max_name_len(self): + var_name = 'a' * self.calculator.MAX_VARIABLE_NAME_LEN + result, variables = self.calculator.process("{} = 42".format(var_name)) + + self.assertIsNone(result, 'None is result of assign.') + self.assertDictEqual( + variables, + { + self.calculator.ANSWER_VARIABLE_NAME: self._default_variable_definition, + var_name: (42, "{} = 42".format(var_name), set()) + }, + 'New variable {} should be created.'.format(var_name) + ) + + def test_variable_with_max_name_in_expr(self): + var_name = 'a' * self.calculator.MAX_VARIABLE_NAME_LEN + result, variables = self.calculator.process("5 + {}".format(var_name)) + + self.assertEqual(result, 5, 'Result should be calculated.') + self.assertDictEqual( + variables, + { + self.calculator.ANSWER_VARIABLE_NAME: (5, '5 + {}'.format(var_name), {var_name}), + var_name: (0, '0', set()) + } + ) From bd19fd36fef29426d034af6ee39d9e44144de421 Mon Sep 17 00:00:00 2001 From: EnkeyMC Date: Thu, 6 Apr 2017 13:27:38 +0200 Subject: [PATCH 082/121] test for assign with too long var name in expr --- src/calculator/core/calculator/calculator.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/calculator/core/calculator/calculator.py b/src/calculator/core/calculator/calculator.py index 5f2926d..6bcfc83 100644 --- a/src/calculator/core/calculator/calculator.py +++ b/src/calculator/core/calculator/calculator.py @@ -3,7 +3,7 @@ from typing import Dict, Tuple, Set, Optional from calculator.core.solver.solver import Solver -from calculator.exceptions import VariableError, VariableRemoveRestrictError +from calculator.exceptions import VariableError, VariableRemoveRestrictError, VariableNameError from calculator import NumericValue, Variable from calculator.utils import OrderedDefaultDict @@ -47,11 +47,15 @@ def process(self, expression: str) -> Tuple[Optional[NumericValue], Dict[str, Va if len(root_node.targets) != 1 or not isinstance(root_node.targets[0], Name): raise SyntaxError('Assign to multiple variables or to indexed variable is not supported.') + variable_name = root_node.targets[0].id + if len(variable_name) > self.MAX_VARIABLE_NAME_LEN: + raise VariableNameError('Variable name "{var}" is too long (max {max} characters.)' + .format(var=variable_name, max=self.MAX_VARIABLE_NAME_LEN)) + value = self._solver.compute(root_node.value, self.variables) used_variables = self._solver.get_used_variables() # test recursive assign - variable_name = root_node.targets[0].id if self._has_circular_dependence(variable_name, used_variables): raise VariableError( "Assignment to a variable '{variable_name}' would create a circular dependency.".format( From 96c90be192a046607bf17d8ff87be145bdc8ffd3 Mon Sep 17 00:00:00 2001 From: EnkeyMC Date: Thu, 6 Apr 2017 13:28:03 +0200 Subject: [PATCH 083/121] Revert "test for assign with too long var name in expr" This reverts commit bd19fd36fef29426d034af6ee39d9e44144de421. --- src/calculator/core/calculator/calculator.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/calculator/core/calculator/calculator.py b/src/calculator/core/calculator/calculator.py index 6bcfc83..5f2926d 100644 --- a/src/calculator/core/calculator/calculator.py +++ b/src/calculator/core/calculator/calculator.py @@ -3,7 +3,7 @@ from typing import Dict, Tuple, Set, Optional from calculator.core.solver.solver import Solver -from calculator.exceptions import VariableError, VariableRemoveRestrictError, VariableNameError +from calculator.exceptions import VariableError, VariableRemoveRestrictError from calculator import NumericValue, Variable from calculator.utils import OrderedDefaultDict @@ -47,15 +47,11 @@ def process(self, expression: str) -> Tuple[Optional[NumericValue], Dict[str, Va if len(root_node.targets) != 1 or not isinstance(root_node.targets[0], Name): raise SyntaxError('Assign to multiple variables or to indexed variable is not supported.') - variable_name = root_node.targets[0].id - if len(variable_name) > self.MAX_VARIABLE_NAME_LEN: - raise VariableNameError('Variable name "{var}" is too long (max {max} characters.)' - .format(var=variable_name, max=self.MAX_VARIABLE_NAME_LEN)) - value = self._solver.compute(root_node.value, self.variables) used_variables = self._solver.get_used_variables() # test recursive assign + variable_name = root_node.targets[0].id if self._has_circular_dependence(variable_name, used_variables): raise VariableError( "Assignment to a variable '{variable_name}' would create a circular dependency.".format( From ac587295cadc98336a6e41589db0d4ab3666970f Mon Sep 17 00:00:00 2001 From: EnkeyMC Date: Thu, 6 Apr 2017 13:31:51 +0200 Subject: [PATCH 084/121] Test for too long var name in assign expr --- .../calculator/core/calculator/calculator.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/tests/calculator/core/calculator/calculator.py b/src/tests/calculator/core/calculator/calculator.py index ef38a15..4a41053 100644 --- a/src/tests/calculator/core/calculator/calculator.py +++ b/src/tests/calculator/core/calculator/calculator.py @@ -237,6 +237,21 @@ def test_too_long_variable_name_in_expr(self): 'No new variables should be created.' ) + def test_assign_with_long_var_in_expr(self): + var_name = 'a' * (self.calculator.MAX_VARIABLE_NAME_LEN + 1) + msg = 'Error when creating variable with name longer ' \ + 'than {} in assign expression.'.format(self.calculator.MAX_VARIABLE_NAME_LEN) + with self.assertRaises(VariableNameError, msg=msg): + self.calculator.process("a = 5 + {}".format(var_name)) + + self.assertDictEqual( + self.calculator.variables, + { + Calculator.ANSWER_VARIABLE_NAME: self._default_variable_definition + }, + 'No new variables should be created.' + ) + def test_variable_with_max_name_len(self): var_name = 'a' * self.calculator.MAX_VARIABLE_NAME_LEN result, variables = self.calculator.process("{} = 42".format(var_name)) @@ -250,7 +265,6 @@ def test_variable_with_max_name_len(self): }, 'New variable {} should be created.'.format(var_name) ) - def test_variable_with_max_name_in_expr(self): var_name = 'a' * self.calculator.MAX_VARIABLE_NAME_LEN result, variables = self.calculator.process("5 + {}".format(var_name)) From cc6f4d078bd08e4f313da05368f62ff80ef3280e Mon Sep 17 00:00:00 2001 From: EnkeyMC Date: Thu, 6 Apr 2017 13:50:45 +0200 Subject: [PATCH 085/121] Restriction for variable name lenght, fixes issue #68 --- src/calculator/core/calculator/calculator.py | 29 +++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/calculator/core/calculator/calculator.py b/src/calculator/core/calculator/calculator.py index 5f2926d..56224f4 100644 --- a/src/calculator/core/calculator/calculator.py +++ b/src/calculator/core/calculator/calculator.py @@ -3,7 +3,7 @@ from typing import Dict, Tuple, Set, Optional from calculator.core.solver.solver import Solver -from calculator.exceptions import VariableError, VariableRemoveRestrictError +from calculator.exceptions import VariableError, VariableRemoveRestrictError, VariableNameError from calculator import NumericValue, Variable from calculator.utils import OrderedDefaultDict @@ -47,11 +47,15 @@ def process(self, expression: str) -> Tuple[Optional[NumericValue], Dict[str, Va if len(root_node.targets) != 1 or not isinstance(root_node.targets[0], Name): raise SyntaxError('Assign to multiple variables or to indexed variable is not supported.') + variable_name = root_node.targets[0].id + if len(variable_name) > self.MAX_VARIABLE_NAME_LEN: + raise VariableNameError('Variable name "{var}" is too long (max {max} characters.)' + .format(var=variable_name, max=self.MAX_VARIABLE_NAME_LEN)) + value = self._solver.compute(root_node.value, self.variables) used_variables = self._solver.get_used_variables() # test recursive assign - variable_name = root_node.targets[0].id if self._has_circular_dependence(variable_name, used_variables): raise VariableError( "Assignment to a variable '{variable_name}' would create a circular dependency.".format( @@ -59,7 +63,9 @@ def process(self, expression: str) -> Tuple[Optional[NumericValue], Dict[str, Va )) # update by new created variables from Solver - self._variables.update(self._solver.variables) + variables = self._solver.variables + self._check_variable_names(variables) + self._variables.update(variables) # create new var self._variables[variable_name] = value, expression, used_variables # and refresh all depending @@ -70,7 +76,10 @@ def process(self, expression: str) -> Tuple[Optional[NumericValue], Dict[str, Va # simple expression result = self._solver.compute(expression, self.variables) # update new vars used by solver resolved from expression - self._variables.update(self._solver.variables) + variables = self._solver.variables + self._check_variable_names(variables) + self._variables.update(variables) + self._variables[self.ANSWER_VARIABLE_NAME] = result, expression, self._solver.get_used_variables() return result, self._variables.copy() @@ -151,3 +160,15 @@ def _get_depending_variables(self, variable: str) -> Set[str]: :return: set of depending vars """ return {name for name, definition in self.variables.items() if variable in definition[2]} + + def _check_variable_names(self, variables: Dict[str, Variable]) -> None: + """ + Check if all variable names are valid, if not raise VariableNameError + + :param variables: variables to check + :return: None + """ + for var in variables: + if len(var) > self.MAX_VARIABLE_NAME_LEN: + raise VariableNameError('Variable name "{var}" is too long (max {max} characters.)' + .format(var=var, max=self.MAX_VARIABLE_NAME_LEN)) From 688898bba4f44c3f541844b3570cf01cfb90e2f0 Mon Sep 17 00:00:00 2001 From: EnkeyMC Date: Thu, 6 Apr 2017 14:31:46 +0200 Subject: [PATCH 086/121] ignore compiled resources --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3a81c5f..db3b771 100644 --- a/.gitignore +++ b/.gitignore @@ -89,4 +89,7 @@ ENV/ .ropeproject # Pycharms -.idea/ \ No newline at end of file +.idea/ + +# complied resources +src/calculator/ui/resources.py \ No newline at end of file From efabcaa40d423ce9925663a30af4d9167075c0db Mon Sep 17 00:00:00 2001 From: Martin Omacht Date: Thu, 6 Apr 2017 14:46:09 +0200 Subject: [PATCH 087/121] Fixed typoo --- doc/doc.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/doc.md b/doc/doc.md index d8e13f3..931a11e 100644 --- a/doc/doc.md +++ b/doc/doc.md @@ -187,7 +187,7 @@ Pokud výraz označíte a a kliknete na funkci, tak se celý výraz vloží do p ![Výraz po použití funkce](enfunc2.png) -Jak vidíte už jsou inicializované nějaké proměnné. V jejich nastavení je možný přepis na `1` nebo `0`, a nebo také proměnnou smazat. Pokud bude proměnnýc příliš, můžete se k nim dostat pomocí posuvníku (případně kolečka myši). +Jak vidíte už jsou inicializované nějaké proměnné. V jejich nastavení je možný přepis na `1` nebo `0`, a nebo také proměnnou smazat. Pokud bude proměnných příliš, můžete se k nim dostat pomocí posuvníku (případně kolečka myši). ![Mnoho proměnných a jejich nastavení](many_vars.png) From a0e1048f0d8f4dd214a2c4b8e60e8e17cb0ba487 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Sat, 8 Apr 2017 23:19:09 +0200 Subject: [PATCH 088/121] New about window - #71 --- src/calculator/ui/assets/images/logo.svg | 12 ++ src/calculator/ui/assets/styles/UIStyles.qml | 7 ++ src/calculator/ui/qml.qrc | 2 + src/calculator/ui/qml/main.qml | 21 +++- src/calculator/ui/qml/windows/About.qml | 115 +++++++++++++++++++ 5 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 src/calculator/ui/assets/images/logo.svg create mode 100644 src/calculator/ui/qml/windows/About.qml diff --git a/src/calculator/ui/assets/images/logo.svg b/src/calculator/ui/assets/images/logo.svg new file mode 100644 index 0000000..0136e8f --- /dev/null +++ b/src/calculator/ui/assets/images/logo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/src/calculator/ui/assets/styles/UIStyles.qml b/src/calculator/ui/assets/styles/UIStyles.qml index 62b3ad5..4781735 100644 --- a/src/calculator/ui/assets/styles/UIStyles.qml +++ b/src/calculator/ui/assets/styles/UIStyles.qml @@ -174,4 +174,11 @@ QtObject { property color hoverTextColor: "white" } } + + property QtObject aboutWindow: QtObject { + property color color: "black" + property color textColor: "lightGray" + property color titleColor: "#ED1946" + property font font: Qt.font({family: "AbeeZee"}) + } } diff --git a/src/calculator/ui/qml.qrc b/src/calculator/ui/qml.qrc index 41452b2..a5a21c0 100644 --- a/src/calculator/ui/qml.qrc +++ b/src/calculator/ui/qml.qrc @@ -52,5 +52,7 @@ qml/visualization/FunctionSignatureDisplay.qml qml/visualization/FlickableAnimatedText.qml qml/menu/AppMenuBar.qml + qml/windows/About.qml + assets/images/logo.svg diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index 4f50c9c..ecf1bbe 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -13,6 +13,7 @@ import "controls" as Control import "menu" import "../easteregg" import "loaders" as Loaders +import "windows" as Windows import "containers" import "visualization" @@ -57,7 +58,9 @@ ApplicationWindow { menuItemHeight: 19 onItemChoosed: { - console.log(item) + if(item == "About") { + aboutWindow.show() + } } } @@ -336,6 +339,22 @@ ApplicationWindow { } + Windows.About { + id: aboutWindow + + revision: "3eecc99" + color: StyleSettings.aboutWindow.color + textColor: StyleSettings.aboutWindow.textColor + titleColor: StyleSettings.aboutWindow.titleColor + font: StyleSettings.aboutWindow.font + + z: 3 + width: mainWindow.width + height: mainWindow.height + + onHidden: expInput.focus = true + } + /** Show suggestion box with filtered suggestions */ diff --git a/src/calculator/ui/qml/windows/About.qml b/src/calculator/ui/qml/windows/About.qml new file mode 100644 index 0000000..8649f00 --- /dev/null +++ b/src/calculator/ui/qml/windows/About.qml @@ -0,0 +1,115 @@ +import QtQuick 2.5 +import QtQuick.Window 2.0 +import QtQuick.Controls 1.4 + +/** + Component which displays info about Calculator app + */ +Item { + id: component + + /** + Used as function to display component + */ + signal show + /** + Used as function to hide component + */ + signal hide + /** + Emit after component is not visible anymore + */ + signal hidden + + /// Padding of text in component + property int padding: 20 + /// Revision from which is app released + property string revision + /// Background color of component + property color color: "black" + /// Text color + property color textColor: "lightGray" + /// Text color of title + property alias titleColor: title.color + /// Used font + property font font + + visible: opacity + focus: visible + opacity: 0 + + onShow: NumberAnimation { target: component; property: "opacity"; to: 1; duration: 200 } + onHide: SequentialAnimation { + NumberAnimation { target: component; property: "opacity"; to: 0; duration: 200 } + ScriptAction { script: { component.hidden() } } + } + + Keys.onEscapePressed: component.hide() + + MouseArea { + anchors.fill: parent + onClicked: component.hide() + } + + Rectangle { + color: component.color + opacity: 0.8 + anchors.fill: parent + } + + Rectangle { + color: component.color + + width: 360 + height: 260 + + anchors.centerIn: parent + + + Image { + source: "qrc:/assets/images/logo.svg" + fillMode: Image.PreserveAspectFit + opacity: 0.2 + + width: parent.width * 0.7 + height: parent.height * 0.7 + + sourceSize.width: width + sourceSize.height: height + + anchors.centerIn: parent + } + + Text { + id: title + + text: qsTr("Barbie calculator 0.1") + + font.pixelSize: 25 + font.family: component.font.family + + anchors.top: parent.top + anchors.left: parent.left + anchors.topMargin: component.padding + anchors.leftMargin: component.padding + } + + Text { + color: component.textColor + font.pixelSize: 15 + font.family: component.font.family + + anchors.left: title.left + anchors.top: title.bottom + + text: qsTr("From revision") + " - " + component.revision + "\n\n" + + qsTr("Created by:") + "\n" + + " Josef Kolář\n" + + " Son Hai Nguyen\n" + + " Martin Omacht\n" + + " Robert Navrátil\n\n" + + "Copyright 2016-2017 Syan Entertainment.\n" + + qsTr("All rights reserved.") + } + } +} From ce05ed68b127544650e57ca6ca59a368fb697019 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Sun, 9 Apr 2017 23:44:13 +0200 Subject: [PATCH 089/121] New expose logging from QML --- src/calculator/ui/app/calculator_app.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/calculator/ui/app/calculator_app.py b/src/calculator/ui/app/calculator_app.py index 108e0be..9e8245e 100644 --- a/src/calculator/ui/app/calculator_app.py +++ b/src/calculator/ui/app/calculator_app.py @@ -2,11 +2,11 @@ import platform from typing import List -from PyQt5.QtCore import QSize +from PyQt5.QtCore import (QSize, QtFatalMsg, QtCriticalMsg, QtWarningMsg, QtInfoMsg, + qInstallMessageHandler, QtDebugMsg) from PyQt5.QtCore import QTranslator from PyQt5.QtCore import QUrl, QLocale from PyQt5.QtGui import QIcon -from PyQt5.QtGui import QPixmap from PyQt5.QtQml import QQmlApplicationEngine, qmlRegisterSingletonType from PyQt5.QtWidgets import QApplication @@ -28,6 +28,25 @@ import calculator.ui.resources +def qt_message_handler(mode, context, message): + modes = { + QtInfoMsg: "Info", + QtWarningMsg: "Warning", + QtCriticalMsg: "Critical", + QtFatalMsg: "Fatal", + QtDebugMsg: "Debug" + } + mode = modes[mode] + + if context.file is None: + print('{mode}: {msg}'.format(mode=mode, msg=message)) + else: + print('{mode}: line: {line}, function: {func}, file: {file}'.format( + mode=mode, line=context.line, func=context.function, file=context.file)) + print('{msg}'.format(msg=message)) + +qInstallMessageHandler(qt_message_handler) + class CalculatorApp(QApplication): def __init__(self, argv: List[str]): super().__init__(argv) From 99310a2f19f59ca1058e17633bd701e94feb28f1 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Mon, 10 Apr 2017 00:09:12 +0200 Subject: [PATCH 090/121] New FilledClickable - base component for all buttons with background and hover mask --- src/calculator/ui/qml.qrc | 1 + .../ui/qml/controls/FilledClickable.qml | 39 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 src/calculator/ui/qml/controls/FilledClickable.qml diff --git a/src/calculator/ui/qml.qrc b/src/calculator/ui/qml.qrc index a5a21c0..84dfaf5 100644 --- a/src/calculator/ui/qml.qrc +++ b/src/calculator/ui/qml.qrc @@ -54,5 +54,6 @@ qml/menu/AppMenuBar.qml qml/windows/About.qml assets/images/logo.svg + qml/controls/FilledClickable.qml diff --git a/src/calculator/ui/qml/controls/FilledClickable.qml b/src/calculator/ui/qml/controls/FilledClickable.qml new file mode 100644 index 0000000..36098c1 --- /dev/null +++ b/src/calculator/ui/qml/controls/FilledClickable.qml @@ -0,0 +1,39 @@ +import QtQuick 2.0 + +/** + Base component for all buttons with background + */ +Clickable { + id: component + + /// Background color + property alias color: background.color + /// Background color of mask when button is hovered + property alias hoverColor: mask.color + /// If true mask is applied when hovered + property alias hoverMaskEnabled: mask.visible + + Rectangle { + id: background + anchors.fill: parent + } + + Rectangle { + id: mask + + opacity: 0 + visible: false + + anchors.fill: parent + } + + hoverEnabled: true + onPressed: mask.opacity = 0.2 + onReleased: { + if(component.hovered) + mask.opacity = 0.1 + } + + onEntered: mask.opacity = 0.1 + onExited: mask.opacity = 0 +} From 80ba0576fa30a2aefafe6b0bf7d4f53e737dec89 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Mon, 10 Apr 2017 00:10:40 +0200 Subject: [PATCH 091/121] Add to Clickble pressed and released signals --- src/calculator/ui/qml/controls/Clickable.qml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/calculator/ui/qml/controls/Clickable.qml b/src/calculator/ui/qml/controls/Clickable.qml index 148897c..dbb70f3 100644 --- a/src/calculator/ui/qml/controls/Clickable.qml +++ b/src/calculator/ui/qml/controls/Clickable.qml @@ -19,6 +19,14 @@ Item { Emits when mouse leaved area of component */ signal exited() + /** + Emits after mouse is pressed + */ + signal pressed() + /** + Emits after mouse is released + */ + signal released() /// If set to true hover is enabled else disable hover property alias hoverEnabled: mouseArea.hoverEnabled @@ -30,6 +38,8 @@ Item { anchors.fill: parent + onPressed: component.pressed() + onReleased: component.released() onClicked: component.clicked(Qt.point(mouse.x, mouse.y)) onContainsMouseChanged: { if(mouseArea.containsMouse) From f61f1af2b17e8c3a96eb473a66273646a78a8795 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Mon, 10 Apr 2017 00:11:44 +0200 Subject: [PATCH 092/121] Change/Add hover animation to function and result buttons - #74 --- src/calculator/ui/assets/styles/UIStyles.qml | 3 ++- .../ui/qml/containers/FunctionsPanel.qml | 6 +++--- .../ui/qml/controls/CalculateButton.qml | 11 ++++------ src/calculator/ui/qml/controls/TextButton.qml | 21 +++---------------- src/calculator/ui/qml/main.qml | 3 ++- 5 files changed, 14 insertions(+), 30 deletions(-) diff --git a/src/calculator/ui/assets/styles/UIStyles.qml b/src/calculator/ui/assets/styles/UIStyles.qml index 4781735..b47fd47 100644 --- a/src/calculator/ui/assets/styles/UIStyles.qml +++ b/src/calculator/ui/assets/styles/UIStyles.qml @@ -10,7 +10,7 @@ QtObject { property QtObject functionPanel: QtObject { property color backgroundColor: "#2A2A2A" property color textColor: "white" - property color hoverTextColor: "#ED1D3D" + property color hoverColor: "white" } property QtObject expressionInput: QtObject { @@ -108,6 +108,7 @@ QtObject { property QtObject calculateButton: QtObject { property color backgroundColor: "#ED1946" + property color hoverColor: "black" } property QtObject errorDialog: QtObject { diff --git a/src/calculator/ui/qml/containers/FunctionsPanel.qml b/src/calculator/ui/qml/containers/FunctionsPanel.qml index 0d095eb..5d9f85e 100644 --- a/src/calculator/ui/qml/containers/FunctionsPanel.qml +++ b/src/calculator/ui/qml/containers/FunctionsPanel.qml @@ -18,8 +18,8 @@ Item { property color backgroundColor /// Text color of labels on buttons property color textColor - /// Text color of labels on buttons, when hovered - property color hoverTextColor + /// Background color of mask when button is hovered + property color hoverColor Grid { id: grid @@ -32,7 +32,7 @@ Item { buttonText: modelData textColor: component.textColor color: component.backgroundColor - hoverTextColor: component.hoverTextColor + hoverColor: component.hoverColor width: component.width / grid.columns height: component.height / grid.rows diff --git a/src/calculator/ui/qml/controls/CalculateButton.qml b/src/calculator/ui/qml/controls/CalculateButton.qml index 9ce560e..b1722a3 100644 --- a/src/calculator/ui/qml/controls/CalculateButton.qml +++ b/src/calculator/ui/qml/controls/CalculateButton.qml @@ -3,14 +3,11 @@ import QtQuick 2.0 /** Button to confirm calculation */ -Clickable { - /// Bakcground color of button - property alias color: background.color +FilledClickable { + id: component - Rectangle { - id: background - anchors.fill: parent - } + hoverMaskEnabled: true + hoverEnabled: true Image { source: "qrc:/assets/images/equal.svg" diff --git a/src/calculator/ui/qml/controls/TextButton.qml b/src/calculator/ui/qml/controls/TextButton.qml index a395585..7f520c4 100644 --- a/src/calculator/ui/qml/controls/TextButton.qml +++ b/src/calculator/ui/qml/controls/TextButton.qml @@ -4,26 +4,16 @@ import "../controls" as Controls /** Button with label and simple animation */ -Controls.Clickable { +FilledClickable { id: component /// Label of button property alias buttonText: innerText.text; - /// Background color - property color color: "black" /// Text color - property color textColor: "black" - /// text color when hovered - property color hoverTextColor: "white" + property color textColor + hoverMaskEnabled: true hoverEnabled: true - onEntered: innerText.color = hoverTextColor - onExited: innerText.color = textColor - - Rectangle { - color: component.color - anchors.fill: parent - } Text { id: innerText @@ -34,10 +24,5 @@ Controls.Clickable { font.pixelSize: component.height * 0.55 anchors.centerIn: parent - - Behavior on color { - ColorAnimation { duration: 300 } - } } - } diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index ecf1bbe..30de486 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -136,7 +136,7 @@ ApplicationWindow { columns: 1 backgroundColor: StyleSettings.functionPanel.backgroundColor textColor: StyleSettings.functionPanel.textColor - hoverTextColor: StyleSettings.functionPanel.hoverTextColor + hoverColor: StyleSettings.functionPanel.hoverColor height: parent.height * 0.45 width: parent.width / 15.7 @@ -238,6 +238,7 @@ ApplicationWindow { width: parent.width / 14 color: StyleSettings.calculateButton.backgroundColor + hoverColor: StyleSettings.calculateButton.hoverColor anchors.top: expInput.top anchors.bottom: parent.bottom From b7e92d753029794139c09dbeb9c00798d45fb2af Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Mon, 10 Apr 2017 00:19:15 +0200 Subject: [PATCH 093/121] Typo --- src/calculator/ui/qml/containers/VariablesPanel.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calculator/ui/qml/containers/VariablesPanel.qml b/src/calculator/ui/qml/containers/VariablesPanel.qml index 0235ec7..ba4e06c 100644 --- a/src/calculator/ui/qml/containers/VariablesPanel.qml +++ b/src/calculator/ui/qml/containers/VariablesPanel.qml @@ -45,7 +45,7 @@ Item { /// Background color of expression while hovering property color expressionHoverColor - /// Bakcground color of Ans variable + /// Background color of Ans variable property alias ansColor: ansItem.color /// Text color of identifier and value of Ans variable property alias ansTextColor: ansItem.textColor From 384ad2bf6e3b73d2c6ad1625ce67ea9b6d44ca29 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Mon, 10 Apr 2017 09:04:31 +0200 Subject: [PATCH 094/121] Add prompter to FlickableAnimatedText and apply it - #73 --- .../ui/assets/images/arrow_left_dark.svg | 7 ++ .../ui/assets/images/arrow_left_light.svg | 7 ++ src/calculator/ui/assets/styles/UIStyles.qml | 4 + src/calculator/ui/qml.qrc | 2 + .../ui/qml/containers/VariablesPanel.qml | 6 +- src/calculator/ui/qml/item/VariableItem.qml | 2 + src/calculator/ui/qml/main.qml | 4 + .../visualization/FlickableAnimatedText.qml | 92 +++++++++++++++++-- .../qml/visualization/ResultSystemDisplay.qml | 13 ++- .../ui/qml/visualization/VariableDisplay.qml | 13 ++- 10 files changed, 131 insertions(+), 19 deletions(-) create mode 100644 src/calculator/ui/assets/images/arrow_left_dark.svg create mode 100644 src/calculator/ui/assets/images/arrow_left_light.svg diff --git a/src/calculator/ui/assets/images/arrow_left_dark.svg b/src/calculator/ui/assets/images/arrow_left_dark.svg new file mode 100644 index 0000000..47f2b42 --- /dev/null +++ b/src/calculator/ui/assets/images/arrow_left_dark.svg @@ -0,0 +1,7 @@ + + + + + + diff --git a/src/calculator/ui/assets/images/arrow_left_light.svg b/src/calculator/ui/assets/images/arrow_left_light.svg new file mode 100644 index 0000000..6799674 --- /dev/null +++ b/src/calculator/ui/assets/images/arrow_left_light.svg @@ -0,0 +1,7 @@ + + + + + + diff --git a/src/calculator/ui/assets/styles/UIStyles.qml b/src/calculator/ui/assets/styles/UIStyles.qml index b47fd47..38b3975 100644 --- a/src/calculator/ui/assets/styles/UIStyles.qml +++ b/src/calculator/ui/assets/styles/UIStyles.qml @@ -67,6 +67,7 @@ QtObject { property color identifierColor: "#ED1946" property color expressionHoverColor: "#3D3D3D" property color scrollBarColor: "#B7B7B7" + property string prompterTheme: "light" property font font: Qt.font({ family: "Roboto Light" }) @@ -78,11 +79,13 @@ QtObject { property color identifierColor: "black" property color expressionHoverColor: "#AAAAAA" property color scrollbarColor: "black" + property string prompterTheme: "dark" property font font: styles.variablesPanel.font } property QtObject variableItem: QtObject { property color scrollbarColor: "#ED1D3D" + property string prompterTheme: "light" property QtObject dots: QtObject { property color color: "#3D3D3D" } @@ -149,6 +152,7 @@ QtObject { property color baseTextColor: "#ED1D3D" property color valueTextColor: "#3D3D3D" property color scrollbarColor: "#ED1D3D" + property string prompterTheme: "dark" property font font: Qt.font({family: "Roboto Light"}) } diff --git a/src/calculator/ui/qml.qrc b/src/calculator/ui/qml.qrc index 84dfaf5..a48d0fc 100644 --- a/src/calculator/ui/qml.qrc +++ b/src/calculator/ui/qml.qrc @@ -55,5 +55,7 @@ qml/windows/About.qml assets/images/logo.svg qml/controls/FilledClickable.qml + assets/images/arrow_left_dark.svg + assets/images/arrow_left_light.svg diff --git a/src/calculator/ui/qml/containers/VariablesPanel.qml b/src/calculator/ui/qml/containers/VariablesPanel.qml index ba4e06c..0634921 100644 --- a/src/calculator/ui/qml/containers/VariablesPanel.qml +++ b/src/calculator/ui/qml/containers/VariablesPanel.qml @@ -55,6 +55,8 @@ Item { property alias ansExpressionHoverColor: ansItem.expressionHoverColor /// Color of value scrollbar Ans variable property alias ansScrollbarColor: ansItem.scrollbarColor + /// Prompter of value flickable theme in Ans + property alias ansPrompterTheme: ansItem.prompterTheme /// Background color of dots(area to slide variable options) property color dotsBackgroundColor @@ -68,7 +70,8 @@ Item { property color settersTextColor /// Color of value scrollbar property color itemScrollbarColor - + /// Prompter of value flickable theme of item + property string prompterTheme /// Font of panel property font font @@ -97,6 +100,7 @@ Item { object.settersHoveredColor = Qt.binding(function() { return component.settersHoveredColor }) object.settersTextColor = Qt.binding(function() { return component.settersTextColor }) object.scrollbarColor = Qt.binding(function() { return component.itemScrollbarColor }) + object.prompterTheme = Qt.binding(function() { return component.prompterTheme }) object.width = Qt.binding(function() { return component.width }) object.height = Qt.binding(function() { return component.itemHeight }) diff --git a/src/calculator/ui/qml/item/VariableItem.qml b/src/calculator/ui/qml/item/VariableItem.qml index 00b868f..f3e6a21 100644 --- a/src/calculator/ui/qml/item/VariableItem.qml +++ b/src/calculator/ui/qml/item/VariableItem.qml @@ -49,6 +49,8 @@ Item { property alias font: content.font /// Color of value scrollbar property alias scrollbarColor: content.scrollbarColor + /// Prompter of value flickable theme + property alias prompterTheme: content.prompterTheme /// Background color of dots(area to slide variable options) property alias dotsBackgroundColor: optionsMenu.dotsBackgroundColor diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index 30de486..e4dbfef 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -106,6 +106,7 @@ ApplicationWindow { ansColor: StyleSettings.ans.backgroundColor ansExpressionHoverColor: StyleSettings.ans.expressionHoverColor ansScrollbarColor: StyleSettings.ans.scrollbarColor + ansPrompterTheme: StyleSettings.ans.prompterTheme dotsBackgroundColor: StyleSettings.variableItem.dots.color removeButtonColor: StyleSettings.variableItem.removeButton.color @@ -113,6 +114,7 @@ ApplicationWindow { settersHoveredColor: StyleSettings.variableItem.setters.hoverColor settersTextColor: StyleSettings.variableItem.setters.textColor itemScrollbarColor: StyleSettings.variableItem.scrollbarColor + prompterTheme: StyleSettings.variableItem.prompterTheme font.family: StyleSettings.variablesPanel.font.family @@ -225,11 +227,13 @@ ApplicationWindow { scrollbarColor: StyleSettings.resultSystemDisplay.scrollbarColor baseTextColor: StyleSettings.resultSystemDisplay.baseTextColor valueTextColor: StyleSettings.resultSystemDisplay.valueTextColor + prompterTheme: StyleSettings.resultSystemDisplay.prompterTheme font: StyleSettings.resultSystemDisplay.font anchors.bottom: functionPanel.top anchors.left: parent.left anchors.right: variablePanel.left + anchors.rightMargin: height / 20 } Control.CalculateButton { diff --git a/src/calculator/ui/qml/visualization/FlickableAnimatedText.qml b/src/calculator/ui/qml/visualization/FlickableAnimatedText.qml index e4a212d..db997ee 100644 --- a/src/calculator/ui/qml/visualization/FlickableAnimatedText.qml +++ b/src/calculator/ui/qml/visualization/FlickableAnimatedText.qml @@ -1,9 +1,10 @@ -import QtQuick 2.0 +import QtQuick 2.7 /** Text with ability of flicking. */ -Flickable { + +Item { id: component /// Displayed text @@ -12,15 +13,88 @@ Flickable { property alias color: text.color /// Used font property alias font: text.font + /// Expose flickable component + readonly property alias flick: flick + /// Color type of theme ["light", "dark"] + property string theme: "dark" + + Flickable { + id: flick + + boundsBehavior: Flickable.StopAtBounds + flickableDirection: Flickable.HorizontalFlick + clip: true + + contentHeight: text.height + contentWidth: text.width + + anchors.fill: parent + + AnimatedText { + id: text + } + } + + Rectangle { + id: prompter + + color: (component.theme == "dark") ?"black" :"white" + opacity: (prompter.width > 0 && flick.visibleArea.widthRatio != 1) ?0.8 :0 + visible: opacity + + width: height * 3 - flick.contentX + height: fontMetrics.height * 0.8 + radius: 3 + + anchors.verticalCenter: flick.verticalCenter + anchors.right: flick.right + anchors.rightMargin: flick.contentX + + Behavior on opacity { + NumberAnimation { duration: 200 } + } + + FontMetrics { + id: fontMetrics + font: component.font + } + + Item { + height: parent.height + width: arrow.paintedWidth + slideText.anchors.leftMargin + fontMetrics.advanceWidth(slideText.text) + anchors.centerIn: parent + + Image { + id: arrow + + source: (component.theme == "dark") ?"qrc:/assets/images/arrow_left_light.svg" + :"qrc:/assets/images/arrow_left_dark.svg" + fillMode: Image.PreserveAspectFit + + //width: height + height: parent.height * 0.6 + + //sourceSize.width: width + sourceSize.height: height + + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + } + + Text { + id: slideText - boundsBehavior: Flickable.StopAtBounds - flickableDirection: Flickable.HorizontalFlick - clip: true + text: qsTr("Slide") + color: (component.theme == "dark") ?"white" + :"black" - contentHeight: text.height - contentWidth: text.width + font.family: "Roboto Light" + font.pixelSize: parent.height * 0.9 - AnimatedText { - id: text + anchors.verticalCenter: parent.verticalCenter + anchors.left: arrow.right + anchors.leftMargin: height / 10 + } + } } } diff --git a/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml b/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml index 294188a..7eaf6c8 100644 --- a/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml +++ b/src/calculator/ui/qml/visualization/ResultSystemDisplay.qml @@ -21,6 +21,8 @@ Rectangle { property color scrollbarColor /// Margin of text readonly property int margin: 10 + /// Prompter of value flickable theme + property string prompterTheme Column { id: container @@ -59,13 +61,13 @@ Rectangle { Rectangle { color: component.scrollbarColor - opacity: (flick.visibleArea.widthRatio != 1 && flick.moving) + opacity: (valueText.flick.visibleArea.widthRatio != 1 && valueText.flick.moving) - x: flick.width * flick.visibleArea.xPosition + flick.x - width: flick.width * flick.visibleArea.widthRatio + x: valueText.width * valueText.flick.visibleArea.xPosition + valueText.x + width: valueText.width * valueText.flick.visibleArea.widthRatio height: 2 - anchors.top: flick.bottom + anchors.top: valueText.bottom anchors.topMargin: height Behavior on opacity { @@ -74,11 +76,12 @@ Rectangle { } FlickableAnimatedText { - id: flick + id: valueText text: Calculator.convertToBase(component.value, component.bases[modelData]) color: component.valueTextColor font: baseText.font + theme: component.prompterTheme width: parent.width - anchors.leftMargin height: parent.height diff --git a/src/calculator/ui/qml/visualization/VariableDisplay.qml b/src/calculator/ui/qml/visualization/VariableDisplay.qml index 72a906e..88f9a0d 100644 --- a/src/calculator/ui/qml/visualization/VariableDisplay.qml +++ b/src/calculator/ui/qml/visualization/VariableDisplay.qml @@ -30,6 +30,8 @@ Rectangle { property font font /// Color of value scrollbar property color scrollbarColor + /// Prompter of value flickable theme + property string prompterTheme QtObject { id: internal @@ -99,10 +101,10 @@ Rectangle { Rectangle { color: component.scrollbarColor - opacity: (expression.visibleArea.widthRatio != 1 && expression.moving) + opacity: (expression.flick.visibleArea.widthRatio != 1 && expression.flick.moving) - x: expression.width * expression.visibleArea.xPosition + expression.x - width: expression.width * expression.visibleArea.widthRatio + x: expression.width * expression.flick.visibleArea.xPosition + expression.x + width: expression.width * expression.flick.visibleArea.widthRatio height: 2 z: 2 @@ -119,17 +121,20 @@ Rectangle { antialiasing: true text: component.variableExpression color: component.textColor + theme: component.prompterTheme font.family: component.font.family font.pixelSize: parent.height * 0.23 width: component.width - internal.sideMargin * 2 - height: contentHeight + height: flick.contentHeight + anchors.left: parent.left anchors.top: parent.top MouseArea { + parent: expression.flick hoverEnabled: true anchors.fill: parent From 2806ae1bf4f923198a33ad654d876e76a08c7598 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Mon, 10 Apr 2017 09:04:50 +0200 Subject: [PATCH 095/121] Remove useless code in AnimatedText --- src/calculator/ui/qml/visualization/AnimatedText.qml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/calculator/ui/qml/visualization/AnimatedText.qml b/src/calculator/ui/qml/visualization/AnimatedText.qml index 8af8ebc..432e46c 100644 --- a/src/calculator/ui/qml/visualization/AnimatedText.qml +++ b/src/calculator/ui/qml/visualization/AnimatedText.qml @@ -26,11 +26,6 @@ Item { }} } - FontMetrics { - id: fontMetrics - font: component.font - } - Text { id: baseText From 9c0daf561871b095d0aae076c2e037c62c097932 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Mon, 10 Apr 2017 14:04:10 +0200 Subject: [PATCH 096/121] Refactor Clickable - expose mouse area and pass mouse event to signals --- src/calculator/ui/qml/controls/Clickable.qml | 35 ++++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/src/calculator/ui/qml/controls/Clickable.qml b/src/calculator/ui/qml/controls/Clickable.qml index dbb70f3..336a1d6 100644 --- a/src/calculator/ui/qml/controls/Clickable.qml +++ b/src/calculator/ui/qml/controls/Clickable.qml @@ -8,9 +8,9 @@ Item { /** Emits when clicked on component - @param pos Position of click + @param mouse Contains event data */ - signal clicked(point pos) + signal clicked(var mouse) /** Emits when mouse entered area of component */ @@ -21,27 +21,48 @@ Item { signal exited() /** Emits after mouse is pressed + @param mouse Contains event data */ - signal pressed() + signal pressed(var mouse) /** Emits after mouse is released + @param mouse Contains event data */ - signal released() + signal released(var mouse) /// If set to true hover is enabled else disable hover property alias hoverEnabled: mouseArea.hoverEnabled /// Holds whether component is hovered readonly property alias hovered: mouseArea.containsMouse + /// Sets to manual signal emitting + property bool manual: false + /// Expose MouseArea + readonly property alias mouseArea: mouseArea MouseArea { id: mouseArea anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + + onPressed: { + if(!component.manual) + component.pressed(mouse) + } + + onReleased: { + if(!component.manual) + component.released(mouse) + } + + onClicked: { + if(!component.manual) + component.clicked(mouse) + } - onPressed: component.pressed() - onReleased: component.released() - onClicked: component.clicked(Qt.point(mouse.x, mouse.y)) onContainsMouseChanged: { + if(component.manual) + return if(mouseArea.containsMouse) component.entered() else From 10fe8e35afceab48f43aa798246b0a938367e486 Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Mon, 10 Apr 2017 14:18:43 +0200 Subject: [PATCH 097/121] Add hover event to variable item --- src/calculator/ui/assets/styles/UIStyles.qml | 11 +++--- .../ui/qml/containers/VariablesPanel.qml | 5 +++ src/calculator/ui/qml/item/VariableItem.qml | 2 + src/calculator/ui/qml/main.qml | 8 ++-- .../ui/qml/visualization/VariableDisplay.qml | 39 +++++++++++++------ 5 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/calculator/ui/assets/styles/UIStyles.qml b/src/calculator/ui/assets/styles/UIStyles.qml index 38b3975..b33980c 100644 --- a/src/calculator/ui/assets/styles/UIStyles.qml +++ b/src/calculator/ui/assets/styles/UIStyles.qml @@ -63,11 +63,7 @@ QtObject { property QtObject variablesPanel: QtObject { property color backgroundColor: "#2A2A2A" - property color textColor: "white" - property color identifierColor: "#ED1946" - property color expressionHoverColor: "#3D3D3D" property color scrollBarColor: "#B7B7B7" - property string prompterTheme: "light" property font font: Qt.font({ family: "Roboto Light" }) @@ -77,15 +73,20 @@ QtObject { property color backgroundColor: "#C1C0C0" property color textColor: "white" property color identifierColor: "black" - property color expressionHoverColor: "#AAAAAA" + property color expressionHoverColor: "gray" property color scrollbarColor: "black" property string prompterTheme: "dark" + property color hoverColor: "black" property font font: styles.variablesPanel.font } property QtObject variableItem: QtObject { property color scrollbarColor: "#ED1D3D" property string prompterTheme: "light" + property color hoverColor: "gray" + property color textColor: "white" + property color identifierColor: "#ED1946" + property color expressionHoverColor: "#3D3D3D" property QtObject dots: QtObject { property color color: "#3D3D3D" } diff --git a/src/calculator/ui/qml/containers/VariablesPanel.qml b/src/calculator/ui/qml/containers/VariablesPanel.qml index 0634921..0e7ca33 100644 --- a/src/calculator/ui/qml/containers/VariablesPanel.qml +++ b/src/calculator/ui/qml/containers/VariablesPanel.qml @@ -57,6 +57,8 @@ Item { property alias ansScrollbarColor: ansItem.scrollbarColor /// Prompter of value flickable theme in Ans property alias ansPrompterTheme: ansItem.prompterTheme + /// Hover color of ans + property alias ansHoverColor: ansItem.hoverColor /// Background color of dots(area to slide variable options) property color dotsBackgroundColor @@ -72,6 +74,8 @@ Item { property color itemScrollbarColor /// Prompter of value flickable theme of item property string prompterTheme + /// Hover color of item + property color hoverColor /// Font of panel property font font @@ -101,6 +105,7 @@ Item { object.settersTextColor = Qt.binding(function() { return component.settersTextColor }) object.scrollbarColor = Qt.binding(function() { return component.itemScrollbarColor }) object.prompterTheme = Qt.binding(function() { return component.prompterTheme }) + object.hoverColor = Qt.binding(function() { return component.hoverColor }) object.width = Qt.binding(function() { return component.width }) object.height = Qt.binding(function() { return component.itemHeight }) diff --git a/src/calculator/ui/qml/item/VariableItem.qml b/src/calculator/ui/qml/item/VariableItem.qml index f3e6a21..fc1ed6c 100644 --- a/src/calculator/ui/qml/item/VariableItem.qml +++ b/src/calculator/ui/qml/item/VariableItem.qml @@ -62,6 +62,8 @@ Item { property alias settersHoveredColor: optionsMenu.settersHoveredColor /// Text color of variable setters property alias settersTextColor: optionsMenu.settersTextColor + /// Hover color of item + property alias hoverColor: content.hoverColor clip: true diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index e4dbfef..6d40990 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -96,10 +96,7 @@ ApplicationWindow { id: variablePanel backgroundColor: StyleSettings.variablesPanel.backgroundColor - textColor: StyleSettings.variablesPanel.textColor - identifierTextColor: StyleSettings.variablesPanel.identifierColor scrollBarColor: StyleSettings.variablesPanel.scrollBarColor - expressionHoverColor: StyleSettings.variablesPanel.expressionHoverColor ansTextColor: StyleSettings.ans.textColor ansIdentifierTextColor: StyleSettings.ans.identifierColor @@ -107,6 +104,7 @@ ApplicationWindow { ansExpressionHoverColor: StyleSettings.ans.expressionHoverColor ansScrollbarColor: StyleSettings.ans.scrollbarColor ansPrompterTheme: StyleSettings.ans.prompterTheme + ansHoverColor: StyleSettings.ans.hoverColor dotsBackgroundColor: StyleSettings.variableItem.dots.color removeButtonColor: StyleSettings.variableItem.removeButton.color @@ -114,7 +112,11 @@ ApplicationWindow { settersHoveredColor: StyleSettings.variableItem.setters.hoverColor settersTextColor: StyleSettings.variableItem.setters.textColor itemScrollbarColor: StyleSettings.variableItem.scrollbarColor + textColor: StyleSettings.variableItem.textColor + identifierTextColor: StyleSettings.variableItem.identifierColor + expressionHoverColor: StyleSettings.variableItem.expressionHoverColor prompterTheme: StyleSettings.variableItem.prompterTheme + hoverColor: StyleSettings.variableItem.hoverColor font.family: StyleSettings.variablesPanel.font.family diff --git a/src/calculator/ui/qml/visualization/VariableDisplay.qml b/src/calculator/ui/qml/visualization/VariableDisplay.qml index 88f9a0d..7e53349 100644 --- a/src/calculator/ui/qml/visualization/VariableDisplay.qml +++ b/src/calculator/ui/qml/visualization/VariableDisplay.qml @@ -1,6 +1,7 @@ import QtQuick 2.0 +import "../controls" as Controls -Rectangle { +Controls.FilledClickable { id: component /** @@ -33,23 +34,25 @@ Rectangle { /// Prompter of value flickable theme property string prompterTheme + hoverEnabled: true + hoverMaskEnabled: true + manual: true + + mouseArea.onClicked: { + if(mouse.button == Qt.LeftButton) + component.expandRequest(component.variableIdentifier) + else + createExpansionRequest() + } + + mouseArea.onContainsMouseChanged: component.handleHoverEvent() + QtObject { id: internal property real sideMargin: height / 6.5 } - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.LeftButton | Qt.RightButton - onClicked: { - if(mouse.button == Qt.LeftButton) - component.expandRequest(component.variableIdentifier) - else - createExpansionRequest() - } - } - Rectangle { id: expressionBackground @@ -134,8 +137,11 @@ Rectangle { anchors.top: parent.top MouseArea { + id: expressionMouseArea + parent: expression.flick hoverEnabled: true + propagateComposedEvents: true anchors.fill: parent @@ -145,6 +151,7 @@ Rectangle { } onContainsMouseChanged: { + component.handleHoverEvent() if(containsMouse) expressionBackground.color = component.expressionHoverColor else @@ -154,6 +161,14 @@ Rectangle { } } + function handleHoverEvent() { + if(component.mouseArea.containsMouse || expressionMouseArea.containsMouse) + component.entered() + else if((!expressionMouseArea.containsMouse) && (!component.mouseArea.containsMouse)){ + component.exited() + } + } + function createExpansionRequest() { if(component.variableExpression.indexOf("=") != -1) component.overwriteRequest(component.variableIdentifier + ' ' + component.variableExpression) From 00e231f12baa0cf3b5ba773d1ad2443220d4ffc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Mon, 10 Apr 2017 19:28:37 +0200 Subject: [PATCH 098/121] Improve for installation process --- .gitignore | 2 ++ MANIFEST.in | 3 ++- debian/changelog | 5 ++++ debian/compat | 1 + debian/control | 36 +++++++++++++++++++++++++ debian/postinst | 1 + debian/py3dist-overrides | 5 ++++ debian/rules | 31 +++++++++++++++++++++ requirements.txt | 9 ++----- setup.py | 22 +++++++-------- src/Makefile | 58 ++++++++++++++++++---------------------- src/calculator/main.py | 15 +++++++---- stdeb.cfg | 5 ++++ 13 files changed, 137 insertions(+), 56 deletions(-) create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/postinst create mode 100644 debian/py3dist-overrides create mode 100755 debian/rules create mode 100644 stdeb.cfg diff --git a/.gitignore b/.gitignore index db3b771..eb711a2 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ var/ *.egg-info/ .installed.cfg *.egg +deb_dist/ # PyInstaller # Usually these files are written by a python script from a template @@ -81,6 +82,7 @@ celerybeat-schedule # virtualenv venv/ ENV/ +.venv/ # Spyder project settings .spyderproject diff --git a/MANIFEST.in b/MANIFEST.in index 114782b..26f6fa7 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ graft src/calculator/ui include src/Makefile -include README.md \ No newline at end of file +include README.md +graft debian \ No newline at end of file diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..1ba3654 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +calculator (0.2-1) unstable; urgency=low + + * source package automatically created by stdeb 0.8.5 + + -- Josef Kolar, Son Hai Nguyen, Martin Omacht, Robert Navratil Mon, 10 Apr 2017 14:25:02 +0200 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7f8f011 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +7 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..dfd206c --- /dev/null +++ b/debian/control @@ -0,0 +1,36 @@ +Source: calculator +Maintainer: Josef Kolar, Son Hai Nguyen, Martin Omacht, Robert Navratil +Section: python +Priority: optional +Build-Depends: python3-setuptools, python3-all, debhelper (>= 7.4.3) +Standards-Version: 3.9.1 + + + + + +Package: python3-calculator +Architecture: all +Depends: foo +Description: UNKNOWN + # IVS-VUT-BIT-2016-2017 + . + [![Build Status](https://travis-ci.com/thejoeejoee/IVS-VUT-BIT-2016-2017.svg?token=MqEeDyeLfZw3xFmAVUzV&branch=develop)](https://travis-ci.com/thejoeejoee/IVS-VUT-BIT-2016-2017) + [![Codeship](https://img.shields.io/codeship/a2ac7ad0-fb4b-0134-7062-02a6a40c3d5e.svg)](https://app.codeship.com/projects/211472) + [![codecov](https://img.shields.io/codecov/c/token/M5EwaVLlg7/github/thejoeejoee/IVS-VUT-BIT-2016-2017/develop.svg)](https://codecov.io/gh/thejoeejoee/IVS-VUT-BIT-2016-2017) + [![License](https://img.shields.io/badge/license-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0.en.html) + . + Grafická kalkulačka jako školní projekt do předmětu IVS na fakultě FIT VUT. + . + . + Platforms + --------- + . + Ubuntu 64bit + . + Authors + ------ + . + /dej/uran/dom + - xkolar71 Josef Kolář + diff --git a/debian/postinst b/debian/postinst new file mode 100644 index 0000000..771cb1d --- /dev/null +++ b/debian/postinst @@ -0,0 +1 @@ +echo "Kurva" \ No newline at end of file diff --git a/debian/py3dist-overrides b/debian/py3dist-overrides new file mode 100644 index 0000000..15f6b0c --- /dev/null +++ b/debian/py3dist-overrides @@ -0,0 +1,5 @@ +pyqt5 python3-pyqt5 +doxyqml python3-doxyqml +doxypypy python3-doxypypy +colour_runner python3-colour-runner +pyopengl python3-pyopengl \ No newline at end of file diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..4807744 --- /dev/null +++ b/debian/rules @@ -0,0 +1,31 @@ +#!/usr/bin/make -f + +# This file was automatically generated by stdeb 0.8.5 at +# Mon, 10 Apr 2017 14:25:02 +0200 + +%: + dh $@ --with python3 --buildsystem=python_distutils + + +override_dh_auto_clean: + python3 setup.py clean -a + find . -name \*.pyc -exec rm {} \; + + + +override_dh_auto_build: + python3 setup.py build --force + + + +override_dh_auto_install: + python3 setup.py install --force --root=debian/python3-calculator --no-compile -O0 --install-layout=deb --prefix=/usr + + + +override_dh_python2: + dh_python2 --no-guessing-versions + + + + diff --git a/requirements.txt b/requirements.txt index c27923e..7d1e077 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,3 @@ -colour-runner -doxypypy -PyQt5==5.7.1 -termcolor -doxyqml +--index-url https://pypi.python.org/simple/ -# on Debian + Nvidia you need to install also package PyOpenGL -PyOpenGL \ No newline at end of file +-e . \ No newline at end of file diff --git a/setup.py b/setup.py index e7c4776..b3a84b8 100644 --- a/setup.py +++ b/setup.py @@ -9,17 +9,12 @@ base_path = abspath(dirname(__file__)) -def install_requires(): - with open(join(base_path, 'requirements.txt')) as f: - return [line.strip().split('#')[0] for line in f.readlines() if line.strip()] - - def setup(): core.setup( name='calculator', version='0.2', license='GNU GENERAL PUBLIC LICENSE Version 3', - long_description=open(join(base_path, './README.md')).read(), + long_description=open(join(base_path, 'README.md')).read(), url='https://github.com/thejoeejoee/IVS-VUT-BIT-2016-2017', classifiers=[ 'Development Status :: 3 - Alpha', @@ -36,13 +31,18 @@ def setup(): 'xomach00@stud.fit.vutbr.cz, xnavra61@stud.fit.vutbr.cz', keywords='calculator expression mathematics', packages=find_packages('src', exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), - install_requires=install_requires() + [ - 'PyOpenGL #;"Debian" in platform_version or "Ubuntu" in platform_version' + install_requires=[ + 'PyOpenGL #;"Debian" in platform_version or "Ubuntu" in platform_version', + 'PyQt5' + ], + requires=[ + 'colour_runner', + 'termcolor', + 'stdeb', + 'doxypy', + 'doxyqml', ], package_dir={'': 'src/'}, - # package_data={ - # '': ['**/*.{file_type}'.format(file_type=file_type) for file_type in file_types] - # }, entry_points={ 'console_scripts': [ 'calculator-console=calculator.console:main', diff --git a/src/Makefile b/src/Makefile index 7f3ee2a..81748b0 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,46 +1,40 @@ REQUIREMENTS=../requirements.txt -ifdef ENVIROMENT - ENVIROMENT:=$(shell echo $(ENVIROMENT) | tr a-z A-Z) -else - ENVIROMENT=$(shell if [ "${VIRTUAL_ENV}" != "" ] > /dev/null; then echo LOCAL; else echo GLOBAL; fi) -endif - -GLOBAL_PYTHON=python3 -GLOBAL_PIP=pip3 -GLOBAL_PYRCC5=pyrcc5 - -VENV_PATH=../.venv -LOCAL_PYTHON=$(VENV_PATH)/bin/$(GLOBAL_PYTHON) -LOCAL_PIP=$(VENV_PATH)/bin/$(GLOBAL_PIP) -LOCAL_PYRCC5=$(VENV_PATH)/bin/$(GLOBAL_PYRCC5) - -PYTHON=$(ENVIROMENT)_PYTHON +PYTHON=python3 +PIP=pip3 +PYRCC5=pyrcc5 +DIR:=$(CURDIR) all: run -LOCAL: - @touch $(VENV_PATH) || pyvenv $(VENV_PATH) - $(info ***** Using LOCAL enviroment with python $(abspath $($(PYTHON))). *****) - -GLOBAL: - $(info ***** Using SYSTEM enviroment with python $(shell which $($(PYTHON))). *****) - -# .PHONY: install $(ENVIROMENT) install-python install-calculator test run +.PHONY: all install install-python install-calculator test run clean-pyc compile-resources clean -install: install-python $(ENVIROMENT) install-calculator +install: install-python install-calculator install-python: @if ! dpkg -s python3.5 > /dev/null && dpkg -s python3-pip > /dev/null; then\ - apt-get update && apt-get -y install python3.5 python3-pip python3-venv;\ + apt-get update && apt-get -y install python3-all python3.5 python3-pip python3-venv;\ fi pip3 install --upgrade pip > /dev/null -install-calculator: $(ENVIROMENT) - $($(PYTHON)) ../setup.py install +install-calculator: + cd ..; $(PYTHON) $(DIR)/../setup.py install + +test: + $(PYTHON) $(DIR)/test.py || true + +run: + $(PYTHON) $(DIR)/calculator/main.py + +clean: + rm -rf ../dist/ ../deb_dist/ ./calculator.egg-info/ + +pack-deb: clean-pyc compile-resources + cd ..; $(PYTHON) setup.py --command-packages=stdeb.command bdist_deb + +clean-pyc: + find . -name "*.pyc" -delete -test: $(ENVIROMENT) - $($(PYTHON)) test.py || true +compile-resources: + $(PYRCC5) -o calculator/ui/resources.py calculator/ui/qml.qrc -run: $(ENVIROMENT) - $($(PYTHON)) calculator/main.py diff --git a/src/calculator/main.py b/src/calculator/main.py index e5ffe33..d35f4c3 100755 --- a/src/calculator/main.py +++ b/src/calculator/main.py @@ -40,7 +40,6 @@ def update_qrc(): Conditionally updates the qrc file from QML and dependent resources. :return: True, if file was successfully updated or update is not required, else None """ - from PyQt5.pyrcc_main import processResourceFile file_types = 'qml qrc js ttf otf svg qm png'.split() last_modification = max( @@ -64,13 +63,20 @@ def update_qrc(): if exists(RESOURCES_FILE) and (last_modification - getmtime(RESOURCES_FILE)) < 2: return True + if exists(RESOURCES_FILE): + print('Resources file {} is outdated.'.format( + RESOURCES_FILE + ), file=sys.stderr) + return True + print('Change in UI files detected, recompiling resources.py...', file=sys.stderr) - if exists(RESOURCES_FILE) and not access(RESOURCES_FILE, W_OK): - print('Resources file {} not exists or is not writable, please call with write permissions.'.format( + if not access(RESOURCES_FILE, W_OK): + print('Resources file {} is not exist and is not writable, please call with write permissions.'.format( RESOURCES_FILE ), file=sys.stderr) - return + return False + from PyQt5.pyrcc_main import processResourceFile if processResourceFile([QRC_FILE], RESOURCES_FILE, False): print('Resources.py successfully recompiled.', file=sys.stderr) return True @@ -78,6 +84,5 @@ def update_qrc(): print('Problem with compiling resources.py.', file=sys.stderr) - if __name__ == "__main__": main() diff --git a/stdeb.cfg b/stdeb.cfg new file mode 100644 index 0000000..123cc28 --- /dev/null +++ b/stdeb.cfg @@ -0,0 +1,5 @@ +[DEFAULT] +XS-Python-Version: 3.5 +Depends: + python3-all +# comment \ No newline at end of file From 37ac0e48bdcaa6c3901e74f22b1a4f4abf24f0d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Tue, 11 Apr 2017 01:30:18 +0200 Subject: [PATCH 099/121] Fix of debian control conf file with uninstallable dependencies --- debian/control | 20 ++++---------------- debian/postinst | 4 +++- debian/py3dist-overrides | 5 ----- setup.py | 2 +- src/Makefile | 2 +- src/calculator/ui/app/calculator_app.py | 2 ++ stdeb.cfg | 2 -- 7 files changed, 11 insertions(+), 26 deletions(-) diff --git a/debian/control b/debian/control index dfd206c..fec8907 100644 --- a/debian/control +++ b/debian/control @@ -4,25 +4,13 @@ Section: python Priority: optional Build-Depends: python3-setuptools, python3-all, debhelper (>= 7.4.3) Standards-Version: 3.9.1 - - - - - Package: python3-calculator Architecture: all -Depends: foo -Description: UNKNOWN - # IVS-VUT-BIT-2016-2017 - . - [![Build Status](https://travis-ci.com/thejoeejoee/IVS-VUT-BIT-2016-2017.svg?token=MqEeDyeLfZw3xFmAVUzV&branch=develop)](https://travis-ci.com/thejoeejoee/IVS-VUT-BIT-2016-2017) - [![Codeship](https://img.shields.io/codeship/a2ac7ad0-fb4b-0134-7062-02a6a40c3d5e.svg)](https://app.codeship.com/projects/211472) - [![codecov](https://img.shields.io/codecov/c/token/M5EwaVLlg7/github/thejoeejoee/IVS-VUT-BIT-2016-2017/develop.svg)](https://codecov.io/gh/thejoeejoee/IVS-VUT-BIT-2016-2017) - [![License](https://img.shields.io/badge/license-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0.en.html) - . +Depends: ${misc:Depends}, ${python3:Depends}, python3-pyopengl, python3-pip, python3-doxyqml, python3-doxypypy, python3-colour-runner, python3-opengl +Description: + IVS-VUT-BIT-2016-2017 Grafická kalkulačka jako školní projekt do předmětu IVS na fakultě FIT VUT. - . - . + Platforms --------- . diff --git a/debian/postinst b/debian/postinst index 771cb1d..615455f 100644 --- a/debian/postinst +++ b/debian/postinst @@ -1 +1,3 @@ -echo "Kurva" \ No newline at end of file +pip3 install --upgrade "PyQt5==5.7.1" +pip3 install --upgrade pip +pip3 install --upgrade PyOpenGL \ No newline at end of file diff --git a/debian/py3dist-overrides b/debian/py3dist-overrides index 15f6b0c..e69de29 100644 --- a/debian/py3dist-overrides +++ b/debian/py3dist-overrides @@ -1,5 +0,0 @@ -pyqt5 python3-pyqt5 -doxyqml python3-doxyqml -doxypypy python3-doxypypy -colour_runner python3-colour-runner -pyopengl python3-pyopengl \ No newline at end of file diff --git a/setup.py b/setup.py index b3a84b8..967f9eb 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ def setup(): keywords='calculator expression mathematics', packages=find_packages('src', exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), install_requires=[ - 'PyOpenGL #;"Debian" in platform_version or "Ubuntu" in platform_version', + 'PyOpenGL', 'PyQt5' ], requires=[ diff --git a/src/Makefile b/src/Makefile index 81748b0..385e379 100644 --- a/src/Makefile +++ b/src/Makefile @@ -29,7 +29,7 @@ run: clean: rm -rf ../dist/ ../deb_dist/ ./calculator.egg-info/ -pack-deb: clean-pyc compile-resources +pack-deb: clean clean-pyc compile-resources cd ..; $(PYTHON) setup.py --command-packages=stdeb.command bdist_deb clean-pyc: diff --git a/src/calculator/ui/app/calculator_app.py b/src/calculator/ui/app/calculator_app.py index 9e8245e..0eed78b 100644 --- a/src/calculator/ui/app/calculator_app.py +++ b/src/calculator/ui/app/calculator_app.py @@ -21,6 +21,8 @@ platform_identifier = platform.platform() # Just in case it also happens on Debian, so it can be added if 'Ubuntu' in platform_identifier or 'Debian' in platform_identifier: + # noinspection PyUnresolvedReferences + from OpenGL import GLU # noinspection PyUnresolvedReferences from OpenGL import GL diff --git a/stdeb.cfg b/stdeb.cfg index 123cc28..5c333a7 100644 --- a/stdeb.cfg +++ b/stdeb.cfg @@ -1,5 +1,3 @@ [DEFAULT] XS-Python-Version: 3.5 -Depends: - python3-all # comment \ No newline at end of file From 257754bf9a50205b331822e8585324d05cdb6464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Tue, 11 Apr 2017 01:50:07 +0200 Subject: [PATCH 100/121] Fix requirements.txt duplicates with setup.py --- debian/control | 2 +- requirements.txt | 11 +++++++++-- setup.py | 2 +- src/calculator/main.py | 2 +- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/debian/control b/debian/control index fec8907..8f55de6 100644 --- a/debian/control +++ b/debian/control @@ -6,7 +6,7 @@ Build-Depends: python3-setuptools, python3-all, debhelper (>= 7.4.3) Standards-Version: 3.9.1 Package: python3-calculator Architecture: all -Depends: ${misc:Depends}, ${python3:Depends}, python3-pyopengl, python3-pip, python3-doxyqml, python3-doxypypy, python3-colour-runner, python3-opengl +Depends: ${misc:Depends}, ${python3:Depends}, python3-pip, python3-doxyqml, python3-doxypypy, python3-colour-runner, python3-opengl Description: IVS-VUT-BIT-2016-2017 Grafická kalkulačka jako školní projekt do předmětu IVS na fakultě FIT VUT. diff --git a/requirements.txt b/requirements.txt index 7d1e077..33d822b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,10 @@ ---index-url https://pypi.python.org/simple/ +# needs to run +PyOpenGL +PyQt5==5.7.1 --e . \ No newline at end of file +# dev deps +colour_runner +termcolor +stdeb +doxypy +doxyqml \ No newline at end of file diff --git a/setup.py b/setup.py index 967f9eb..5a1aa3e 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ def setup(): packages=find_packages('src', exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), install_requires=[ 'PyOpenGL', - 'PyQt5' + 'PyQt5==5.7.1' ], requires=[ 'colour_runner', diff --git a/src/calculator/main.py b/src/calculator/main.py index d35f4c3..80779cf 100755 --- a/src/calculator/main.py +++ b/src/calculator/main.py @@ -63,7 +63,7 @@ def update_qrc(): if exists(RESOURCES_FILE) and (last_modification - getmtime(RESOURCES_FILE)) < 2: return True - if exists(RESOURCES_FILE): + if exists(RESOURCES_FILE) and not access(RESOURCES_FILE, W_OK): print('Resources file {} is outdated.'.format( RESOURCES_FILE ), file=sys.stderr) From 8880c91e120036573787424a99edd0c22432f39d Mon Sep 17 00:00:00 2001 From: Son Hai Nguyen Date: Tue, 11 Apr 2017 16:55:19 +0200 Subject: [PATCH 101/121] Init help window - #70 --- src/calculator.pro | 3 +- src/calculator/ui/assets/contents/help.js | 10 +++ src/calculator/ui/qml.qrc | 2 + src/calculator/ui/qml/main.qml | 5 ++ src/calculator/ui/qml/windows/Help.qml | 76 +++++++++++++++++++++++ 5 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 src/calculator/ui/assets/contents/help.js create mode 100644 src/calculator/ui/qml/windows/Help.qml diff --git a/src/calculator.pro b/src/calculator.pro index f1d42c8..6b7225f 100644 --- a/src/calculator.pro +++ b/src/calculator.pro @@ -1,5 +1,4 @@ -SOURCES = $$files(*.py, true) $$files(*.qml, true) - +SOURCES = $$files(*.py, true) $$files(*.qml, true) $$files(*.js, true) TRANSLATIONS = calculator/ui/translations/cs.ts CODECFORTR = UTF-8 diff --git a/src/calculator/ui/assets/contents/help.js b/src/calculator/ui/assets/contents/help.js new file mode 100644 index 0000000..adafaf9 --- /dev/null +++ b/src/calculator/ui/assets/contents/help.js @@ -0,0 +1,10 @@ +var content = [ + { + "title": qsTr(""), + "content": qsTr("") + }, + { + "subtitle": qsTr(""), + "content": qsTr("") + } +] diff --git a/src/calculator/ui/qml.qrc b/src/calculator/ui/qml.qrc index a48d0fc..ce08015 100644 --- a/src/calculator/ui/qml.qrc +++ b/src/calculator/ui/qml.qrc @@ -57,5 +57,7 @@ qml/controls/FilledClickable.qml assets/images/arrow_left_dark.svg assets/images/arrow_left_light.svg + qml/windows/Help.qml + assets/contents/help.js diff --git a/src/calculator/ui/qml/main.qml b/src/calculator/ui/qml/main.qml index 6d40990..959bf97 100644 --- a/src/calculator/ui/qml/main.qml +++ b/src/calculator/ui/qml/main.qml @@ -362,6 +362,11 @@ ApplicationWindow { onHidden: expInput.focus = true } + Windows.Help { + width: 300 + height: 600 + } + /** Show suggestion box with filtered suggestions */ diff --git a/src/calculator/ui/qml/windows/Help.qml b/src/calculator/ui/qml/windows/Help.qml new file mode 100644 index 0000000..e52cbd1 --- /dev/null +++ b/src/calculator/ui/qml/windows/Help.qml @@ -0,0 +1,76 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.0 +import "../../assets/contents/help.js" as HelpContent + +ApplicationWindow { + visible: true + + Flickable { + id: flick + + clip: true + boundsBehavior: Flickable.StopAtBounds + + contentWidth: width + contentHeight: text.height + + anchors.fill: parent + + Text { + id: text + + readonly property string style: '' + .arg("red") + .arg("blue") + .arg(15) + .arg(10) + .arg(25) + .arg(35) + + width: parent.width - 7 + textFormat: Text.RichText + wrapMode: TextEdit.WordWrap + text: '' + + Component.onCompleted: { + var content = "" + + for(var key in HelpContent.content) { + var section = HelpContent.content[key] + + if(typeof section["title"] != "undefined") + content += '