Viadeo Twitter Google Bookmarks ! Facebook Digg del.icio.us MySpace Yahoo MyWeb Blinklist Netvouz Reddit Simpy StumbleUpon Bookmarks Windows Live Favorites 
Logo Documentation Qt ·  Page d'accueil  ·  Toutes les classes  ·  Classes principales  ·  Annotées  ·  Classes groupées  ·  Modules  ·  Fonctions  · 

Calculator Example

Files:

The example shows how to use signals and slots to implement the functionality of a calculator widget, and how to use QGridLayout to place child widgets in a grid. This example also demonstrates how to use event filters to customize a widget's behavior.

Screenshot of the Calculator example

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

    class Calculator : public QDialog
    {
        Q_OBJECT

    public:
        Calculator(QWidget *parent = 0);

    protected:
        bool eventFilter(QObject *target, QEvent *event);

    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, , 1/x), additive operators (+, -), and multiplicative operators (×, ÷). The other buttons have their own slots.

    private:
        Button *createButton(const QString &text, const QColor &color,
                             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 ÷).

        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 InputDisplaySum so FarAdd. Op.Factor so FarMult. Op.Waiting for Operand?
00true
110false
1 +11+true
1 + 221+false
1 + 2 ÷21+2÷true
1 + 2 ÷ 331+2÷false
1 + 2 ÷ 3 -1.666671.66667-true
1 + 2 ÷ 3 - 441.66667-false
1 + 2 ÷ 3 - 4 =-2.333330true

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.

        QLineEdit *display;

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

        Button *pointButton;
        Button *changeSignButton;
        Button *backspaceButton;
        Button *clearButton;
        Button *clearAllButton;
        Button *clearMemoryButton;
        Button *readMemoryButton;
        Button *setMemoryButton;
        Button *addToMemoryButton;

        Button *divisionButton;
        Button *timesButton;
        Button *minusButton;
        Button *plusButton;
        Button *squareRootButton;
        Button *powerButton;
        Button *reciprocalButton;
        Button *equalButton;
    };

Finally, we declare the variables associated with the child widgets.

Calculator Class Implementation

    Calculator::Calculator(QWidget *parent)
        : QDialog(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.

        display = new QLineEdit("0");
        display->setReadOnly(true);
        display->setAlignment(Qt::AlignRight);
        display->setMaxLength(15);
        display->installEventFilter(this);

        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. The QObject::installEventFilter() call installs the Calculator object (this) as an event filter for display; we will come back to it later.

We also enlarge display's font by 8 points.

        QColor digitColor(150, 205, 205);
        QColor backspaceColor(225, 185, 135);
        QColor memoryColor(100, 155, 155);
        QColor operatorColor(155, 175, 195);

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

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

        backspaceButton = createButton(tr("Backspace"), backspaceColor,
                                       SLOT(backspaceClicked()));
        clearButton = createButton(tr("Clear"), backspaceColor, SLOT(clear()));
        clearAllButton = createButton(tr("Clear All"), backspaceColor.light(120),
                                      SLOT(clearAll()));

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

        divisionButton = createButton(tr("\367"), operatorColor,
                                      SLOT(multiplicativeOperatorClicked()));
        timesButton = createButton(tr("\327"), operatorColor,
                                   SLOT(multiplicativeOperatorClicked()));
        minusButton = createButton(tr("-"), operatorColor,
                                   SLOT(additiveOperatorClicked()));
        plusButton = createButton(tr("+"), operatorColor,
                                  SLOT(additiveOperatorClicked()));

        squareRootButton = createButton(tr("Sqrt"), operatorColor,
                                        SLOT(unaryOperatorClicked()));
        powerButton = createButton(tr("x\262"), operatorColor,
                                   SLOT(unaryOperatorClicked()));
        reciprocalButton = createButton(tr("1/x"), operatorColor,
                                        SLOT(unaryOperatorClicked()));
        equalButton = createButton(tr("="), operatorColor.light(120),
                                   SLOT(equalClicked()));

We define four colors by specifying the red, green, and blue components on a scale from 0 to 255. Then, for each button, we call the private createButton() function with the proper text label, the associated color, and a slot to connect to the button.

To make the Clear All and = buttons stand out, we call QColor::light() with a factor of 120%, making these buttons 20% brighter than their neighbors.

        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, 4);
        mainLayout->addWidget(plusButton, 5, 4);

        mainLayout->addWidget(squareRootButton, 2, 5);
        mainLayout->addWidget(powerButton, 3, 5);
        mainLayout->addWidget(reciprocalButton, 4, 5);
        mainLayout->addWidget(equalButton, 5, 5);
        setLayout(mainLayout);

        setWindowTitle(tr("Calculator"));
    }

