MADCALC V1.1

Совершенно неожиданно, нежданно и негаданно решил написать свой собственный калькулятор с блэкджэком и... симпатичным на мой вкус вырвиглазным веселеньким дизайном. Из функций приятно, что операнды складываются подряд длинной цепочкой, один за другим, а не то чтобы sum1+sum2=result и все. Цифры набираются как с кнопок GUI, так и с клавиатуры. Имеется возможность взять квадратный корень от x. Работа с процентами реализована даже лучше чем в стандартном калькуляторе Windows 10. Например, если мы попробуем в этой дешевой микрософтовской поделке вычислить выражение 110*25%, то он воспримет его как 110*0.25 и при нажатии кнопки = покажет нам результат 27,5. Мой же калькулятор воспримет то же выражение 110*25% как 110*(110*25/100)=100*27.5=3025, что, собственно и требовалось. Вероятно, следующая версия программы будет уметь решать уравнения типа (2х–1)^4–25(2х–1)^2+144=0 прям вот так вот из строки. Еще хочется добавить некоторую поддержку комплексных чисел.
Как обычно, я не буду здесь сильно расписывать процесс создания, отмечу только что GUI были прорисованы ручками в Designer-e. Конечно, в Qt имеются способы программно генерировать расположение кнопок и прочих элементов, однако как по мне, так лучше задать их ui файлом, что наглядней и понятней. На счет остального - код обильно сопровождается комментариями. Старался по мере разумения как для себя. Да почему "как"? Для себя же любимого и сделано, с целью учебы.
madCalc.pro
QT       += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
    about.cpp \
    main.cpp \
    madcalc.cpp
HEADERS += \
    about.h \
    madcalc.h
FORMS += \
    about.ui \
    madcalc.ui
TRANSLATIONS += \
    madCalc_ru_RU.ts
