Calculator Example

Image non disponible

The example consists of two classes:

  • Calculator is the calculator widget, with all the calculator functionality.

  • Button is the widget used for each of the calculator button. It derives from QToolButton.

We will start by reviewing Calculator, then we will take a look at Button.

Calculator Class Definition

 
Sélectionnez
class Calculator : public QWidget
{
    Q_OBJECT

public:
    Calculator(QWidget *parent = nullptr);

private slots:
    void digitClicked();
    void unaryOperatorClicked();
    void additiveOperatorClicked();
    void multiplicativeOperatorClicked();
    void equalClicked();
    void pointClicked();
    void changeSignClicked();
    void backspaceClicked();
    void clear();
    void clearAll();
    void clearMemory();
    void readMemory();
    void setMemory();
    void addToMemory();

The Calculator class provides a simple calculator widget. It inherits from QDialog and has several private slots associated with the calculator's buttons. QObject::eventFilter() is reimplemented to handle mouse events on the calculator's display.

Buttons are grouped in categories according to their behavior. For example, all the digit buttons (labeled 0 to 9) append a digit to the current operand. For these, we connect multiple buttons to the same slot (e.g., digitClicked()). The categories are digits, unary operators (Sqrt, x², 1/x), additive operators (+, -), and multiplicative operators (×, ÷). The other buttons have their own slots.

 
Sélectionnez
private:
    Button *createButton(const QString &text, const char *member);
    void abortOperation();
    bool calculate(double rightOperand, const QString &pendingOperator);

The private createButton() function is used as part of the widget construction. abortOperation() is called whenever a division by zero occurs or when a square root operation is applied to a negative number. calculate() applies a binary operator (+, -, ×, or ÷).

 
Sélectionnez
    double sumInMemory;
    double sumSoFar;
    double factorSoFar;
    QString pendingAdditiveOperator;
    QString pendingMultiplicativeOperator;
    bool waitingForOperand;

These variables, together with the contents of the calculator display (a QLineEdit), encode the state of the calculator:

  • sumInMemory contains the value stored in the calculator's memory (using MS, M+, or MC).

  • sumSoFar stores the value accumulated so far. When the user clicks =, sumSoFar is recomputed and shown on the display. Clear All resets sumSoFar to zero.

  • factorSoFar stores a temporary value when doing multiplications and divisions.

  • pendingAdditiveOperator stores the last additive operator clicked by the user.

  • pendingMultiplicativeOperator stores the last multiplicative operator clicked by the user.

  • waitingForOperand is true when the calculator is expecting the user to start typing an operand.

Additive and multiplicative operators are treated differently because they have different precedences. For example, 1 + 2 ÷ 3 is interpreted as 1 + (2 ÷ 3) because ÷ has higher precedence than +.

The table below shows the evolution of the calculator state as the user enters a mathematical expression.

User Input

Display

Sum so Far

Add. Op.

Factor so Far

Mult. Op.

Waiting for Operand?

 

0

0

     

true

1

1

0

     

false

1 +

1

1

+

   

true

1 + 2

2

1

+

   

false

1 + 2 ÷

2

1

+

2

÷

true

1 + 2 ÷ 3

3

1

+

2

÷

false

1 + 2 ÷ 3 -

1.66667

1.66667

-

   

true

1 + 2 ÷ 3 - 4

4

1.66667

-

   

false

1 + 2 ÷ 3 - 4 =

-2.33333

0

     

true

Unary operators, such as Sqrt, require no special handling; they can be applied immediately since the operand is already known when the operator button is clicked.

 
Sélectionnez
    QLineEdit *display;

    enum { NumDigitButtons = 10 };
    Button *digitButtons[NumDigitButtons];
};

Finally, we declare the variables associated with the display and the buttons used to display numerals.