The layout is handled by a single QGridLayout. The QLayout::setSizeConstraint() call ensures that the Calculator widget is always shown as its optimal size (its size hint), preventing the user from resizing the calculator. The size hint is determined by the size and size policy of the child widgets.

Most child widgets occupy only one cell in the grid layout. For these, we only need to pass a row and a column to QGridLayout::addWidget(). The display, backspaceButton, clearButton, and clearAllButton widgets occupy more than one column; for these we must also pass a row span and a column span.

    void Calculator::digitClicked()
    {
        Button *clickedButton = qobject_cast<Button *>(sender());
        int digitValue = clickedButton->text().toInt();
        if (display->text() == "0" && digitValue == 0.0)
            return;

        if (waitingForOperand) {
            display->clear();
            waitingForOperand = false;
        }
        display->setText(display->text() + QString::number(digitValue));
    }

Pressing one of the calculator's digit buttons will emit the button's clicked() signal, which will trigger the digitClicked() slot.

First, we find out which button sent the signal using QObject::sender(). This function returns the sender as a QObject pointer. Since we know that the sender is a Button object, we can safely cast the QObject. We could have used a C-style cast or a C++ static_cast<>(), but as a defensive programming technique we use a qobject_cast(). The advantage is that if the object has the wrong type, a null pointer is returned. Crashes due to null pointers are much easier to diagnose than crashes due to unsafe casts. Once we have the button, we extract the operator using QToolButton::text().

The slot needs to consider two situations in particular. If display contains "0" and the user clicks the 0 button, it would be silly to show "00". And if the calculator is in a state where it is waiting for a new operand, the new digit is the first digit of that new operand; in that case, any result of a previous calculation must be cleared first.

At the end, we append the new digit to the value in the display.

    void Calculator::unaryOperatorClicked()
    {
        Button *clickedButton = qobject_cast<Button *>(sender());
        QString clickedOperator = clickedButton->text();
        double operand = display->text().toDouble();
        double result = 0.0;

        if (clickedOperator == tr("Sqrt")) {
            if (operand < 0.0) {
                abortOperation();
                return;
            }
            result = sqrt(operand);
        } else if (clickedOperator == tr("x\262")) {
            result = pow(operand, 2.0);
        } else if (clickedOperator == tr("1/x")) {
            if (operand == 0.0) {
                abortOperation();
                return;
            }
            result = 1.0 / operand;
        }
        display->setText(QString::number(result));
        waitingForOperand = true;
    }

The unaryOperatorClicked() slot is called whenever one of the unary operator buttons is clicked. Again a pointer to the clicked button is retrieved using QObject::sender(). The operator is extracted from the button's text and stored in clickedOperator. The operand is obtained from display.

