MADCALC V1.1

screen

Готовая программа под Windows

Release под Windows

Исходники

Совершенно неожиданно, нежданно и негаданно решил написать свой собственный калькулятор с блэкджэком и... симпатичным на мой вкус вырвиглазным веселеньким дизайном. Из функций приятно, что операнды складываются подряд длинной цепочкой, один за другим, а не то чтобы 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();
}