diff --git a/Utilities/Calculator.app/Resources/CHANGELOGS.md b/Utilities/Calculator.app/Resources/CHANGELOGS.md new file mode 100644 index 000000000..3218a0637 --- /dev/null +++ b/Utilities/Calculator.app/Resources/CHANGELOGS.md @@ -0,0 +1,11 @@ +# helloSystem Calculator +## v0.2 +* Change size +* Spanning for 0 and = +* Introduce memory line MC, M+, M-, MR +* Introduce ± key +* Optimize layout +* Use true operator symbol and convert them before the eval +* Clean up +## v0.1 +* Initial commit diff --git a/Utilities/Calculator.app/Resources/LICENSE b/Utilities/Calculator.app/Resources/LICENSE index ab22a4e1f..792f2012e 100644 --- a/Utilities/Calculator.app/Resources/LICENSE +++ b/Utilities/Calculator.app/Resources/LICENSE @@ -5,6 +5,7 @@ The MIT License (MIT) Copyright (c) 2019, Leodanis Pozo Ramos Portions Copyright (c) 2020, Simon Peter +Portions Copyright (c) 2023, Jérôme Ornech alias Hierosme Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Utilities/Calculator.app/Resources/README.md b/Utilities/Calculator.app/Resources/README.md new file mode 100644 index 000000000..341d6c8a6 --- /dev/null +++ b/Utilities/Calculator.app/Resources/README.md @@ -0,0 +1,140 @@ +# Calculator + + +Calculator.app displays a calculator window ready to perform common operations like addition, subtraction, multiplication, and division, as well as complex calculations and conversions. + +To launch Calculator +Double-click the Calculator icon in the Utilities folder (Figure 1). + +Or + +1. Click the Calculator icon in the Utilities folder (Figure 1) to select it. +2. Choose File > Open, or press . + +The Calculator window appears (Figure 2). + +Figure 2. At startup the Calculator looks like any pocket calculator. + +SCREEN HERE + +## Perform basic calculations +You can use your mouse to click buttons for numbers and operators. + +Or + +Press keyboard keys corresponding to numbers and operators. + +The numbers you enter and the results of your calculations appear at the top of the Calculator window. + + Tip + +You can use the Cut, Copy, and Paste commands to copy the results of calculations into documents. + + +To keep track of your entries +Click View > Show Paper Tape (Figure 3). The Paper Tape dialog appears. It displays entries history (Figure 4). + + +Figure 3. Calculator View menu. +SCREEN HERE + + +Figure 4. By click Show Paper Tape from the View menu, your entries history appear in a separate window dialog. +SCREEN HERE + +To hide the Paper Tape window dialog, click View > Hide Paper Tape or use the Paper Tape window's close button. +You can click the Clear button , To start with a fresh tape. + +You can use commands under the File menu (Figure 41) to save or print the paper tape. + +Figure 41. The File menu includes commands for saving and printing the paper tape. + + + + +## Perform scientific calculations +1. Choose View > Scientific (Figure 39) or press . The window expands to show a variety of functions used for scientific calculations (Figure 42). + +Figure 42. Choosing Scientific from the View menu expands the Calculator to display scientific functions. +SCREEN HERE + + +2. Click buttons for the functions, values, and operators to perform your calculations. + + + Tip + +To hide scientific functions, choose View > Basic (Figure 39) or press . + + +## Perform programmer functions +1. Choose View > Programmer (Figure 39) or press . The window expands to show a variety of programming-related functions (Figure 43). + +Figure 43. Choosing Programmer from the View menu displays programming-related functions. +SCREEN HERE + + +2. Click buttons for the functions, values, and operators to perform your calculations. + + + Tip + +To hide programmer functions, choose View > Basic (Figure 39) or press . + + +To use Reverse Polish Notation +1. + +Choose View > RPN (Figure 39). The letters RPN appear in the calculator's window (Figure 44). + +Figure 44. The letters RPN indicate the calculator is configured for Reverse Polish Notation. + + + +2. + +Click buttons for the functions, values, and operators to perform your calculations. + + + Tips + +Reverse Polish Notation, or RPN, is an alternative format for entering calculations. It is commonly used on Hewlett-Packard brand calculators. + +If you don't know what Reverse Polish Notation is, you probably don't need to use it. + + +To perform conversions +1. + +Enter the value you want to convert. + + +2. + +Choose the conversion you want from the Convert menu (Figure 45). + +Figure 45. The Convert menu lists a variety of common conversions. + + + +3. + +In the dialog that appears, set options for the conversion you want to perform. Figure 46 shows an example that converts speed from miles per hour to knots. + +Figure 46. Set conversion options in a dialog sheet like this. + + + +4. + +Click OK. The original value you entered is converted and appears at the top of the Calculator window. + + + Tips + +The Convert menu's Recent Conversions submenu makes it easy to repeat conversions you have done recently. + +If you have an Internet connection, you should choose Convert > Update Currency Exchange Rates before using the Convert menu to perform currency conversions. + + + diff --git a/Utilities/Calculator.app/Resources/calculator.py b/Utilities/Calculator.app/Resources/calculator.py index 2aa45bf9a..4ddd3eb58 100755 --- a/Utilities/Calculator.app/Resources/calculator.py +++ b/Utilities/Calculator.app/Resources/calculator.py @@ -3,6 +3,7 @@ # Calculator Construction Set # for a fun story, see # https://www.folklore.org/StoryView.py?story=Calculator_Construction_Set.txt +# https://doc.qt.io/qtforpython-5/overviews/qtwidgets-widgets-calculator-example.html#calculator-example # Based on PyCalc # https://github.com/realpython/materials/tree/master/pyqt-calculator-tutorial/pycalc @@ -11,6 +12,7 @@ # # Copyright (c) 2019, Leodanis Pozo Ramos # Portions Copyright (c) 2020, Simon Peter +# Portions Copyright (c) 2023, Jérôme Ornech alias Hierosme # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -33,95 +35,220 @@ """PyCalc is a simple calculator built using Python and PyQt5.""" -import os, sys +from os import path +import sys +from math import cos, log, tan, sin, cosh, tanh, sinh, pow, pi from functools import partial # Import QApplication and the required widgets from PyQt5.QtWidgets from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QApplication -from PyQt5.QtWidgets import QGridLayout -from PyQt5.QtWidgets import QLineEdit from PyQt5.QtWidgets import QMainWindow -from PyQt5.QtWidgets import QPushButton -from PyQt5.QtWidgets import QVBoxLayout -from PyQt5.QtWidgets import QWidget -from PyQt5.QtWidgets import QAction -from PyQt5.QtWidgets import QMessageBox -from PyQt5.QtWidgets import qApp -from PyQt5.QtGui import QPixmap +from PyQt5.QtWidgets import QMessageBox, QSpacerItem, QSizePolicy +from PyQt5.QtGui import QPixmap, QColor, QIcon -__version__ = "0.1-mod1" -__author__ = "Leodanis Pozo Ramos & Contributors" +# The Main Window +from main_window_ui import Ui_MainWindow + +from widget_calculator_button import CalculatorButton +from dialog_paper_tape import PaperTape + +__version__ = "0.2" +__author__ = ["Leodanis Pozo Ramos & Contributors", "Jérôme ORNECH alias Hierosme"] ERROR_MSG = "ERROR" -# Create a subclass of QMainWindow to setup the calculator's GUI -class PyCalcUi(QMainWindow): +# Create a subclass of QMainWindow to set up the calculator's GUI +class Window(QMainWindow, Ui_MainWindow): """PyCalc's View (GUI).""" - def __init__(self): - """View initializer.""" - super().__init__() - # Set some main window's properties - self.setWindowTitle("Calculator") - self.setFixedSize(160, 230) - # Set the central widget and the general layout - self.generalLayout = QVBoxLayout() - self._centralWidget = QWidget(self) - self.setCentralWidget(self._centralWidget) - self._centralWidget.setLayout(self.generalLayout) - # Create the display and the buttons - self._createDisplay() - self._createButtons() - self._showMenu() - - def _createDisplay(self): - """Create the display.""" - # Create the display widget - self.display = QLineEdit() - # Set some display's properties - # self.display.setFixedHeight(35) + def __init__(self, parent=None): + super().__init__(parent) + self.paper_tape_dialog = None + self.scientific_buttons = None + self.basic_buttons = None + self.asking_question = None + self.setupUi(self) + self.setupCustomUi() + + self.setupInitialState() + self.create_basic_layout() + self.create_scientific_layout() + + self.connectSignalsSlots() + self.setWindowIcon(QIcon(path.join(path.dirname(__file__), "Calculator.png"))) + self._display_basic() + self.show() + + def setupInitialState(self): + self.asking_question = False self.display.setAlignment(Qt.AlignRight) - self.display.setReadOnly(False) - # Add the display to the general layout - self.generalLayout.addWidget(self.display) - - def _createButtons(self): - """Create the buttons.""" - self.buttons = {} - buttonsLayout = QGridLayout() + self.scientific_buttons = {} + self.basic_buttons = {} + self.paper_tape_dialog.hide() + + def connectSignalsSlots(self): + # Menu and ToolBar + self.ActionMenuHelpAbout.triggered.connect(self._showAboutDialog) + self.actionView_Show_Paper_Tape.triggered.connect(self._showPaperTape) + self.actionView_Basic.triggered.connect(self._display_basic) + self.actionView_Scientific.triggered.connect(self._display_scientific) + + def setupCustomUi(self): + # Paper Tape + self.paper_tape_dialog = PaperTape() + + def create_basic_layout(self): + """Create the basic layout buttons.""" + # Button text | position on the QGridLayout buttons = { - "7": (1, 0), - "8": (1, 1), - "9": (1, 2), - "/": (0, 3), - "C": (0, 0), - "4": (2, 0), - "5": (2, 1), - "6": (2, 2), - "*": (1, 3), - "(": (0, 1), - "1": (3, 0), - "2": (3, 1), - "3": (3, 2), - "-": (2, 3), - ")": (0, 2), - "0": (4, 0), - # "00": (3, 1), - ".": (4, 2), + # First Line + "MC": (0, 0), + "M+": (0, 1), + "M−": (0, 2), + "MR": (0, 3), + # Second line + "C": (1, 0), + "±": (1, 1), + "÷": (1, 2), + "×": (1, 3), + # Third line + "7": (2, 0), + "8": (2, 1), + "9": (2, 2), + "−": (2, 3), + # etc ... + "4": (3, 0), + "5": (3, 1), + "6": (3, 2), "+": (3, 3), + "1": (4, 0), + "2": (4, 1), + "3": (4, 2), "=": (4, 3), + # the last line got only 2 buttons + "0": (5, 0), + ".": (5, 2), } # Create the buttons and add them to the grid layout for btnText, pos in buttons.items(): - self.buttons[btnText] = QPushButton(btnText) - self.buttons[btnText].setFixedSize(34, 36) - buttonsLayout.addWidget(self.buttons[btnText], pos[0], pos[1]) - # Add buttonsLayout to the general layout - self.generalLayout.addLayout(buttonsLayout) + # Create a button + self.basic_buttons[btnText] = CalculatorButton(text=btnText) + + # Apply Color + if btnText in ["−", "±", "÷", "×", "+", "MC", "M+", "M−", "MR"]: + self.basic_buttons[btnText].setColor(QColor("#7a7a7b")) + self.basic_buttons[btnText].setFontColor(QColor("#f7f6f6")) + elif btnText == "=": + self.basic_buttons[btnText].setColor(QColor("#f09648")) + self.basic_buttons[btnText].setFontColor(QColor("#ffffff")) + elif btnText == "C": + self.basic_buttons[btnText].setColor(QColor("#f0003b")) + self.basic_buttons[btnText].setFontColor(QColor("#ffffff")) + else: + self.basic_buttons[btnText].setColor(QColor("#eeeeed")) + self.basic_buttons[btnText].setFontColor(QColor("#3f3f3f")) + + # Apply location + if btnText == "=": + self.basic_buttons_layout.addWidget(self.basic_buttons[btnText], pos[0], pos[1], 2, 1) + elif btnText == "0": + self.basic_buttons_layout.addWidget(self.basic_buttons[btnText], pos[0], pos[1], 1, 2) + else: + self.basic_buttons_layout.addWidget(self.basic_buttons[btnText], pos[0], pos[1], 1, 1) + + def create_scientific_layout(self): + """Create the basic layout buttons.""" + self.scientific_buttons = {} + # Button text | position on the QGridLayout + buttons = { + # First Line + "2ⁿᵈ": (0, 0), + "⟮": (0, 1), + "⟯": (0, 2), + "%": (0, 3), + "MC": (0, 5), + "M+": (0, 6), + "M−": (0, 7), + "MR": (0, 8), + # Second line + "1/x": (1, 0), + "x²": (1, 1), + "x³": (1, 2), + "yˣ": (1, 3), + "C": (1, 5), + "±": (1, 6), + "÷": (1, 7), + "×": (1, 8), + # Third line + "x!": (2, 0), + "√": (2, 1), + "ˣ√𝑦": (2, 2), + "In": (2, 3), + "7": (2, 5), + "8": (2, 6), + "9": (2, 7), + "−": (2, 8), + # etc ... + "sin": (3, 0), + "cos": (3, 1), + "tan": (3, 2), + "log": (3, 3), + "4": (3, 5), + "5": (3, 6), + "6": (3, 7), + "+": (3, 8), + "sinh": (4, 0), + "cosh": (4, 1), + "tanh": (4, 2), + "eˣ": (4, 3), + "1": (4, 5), + "2": (4, 6), + "3": (4, 7), + "=": (4, 8), + # the last line + "Rad": (5, 0), + "⫪": (5, 1), + "EE": (5, 2), + "RN": (5, 3), + "0": (5, 5), + ".": (5, 7), + } + # Create the buttons and add them to the grid layout + for btnText, pos in buttons.items(): + # Create a button + self.scientific_buttons[btnText] = CalculatorButton(text=btnText) + + # Apply Color + if btnText in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "."]: + self.scientific_buttons[btnText].setColor(QColor("#eeeeed")) + self.scientific_buttons[btnText].setFontColor(QColor("#3f3f3f")) + elif btnText == "=": + self.scientific_buttons[btnText].setColor(QColor("#f09648")) + self.scientific_buttons[btnText].setFontColor(QColor("#ffffff")) + elif btnText == "C": + self.scientific_buttons[btnText].setColor(QColor("#f0003b")) + self.scientific_buttons[btnText].setFontColor(QColor("#ffffff")) + else: + self.scientific_buttons[btnText].setColor(QColor("#7a7a7b")) + self.scientific_buttons[btnText].setFontColor(QColor("#f7f6f6")) + + # Apply location + if btnText == "=": + self.scientific_buttons_layout.addWidget(self.scientific_buttons[btnText], pos[0], pos[1], 2, 1) + elif btnText == "0": + self.scientific_buttons_layout.addWidget(self.scientific_buttons[btnText], pos[0], pos[1], 1, 2) + else: + self.scientific_buttons_layout.addWidget(self.scientific_buttons[btnText], pos[0], pos[1], 1, 1) + + if btnText in ["Rad", "EE", "RN", "eˣ", "2ⁿᵈ", "n", "yˣ", "In", "x!", "ˣ√𝑦", "√", "%"]: + self.scientific_buttons[btnText].setEnabled(False) + + spacer = QSpacerItem(6, 6, QSizePolicy.Minimum, QSizePolicy.Expanding) + self.scientific_buttons_layout.addItem(spacer, 0, 4, 6, 1) def setDisplayText(self, text): """Set display's text.""" @@ -136,42 +263,71 @@ def clearDisplay(self): """Clear the display.""" self.setDisplayText("") - def _showMenu(self): - exitAct = QAction('&Exit', self) - exitAct.setShortcut('Ctrl+Q') - exitAct.setStatusTip('Exit application') - exitAct.triggered.connect(qApp.quit) - menubar = self.menuBar() - fileMenu = menubar.addMenu('&File') - fileMenu.addAction(exitAct) - - aboutAct = QAction('&About', self) - aboutAct.setStatusTip('About this application') - aboutAct.triggered.connect(self._showAbout) - helpMenu = menubar.addMenu('&Help') - helpMenu.addAction(aboutAct) - - def _showAbout(self): - print("showDialog") + def closeEvent(self, event): + self.paper_tape_dialog.have_to_close = True + self.paper_tape_dialog.close() + + super(Window, self).closeEvent(event) + + @staticmethod + def _showAboutDialog(): msg = QMessageBox() msg.setWindowTitle("About") - msg.setIconPixmap(QPixmap(os.path.dirname(__file__) + "/Calculator.png")) - candidates = ["COPYRIGHT", "COPYING", "LICENSE"] - for candidate in candidates: - if os.path.exists(os.path.dirname(__file__) + "/" + candidate): - with open(os.path.dirname(__file__) + "/" + candidate, 'r') as file: + msg.setIconPixmap( + QPixmap(path.join(path.dirname(__file__), "Calculator.png")).scaled( + 64, 64, Qt.KeepAspectRatio, Qt.SmoothTransformation + ) + ) + for candidate in ["COPYRIGHT", "COPYING", "LICENSE"]: + if path.exists(path.join(path.dirname(__file__), candidate)): + with open(path.join(path.dirname(__file__), candidate), "r") as file: data = file.read() msg.setDetailedText(data) msg.setText("