Then we perform the operation. If Sqrt is applied to a negative number or 1/x to zero, we call abortOperation(). If everything goes well, we display the result of the operation in the line edit and we set waitingForOperand to true. This ensures that if the user types a new digit, the digit will be considered as a new operand, instead of being appended to the current value.

    void Calculator::additiveOperatorClicked()
    {
        Button *clickedButton = qobject_cast<Button *>(sender());
        QString clickedOperator = clickedButton->text();
        double operand = display->text().toDouble();

The additiveOperatorClicked() slot is called when the user clicks the + or - button.

Before we can actually do something about the clicked operator, we must handle any pending operations. We start with the multiplicative operators, since these have higher precedence than additive operators:

        if (!pendingMultiplicativeOperator.isEmpty()) {
            if (!calculate(operand, pendingMultiplicativeOperator)) {
                abortOperation();
                return;
            }
            display->setText(QString::number(factorSoFar));
            operand = factorSoFar;
            factorSoFar = 0.0;
            pendingMultiplicativeOperator.clear();
        }

If × or ÷ has been clicked earlier, without clicking = afterward, the current value in the display is the right operand of the × or ÷ operator and we can finally perform the operation and update the display.

        if (!pendingAdditiveOperator.isEmpty()) {
            if (!calculate(operand, pendingAdditiveOperator)) {
                abortOperation();
                return;
            }
            display->setText(QString::number(sumSoFar));
        } else {
            sumSoFar = operand;
        }

If + or - has been clicked earlier, sumSoFar is the left operand and the current value in the display is the right operand of the operator. If there is no pending additive operator, sumSoFar is simply set to be the text in the display.

        pendingAdditiveOperator = clickedOperator;
        waitingForOperand = true;
    }

Finally, we can take care of the operator that was just clicked. Since we don't have the right-hand operand yet, we store the clicked operator in the pendingAdditiveOperator variable. We will apply the operation later, when we have a right operand, with sumSoFar as the left operand.

    void Calculator::multiplicativeOperatorClicked()
    {
        Button *clickedButton = qobject_cast<Button *>(sender());
        QString clickedOperator = clickedButton->text();
        double operand = display->text().toDouble();

        if (!pendingMultiplicativeOperator.isEmpty()) {
            if (!calculate(operand, pendingMultiplicativeOperator)) {
                abortOperation();
                return;
            }
            display->setText(QString::number(factorSoFar));
        } else {
            factorSoFar = operand;
        }

        pendingMultiplicativeOperator = clickedOperator;
        waitingForOperand = true;
    }

The multiplicativeOperatorClicked() slot is similar to additiveOperatorClicked(). We don't need to worry about pending additive operators here, because multiplicative operators have precedence over additive operators.

    void Calculator::equalClicked()
    {
        double operand = display->text().toDouble();

        if (!pendingMultiplicativeOperator.isEmpty()) {
            if (!calculate(operand, pendingMultiplicativeOperator)) {
                abortOperation();
                return;
            }
            operand = factorSoFar;
            factorSoFar = 0.0;
            pendingMultiplicativeOperator.clear();
        }
        if (!pendingAdditiveOperator.isEmpty()) {
            if (!calculate(operand, pendingAdditiveOperator)) {
                abortOperation();
                return;
            }
            pendingAdditiveOperator.clear();
        } else {
            sumSoFar = operand;
        }

        display->setText(QString::number(sumSoFar));
        sumSoFar = 0.0;
        waitingForOperand = true;
    }

Like in additiveOperatorClicked(), we start by handing any pending multiplicative and additive operators. Then we display sumSoFar and reset the variable to zero. Resetting the variable to zero is necessary to avoid counting the value twice.

    void Calculator::pointClicked()
    {
        if (waitingForOperand)
            display->setText("0");
        if (!display->text().contains("."))
            display->setText(display->text() + tr("."));
        waitingForOperand = false;
    }

The pointClicked() slot adds a decimal point to the content in display.

    void Calculator::changeSignClicked()
    {
        QString text = display->text();
        double value = text.toDouble();

        if (value > 0.0) {
            text.prepend(tr("-"));
        } else if (value < 0.0) {
            text.remove(0, 1);
        }
        display->setText(text);
    }