Calculator Class Implementation

 
Sélectionnez
Calculator::Calculator(QWidget *parent)
    : QWidget(parent)
{
    sumInMemory = 0.0;
    sumSoFar = 0.0;
    factorSoFar = 0.0;
    waitingForOperand = true;

In the constructor, we initialize the calculator's state. The pendingAdditiveOperator and pendingMultiplicativeOperator variables don't need to be initialized explicitly, because the QString constructor initializes them to empty strings.

 
Sélectionnez
    display = new QLineEdit("0");
    display->setReadOnly(true);
    display->setAlignment(Qt::AlignRight);
    display->setMaxLength(15);

    QFont font = display->font();
    font.setPointSize(font.pointSize() + 8);
    display->setFont(font);

We create the QLineEdit representing the calculator's display and set up some of its properties. In particular, we set it to be read-only.

We also enlarge display's font by 8 points.

 
Sélectionnez
    for (int i = 0; i < NumDigitButtons; ++i) {
        digitButtons[i] = createButton(QString::number(i), SLOT(digitClicked()));
    }

    Button *pointButton = createButton(tr("."), SLOT(pointClicked()));
    Button *changeSignButton = createButton(tr("\302\261"), SLOT(changeSignClicked()));

    Button *backspaceButton = createButton(tr("Backspace"), SLOT(backspaceClicked()));
    Button *clearButton = createButton(tr("Clear"), SLOT(clear()));
    Button *clearAllButton = createButton(tr("Clear All"), SLOT(clearAll()));

    Button *clearMemoryButton = createButton(tr("MC"), SLOT(clearMemory()));
    Button *readMemoryButton = createButton(tr("MR"), SLOT(readMemory()));
    Button *setMemoryButton = createButton(tr("MS"), SLOT(setMemory()));
    Button *addToMemoryButton = createButton(tr("M+"), SLOT(addToMemory()));

    Button *divisionButton = createButton(tr("\303\267"), SLOT(multiplicativeOperatorClicked()));
    Button *timesButton = createButton(tr("\303\227"), SLOT(multiplicativeOperatorClicked()));
    Button *minusButton = createButton(tr("-"), SLOT(additiveOperatorClicked()));
    Button *plusButton = createButton(tr("+"), SLOT(additiveOperatorClicked()));

    Button *squareRootButton = createButton(tr("Sqrt"), SLOT(unaryOperatorClicked()));
    Button *powerButton = createButton(tr("x\302\262"), SLOT(unaryOperatorClicked()));
    Button *reciprocalButton = createButton(tr("1/x"), SLOT(unaryOperatorClicked()));
    Button *equalButton = createButton(tr("="), SLOT(equalClicked()));

For each button, we call the private createButton() function with the proper text label and a slot to connect to the button.

 
Sélectionnez
    QGridLayout *mainLayout = new QGridLayout;
    mainLayout->setSizeConstraint(QLayout::SetFixedSize);
    mainLayout->addWidget(display, 0, 0, 1, 6);
    mainLayout->addWidget(backspaceButton, 1, 0, 1, 2);
    mainLayout->addWidget(clearButton, 1, 2, 1, 2);
    mainLayout->addWidget(clearAllButton, 1, 4, 1, 2);

    mainLayout->addWidget(clearMemoryButton, 2, 0);
    mainLayout->addWidget(readMemoryButton, 3, 0);
    mainLayout->addWidget(setMemoryButton, 4, 0);
    mainLayout->addWidget(addToMemoryButton, 5, 0);

    for (int i = 1; i < NumDigitButtons; ++i) {
        int row = ((9 - i) / 3) + 2;
        int column = ((i - 1) % 3) + 1;
        mainLayout->addWidget(digitButtons[i], row, column);
    }

    mainLayout->addWidget(digitButtons[0], 5, 1);
    mainLayout->addWidget(pointButton, 5, 2);
    mainLayout->addWidget(changeSignButton, 5, 3);

    mainLayout->addWidget(divisionButton, 2, 4);
    mainLayout->addWidget(timesButton, 3, 4);
    mainLayout->addWidget(minusButton, 4,