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(); }