Calculator

") - msg.setInformativeText("A simple calculator application written in PyQt5

https://github.com/helloSystem/Utilities") + msg.setInformativeText( + "A simple calculator application written in PyQt5

" + "https://github.com/helloSystem/Utilities" + ) msg.exec() + def _showPaperTape(self): + if self.paper_tape_dialog.isVisible(): + self.paper_tape_dialog.hide() + else: + self.paper_tape_dialog.move(self.pos().x() - self.paper_tape_dialog.width(), self.pos().y()) + self.paper_tape_dialog.show() + + self.activateWindow() + self.setFocus() + self.raise_() + + def _display_basic(self): + self.setFixedWidth(200) + self.setFixedHeight(250) + self.stacked_widget.setCurrentIndex(0) + + def _display_scientific(self): + self.setFixedWidth(400) + self.setFixedHeight(250) + self.stacked_widget.setCurrentIndex(1) + + # Create a Model to handle the calculator's operation def evaluateExpression(expression): """Evaluate an expression.""" + if "÷" in expression: + expression = expression.replace("÷", "/") + if "×" in expression: + expression = expression.replace("×", "*") + if "−" in expression: + expression = expression.replace("−", "-") + if "⟮" in expression: + expression = expression.replace("⟮", "(") + if "⟯" in expression: + expression = expression.replace("⟯", ")") try: result = str(eval(expression, {}, {})) - except Exception: + except (Exception, BaseException): result = ERROR_MSG return result @@ -185,48 +341,333 @@ def __init__(self, model, view): """Controller initializer.""" self._evaluate = model self._view = view + self._memory = None + self.memory = None # Connect signals and slots self._connectSignals() + @property + def memory(self): + return self._memory + + @memory.setter + def memory(self, value): + if value is None: + self._memory = None + return + if self.memory != value: + self._memory = value + def _calculateResult(self): """Evaluate expressions.""" + if not self._view.asking_question: + result = self._evaluate(expression=self._view.displayText()) + if result: + self._view.paper_tape_dialog.plainTextEdit.setPlainText( + "%s\n\n%s" % (self._view.paper_tape_dialog.plainTextEdit.toPlainText(), self._view.displayText()) + ) + self._view.paper_tape_dialog.plainTextEdit.setPlainText( + "%s\n= %s" % (self._view.paper_tape_dialog.plainTextEdit.toPlainText(), result) + ) + self._view.setDisplayText(result) + + def _memory_clear(self): + """Clear memory by set value to None""" + self.memory = None + self._view.display.setFocus() + + def _memory_subtract(self): + """Add the result of display expression to the memory""" result = self._evaluate(expression=self._view.displayText()) - self._view.setDisplayText(result) + if result and "ERROR" not in result: + if self.memory is None: + self.memory = 0 + if "." in result: + if self.memory: + self.memory -= float(result) + else: + self.memory -= int(result) + self._view.display.setFocus() + + def _memory_add(self): + """Subtract the result of display expression to the memory""" + result = self._evaluate(expression=self._view.displayText()) + if result and "ERROR" not in result: + if self.memory is None: + self.memory = 0 + if "." in result: + self.memory += float(result) + else: + self.memory += int(result) + self._view.display.setFocus() + + def _memory_print(self): + """If memory value, flush the display with it value""" + if self.memory is not None: + self._view.clearDisplay() + self._view.setDisplayText("%s" % self.memory) + else: + self._view.display.setFocus() + + def _neg(self): + """Evaluate expressions value and display the negative value""" + result = self._evaluate(expression=self._view.displayText()) + if result and "ERROR" not in result: + if "." in result: + if float(result) > 0: + result = -abs(float(result)) + else: + result = abs(float(result)) + else: + if int(result) > 0: + result = -abs(int(result)) + else: + result = abs(int(result)) + + self._view.setDisplayText(str(result)) + + def _cos(self): + """Evaluate expressions value and display the cos of the value""" + result = self._evaluate(expression=self._view.displayText()) + if result and "ERROR" not in result: + try: + if "." in result: + result = cos(float(result)) + else: + result = cos(int(result)) + except OverflowError: + result = ERROR_MSG + + self._view.setDisplayText(str(result)) + + def _cosh(self): + """Evaluate expressions value and display the cosh of the value""" + result = self._evaluate(expression=self._view.displayText()) + if result and "ERROR" not in result: + try: + if "." in result: + result = cosh(float(result)) + else: + result = cosh(int(result)) + except OverflowError: + result = ERROR_MSG + + self._view.setDisplayText(str(result)) + + def _sin(self): + """Evaluate expressions value and display the sin of the value""" + result = self._evaluate(expression=self._view.displayText()) + if result and "ERROR" not in result: + try: + if "." in result: + result = sin(float(result)) + else: + result = sin(int(result)) + except OverflowError: + result = ERROR_MSG + + self._view.setDisplayText(str(result)) + + def _sinh(self): + """Evaluate expressions value and display the sinh of the value""" + result = self._evaluate(expression=self._view.displayText()) + if result and "ERROR" not in result: + try: + if "." in result: + result = sinh(float(result)) + else: + result = sinh(int(result)) + except OverflowError: + result = ERROR_MSG + + self._view.setDisplayText(str(result)) + + def _tan(self): + """Evaluate expressions value and display the tan of the value""" + result = self._evaluate(expression=self._view.displayText()) + if result and "ERROR" not in result: + try: + if "." in result: + result = tan(float(result)) + else: + result = tan(int(result)) + except OverflowError: + result = ERROR_MSG + + self._view.setDisplayText(str(result)) + + def _tanh(self): + """Evaluate expressions value and display the tanh of the value""" + result = self._evaluate(expression=self._view.displayText()) + if result and "ERROR" not in result: + try: + if "." in result: + result = tanh(float(result)) + else: + result = tanh(int(result)) + except OverflowError: + result = ERROR_MSG + + self._view.setDisplayText(str(result)) + + def _log(self): + """Evaluate expressions value and display the log of the value""" + result = self._evaluate(expression=self._view.displayText()) + if result and "ERROR" not in result: + try: + if "." in result: + result = log(float(result)) + else: + result = log(int(result)) + except (OverflowError, ValueError): + result = ERROR_MSG + + self._view.setDisplayText(str(result)) + + def _power2(self): + """Evaluate expressions value and display the power2 of the value""" + result = self._evaluate(expression=self._view.displayText()) + if result and "ERROR" not in result: + try: + if "." in result: + result = pow(float(result), 2) + else: + result = pow(int(result), 2) + except (OverflowError, ValueError): + result = ERROR_MSG + + self._view.setDisplayText(str(result)) + + def _power3(self): + """Evaluate expressions value and display the power3 of the value""" + result = self._evaluate(expression=self._view.displayText()) + if result and "ERROR" not in result: + try: + if "." in result: + result = pow(float(result), 3) + else: + result = pow(int(result), 3) + except (OverflowError, ValueError): + result = ERROR_MSG + + self._view.setDisplayText(str(result)) + + def _powerx(self): + """Evaluate expressions value and display the powerX of the value""" + result = self._evaluate(expression=self._view.displayText()) + if result and "ERROR" not in result: + self._view.asking_question = True + self._view.setDisplayText("Expose ") + + self._view.asking_question = False + try: + if "." in result: + result = pow(float(result), 3) + else: + result = pow(int(result), 3) + except (OverflowError, ValueError): + result = ERROR_MSG + + self._view.setDisplayText(str(result)) + + def _inverse(self): + """Evaluate expressions value and display the 1/x of the value""" + result = self._evaluate(expression=self._view.displayText()) + if result and "ERROR" not in result: + try: + if "." in result: + result = 1 / float(result) + else: + result = 1 / int(result) + except (OverflowError, ValueError): + result = ERROR_MSG + + self._view.setDisplayText(str(result)) def _buildExpression(self, sub_exp): """Build expression.""" if self._view.displayText() == ERROR_MSG: self._view.clearDisplay() - expression = self._view.displayText() + sub_exp + expression = "".join([self._view.displayText(), str(sub_exp)]) self._view.setDisplayText(expression) def _connectSignals(self): """Connect signals and slots.""" - for btnText, btn in self._view.buttons.items(): - if btnText not in {"=", "C"}: + # Display signals + self._view.display.returnPressed.connect(self._calculateResult) + # self._view.display.escapePressed.connect(self._view.clearDisplay) + + # Connect Basic Layout Button + for btnText, btn in self._view.basic_buttons.items(): + if btnText not in {"=", "C", "MC", "M+", "M−", "MR", "±"}: btn.clicked.connect(partial(self._buildExpression, btnText)) - self._view.buttons["="].clicked.connect(self._calculateResult) - self._view.display.returnPressed.connect(self._calculateResult) - self._view.buttons["C"].clicked.connect(self._view.clearDisplay) - """self._view.display.escapePressed.connect(self._view.clearDisplay)""" - - -# Client code -def main(): - """Main function.""" - # Create an instance of `QApplication` - pycalc = QApplication(sys.argv) - # Show the calculator's GUI - view = PyCalcUi() - view.show() - # Create instances of the model and the controller - model = evaluateExpression - PyCalcCtrl(model=model, view=view) - # Execute calculator's main loop - sys.exit(pycalc.exec_()) + self._view.basic_buttons["="].clicked.connect(self._calculateResult) + self._view.basic_buttons["C"].clicked.connect(self._view.clearDisplay) + self._view.basic_buttons["±"].clicked.connect(self._neg) + self._view.basic_buttons["MC"].clicked.connect(self._memory_clear) + self._view.basic_buttons["M+"].clicked.connect(self._memory_add) + self._view.basic_buttons["M−"].clicked.connect(self._memory_subtract) + self._view.basic_buttons["MR"].clicked.connect(self._memory_print) + + # Connect Scientific Layout Button + for btnText, btn in self._view.scientific_buttons.items(): + if btnText not in [ + "=", + "C", + "MC", + "M+", + "M−", + "MR", + "±", + "cos", + "sin", + "tan", + "cosh", + "sinh", + "tanh", + "log", + "x²", + "x³", + "yˣ", + "⫪", + "1/x", + ]: + btn.clicked.connect(partial(self._buildExpression, btnText)) + + self._view.scientific_buttons["="].clicked.connect(self._calculateResult) + + self._view.scientific_buttons["C"].clicked.connect(self._view.clearDisplay) + self._view.scientific_buttons["±"].clicked.connect(self._neg) + self._view.scientific_buttons["MC"].clicked.connect(self._memory_clear) + self._view.scientific_buttons["M+"].clicked.connect(self._memory_add) + self._view.scientific_buttons["M−"].clicked.connect(self._memory_subtract) + self._view.scientific_buttons["MR"].clicked.connect(self._memory_print) + + self._view.scientific_buttons["cos"].clicked.connect(self._cos) + self._view.scientific_buttons["cosh"].clicked.connect(self._cosh) + + self._view.scientific_buttons["sin"].clicked.connect(self._sin) + self._view.scientific_buttons["sinh"].clicked.connect(self._sinh) + + self._view.scientific_buttons["tan"].clicked.connect(self._tan) + self._view.scientific_buttons["tanh"].clicked.connect(self._tanh) + + self._view.scientific_buttons["log"].clicked.connect(self._log) + + self._view.scientific_buttons["x²"].clicked.connect(self._power2) + self._view.scientific_buttons["x³"].clicked.connect(self._power3) + # self._view.scientific_buttons["yˣ"].clicked.connect(self._powerx) + + self._view.scientific_buttons["⫪"].clicked.connect(partial(self._buildExpression, pi)) + + self._view.scientific_buttons["1/x"].clicked.connect(self._inverse) if __name__ == "__main__": - main() + app = QApplication(sys.argv) + win = Window() + model = evaluateExpression + PyCalcCtrl(model=model, view=win) + sys.exit(app.exec()) diff --git a/Utilities/Calculator.app/Resources/dialog_paper_tape.py b/Utilities/Calculator.app/Resources/dialog_paper_tape.py new file mode 100644 index 000000000..c6182ee4d --- /dev/null +++ b/Utilities/Calculator.app/Resources/dialog_paper_tape.py @@ -0,0 +1,36 @@ +from PyQt5.QtWidgets import QWidget +from PyQt5.QtCore import Qt + +from dialog_paper_tape_ui import Ui_PaperTape + + +class PaperTape(QWidget, Ui_PaperTape): + + def __init__(self, parent=None, process=None): + super(PaperTape, self).__init__(parent) + self.setupUi(self) + self.process = process + + # When you want to destroy the dialog set this to True + self.have_to_close = False + self.setFocusPolicy(Qt.ClickFocus) + + + + def closeEvent(self, evnt): + # That widget is call as a window, and should be close with the main app + # Else ---> The widget is hide then continue to store CPU data + if self.have_to_close: + super(PaperTape, self).closeEvent(evnt) + else: + evnt.ignore() + self.hide() + + + + + # def focusOutEvent(self, event): + # self.setFocus() + # self.activateWindow() + # self.raise_() + # self.show() diff --git a/Utilities/Calculator.app/Resources/dialog_paper_tape.ui b/Utilities/Calculator.app/Resources/dialog_paper_tape.ui new file mode 100644 index 000000000..1a16ddb90 --- /dev/null +++ b/Utilities/Calculator.app/Resources/dialog_paper_tape.ui @@ -0,0 +1,58 @@ + + + PaperTape + + + + 0 + 0 + 244 + 251 + + + + Paper Tape + + + + + + Clear + + + + + + + false + + + Recalculate Totals + + + + + + + + + + + + pushButton_clear + clicked() + plainTextEdit + clear() + + + 194 + 226 + + + 121 + 106 + + + + + diff --git a/Utilities/Calculator.app/Resources/dialog_paper_tape_ui.py b/Utilities/Calculator.app/Resources/dialog_paper_tape_ui.py new file mode 100644 index 000000000..99d419b7c --- /dev/null +++ b/Utilities/Calculator.app/Resources/dialog_paper_tape_ui.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './dialog_paper_tape.ui' +# +# Created by: PyQt5 UI code generator 5.15.9 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_PaperTape(object): + def setupUi(self, PaperTape): + PaperTape.setObjectName("PaperTape") + PaperTape.resize(244, 251) + self.gridLayout = QtWidgets.QGridLayout(PaperTape) + self.gridLayout.setObjectName("gridLayout") + self.pushButton_clear = QtWidgets.QPushButton(PaperTape) + self.pushButton_clear.setObjectName("pushButton_clear") + self.gridLayout.addWidget(self.pushButton_clear, 1, 1, 1, 1) + self.pushButton_recalculate_totals = QtWidgets.QPushButton(PaperTape) + self.pushButton_recalculate_totals.setEnabled(False) + self.pushButton_recalculate_totals.setObjectName("pushButton_recalculate_totals") + self.gridLayout.addWidget(self.pushButton_recalculate_totals, 1, 0, 1, 1) + self.plainTextEdit = QtWidgets.QPlainTextEdit(PaperTape) + self.plainTextEdit.setObjectName("plainTextEdit") + self.gridLayout.addWidget(self.plainTextEdit, 0, 0, 1, 2) + + self.retranslateUi(PaperTape) + self.pushButton_clear.clicked.connect(self.plainTextEdit.clear) # type: ignore + QtCore.QMetaObject.connectSlotsByName(PaperTape) + + def retranslateUi(self, PaperTape): + _translate = QtCore.QCoreApplication.translate + PaperTape.setWindowTitle(_translate("PaperTape", "Paper Tape")) + self.pushButton_clear.setText(_translate("PaperTape", "Clear")) + self.pushButton_recalculate_totals.setText(_translate("PaperTape", "Recalculate Totals")) diff --git a/Utilities/Calculator.app/Resources/main_window.ui b/Utilities/Calculator.app/Resources/main_window.ui new file mode 100644 index 000000000..5ba6b995b --- /dev/null +++ b/Utilities/Calculator.app/Resources/main_window.ui @@ -0,0 +1,522 @@ + + + MainWindow + + + + 0 + 0 + 286 + 183 + + + + Calculator + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 0 + 0 + + + + false + + + background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(42, 53, 28, 255), stop:0.0299625 rgba(175, 190, 171, 255), stop:0.962547 rgba(156, 187, 151, 255), stop:1 rgba(220, 239, 210, 255)); +color: "#3f3f3f"; + + + QFrame::WinPanel + + + QFrame::Sunken + + + 6 + + + 4 + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 48 + + + + + 16777215 + 48 + + + + + Nimbus Mono PS + 20 + + + + false + + + false + + + + + + + + + + + + + + 0 + 0 + + + + 1 + + + 1 + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 3 + + + 0 + + + 3 + + + 3 + + + 3 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + + + + + + + + 0 + 0 + 286 + 28 + + + + + File + + + + + + + + + + View + + + + + + + + + + + + + + Help + + + + + + Convert + + + + + + + + + + + + + + + + + + + + + + + + + Close + + + Ctrl+W + + + + + false + + + Save Tape As... + + + Ctrl+Shift+S + + + + + false + + + Page Setup... + + + Ctrl+Shift+P + + + + + false + + + Print Tape... + + + Ctrl+P + + + + + Basic + + + Ctrl+1 + + + + + Scientific + + + Ctrl+2 + + + + + false + + + Programmer + + + Ctrl+3 + + + + + Show Paper Tape + + + + + false + + + RPN + + + + + false + + + Precision + + + + + false + + + Recent Conversions + + + + + false + + + Area... + + + + + false + + + Currency... + + + + + false + + + Energy or Work... + + + + + false + + + Temperature... + + + Temperature + + + + + false + + + Length... + + + + + false + + + Weigths and Masses... + + + + + false + + + Speed... + + + + + false + + + Pressure... + + + + + false + + + Power... + + + + + false + + + Volume... + + + + + false + + + Update Currency Exchanges Rates... + + + + + false + + + Last Update: + + + + + About + + + + + + + actionClose_Calculator_Window + triggered() + MainWindow + close() + + + 140 + 92 + + + 140 + 92 + + + + + diff --git a/Utilities/Calculator.app/Resources/main_window_ui.py b/Utilities/Calculator.app/Resources/main_window_ui.py new file mode 100644 index 000000000..615b2aa6d --- /dev/null +++ b/Utilities/Calculator.app/Resources/main_window_ui.py @@ -0,0 +1,268 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './main_window.ui' +# +# Created by: PyQt5 UI code generator 5.15.9 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(286, 183) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget) + self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_2.setSpacing(0) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.widget_19 = QtWidgets.QWidget(self.centralwidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Minimum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.widget_19.sizePolicy().hasHeightForWidth()) + self.widget_19.setSizePolicy(sizePolicy) + self.widget_19.setObjectName("widget_19") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.widget_19) + self.verticalLayout_3.setContentsMargins(3, 3, 3, 3) + self.verticalLayout_3.setSpacing(0) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.frame = QtWidgets.QFrame(self.widget_19) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.frame.sizePolicy().hasHeightForWidth()) + self.frame.setSizePolicy(sizePolicy) + self.frame.setAutoFillBackground(False) + self.frame.setStyleSheet("background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(42, 53, 28, 255), stop:0.0299625 rgba(175, 190, 171, 255), stop:0.962547 rgba(156, 187, 151, 255), stop:1 rgba(220, 239, 210, 255));\n" +"color: \"#3f3f3f\";") + self.frame.setFrameShape(QtWidgets.QFrame.WinPanel) + self.frame.setFrameShadow(QtWidgets.QFrame.Sunken) + self.frame.setLineWidth(6) + self.frame.setMidLineWidth(4) + self.frame.setObjectName("frame") + self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.frame) + self.verticalLayout_4.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_4.setSpacing(0) + self.verticalLayout_4.setObjectName("verticalLayout_4") + self.display = QtWidgets.QLineEdit(self.frame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.display.sizePolicy().hasHeightForWidth()) + self.display.setSizePolicy(sizePolicy) + self.display.setMinimumSize(QtCore.QSize(0, 48)) + self.display.setMaximumSize(QtCore.QSize(16777215, 48)) + font = QtGui.QFont() + font.setFamily("Nimbus Mono PS") + font.setPointSize(20) + self.display.setFont(font) + self.display.setAutoFillBackground(False) + self.display.setFrame(False) + self.display.setObjectName("display") + self.verticalLayout_4.addWidget(self.display) + self.verticalLayout_3.addWidget(self.frame) + self.verticalLayout_2.addWidget(self.widget_19) + self.stacked_widget = QtWidgets.QStackedWidget(self.centralwidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.stacked_widget.sizePolicy().hasHeightForWidth()) + self.stacked_widget.setSizePolicy(sizePolicy) + self.stacked_widget.setLineWidth(1) + self.stacked_widget.setObjectName("stacked_widget") + self.basic = QtWidgets.QWidget() + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.basic.sizePolicy().hasHeightForWidth()) + self.basic.setSizePolicy(sizePolicy) + self.basic.setObjectName("basic") + self.verticalLayout = QtWidgets.QVBoxLayout(self.basic) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setSpacing(0) + self.verticalLayout.setObjectName("verticalLayout") + self.basic_buttons_layout = QtWidgets.QGridLayout() + self.basic_buttons_layout.setContentsMargins(3, 0, 3, 3) + self.basic_buttons_layout.setSpacing(3) + self.basic_buttons_layout.setObjectName("basic_buttons_layout") + self.verticalLayout.addLayout(self.basic_buttons_layout) + self.stacked_widget.addWidget(self.basic) + self.scientific = QtWidgets.QWidget() + self.scientific.setObjectName("scientific") + self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.scientific) + self.verticalLayout_5.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_5.setSpacing(0) + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.scientific_buttons_layout = QtWidgets.QGridLayout() + self.scientific_buttons_layout.setContentsMargins(3, -1, 3, 3) + self.scientific_buttons_layout.setSpacing(3) + self.scientific_buttons_layout.setObjectName("scientific_buttons_layout") + self.verticalLayout_5.addLayout(self.scientific_buttons_layout) + self.stacked_widget.addWidget(self.scientific) + self.verticalLayout_2.addWidget(self.stacked_widget) + self.verticalLayout_2.setStretch(1, 1) + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 286, 28)) + self.menubar.setObjectName("menubar") + self.menuFile = QtWidgets.QMenu(self.menubar) + self.menuFile.setObjectName("menuFile") + self.menuView = QtWidgets.QMenu(self.menubar) + self.menuView.setObjectName("menuView") + self.menuHelp = QtWidgets.QMenu(self.menubar) + self.menuHelp.setObjectName("menuHelp") + self.menuConvert = QtWidgets.QMenu(self.menubar) + self.menuConvert.setObjectName("menuConvert") + MainWindow.setMenuBar(self.menubar) + self.actionClose_Calculator_Window = QtWidgets.QAction(MainWindow) + self.actionClose_Calculator_Window.setObjectName("actionClose_Calculator_Window") + self.actionSave_Tape_As = QtWidgets.QAction(MainWindow) + self.actionSave_Tape_As.setEnabled(False) + self.actionSave_Tape_As.setObjectName("actionSave_Tape_As") + self.actionPage_Setup = QtWidgets.QAction(MainWindow) + self.actionPage_Setup.setEnabled(False) + self.actionPage_Setup.setObjectName("actionPage_Setup") + self.actionPrint_Tape = QtWidgets.QAction(MainWindow) + self.actionPrint_Tape.setEnabled(False) + self.actionPrint_Tape.setObjectName("actionPrint_Tape") + self.actionView_Basic = QtWidgets.QAction(MainWindow) + self.actionView_Basic.setObjectName("actionView_Basic") + self.actionView_Scientific = QtWidgets.QAction(MainWindow) + self.actionView_Scientific.setObjectName("actionView_Scientific") + self.actionView_Programation = QtWidgets.QAction(MainWindow) + self.actionView_Programation.setEnabled(False) + self.actionView_Programation.setObjectName("actionView_Programation") + self.actionView_Show_Paper_Tape = QtWidgets.QAction(MainWindow) + self.actionView_Show_Paper_Tape.setObjectName("actionView_Show_Paper_Tape") + self.actionView_RPN = QtWidgets.QAction(MainWindow) + self.actionView_RPN.setEnabled(False) + self.actionView_RPN.setObjectName("actionView_RPN") + self.actionView_Precision = QtWidgets.QAction(MainWindow) + self.actionView_Precision.setEnabled(False) + self.actionView_Precision.setObjectName("actionView_Precision") + self.actionConvert_Recent_Convertions = QtWidgets.QAction(MainWindow) + self.actionConvert_Recent_Convertions.setEnabled(False) + self.actionConvert_Recent_Convertions.setObjectName("actionConvert_Recent_Convertions") + self.actionConvert_Area = QtWidgets.QAction(MainWindow) + self.actionConvert_Area.setEnabled(False) + self.actionConvert_Area.setObjectName("actionConvert_Area") + self.actionConvert_Currency = QtWidgets.QAction(MainWindow) + self.actionConvert_Currency.setEnabled(False) + self.actionConvert_Currency.setObjectName("actionConvert_Currency") + self.actionConvert_Energy_or_Work = QtWidgets.QAction(MainWindow) + self.actionConvert_Energy_or_Work.setEnabled(False) + self.actionConvert_Energy_or_Work.setObjectName("actionConvert_Energy_or_Work") + self.actionConvert_Temperature = QtWidgets.QAction(MainWindow) + self.actionConvert_Temperature.setEnabled(False) + self.actionConvert_Temperature.setObjectName("actionConvert_Temperature") + self.actionConvert_Length = QtWidgets.QAction(MainWindow) + self.actionConvert_Length.setEnabled(False) + self.actionConvert_Length.setObjectName("actionConvert_Length") + self.actionConvert_Weigths_and_Masses = QtWidgets.QAction(MainWindow) + self.actionConvert_Weigths_and_Masses.setEnabled(False) + self.actionConvert_Weigths_and_Masses.setObjectName("actionConvert_Weigths_and_Masses") + self.actionConvert_Speed = QtWidgets.QAction(MainWindow) + self.actionConvert_Speed.setEnabled(False) + self.actionConvert_Speed.setObjectName("actionConvert_Speed") + self.actionConvert_Pressure = QtWidgets.QAction(MainWindow) + self.actionConvert_Pressure.setEnabled(False) + self.actionConvert_Pressure.setObjectName("actionConvert_Pressure") + self.actionConvert_Power = QtWidgets.QAction(MainWindow) + self.actionConvert_Power.setEnabled(False) + self.actionConvert_Power.setObjectName("actionConvert_Power") + self.actionConvert_Volume = QtWidgets.QAction(MainWindow) + self.actionConvert_Volume.setEnabled(False) + self.actionConvert_Volume.setObjectName("actionConvert_Volume") + self.actionConvert_Update_currencies_rate = QtWidgets.QAction(MainWindow) + self.actionConvert_Update_currencies_rate.setEnabled(False) + self.actionConvert_Update_currencies_rate.setObjectName("actionConvert_Update_currencies_rate") + self.actionConvert_Last_Update = QtWidgets.QAction(MainWindow) + self.actionConvert_Last_Update.setEnabled(False) + self.actionConvert_Last_Update.setObjectName("actionConvert_Last_Update") + self.ActionMenuHelpAbout = QtWidgets.QAction(MainWindow) + self.ActionMenuHelpAbout.setObjectName("ActionMenuHelpAbout") + self.menuFile.addAction(self.actionClose_Calculator_Window) + self.menuFile.addAction(self.actionSave_Tape_As) + self.menuFile.addSeparator() + self.menuFile.addAction(self.actionPage_Setup) + self.menuFile.addAction(self.actionPrint_Tape) + self.menuView.addAction(self.actionView_Basic) + self.menuView.addAction(self.actionView_Scientific) + self.menuView.addAction(self.actionView_Programation) + self.menuView.addSeparator() + self.menuView.addAction(self.actionView_Show_Paper_Tape) + self.menuView.addSeparator() + self.menuView.addAction(self.actionView_RPN) + self.menuView.addSeparator() + self.menuView.addAction(self.actionView_Precision) + self.menuHelp.addAction(self.ActionMenuHelpAbout) + self.menuConvert.addAction(self.actionConvert_Recent_Convertions) + self.menuConvert.addSeparator() + self.menuConvert.addAction(self.actionConvert_Area) + self.menuConvert.addAction(self.actionConvert_Currency) + self.menuConvert.addAction(self.actionConvert_Energy_or_Work) + self.menuConvert.addAction(self.actionConvert_Temperature) + self.menuConvert.addAction(self.actionConvert_Length) + self.menuConvert.addAction(self.actionConvert_Weigths_and_Masses) + self.menuConvert.addAction(self.actionConvert_Speed) + self.menuConvert.addAction(self.actionConvert_Pressure) + self.menuConvert.addAction(self.actionConvert_Power) + self.menuConvert.addAction(self.actionConvert_Volume) + self.menuConvert.addSeparator() + self.menuConvert.addAction(self.actionConvert_Update_currencies_rate) + self.menuConvert.addAction(self.actionConvert_Last_Update) + self.menubar.addAction(self.menuFile.menuAction()) + self.menubar.addAction(self.menuView.menuAction()) + self.menubar.addAction(self.menuConvert.menuAction()) + self.menubar.addAction(self.menuHelp.menuAction()) + + self.retranslateUi(MainWindow) + self.stacked_widget.setCurrentIndex(1) + self.actionClose_Calculator_Window.triggered.connect(MainWindow.close) # type: ignore + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "Calculator")) + self.menuFile.setTitle(_translate("MainWindow", "File")) + self.menuView.setTitle(_translate("MainWindow", "View")) + self.menuHelp.setTitle(_translate("MainWindow", "Help")) + self.menuConvert.setTitle(_translate("MainWindow", "Convert")) + self.actionClose_Calculator_Window.setText(_translate("MainWindow", "Close")) + self.actionClose_Calculator_Window.setShortcut(_translate("MainWindow", "Ctrl+W")) + self.actionSave_Tape_As.setText(_translate("MainWindow", "Save Tape As...")) + self.actionSave_Tape_As.setShortcut(_translate("MainWindow", "Ctrl+Shift+S")) + self.actionPage_Setup.setText(_translate("MainWindow", "Page Setup...")) + self.actionPage_Setup.setShortcut(_translate("MainWindow", "Ctrl+Shift+P")) + self.actionPrint_Tape.setText(_translate("MainWindow", "Print Tape...")) + self.actionPrint_Tape.setShortcut(_translate("MainWindow", "Ctrl+P")) + self.actionView_Basic.setText(_translate("MainWindow", "Basic")) + self.actionView_Basic.setShortcut(_translate("MainWindow", "Ctrl+1")) + self.actionView_Scientific.setText(_translate("MainWindow", "Scientific")) + self.actionView_Scientific.setShortcut(_translate("MainWindow", "Ctrl+2")) + self.actionView_Programation.setText(_translate("MainWindow", "Programmer")) + self.actionView_Programation.setShortcut(_translate("MainWindow", "Ctrl+3")) + self.actionView_Show_Paper_Tape.setText(_translate("MainWindow", "Show Paper Tape")) + self.actionView_RPN.setText(_translate("MainWindow", "RPN")) + self.actionView_Precision.setText(_translate("MainWindow", "Precision")) + self.actionConvert_Recent_Convertions.setText(_translate("MainWindow", "Recent Conversions")) + self.actionConvert_Area.setText(_translate("MainWindow", "Area...")) + self.actionConvert_Currency.setText(_translate("MainWindow", "Currency...")) + self.actionConvert_Energy_or_Work.setText(_translate("MainWindow", "Energy or Work...")) + self.actionConvert_Temperature.setText(_translate("MainWindow", "Temperature...")) + self.actionConvert_Temperature.setToolTip(_translate("MainWindow", "Temperature")) + self.actionConvert_Length.setText(_translate("MainWindow", "Length...")) + self.actionConvert_Weigths_and_Masses.setText(_translate("MainWindow", "Weigths and Masses...")) + self.actionConvert_Speed.setText(_translate("MainWindow", "Speed...")) + self.actionConvert_Pressure.setText(_translate("MainWindow", "Pressure...")) + self.actionConvert_Power.setText(_translate("MainWindow", "Power...")) + self.actionConvert_Volume.setText(_translate("MainWindow", "Volume...")) + self.actionConvert_Update_currencies_rate.setText(_translate("MainWindow", "Update Currency Exchanges Rates...")) + self.actionConvert_Last_Update.setText(_translate("MainWindow", "Last Update:")) + self.ActionMenuHelpAbout.setText(_translate("MainWindow", "About")) diff --git a/Utilities/Calculator.app/Resources/widget_calculator_button.py b/Utilities/Calculator.app/Resources/widget_calculator_button.py new file mode 100644 index 000000000..ff13e7c5a --- /dev/null +++ b/Utilities/Calculator.app/Resources/widget_calculator_button.py @@ -0,0 +1,244 @@ +#!/usr/bin/env python3 + +from PyQt5.QtCore import Qt, pyqtSignal, QRectF, QSize +from PyQt5.QtGui import ( + QPaintEvent, + QPainter, + QPen, + QColor, + QFontMetrics, + QFont, + QPainterPath, + QLinearGradient, +) +from PyQt5.QtWidgets import QAbstractButton, QSizePolicy + + +class CalculatorButton(QAbstractButton): + """ + Custom Qt Widget to show a colored button color. + """ + + colorChanged = pyqtSignal(object) + clicked = pyqtSignal(bool) + + def __init__(self, *args, **kwargs): + super(CalculatorButton, self).__init__(*args, **kwargs) + + self._text = kwargs.get("text") or None + self._color = None + self._font_color = None + self._font_size = None + self._border_color = None + self._border_size = None + self._border_pen = None + + self.__mouse_checked = False + self.__mouse_over = False + + self.font = None + self.font_metric = None + self.painter = None + + self.color_disable = QColor(Qt.lightGray) + self.font_color_disable = QColor(Qt.lightGray) + self.font_shadow_color_disable = QColor(Qt.darkGray) + self.border_color_disable = QColor(Qt.lightGray) + + self.setupUI() + + def setupUI(self): + self.setSizePolicy(QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)) + self.font = QFont("Nimbus Sans", 13) + self.font_metric = QFontMetrics(self.font) + self.setBorderColor() + self.setBorderSize(2) + self.setBorderPen(QPen(self.border_color(), self.border_size())) + self.setMouseTracking(True) + self.painter = QPainter() + + def minimumSizeHint(self): + return QSize( + self.font_metric.width(self.text()) + (self.border_size() * 2), + self.font_metric.height() + (self.border_size() * 2), + ) + + def paintEvent(self, e: QPaintEvent) -> None: + self.painter.begin(self) + self.draw_square(event=e) + self.draw_text() + self.painter.end() + + def draw_text(self): + if self.text() not in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]: + if self.isEnabled(): + if self.font_color(): + self.painter.setPen(QPen(self.font_color(), 1, Qt.SolidLine)) + else: + self.painter.setPen(QPen(Qt.black, 1, Qt.SolidLine)) + else: + self.painter.setPen(QPen(Qt.lightGray, 1, Qt.SolidLine)) + + self.painter.setFont(self.font) + self.painter.drawText( + (self.width() / 2) - (self.font_metric.width(self.text()) / 2), + (self.height() / 2) + self.font_metric.height() / 4, + self.text(), + ) + + def draw_square(self, event): + self.painter.setRenderHint(QPainter.Antialiasing) + # Create the path + path = QPainterPath() + if self.isEnabled(): + if not self.__mouse_checked: + if self.__mouse_over: + gradient = QLinearGradient(0, 0, 0, self.height()) + gradient.setColorAt(0.0, self.color().darker(110)) + gradient.setColorAt(0.1, self.color().lighter(180)) + gradient.setColorAt(0.15, self.color().lighter(140)) + gradient.setColorAt(0.40, self.color().lighter(130)) + gradient.setColorAt(0.45, self.color()) + gradient.setColorAt(0.51, self.color().darker(110)) + gradient.setColorAt(0.9, self.color().darker(120)) + gradient.setColorAt(0.95, self.color().darker(180)) + gradient.setColorAt(1.0, self.color().darker(160)) + else: + gradient = QLinearGradient(0, 0, 0, self.height()) + gradient.setColorAt(0.0, self.color().darker(110)) + gradient.setColorAt(0.1, self.color().lighter(190)) + gradient.setColorAt(0.15, self.color().lighter(130)) + gradient.setColorAt(0.40, self.color().lighter(120)) + gradient.setColorAt(0.45, self.color()) + gradient.setColorAt(0.51, self.color().darker(120)) + gradient.setColorAt(0.9, self.color().darker(130)) + gradient.setColorAt(0.95, self.color().darker(190)) + gradient.setColorAt(1.0, self.color().darker(160)) + else: + gradient = QLinearGradient(0, 0, 0, self.height()) + gradient.setColorAt(0.0, self.color().darker(110)) + gradient.setColorAt(0.1, self.color().darker(120)) + gradient.setColorAt(0.15, self.color().darker(110)) + gradient.setColorAt(0.40, self.color().darker(105)) + gradient.setColorAt(0.5, self.color()) + gradient.setColorAt(0.51, self.color().lighter(105)) + gradient.setColorAt(0.9, self.color().lighter(110)) + gradient.setColorAt(0.95, self.color().lighter(170)) + gradient.setColorAt(1.0, self.color().lighter(140)) + else: + gradient = QLinearGradient(0, 0, 0, self.height()) + gradient.setColorAt(0.0, self.color().darker(110)) + gradient.setColorAt(0.1, self.color().darker(120)) + gradient.setColorAt(0.15, self.color().darker(110)) + gradient.setColorAt(0.40, self.color().darker(105)) + gradient.setColorAt(0.5, self.color()) + gradient.setColorAt(0.51, self.color().lighter(105)) + gradient.setColorAt(0.9, self.color().lighter(110)) + gradient.setColorAt(0.95, self.color().lighter(170)) + gradient.setColorAt(1.0, self.color().lighter(140)) + + # Set painter colors to given values. + self.painter.setPen(self.border_pen()) + self.painter.setBrush(gradient) + + rect = QRectF(event.rect()) + # Slighly shrink dimensions to account for self.border_size(). + rect.adjust(self.border_size() / 2, self.border_size() / 2, -self.border_size() / 2, -self.border_size() / 2) + + # Add the rect to path. + path.addRoundedRect(rect, 12, 12) + self.painter.setClipPath(path) + + # Fill shape, draw the border and center the text. + self.painter.fillPath(path, self.painter.brush()) + self.painter.strokePath(path, self.painter.pen()) + + # Text is use a drop shadow + if self.isEnabled(): + self.painter.setPen(QPen(Qt.black, 1, Qt.SolidLine)) + else: + self.painter.setPen(QPen(self.font_shadow_color_disable, 1, Qt.SolidLine)) + self.painter.setFont(self.font) + self.painter.drawText(rect, Qt.AlignCenter, self.text()) + + def setText(self, text): + if text != self._text: + self._text = text + # self.textChanged.emit(self._text) + + def text(self): + return self._text + + def setColor(self, color): + if color != self._color: + self._color = color + # self.colorChanged.emit(self._text) + + def color(self): + if self.isEnabled(): + return self._color + else: + return self.color_disable + + def setFontColor(self, color): + if color is None: + color = Qt.black + if color != self._font_color: + self._font_color = color + # self.fontChanged.emit(self._text) + + def font_color(self): + if self.isEnabled(): + return self._font_color + else: + return self.font_color_disable + + def setBorderColor(self, color=None): + if color is None: + color = QColor("#1e1e1f") + if color != self._border_color: + self._border_color = color + + def border_color(self): + if self.isEnabled(): + return self._border_color + else: + return self.border_color_disable + + def setBorderSize(self, size): + if size is None: + size = 2 + if type(size) != int: + raise TypeError("'size' must be int type or None") + if size != self._border_size: + self._border_size = size + + def border_size(self): + return self._border_size + + def setBorderPen(self, pen): + if pen is None: + pen = QPen(self.border_color(), self.border_size()) + if not isinstance(pen, QPen): + raise TypeError("'pen' must be QPen instance or None") + if pen != self._border_pen: + self._border_pen = pen + + def border_pen(self): + return self._border_pen + + def mousePressEvent(self, event): + self.__mouse_checked = True + self.update() + + def mouseReleaseEvent(self, event): + self.__mouse_checked = False + # noinspection PyUnresolvedReferences + self.clicked.emit(True) + self.update() + + def mouseMoveEvent(self, event): + self.__mouse_over = True + + def leaveEvent(self, event): + self.__mouse_over = False