RC_FILE = icon.rc #иконка приложения для для Windows
RESOURCES += res.qrc #Все остальные ресурсы, в том числе иконка приложения для Linux
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
about.h
#ifndef ABOUT_H
#define ABOUT_H
#include 
namespace Ui {
class about;
}
class about : public QWidget
{
    Q_OBJECT
public:
    explicit about(QWidget *parent = nullptr);
    ~about();
private slots:
    void on_btnOk_clicked();
private:
    Ui::about *ui;
};
#endif // ABOUT_H
madcalc.h
#ifndef MADCALC_H
#define MADCALC_H
#include "QMainWindow"
//Далее инклюды для keyBinding
#include "QWidget"
#include "QGridLayout"
#include "QKeyEvent"
#include "QDialog"
//далее про структуру программы в целом
QT_BEGIN_NAMESPACE
namespace Ui { class madCalc; }
QT_END_NAMESPACE
class madCalc : public QMainWindow
{
    Q_OBJECT
public:
    madCalc(QWidget *parent = nullptr);
    ~madCalc();
    // Тут про GUI
private:
    Ui::madCalc *ui;
private:
    void abortOperation();    //Объявляем функцию отмены операции (в связи с ошибкой)
    void StatusBar();         //Объявляем функцию статус-бара, чтобы выводить туда сообщение. 
                              //В этом случае там будет про сумму, записанную в памяти.
    bool calculate (double rightOperand, const QString &pendingOperator);
    double sumInMemory = 0.0; //Переменная для реализации кнопок управления памятью.
    double num = 0.0;         //Переменная для хранения первого операнда
    QString pendingAdditiveOperator; //Содержит знак выполняемого оператора
    QString pendingMultiplicativeOperator; //Аналогично для мутипликативных операторов
    //на самом деле, наверно, для этих целей нет смысла создавать два отдельных класса, а то получается какая-то нелепая куча говнокода,
    //но у нас учебная программа, поэтому сойдет и так
    bool waitingForOperand; //переменная двоичного типа (false или true) для того чтобы определиться, ждем мы следующего операнда или нет
    QString clickedOperator;
    double PER; //Вот эта переменная нужна для функции void Percent(); чтобы вычислять проценты
    double digitValue;
protected:
   virtual void keyPressEvent(QKeyEvent *event); //Здесь заканчивается конструкция которая позволяет вводить цифры с клавиатуры
private slots:
    void digitClicked();                    //Обработчик нажатия цифровых кнопок из GUI
    void unaryOperatorClicked();            //Унарные операторы
    void additiveOperatorClicked();         // Операторы сложения/вычитания
    void multiplicativeOperatorClicked();   // Операторы умножения/деления
    void equalClicked();                    //Функция знака =
    void on_btnDot_clicked();               //Это если нажать "точку" Dot(".")
    void funcPM();                          //Умножает число на lcd1 на -1, нужно для кнопки +/-
    void Backspase();                       //Функция удаления последнего знака
    //Блок управления памятью
    void clearMemory();  //Очистить память
    void readMemory();   //Прочесть память и вывести на экран
    void subtMemory();   //Записать в память
    void addToMemory();  //Прибавить к записанному в памяти числу содержимое экрана lcd1
    void Operator();         //Приемник для кнопок операторов, ловит их значение и передает далее по структуре.
    void OperatorSender();   // Сюда. Это нужно чтобы объединить приемник сигналов с GUI и с клавиатуры
    void Digit();            //Обработчик нажатия всех кнопок, как с GUI так и с клавиатуры
    void Percent();          //Объявляем функцию вычисления процентов
    void DEBUG();            //Функция DEBUG выводит в статус бар содержание основных переменных, из названия понятно что это для отладки
    void ClearAll();         //Функция сброса вычислений
    //Далее все остальное
    void on_btnCE_clicked(); //Функция сброса текущего операнда
    void on_btnAb_clicked(); //Функция вызова формы about
};
#endif // MADCALC_H
about.cpp
#include "about.h"
#include "ui_about.h"
about::about(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::about)
{
    ui->setupUi(this);
}
about::~about()
{
    delete ui;
}
void about::on_btnOk_clicked()
{
    QWidget::close();
}
madcalc.cpp
#include "madcalc.h"
#include "about.h"
#include "ui_madcalc.h"
#include "global.h"
#include "qmath.h" //Библиотека для поддержки математических операций, таких как sqrt и pow
#include "QDialog"
#include "button.h"
madCalc::madCalc(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::madCalc)
{
    ui->setupUi(this);
    this->statusBar()->setSizeGripEnabled(false); //Данная строчка отключает ресайз формы.
    //Конечно, форме можно задать фиксированный размер через дизайнер,
    //но этот способ лучше, так как отключает треугольничек в правом нижнем углу
    //Далее подключаем цифровые кнопки
    connect(ui->btn0, SIGNAL(clicked()), this, SLOT(digitClicked()));
    connect(ui->btn1, SIGNAL(clicked()), this, SLOT(digitClicked()));
    connect(ui->btn2, SIGNAL(clicked()), this, SLOT(digitClicked()));
    connect(ui->btn3, SIGNAL(clicked()), this, SLOT(digitClicked()));
    connect(ui->btn4, SIGNAL(clicked()), this, SLOT(digitClicked()));
    connect(ui->btn5, SIGNAL(clicked()), this, SLOT(digitClicked()));
    connect(ui->btn6, SIGNAL(clicked()), this, SLOT(digitClicked()));
    connect(ui->btn7, SIGNAL(clicked()), this, SLOT(digitClicked()));
    connect(ui->btn8, SIGNAL(clicked()), this, SLOT(digitClicked()));
    connect(ui->btn9, SIGNAL(clicked()), this, SLOT(digitClicked()));
    //Кнопки унарных операций
    connect(ui->btn1x, SIGNAL(clicked()), this,  SLOT(unaryOperatorClicked()));  //Кнопка btnSin вычисляет 1/x
    connect(ui->btnRoot, SIGNAL(clicked()), this, SLOT(unaryOperatorClicked()));  //Кнопка для вычисления корня
    //Операции сложения (+ и -)
    connect(ui->btnPlus, SIGNAL(clicked()), this, SLOT(Operator()));
    connect(ui->btnMinus, SIGNAL(clicked()), this, SLOT(Operator()));
    //Мультипликативные операции
    connect(ui->btnDivide, SIGNAL(clicked()), this, SLOT(Operator()));
    connect(ui->btnMultiply, SIGNAL(clicked()), this, SLOT(Operator()));
    //Кнопка +/-
    connect(ui->btnPM, SIGNAL(clicked()), this, SLOT(funcPM()));
    //Кнопки сброса
    connect(ui->btnC, SIGNAL(clicked()), this, SLOT(ClearAll())); //Кнопка С
    //connect(ui->btnCE, SIGNAL(clicked()), this, SLOT(сlear()));//Кнопка CE
    //Кнопка "." для цифрового ввода
    connect(ui->btnDot, SIGNAL(clicked()), this, SLOT(on_btnDot_clicked()));
    //Кнопка которая удалет последний знак в строке lcd1
    connect(ui->btnBackspace, SIGNAL(clicked()), this, SLOT(Backspase()));
    //Кнопка =
    connect(ui->btnEqual, SIGNAL(clicked()), this, SLOT(equalClicked()));
    //Кнопки управления памятью
    connect(ui->btnMC, SIGNAL(clicked()), this, SLOT(clearMemory()));
    connect(ui->btnMR, SIGNAL(clicked()), this, SLOT(readMemory()));
    connect(ui->btnMP, SIGNAL(clicked()), this, SLOT(addToMemory()));
    connect(ui->btnMM, SIGNAL(clicked()), this, SLOT(subtMemory()));
    //Кнопка вычисления процентов
    connect(ui->btnPer, SIGNAL(clicked()), this,  SLOT(Percent()));
    StatusBar(); //Вызываем функцию StatsuBar()
}
madCalc::~madCalc()
{
    delete ui;
}
void madCalc::StatusBar()
{
    //Выводим в статус бар сообщение о содержимом памяти, но чтобы это получилось, для начала переводим double в qstring
    QString valueAsString = QString::number(sumInMemory);
    statusBar()->showMessage("Число в памяти: " + valueAsString);
}
//Кнопка backspase
void madCalc::Backspase()
{
    double operand = ui->lcd1->text().toDouble(); //Переписываем содержимое экрана в переменную operand типа double
    if (operand == 0){                            //Если операнд на экране равен нулю, то
        ui->lcd1->setText("0");                   //Оставляем все как есть
    }
    else {                                        //В противном случае подрезаем его справа
    QString str;
    str = ui->lcd1->text();
    str.resize(str.size() - 1);                   //На единицу
    ui->lcd1->setText(str);                       //И возвращаем результат на экран
    }
    int sizeOfText = ui->lcd1->text().size();     //Здесь мы объявляем переменную sizeOfTextl которая принимает кол-во символов в строке
    if (sizeOfText < 1) {                         //И запускаем проверку. Если на экране остался меньше 1 символа, то
        ui->lcd1->setText("0");                   //Записываем на экран 0
    }
    waitingForOperand = true;                     //А вот без этой строчки при дальнейшем наборе циферек будет нечто типа 0123 и т. п.
}
//Функции управления памятью
//Кнопка MR - число из ячейки памяти выводится на дисплей.
void madCalc::readMemory() {
 ui->lcd1->setText(QString::number(sumInMemory));
 waitingForOperand=false; //После того как число из памяти будет выведено на экран, к нему можно будет дописать еще какие-нибудь циферьки
}
//MC стирает данные из ячейки памяти.
void madCalc::clearMemory() {
 sumInMemory = 0.0;
 StatusBar();
 waitingForOperand=false;
}
//M+ прибавляет к числу из памяти число, отображенное на дисплее и записывает результат в память вместо предыдущего
void madCalc::addToMemory() {
 equalClicked();
 sumInMemory += ui->lcd1->text().toDouble();
 StatusBar();
 waitingForOperand=false;
}
//MS - сохраняет в память число, отображенное на дисплее
void madCalc::subtMemory() {
 equalClicked();
 sumInMemory -= ui->lcd1->text().toDouble();
 StatusBar();
 waitingForOperand=false;
 }
