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