The changeSignClicked() slot changes the sign of the value in display. If the current value is positive, we prepend a minus sign; if the current value is negative, we remove the first character from the value (the minus sign).

    void Calculator::backspaceClicked()
    {
        if (waitingForOperand)
            return;

        QString text = display->text();
        text.chop(1);
        if (text.isEmpty()) {
            text = "0";
            waitingForOperand = true;
        }
        display->setText(text);
    }

The backspaceClicked() removes the rightmost character in the display. If we get an empty string, we show "0" and set waitingForOperand to true.

    void Calculator::clear()
    {
        if (waitingForOperand)
            return;

        display->setText("0");
        waitingForOperand = true;
    }

The clear() slot resets the current operand to zero. It is equivalent to clicking Backspace enough times to erase the entire operand.

    void Calculator::clearAll()
    {
        sumSoFar = 0.0;
        factorSoFar = 0.0;
        pendingAdditiveOperator.clear();
        pendingMultiplicativeOperator.clear();
        display->setText("0");
        waitingForOperand = true;
    }

The clearAll() slot resets the calculator to its initial state.

    void Calculator::clearMemory()
    {
        sumInMemory = 0.0;
    }

    void Calculator::readMemory()
    {
        display->setText(QString::number(sumInMemory));
        waitingForOperand = true;
    }

    void Calculator::setMemory()
    {
        equalClicked();
        sumInMemory = display->text().toDouble();
    }

    void Calculator::addToMemory()
    {
        equalClicked();
        sumInMemory += display->text().toDouble();
    }

The clearMemory() slot erases the sum kept in memory, readMemory() displays the sum as an operand, setMemory() replace the sum in memory with the current sum, and addToMemory() adds the current value to the value in memory. For setMemory() and addToMemory(), we start by calling equalClicked() to update sumSoFar and the value in the display.

    Button *Calculator::createButton(const QString &text, const QColor &color,
                                     const char *member)
    {
        Button *button = new Button(text, color);
        connect(button, SIGNAL(clicked()), this, member);
        return button;
    }

The private createButton() function is called from the constructor to create calculator buttons.

    void Calculator::abortOperation()
    {
        clearAll();
        display->setText(tr("####"));
    }

The private abortOperation() function is called whenever a calculation fails. It resets the calculator state and displays "####".

    bool Calculator::calculate(double rightOperand, const QString &pendingOperator)
    {
        if (pendingOperator == tr("+")) {
            sumSoFar += rightOperand;
        } else if (pendingOperator == tr("-")) {
            sumSoFar -= rightOperand;
        } else if (pendingOperator == tr("\327")) {
            factorSoFar *= rightOperand;
        } else if (pendingOperator == tr("\367")) {
            if (rightOperand == 0.0)
                return false;
            factorSoFar /= rightOperand;
        }
        return true;
    }

The private calculate() function performs a binary operation. The right operand is given by rightOperand. For additive operators, the left operand is sumSoFar; for multiplicative operators, the left operand is factorSoFar. The function return false if a division by zero occurs.

    bool Calculator::eventFilter(QObject *target, QEvent *event)
    {
        if (target == display) {
            if (event->type() == QEvent::MouseButtonPress
                    || event->type() == QEvent::MouseButtonDblClick
                    || event->type() == QEvent::MouseButtonRelease
                    || event->type() == QEvent::ContextMenu) {
                QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
                if (mouseEvent->buttons() & Qt::LeftButton) {
                    QPalette newPalette = palette();
                    newPalette.setColor(QPalette::Base,
                                        display->palette().color(QPalette::Text));
                    newPalette.setColor(QPalette::Text,
                                        display->palette().color(QPalette::Base));
                    display->setPalette(newPalette);
                } else {
                    display->setPalette(palette());
                }
                return true;
            }
        }
        return QDialog::eventFilter(target, event);
    }

The eventFilter() function is inherited from QObject. In the constructor, we installed it on display. While the user holds the left mouse button pressed, we use an inverted color scheme. Any other events are ignored and simply passed on to the base class's eventFilter() implementation.