//Функция кнопки +/-
void madCalc::funcPM()
{
    QPushButton *button = (QPushButton *)sender();
        double all_numbers;
        QString new_label;
        if(button->text() == "+/-"){ //Если текст кнопки содержит "+/-", то выполняем следующее:
            all_numbers = (ui->lcd1->text().toDouble()); //Переводим содержимое lcd1 в формат Double и записываем в переменную all_numbers
            all_numbers = all_numbers * -1; //Умножаем all_numbers на -1
            new_label = QString::number(all_numbers, 'g', 13); //Ограничиваем строку в 13 знаков
            ui->lcd1->setText(new_label); // выводим сообщение в lcd1
        }
}
//Сообщение об ошибке в случае ошибки (например, деление на 0)
void madCalc::abortOperation() {
    //Обнуляем все наши переменные
    num = 0.0;
   // factorSoFar = 0.0;
    pendingAdditiveOperator.clear();
    pendingMultiplicativeOperator.clear();
    //Ждем следующего операнда
    waitingForOperand = true;
    //Выводим на экран сообщение об ошибке
 ui->lcd1->setText(tr("ERROR"));
}
//Ввод с клавиатуры
void madCalc::keyPressEvent(QKeyEvent *event) {
 int key=event->key();                   //event->key() - целочисленный код клавиши
 QString str = QString(QChar(key));
 if (key>=Qt::Key_0 && key<=Qt::Key_9) { //Цифровые клавиши 0..9
 digitValue = str.toDouble();
 Digit();
 }
 else if (key==Qt::Key_Backspace) { //BackSpace стирает символ
  Backspase();
 }
 else if (key==Qt::Key_Delete) { //Delete стирает всё
  ClearAll();
 }
 else if (key==Qt::Key_Plus || key==Qt::Key_Minus || key==Qt::Key_Asterisk || key==Qt::Key_Slash ) {
 clickedOperator = str;
 OperatorSender();
 }
 else if (key==Qt::Key_Enter) {
 equalClicked();
 }
 else if (key==Qt::Key_Period) {
  if(!(ui->lcd1->text().contains('.'))) //Вот эта фигня проверяет lcd1 на содержание точки "."
  ui->lcd1->setText(ui->lcd1->text() + ".");
 }
}
//Конец конструкции ввода с клавиатуры
#ifdef OLD
//Цифровой ввод из GUI ввод старой модификации
void madCalc::digitClicked() {
    QPushButton *button = (QPushButton *)sender();
        double all_numbers;
        QString new_label;
            if(ui->lcd1->text().contains(".") && button->text() == "0") { //если текст на экране содержит точку и нажатая кнопка это ноль, тогда можно писать дальше (это типа для нуля после нуля после точки)
            new_label = ui->lcd1->text() + button->text();
        } else {
        all_numbers = (ui->lcd1->text() + button->text()).toDouble();
        new_label = QString::number(all_numbers, 'g', 13);
        }
        ui->lcd1->setText(new_label);
        statusBar()->showMessage(new_label);
}
#else
//Данная функция принимает сигналы от кнопок из GUI а затем записывает их содержание в digitValue и вызывает метод Digit()
void madCalc::digitClicked() {
 QPushButton *button = (QPushButton *)sender();
 digitValue = button->text().toInt();
 Digit();
}
#endif
//Метод Digit()
void madCalc::Digit(){
 if (ui->lcd1->text() == "0" && digitValue == 0.0) return;
 if (waitingForOperand) {
  ui->lcd1->clear();
  waitingForOperand = false;
 }
 ui->lcd1->setText(ui->lcd1->text() + QString::number(digitValue));
}
#ifdef OLD //Условия компиляции для ненужного кода, который не будет отображаться пока выше не будет объявлена переменная OLD
//Кнопка Dot(точка("."))
void madCalc::on_btnDot_clicked()
{
    if(!(ui->lcd1->text().contains('.'))) //Вот эта фигня проверяет lcd1 на содержание точки "."
    //Если точки нет, тогда выполняется следующая строка, а если нет, то нет.
    ui->lcd1->setText(ui->lcd1->text() + ".");
    waitingForOperand = false;
}
#else
//Актуальная конструкция для точки Dot(".")
void madCalc::on_btnDot_clicked() {
 if (waitingForOperand) ui->lcd1->setText("0");
 if (!ui->lcd1->text().contains(".")) ui->lcd1->setText(ui->lcd1->text() + tr("."));
 waitingForOperand = false;
}
#endif
//Вычисление процента
void madCalc::Percent()
{
 double operand = ui->lcd1->text().toDouble(); // берем операнд c экрана
 PER = num*operand/100;  // Вычисляем процент num от operand и сохраняем это в переменную PER
 ui->lcd1->setText(QString::number(PER));      // Выводим результат на экран
}
//Унарные операции
void madCalc::unaryOperatorClicked() {
 QPushButton *button = (QPushButton *)sender(); //Слушаем sender
 clickedOperator = button->text();              //Пишем в clickedOperator знак, прочитанный с кнопки
 double operand = ui->lcd1->text().toDouble();  //Записываем в operand содержимое lcd1
 double result = 0.0;                           //объявляем переменную result, равную 0
 if (clickedOperator == tr("√")) { //Запускаем проверку: если корень извлекается из отрицательного числа, запускаем abortOperation();
  if (operand < 0.0) { // Заострить внимание на этом моменте, в дальнейшем я бы хотел запилить поддержку комплексных чисел!
   abortOperation();
   return;
  }
  result = sqrt(operand); //А вообще вычисляем корень из операнда
 }
 else if (clickedOperator == tr("1/x")) { //следующая функция занимается делением единицы на операнд
  if (operand == 0.0) {
   abortOperation();
   return;
  }
  result = 1.0 / operand;
 }
 ui->lcd1->setText(QString::number(result)); //Выводим result на экран
 waitingForOperand = true;                   //ждем следующего операнда
}
//Заканчиваем с унарными операторами
//Здесь мы как обычно слушаем sender и считываем что написано на кнопках из GUI
void madCalc::Operator(){
    QPushButton *button = (QPushButton *)sender();
    clickedOperator = button->text(); //А затем записываем полученные значения в в clickedOperator
    OperatorSender(); //И вызываем функцию OperatorSender
}
//Данная функция определяет что делать с операторами сложения/вычитания и умножения/деления
void madCalc::OperatorSender(){
    if (clickedOperator == "+"||"-")      {additiveOperatorClicked();} //Для операторов сложения вызываем это
    else if (clickedOperator == "*"||"/") {multiplicativeOperatorClicked();} //Для мультипликативных это
}
//Тут происходит неведомая фигня... Попробуй, разберись )
void madCalc::additiveOperatorClicked() {
 double operand = ui->lcd1->text().toDouble(); //Объявляем operand, содержащий циферьки с lcd1
 if (!pendingMultiplicativeOperator.isEmpty()) { //Запускаем проверку: если переменная оператора не пуста, то
  if (!calculate(operand, pendingMultiplicativeOperator)) { // запускаем проверку: если calculate не содержит operand и оператор, то
   abortOperation(); //Функция аборта
   return;
  }
  ui->lcd1->setText(QString::number(num)); //записываем на экран содержимое переменной num
  operand = num; //приравниваем operand к num
  num = 0.0;     //А теперь num к нулю
  pendingMultiplicativeOperator.clear(); //И очищаем переменную, содержащую знак текущего оператора
 }
 if (!pendingAdditiveOperator.isEmpty()) { //Если эта переменная не пуста, то аборт
  if (!calculate(operand, pendingAdditiveOperator)) {
   abortOperation();
   return;
  }
  ui->lcd1->setText(QString::number(num));      //выводим num на экран
 }
 else {
  num = operand;                                //Записываем operand в num
 }
 pendingAdditiveOperator = clickedOperator;
 waitingForOperand = true;                      //Ждем следующего операнда
}
//Закончили с  операторами сложения
//Мультипликативные операторы. Функция работает примерно так же как так предыдущая
void madCalc::multiplicativeOperatorClicked() {
 double operand = ui->lcd1->text().toDouble(); //Берем операнд с экрана, переводим его в переменную типа double
 if (!pendingMultiplicativeOperator.isEmpty()) {
  if (!calculate(operand, pendingMultiplicativeOperator)) {
   abortOperation();
   return;
  }
  ui->lcd1->setText(QString::number(num));
 }
 else {
  num = operand;
 }
 pendingMultiplicativeOperator = clickedOperator;
 waitingForOperand = true;
}
//Функция знака =
void madCalc::equalClicked() {
 double operand = ui->lcd1->text().toDouble();
 if (!pendingMultiplicativeOperator.isEmpty()) {
  if (!calculate(operand, pendingMultiplicativeOperator)) {
   abortOperation();
   return;
  }
  operand = num;
  num = 0.0;
  pendingMultiplicativeOperator.clear();
 }
 if (!pendingAdditiveOperator.isEmpty()) {
  if (!calculate(operand, pendingAdditiveOperator)) {
   abortOperation();
   return;
  }
  pendingAdditiveOperator.clear();
 }
 else {
  num = operand;
 }
 ui->lcd1->setText(QString::number(num));
 num= 0.0;
 waitingForOperand = true;
}
//функция вычислений
bool madCalc::calculate(double rightOperand, const QString &pendingOperator) {
 if (pendingOperator == tr("+")) num += rightOperand;
 else if (pendingOperator == tr("-")) num -= rightOperand;
 else if (pendingOperator == tr("*")) num *= rightOperand;
 else if (pendingOperator == tr("/")) {
  if (rightOperand == 0.0) return false;
  num /= rightOperand;
   }
  return true;
}
void madCalc::on_btnCE_clicked()
{
    if (waitingForOperand) return;
    ui->lcd1->setText("0");
    waitingForOperand = true;
}
void madCalc::ClearAll()
{
    num = 0.0; //Приравниваем предыдущий операнд к нулю
    pendingAdditiveOperator.clear(); //Очищаем переменную, отвечающую за хранения знака оператора сложения/вычитания
    pendingMultiplicativeOperator.clear(); //Очищаем переменную, отвечающую за хранения знака оператора умножения/деления
    ui->lcd1->setText("0"); //Устанавливаем 0 на наш экран
    waitingForOperand = true; //Ждем следующего операнда
}
//Вызов формы с информацией о программе
void madCalc::on_btnAb_clicked()
{
    about *frm = new about();
                 frm->show();
}
void madCalc::DEBUG() //Выводим в статус бар содержимое переменных num и PER
{
    //Переводим наши переменные из double в qstring
    QString NUMstr = QString::number(num);
    //QString FACTORstr = QString::number(factorSoFar);
    QString PERstr = QString::number(PER);
    //Далее выводим все это дело в статус-бар
    statusBar()->showMessage("DEBUG - num: " + NUMstr + " per: " + PERstr);
}
main.cpp
#include "about.h"
#include "ui_about.h"
about::about(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::about)
{
    ui->setupUi(this);
}
about::~about()
{
    delete ui;
}
void about::on_btnOk_clicked()
{
    QWidget::close();
}
	