Button Class Definition

Let's now take a look at the Button class:

    class Button : public QToolButton
    {
        Q_OBJECT

    public:
        Button(const QString &text, const QColor &color, QWidget *parent = 0);

        QSize sizeHint() const;
    };

The Button class has a convenience constructor that takes a text label and a color, and it reimplements QWidget::sizeHint() to provide more space around the text than what QToolButton normally provides.

Button Class Implementation

    Button::Button(const QString &text, const QColor &color, QWidget *parent)
        : QToolButton(parent)
    {
        setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
        setText(text);

        QPalette newPalette = palette();
        newPalette.setColor(QPalette::Button, color);
        setPalette(newPalette);
    }

The buttons' appearance is determined by the layout of the calculator widget through the size and size policy of the layout's child widgets. The call to the setSizePolicy() function in the constructor ensures that the button will expand horizontally to fill all the available space; by default, QToolButtons don't expand to fill available space. Without this call, the different buttons in a same column would have different widths.

    QSize Button::sizeHint() const
    {
        QSize size = QToolButton::sizeHint();
        size.rheight() += 20;
        size.rwidth() = qMax(size.width(), size.height());
        return size;
    }

In sizeHint(), we try to return a size that looks good for most buttons. We reuse the size hint of the base class (QToolButton) but modify it in the following ways:

  • We add 20 to the height component of the size hint.
  • We make the width component of the size hint at least as much as the height.

This ensures that with most fonts, the digit and operator buttons will be square, without truncating the text on the Backspace, Clear, and Clear All buttons.

The screenshot below shows how the Calculator widget would would like if we didn't set the horizontal size policy to QSizePolicy::Expanding in the constructor and if we didn't reimplement QWidget::sizeHint().

The Calculator example with default size policies and size hints

This completes the Calculator example. Trolltech will send a T-shirt to the first finder of a bug in the example.

Publicité

Best Of

Actualités les plus lues

Semaine
Mois
Année
  1. « Quelque chose ne va vraiment pas avec les développeurs "modernes" », un développeur à "l'ancienne" critique la multiplication des bibliothèques 65
  2. Apercevoir la troisième dimension ou l'utilisation multithreadée d'OpenGL dans Qt, un article des Qt Quarterly traduit par Guillaume Belz 0
  3. Les développeurs ignorent-ils trop les failles découvertes dans leur code ? Prenez-vous en compte les remarques des autres ? 17
  4. BlackBerry 10 : premières images du prochain OS de RIM qui devrait intégrer des widgets et des tuiles inspirées de Windows Phone 0
  5. Quelles nouveautés de C++11 Visual C++ doit-il rapidement intégrer ? Donnez-nous votre avis 10
  6. Adieu qmake, bienvenue qbs : Qt Building Suite, un outil déclaratif et extensible pour la compilation de projets Qt 17
  7. La rubrique Qt a besoin de vous ! 1
Page suivante

Le Qt Developer Network au hasard

Logo

Applications mobiles modernes avec Qt et QML

Le Qt Developer Network est un réseau de développeurs Qt anglophone, où ils peuvent partager leur expérience sur le framework. Lire l'article.

Communauté

Ressources

Liens utiles

Contact

  • Vous souhaitez rejoindre la rédaction ou proposer un tutoriel, une traduction, une question... ? Postez dans le forum Contribuez ou contactez-nous par MP ou par email (voir en bas de page).

Qt dans le magazine

Cette page est une traduction d'une page de la documentation de Qt, écrite par Nokia Corporation and/or its subsidiary(-ies). Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia. Qt 4.1
Copyright © 2012 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon, vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts. Cette page est déposée à la SACD.
Vous avez déniché une erreur ? Un bug ? Une redirection cassée ? Ou tout autre problème, quel qu'il soit ? Ou bien vous désirez participer à ce projet de traduction ? N'hésitez pas à nous contacter ou par MP !
 
 
 
 
Partenaires

Hébergement Web