Home · All Classes · Main Classes · Grouped Classes · Modules · Functions

Icons Example

Files:

The Icons example shows how QIcon can generate pixmaps reflecting an icon's state, mode and size. These pixmaps are generated from the set of pixmaps made available to the icon, and are used by Qt widgets to show an icon representing a particular action.

Screenshot of the Icons example

Contents:

QIcon Overview

The QIcon class provides scalable icons in different modes and states. An icon's state and mode are depending on the intended use of the icon. There are currently three defined modes:

ModeDescription
QIcon::NormalDisplay the pixmap when the user is not interacting with the icon, but the functionality represented by the icon is available.
QIcon::DisabledDisplay the pixmap when the functionality represented by the icon is not available.
QIcon::ActiveDisplay the pixmap when the functionality represented by the icon is available and the user is interacting with the icon, for example, moving the mouse over it or clicking it.

QIcon's states are QIcon::On and QIcon::Off, which will display the pixmap when the widget is in the respective state. The most common usage of QIcon's states are when displaying checkable tool buttons or menu entries. When a tool button or menu entry is checked, the QIcon's state is On, otherwise it's Off. You can, for example, use the QIcon's states to display differing pixmaps depending on whether the tool button or menu entry is checked or not.

A QIcon can generate smaller, larger, active, and disabled pixmaps from the set of pixmaps it is given. Such pixmaps are used by Qt widgets to show an icon representing a particular action.

Overview of the Icons Application

With the Icons application you get a preview of an icon's generated pixmaps reflecting its different states, modes and size.

When an image is loaded into the application, it is converted into a pixmap and becomes a part of the set of pixmaps available to the icon. An image can be excluded from this set by checking off the related checkbox. The application provides a sub directory containing sets of images explicitly designed to illustrate how Qt renders an icon in different modes and states.

The application allows you to manipulate the icon size with some predefined sizes and a spin box. The predefined sizes are style dependent, but most of the styles have the same values: Only the Macintosh style differ by using 32 pixels, instead of 16 pixels, for toolbar buttons. You can navigate between the available styles using the View menu.

Screenshot of the View menu

The View menu also provide the option to make the application guess the icon state and mode from an image's file name. The File menu provide the options of adding an image and removing all images. These last options are also available through a context menu that appears if you press the right mouse button within the table of image files. In addition, the File menu provide an Exit option, and the Help menu provide information about the example and about Qt.

Screenshot of the Find Files

The screenshot above shows the application with one image file loaded. The Guess Image Mode/State is enabled and the style is Plastique.

When QIcon is provided with only one available pixmap, that pixmap is used for all the states and modes. In this case the pixmap's icon mode is set to normal, and the generated pixmaps for the normal and active modes will look the same. But in disabled mode Qt will generate disabled pixmaps, i.e. give the generated pixmaps a gray color scale.

Screenshot of the Find Files

The next screenshot shows the application with an additional file loaded, providing QIcon with two available pixmaps. Note that the new image file's mode is set to disabled. When rendering the Disabled mode pixmaps, Qt will now use the new image. We can see the difference: The generated disabled pixmap in the first screenshot is slightly darker than the pixmap with the originally set disabled mode in the second screenshot.

When Qt renders the icon's pixmaps it searches through the set of available pixmaps following a particular algorithm. The algorithm is documented in QIcon, but we will describe some particular cases below.

Screenshot of the Find Files

In the screenshot above we have played around with the images' modes and states. We changed the find_normal.png image's state to on, and the find_disabled.png image's mode to active.

When rendering the icon's pixmaps, the mentioned search algorithm is essential: To render the Normal/Off pixmap, Qt will first see if there is a pixmap set for this combination of icon state and mode. If there isn't, Qt will look for other combinations according to the table below.

Requested PixmapPreferred Alternatives (mode/state)
ModeState123456
NormalOffN/OffA/OffN/OnA/OnD/OffD/On
NormalOnN/OnA/OnN/OffA/OffD/OnD/Off
ActiveOffA/OffN/OffA/OnN/OnD/OffD/On
ActiveOnA/OnN/OnA/OffN/OffD/OnD/Off
DisabledOffD/Offgray(N/Off)gray(A/Off)D/Ongray(N/On)gray(A/On)
DisabledOnD/Ongray(N/On)gray(A/On)D/Offgray(N/Off)gray(A/Off)

The alternatives used in the screenshot above are marked with a bold font. For example, the find_disabled image is now Active/Off, and is the preferred alternative for the Normal/Off pixmap. The algorithm is similar for all the normal and active mode pixmaps, but differ when it comes to the disabled mode.

When rendering the Disabled/Off pixmap, Qt will first see if any of the available pixmaps are set for this combination of icon state and mode. If no one is, it will look for pixmaps set for Normal/Off, and as long as it needs to it will look for a suitable pixmap following the line of search described in the table above. As before: If the preferred alternative is a pixmap that is set for another icon mode, QIcon will generate a disabled pixmap, i.e. give the generated pixmap a gray color scale.

Screenshot of the Monkey FilesScreenshot of the Monkey Files

In the next screenshots we have loaded the set of monkey images. This set of images show in particular QIcon's on and off states.

When rendering the generated pixmaps, Qt gives priority to the state. However, it will never use a disabled pixmap to render a normal or active pixmap, unless it has no other choice. The effect is illustrated in the right screenshot above.

Screenshot of the Qtopia FilesScreenshot of the Qtopia FilesScreenshot of the Qtopia Files
8 x 816 x 1617 x 17
Screenshot of the Qtopia FilesScreenshot of the Qtopia FilesScreenshot of the Qtopia FilesScreenshot of the Qtopia Files
32 x 3233 x 3348 x 4864 x 64

The screenshots above show how QIcon behaves when we ask it for pixmaps of various sizes. Here we have played around with the sizes using a set of Qtopia images which only differ in size (qtopia_16x16.png, qtopia_32x32.png, qtopia_48x48.png).

QIcon can always generate a smaller pixmap from the set of pixmaps it is given, but it never scales them up, because this rarely looks good. This is why we get the 48 x 48 pixmap when we request a 64 x 64 pixmap.

In summary, for sizes up to 16 x 16, QIcon uses qtopia_16x16.png and scales it down if necessary. Similarly, for sizes between 17 x 17 and 32 x 32, it uses qtopia_32x32.png, and for sizes above 32 x 32, it uses qtopia_48x48.png.

Line-by-Line Walkthrough

The Icons example consists of four classes:

We will start by reviewing the IconPreviewArea class before we take a look at the MainWindow class. Finally, we will review the IconSizeSpinBox and ImageDelegate classes.

IconPreviewArea Class Definition

An IconPreviewArea widget consists of a group box containing a grid of QLabel widgets displaying headers and pixmaps.

Screenshot of IconPreviewArea.

    class IconPreviewArea : public QWidget
    {
        Q_OBJECT

    public:
        IconPreviewArea(QWidget *parent = 0);

        void setIcon(const QIcon &icon);
        void setSize(const QSize &size);

    private:
        QLabel *createHeaderLabel(const QString &text);
        QLabel *createPixmapLabel();
        void updatePixmapLabels();

        enum { NumModes = 3, NumStates = 2 };

        QIcon icon;
        QSize size;
        QLabel *stateLabels[NumStates];
        QLabel *modeLabels[NumModes];
        QLabel *pixmapLabels[NumModes][NumStates];
    };

The IconPreviewArea class inherits QWidget. It displays the generated pixmaps corresponding to an icon's possible states and modes at a given size.

We need two public functions to set the current icon and the icon's size. In addition the class has three private functions: We use the createHeaderLabel() and createPixmapLabel() functions when constructing the preview area, and we need the updatePixmapLabels() function to update the preview area when the icon or the icon's size has changed.

The NumModes and NumStates constants reflect QIcon's number of currently defined modes and states.

IconPreviewArea Class Implementation

    IconPreviewArea::IconPreviewArea(QWidget *parent)
        : QWidget(parent)
    {
        QGridLayout *mainLayout = new QGridLayout;
        setLayout(mainLayout);

        stateLabels[0] = createHeaderLabel(tr("Off"));
        stateLabels[1] = createHeaderLabel(tr("On"));
        Q_ASSERT(NumStates == 2);

        modeLabels[0] = createHeaderLabel(tr("Normal"));
        modeLabels[1] = createHeaderLabel(tr("Active"));
        modeLabels[2] = createHeaderLabel(tr("Disabled"));
        Q_ASSERT(NumModes == 3);

        for (int j = 0; j < NumStates; ++j)
            mainLayout->addWidget(stateLabels[j], j + 1, 0);

        for (int i = 0; i < NumModes; ++i) {
            mainLayout->addWidget(modeLabels[i], 0, i + 1);

            for (int j = 0; j < NumStates; ++j) {
                pixmapLabels[i][j] = createPixmapLabel();
                mainLayout->addWidget(pixmapLabels[i][j], j + 1, i + 1);
            }
        }
    }

In the constructor we create the labels displaying the headers and the icon's generated pixmaps, and add them to a grid layout.

When creating the header labels, we make sure the enums NumModes and NumStates defined in the .h file, correspond with the number of labels that we create. Then if the enums at some point are changed, the Q_ASSERT() macro will alert that this part of the .cpp file needs to be updated as well.

If the application is built in debug mode, the Q_ASSERT() macro will expand to

    if (!condition)
         qFatal("ASSERT: "condition" in file ...");

In release mode, the macro simply disappear. The mode can be set in the application's .pro file. One way to do so is to add an option to qmake when building the application:

    qmake "CONFIG += debug" icons.pro

or

    qmake "CONFIG += release" icons.pro

Another approach is to add this line directly to the .pro file.

    void IconPreviewArea::setIcon(const QIcon &icon)
    {
        this->icon = icon;
        updatePixmapLabels();
    }

    void IconPreviewArea::setSize(const QSize &size)
    {
        if (size != this->size) {
            this->size = size;
            updatePixmapLabels();
        }
    }

The public setIcon() and setSize() functions change the icon or the icon size, and make sure that the generated pixmaps are updated.

    QLabel *IconPreviewArea::createHeaderLabel(const QString &text)
    {
        QLabel *label = new QLabel(tr("<b>%1</b>").arg(text));
        label->setAlignment(Qt::AlignCenter);
        return label;
    }

    QLabel *IconPreviewArea::createPixmapLabel()
    {
        QLabel *label = new QLabel;
        label->setEnabled(false);
        label->setAlignment(Qt::AlignCenter);
        label->setFrameShape(QFrame::Box);
        label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
        label->setBackgroundRole(QPalette::Base);
        label->setMinimumSize(132, 132);
        return label;
    }

We use the createHeaderLabel() and createPixmapLabel() functions to create the preview area's labels displaying the headers and the icon's generated pixmaps. Both functions return the QLabel that is created.

    void IconPreviewArea::updatePixmapLabels()
    {
        for (int i = 0; i < NumModes; ++i) {
            QIcon::Mode mode;
            if (i == 0) {
                mode = QIcon::Normal;
            } else if (i == 1) {
                mode = QIcon::Active;
            } else {
                mode = QIcon::Disabled;
            }

            for (int j = 0; j < NumStates; ++j) {
                QIcon::State state = (j == 0) ? QIcon::Off : QIcon::On;
                QPixmap pixmap = icon.pixmap(size, mode, state);
                pixmapLabels[i][j]->setPixmap(pixmap);
                pixmapLabels[i][j]->setEnabled(!pixmap.isNull());
            }
        }
    }

We use the private updatePixmapLabel() function to update the generated pixmaps displayed in the preview area.

For each mode, and for each state, we retrieve a pixmap using the QIcon::pixmap() function, which generates a pixmap corresponding to the given state, mode and size.

MainWindow Class Definition

The MainWindow widget consists of three main elements: an images group box, an icon size group box and a preview area.

Screenshot of the Icons example

    class MainWindow : public QMainWindow
    {
        Q_OBJECT

    public:
        MainWindow();

    private slots:
        void about();
        void changeStyle(bool checked);
        void changeSize();
        void changeIcon();
        void addImage();
        void removeAllImages();

    private:
        void createPreviewGroupBox();
        void createImagesGroupBox();
        void createIconSizeGroupBox();
        void createActions();
        void createMenus();
        void createContextMenu();
        void checkCurrentStyle();

        QWidget *centralWidget;

        QGroupBox *previewGroupBox;
        IconPreviewArea *previewArea;

        QGroupBox *imagesGroupBox;
        QTableWidget *imagesTable;

        QGroupBox *iconSizeGroupBox;
        QRadioButton *smallRadioButton;
        QRadioButton *largeRadioButton;
        QRadioButton *toolBarRadioButton;
        QRadioButton *listViewRadioButton;
        QRadioButton *iconViewRadioButton;
        QRadioButton *otherRadioButton;
        IconSizeSpinBox *otherSpinBox;

        QMenu *fileMenu;
        QMenu *viewMenu;
        QMenu *helpMenu;
        QAction *addImageAct;
        QAction *removeAllImagesAct;
        QAction *exitAct;
        QAction *guessModeStateAct;
        QActionGroup *styleActionGroup;
        QAction *aboutAct;
        QAction *aboutQtAct;
    };

The MainWindow class inherits from QMainWindow. We reimplement the constructor, and declare several private slots:

In addition we declare several private functions to simplify the constructor.

MainWindow Class Implementation

    MainWindow::MainWindow()
    {
        centralWidget = new QWidget;
        setCentralWidget(centralWidget);

        createPreviewGroupBox();
        createImagesGroupBox();
        createIconSizeGroupBox();

        createActions();
        createMenus();
        createContextMenu();

        QGridLayout *mainLayout = new QGridLayout;
        mainLayout->addWidget(imagesGroupBox, 0, 0);
        mainLayout->addWidget(iconSizeGroupBox, 1, 0);
        mainLayout->addWidget(previewGroupBox, 0, 1, 2, 1);
        centralWidget->setLayout(mainLayout);

        setWindowTitle(tr("Icons"));
        checkCurrentStyle();
        otherRadioButton->click();

        resize(860, 400);
    }

In the constructor we first create the main window's central widget and its child widgets, and put them in a grid layout. Then we create the menus with their associated entries and actions.

Before we resize the application window to a suitable size, we set the window title and determine the current style for the application. We also enable the icon size spin box by clicking the associated radio button, making the current value of the spin box the icon's initial size.

    void MainWindow::about()
    {
        QMessageBox::about(this, tr("About Icons"),
                tr("The <b>Icons</b> example illustrates how Qt renders an icon in "
                   "different modes (active, normal, and disabled) and states (on "
                   "and off) based on a set of images."));
    }

The about() slot displays a message box using the static QMessageBox::about() function. In this example it displays a simple box with information about the example.

The about() function looks for a suitable icon in four locations: It prefers its parent's icon if that exists. If it doesn't, the function tries the top-level widget containing parent, and if that fails, it tries the active window. As a last resort it uses the QMessageBox's Information icon.

    void MainWindow::changeStyle(bool checked)
    {
        if (!checked)
            return;

        QAction *action = qobject_cast<QAction *>(sender());

In the changeStyle() slot we first check the slot's parameter. If it is false we immediately return, otherwise we find out which style to change to, i.e. which action that triggered the slot, using the QObject::sender() function.

This function returns the sender as a QObject pointer. Since we know that the sender is a QAction 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.

        QStyle *style = QStyleFactory::create(action->data().toString());
        Q_ASSERT(style);
        QApplication::setStyle(style);

        smallRadioButton->setText(tr("Small (%1 x %1)")
                .arg(style->pixelMetric(QStyle::PM_SmallIconSize)));
        largeRadioButton->setText(tr("Large (%1 x %1)")
                .arg(style->pixelMetric(QStyle::PM_LargeIconSize)));
        toolBarRadioButton->setText(tr("Toolbars (%1 x %1)")
                .arg(style->pixelMetric(QStyle::PM_ToolBarIconSize)));
        listViewRadioButton->setText(tr("List views (%1 x %1)")
                .arg(style->pixelMetric(QStyle::PM_ListViewIconSize)));
        iconViewRadioButton->setText(tr("Icon views (%1 x %1)")
                .arg(style->pixelMetric(QStyle::PM_IconViewIconSize)));

        changeSize();
    }

Once we have the action, we extract the style name using QAction::data(). Then we create a QStyle object using the static QStyleFactory::create() function.

Although we can assume that the style is supported by the QStyleFactory: To be on the safe side, we use the Q_ASSERT() macro to check if the created style is valid before we use the QApplication::setStyle() function to set the application's GUI style to the new style. QApplication will automatically delete the style object when a new style is set or when the application exits.

The predefined icon size options provided in the application are style dependent, so we need to update the labels in the icon size group box and in the end call the changeSize() slot to update the icon's size.

    void MainWindow::changeSize()
    {
        int extent;

        if (otherRadioButton->isChecked()) {
            extent = otherSpinBox->value();
        } else {
            QStyle::PixelMetric metric;

            if (smallRadioButton->isChecked()) {
                metric = QStyle::PM_SmallIconSize;
            } else if (largeRadioButton->isChecked()) {
                metric = QStyle::PM_LargeIconSize;
            } else if (toolBarRadioButton->isChecked()) {
                metric = QStyle::PM_ToolBarIconSize;
            } else if (listViewRadioButton->isChecked()) {
                metric = QStyle::PM_ListViewIconSize;
            } else {
                metric = QStyle::PM_IconViewIconSize;
            }
            extent = QApplication::style()->pixelMetric(metric);
        }
        previewArea->setSize(QSize(extent, extent));
        otherSpinBox->setEnabled(otherRadioButton->isChecked());
    }

The changeSize() slot sets the size for the preview area's icon.

To determine the new size we first check if the spin box is enabled. If it is, we extract the extent of the new size from the box. If it's not, we search through the predefined size options, extract the QStyle::PixelMetric and use the QStyle::pixelMetric() function to determine the extent. Then we create a QSize object based on the extent, and use that object to set the size of the preview area's icon.

    void MainWindow::addImage()
    {
        QStringList fileNames = QFileDialog::getOpenFileNames(this,
                                        tr("Open Images"), "",
                                        tr("Images (*.png *.xpm *.jpg);;"
                                           "All Files (*)"));
        if (!fileNames.isEmpty()) {
            foreach (QString fileName, fileNames) {
                int row = imagesTable->rowCount();

The first thing we do when the addImage() slot is called, is to show a file dialog to the user. The easiest way to create a file dialog is to use QFileDialog's static functions. Here we use the getOpenFileNames() function that will return one or more existing files selected by the user.

For each of the files the file dialog returns, we add a row to the table widget. The table widget is listing the images the user has loaded into the application.

                QString imageName = QFileInfo(fileName).baseName();
                QTableWidgetItem *item0 = new QTableWidgetItem(imageName);
                item0->setData(Qt::UserRole, fileName);
                item0->setFlags(item0->flags() & ~Qt::ItemIsEditable);

We retrieve the image name using the QFileInfo::baseName() function that returns the base name of the file without the path, and create the first table widget item in the row. Then we add the file's complete name to the item's data. Since an item can hold several information pieces, we need to assign the file name a role that will distinguish it from other data. This role can be Qt::UserRole or any value above it.

We also make sure that the item is not editable by removing the Qt::ItemIsEditable flag. Table items are editable by default.

                QTableWidgetItem *item1 = new QTableWidgetItem(tr("Normal"));
                QTableWidgetItem *item2 = new QTableWidgetItem(tr("Off"));

                if (guessModeStateAct->isChecked()) {
                    if (fileName.contains("_act")) {
                        item1->setText(tr("Active"));
                    } else if (fileName.contains("_dis")) {
                        item1->setText(tr("Disabled"));
                    }

                    if (fileName.contains("_on"))
                        item2->setText(tr("On"));
                }

Then we create the second and third items in the row making the default mode Normal and the default state Off. But if the Guess Image Mode/State option is checked, and the file name contains "_act" or "_dis", the modes are changed to Active or Disabled respectively. And if the file name contains "on", the state is changed to On. The sample files in the example's images subdirectory respect this naming convension.

                imagesTable->setItem(row, 0, item0);
                imagesTable->setItem(row, 1, item1);
                imagesTable->setItem(row, 2, item2);
                imagesTable->openPersistentEditor(item1);
                imagesTable->openPersistentEditor(item2);

                item0->setCheckState(Qt::Checked);
            }
        }
    }

In the end we add the items to the associated row, and use the QTableWidget::openPersistentEditor() function to create comboboxes for the mode and state columns of the items.

Due to the the connection between the table widget's itemChanged() signal and the changeIcon() slot, the new image is automatically converted into a pixmap and made part of the set of pixmaps available to the icon in the preview area. So, corresponding to this fact, we need to make sure that the new image's check box is enabled.

    void MainWindow::changeIcon()
    {
        QIcon icon;

        for (int row = 0; row < imagesTable->rowCount(); ++row) {
            QTableWidgetItem *item0 = imagesTable->item(row, 0);
            QTableWidgetItem *item1 = imagesTable->item(row, 1);
            QTableWidgetItem *item2 = imagesTable->item(row, 2);

            if (item0->checkState() == Qt::Checked) {
                QIcon::Mode mode;
                if (item1->text() == tr("Normal")) {
                    mode = QIcon::Normal;
                } else if (item1->text() == tr("Active")) {
                    mode = QIcon::Active;
                } else {
                    mode = QIcon::Disabled;
                }

                QIcon::State state;
                if (item2->text() == tr("On")) {
                    state = QIcon::On;
                } else {
                    state = QIcon::Off;
                }

The changeIcon() slot is called when the user alters the set of images listed in the QTableWidget, to update the QIcon object rendered by the IconPreviewArea.

We first create a QIcon object, and then we run through the QTableWidget, which lists the images the user has loaded into the application.

                QString fileName = item0->data(Qt::UserRole).toString();
                QImage image(fileName);
                if (!image.isNull())
                    icon.addPixmap(QPixmap::fromImage(image), mode, state);
            }
        }

We also extract the image file's name using the QTableWidgetItem::data() function. This function takes a Qt::DataItemRole as an argument to retrieve the right data (remember that an item can hold several pieces of information) and returns it as a QVariant. Then we use the QVariant::toString() function to get the file name as a QString.

To create a pixmap from the file, we need to first create an image and then convert this image into a pixmap using QPixmap::fromImage(). Once we have the final pixmap, we add it, with its associated mode and state, to the QIcon's set of available pixmaps.

        previewArea->setIcon(icon);
    }

After running through the entire list of images, we change the icon of the preview area to the one we just created.

    void MainWindow::removeAllImages()
    {
        imagesTable->setRowCount(0);
        changeIcon();
    }

In the removeAllImages() slot, we simply set the table widget's row count to zero, automatically removing all the images the user has loaded into the application. Then we update the set of pixmaps available to the preview area's icon using the changeIcon() slot.

Screenshot of the images group box

The createImagesGroupBox() function is implemented to simplify the constructor. The main purpose of the function is to create a QTableWidget that will keep track of the images the user has loaded into the application.

    void MainWindow::createImagesGroupBox()
    {
        imagesGroupBox = new QGroupBox(tr("Images"));
        imagesGroupBox->setSizePolicy(QSizePolicy::Expanding,
                                      QSizePolicy::Expanding);

First we create a group box that will contain the table widget. We set the group box's size policy to be expanding both horizontally and vertically. The expanding size policy means that the size hint is a sensible size, but the widget can be shrunk and still be useful. The widget can make use of extra space, so it should get as much space as possible.

        QStringList labels;
        labels << tr("Image") << tr("Mode") << tr("State");

        imagesTable = new QTableWidget;
        imagesTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Ignored);
        imagesTable->setSelectionMode(QAbstractItemView::NoSelection);
        imagesTable->setColumnCount(3);
        imagesTable->setHorizontalHeaderLabels(labels);

Then we customize the table widget to suit its purpose.

Its size policy is set to expanding horizontally, and ignored vertically. The ignored size policy means that the widget's size hint is ignored, and the widget will get as much space as possible.

We call QAbstractItemView::setSelectionMode() to prevent the user from selecting items.

        imagesTable->setItemDelegate(new ImageDelegate(this));

The QAbstractItemView::setItemDelegate() function sets the item delegate for the table widget. We create a ImageDelegate that we make the item delegate for our view.

The QItemDelegate class can be used to provide an editor for an item view class that is subclassed from QAbstractItemView. Using a delegate for this purpose allows the editing mechanism to be customized and developed independently from the model and view.

In this example we derive ImageDelegate from QItemDelegate. QItemDelegate usually provides line editors, while our subclass ImageDelegate, provides comboboxes for the mode and state fields.

        imagesTable->horizontalHeader()->resizeSection(0, 160);
        imagesTable->horizontalHeader()->resizeSection(1, 80);
        imagesTable->horizontalHeader()->resizeSection(2, 80);
        imagesTable->verticalHeader()->hide();

        connect(imagesTable, SIGNAL(itemChanged(QTableWidgetItem *)),
                this, SLOT(changeIcon()));

        QVBoxLayout *layout = new QVBoxLayout;
        layout->addWidget(imagesTable);
        imagesGroupBox->setLayout(layout);
    }

Then we resize the sections of the table widget to suitable sizes, and hide the table's vertical headers.

In the end we connect the QTableWidget::itemChanged() signal to the changeIcon() slot, updating the set of pixmaps available to the preview area's icon whenever the user adds a new image, disables an image or makes changes to an image's mode or state.

Screenshot of the icon size group box

The createIconSizeGroupBox() function is called from the constructor. It creates the widgets controlling the size of the preview area's icon.

    void MainWindow::createIconSizeGroupBox()
    {
        iconSizeGroupBox = new QGroupBox(tr("Icon Size"));

        smallRadioButton = new QRadioButton;
        largeRadioButton = new QRadioButton;
        toolBarRadioButton = new QRadioButton;
        listViewRadioButton = new QRadioButton;
        iconViewRadioButton = new QRadioButton;
        otherRadioButton = new QRadioButton(tr("Other:"));

        otherSpinBox = new IconSizeSpinBox;
        otherSpinBox->setRange(8, 128);
        otherSpinBox->setValue(64);

First we create a group box that will contain all the widgets; then we create the radio buttons and the spin box.

The spin box is not a regular QSpinBox but an IconSizeSpinBox. The IconSizeSpinBox class inherits QSpinBox and reimplements two functions: QSpinBox::textFromValue() and QSpinBox::valueFromText(). The IconSizeSpinBox is designed to handle icon sizes, e.g., "32 x 32", instead of plain integer values.

        connect(toolBarRadioButton, SIGNAL(toggled(bool)),
                this, SLOT(changeSize()));
        connect(listViewRadioButton, SIGNAL(toggled(bool)),
                this, SLOT(changeSize()));
        connect(iconViewRadioButton, SIGNAL(toggled(bool)),
                this, SLOT(changeSize()));
        connect(smallRadioButton, SIGNAL(toggled(bool)), this, SLOT(changeSize()));
        connect(largeRadioButton, SIGNAL(toggled(bool)), this, SLOT(changeSize()));
        connect(otherRadioButton, SIGNAL(toggled(bool)), this, SLOT(changeSize()));
        connect(otherSpinBox, SIGNAL(valueChanged(int)), this, SLOT(changeSize()));

        QHBoxLayout *otherSizeLayout = new QHBoxLayout;
        otherSizeLayout->addWidget(otherRadioButton);
        otherSizeLayout->addWidget(otherSpinBox);

        QGridLayout *layout = new QGridLayout;
        layout->addWidget(smallRadioButton, 0, 0);
        layout->addWidget(largeRadioButton, 1, 0);
        layout->addWidget(toolBarRadioButton, 2, 0);
        layout->addWidget(listViewRadioButton, 0, 1);
        layout->addWidget(iconViewRadioButton, 1, 1);
        layout->addLayout(otherSizeLayout, 2, 1);
        iconSizeGroupBox->setLayout(layout);
    }

Then we connect all of the radio buttons toggled() signals and the spin box's valueChanged() signal to the changeSize() slot to make sure that the size of the preview area's icon is updated whenever the user changes the icon size. In the end we put the widgets in a layout that we install on the group box.

    void MainWindow::createActions()
    {
        addImageAct = new QAction(tr("&Add Image..."), this);
        addImageAct->setShortcut(tr("Ctrl+A"));
        connect(addImageAct, SIGNAL(triggered()), this, SLOT(addImage()));

        removeAllImagesAct = new QAction(tr("&Remove All Images"), this);
        removeAllImagesAct->setShortcut(tr("Ctrl+R"));
        connect(removeAllImagesAct, SIGNAL(triggered()),
                this, SLOT(removeAllImages()));

        exitAct = new QAction(tr("&Quit"), this);
        exitAct->setShortcut(tr("Ctrl+Q"));
        connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));

        styleActionGroup = new QActionGroup(this);
        foreach (QString styleName, QStyleFactory::keys()) {
            QAction *action = new QAction(styleActionGroup);
            action->setText(tr("%1 Style").arg(styleName));
            action->setData(styleName);
            action->setCheckable(true);
            connect(action, SIGNAL(triggered(bool)), this, SLOT(changeStyle(bool)));
        }

        guessModeStateAct = new QAction(tr("&Guess Image Mode/State"), this);
        guessModeStateAct->setCheckable(true);
        guessModeStateAct->setChecked(true);

        aboutAct = new QAction(tr("&About"), this);
        connect(aboutAct, SIGNAL(triggered()), this, SLOT(about()));

        aboutQtAct = new QAction(tr("About &Qt"), this);
        connect(aboutQtAct, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
    }

In the createActions() function we create and customize all the actions needed to implement the functionality associated with the menu entries in the application.

In particular we create the styleActionGroup based on the currently available GUI styles using QStyleFactory. QStyleFactory::keys() returns a list of valid keys, typically including "windows", "motif", "cde", and "plastique". Depending on the platform, "windowsxp" and "macintosh" may be available.

We create one action for each key, and adds the action to the action group. Also, for each action, we call QAction::setData() with the style name. We will retrieve it later using QAction::data().

    void MainWindow::createMenus()
    {
        fileMenu = menuBar()->addMenu(tr("&File"));
        fileMenu->addAction(addImageAct);
        fileMenu->addAction(removeAllImagesAct);
        fileMenu->addSeparator();
        fileMenu->addAction(exitAct);

        viewMenu = menuBar()->addMenu(tr("&View"));
        foreach (QAction *action, styleActionGroup->actions())
            viewMenu->addAction(action);
        viewMenu->addSeparator();
        viewMenu->addAction(guessModeStateAct);

        menuBar()->addSeparator();

        helpMenu = menuBar()->addMenu(tr("&Help"));
        helpMenu->addAction(aboutAct);
        helpMenu->addAction(aboutQtAct);
    }

In the createMenu() function, we add the previously created actions to the File, View and Help menus.

The QMenu class provides a menu widget for use in menu bars, context menus, and other popup menus. We put each menu in the application's menu bar, which we retrieve using QMainWindow::menuBar().

    void MainWindow::createContextMenu()
    {
        imagesTable->setContextMenuPolicy(Qt::ActionsContextMenu);
        imagesTable->addAction(addImageAct);
        imagesTable->addAction(removeAllImagesAct);
    }

QWidgets have a contextMenuPolicy property that controls how the widget should behave when the user requests a context menu (e.g., by right-clicking). We set the QTableWidget's context menu policy to Qt::ActionsContextMenu, meaning that the QActions associated with the widget should appear in its context menu.

Then we add the Add Image and Remove All Images actions to the table widget. They will then appear in the table widget's context menu.

    void MainWindow::checkCurrentStyle()
    {
        foreach (QAction *action, styleActionGroup->actions()) {
            QString styleName = action->data().toString();
            QStyle *candidate = QStyleFactory::create(styleName);
            Q_ASSERT(candidate);
            if (candidate->metaObject()->className()
                    == QApplication::style()->metaObject()->className()) {
                action->trigger();
                return;
            }
            delete candidate;
        }
    }

In the checkCurrentStyle() function we go through the group of style actions, looking for the current GUI style.

For each action, we first extract the style name using QAction::data(). Since this is only a QStyleFactory key (e.g., "macintosh"), we cannot compare it directly to the current style's class name. We need to create a QStyle object using the static QStyleFactory::create() function and compare the class name of the created QStyle object with that of the current style. As soon as we are done with a QStyle candidate, we delete it.

For all QObject subclasses that use the Q_OBJECT macro, the class name of an object is available through its meta-object.

We can assume that the style is supported by QStyleFactory, but to be on the safe side we use the Q_ASSERT() macro to make sure that QStyleFactory::create() returned a valid pointer.

IconSizeSpinBox Class Definition

    class IconSizeSpinBox : public QSpinBox
    {
        Q_OBJECT

    public:
        IconSizeSpinBox(QWidget *parent = 0);

        int valueFromText(const QString &text) const;
        QString textFromValue(int value) const;
    };

The IconSizeSpinBox class is a subclass of QSpinBox. A plain QSpinBox can only handle integers. But since we want to display the spin box's values in a more sophisticated way, we need to subclass QSpinBox and reimplement the QSpinBox::textFromValue() and QSpinBox::valueFromText() functions.

Screenshot of the icon size spinbox

IconSizeSpinBox Class Implementation

    IconSizeSpinBox::IconSizeSpinBox(QWidget *parent)
        : QSpinBox(parent)
    {
    }

The constructor is trivial.

    QString IconSizeSpinBox::textFromValue(int value) const
    {
        return tr("%1 x %1").arg(value);
    }

QSpinBox::textFromValue() is used by the spin box whenever it needs to display a value. The default implementation returns a base 10 representation of the value parameter.

Our reimplementation returns a QString of the form "32 x 32".

    int IconSizeSpinBox::valueFromText(const QString &text) const
    {
        QRegExp regExp(tr("(\\d+)(\\s*[xx]\\s*\\d+)?"));

        if (regExp.exactMatch(text)) {
            return regExp.cap(1).toInt();
        } else {
            return 0;
        }
    }

The QSpinBox::valueFromText() function is used by the spin box whenever it needs to interpret text typed in by the user. Since we reimplement the textFromValue() function we also need to reimplement the valueFromText() function to interpret the parameter text and return the associated int value.

We parse the text using a regular expression (a QRegExp). We define an expression that matches one or several digits, optionally followed by whitespace, an "x" or the times symbol, whitespace and one or several digits again.

The first digits of the regular expression are captured using parentheses. This enables us to use the QRegExp::cap() or QRegExp::capturedTexts() functions to extract the matched characters. If the first and second numbers of the spin box value differ (e.g., "16 x 24"), we use the first number.

When the user presses Enter, QSpinBox first calls QSpinBox::valueFromText() to interpret the text typed by the user, then QSpinBox::textFromValue() to present it in a canonical format (e.g., "16 x 16").

ImageDelegate Class Definition

    class ImageDelegate : public QItemDelegate
    {
        Q_OBJECT

    public:
        ImageDelegate(QObject *parent = 0);

The ImageDelegate class is a subclass of QItemDelegate. The QItemDelegate class provides display and editing facilities for data items from a model. A single QItemDelegate object is responsible for all items displayed in a item view (in our case, a QTableWidget).

A QItemDelegate can be used to provide an editor for an item view class that is subclassed from QAbstractItemView. Using a delegate for this purpose allows the editing mechanism to be customized and developed independently from the model and view.

        QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                              const QModelIndex &index) const;
        void setEditorData(QWidget *editor, const QModelIndex &index) const;
        void setModelData(QWidget *editor, QAbstractItemModel *model,
                          const QModelIndex &index) const;

The default implementation of QItemDelegate creates a QLineEdit. Since we want the editor to be a QComboBox, we need to subclass QItemDelegate and reimplement the QItemDelegate::createEditor(), QItemDelegate::setEditorData() and QItemDelegate::setModelData() functions.

    private slots:
        void emitCommitData();
    };

The emitCommitData() slot is used to emit the QImageDelegate::commitData() signal with the appropriate argument.

ImageDelegate Class Implementation

    ImageDelegate::ImageDelegate(QObject *parent)
        : QItemDelegate(parent)
    {
    }

The constructor is trivial.

    QWidget *ImageDelegate::createEditor(QWidget *parent,
                                         const QStyleOptionViewItem & /* option */,
                                         const QModelIndex &index) const
    {
        QComboBox *comboBox = new QComboBox(parent);
        if (index.column() == 1) {
            comboBox->addItem(tr("Normal"));
            comboBox->addItem(tr("Active"));
            comboBox->addItem(tr("Disabled"));
        } else if (index.column() == 2) {
            comboBox->addItem(tr("Off"));
            comboBox->addItem(tr("On"));
        }

        connect(comboBox, SIGNAL(activated(int)), this, SLOT(emitCommitData()));

        return comboBox;
    }

The default QItemDelegate::createEditor() implementation returns the widget used to edit the item specified by the model and item index for editing. The parent widget and style option are used to control the appearance of the editor widget.

Our reimplementation create and populate a combobox instead of the default line edit. The contents of the combobox depends on the column in the table for which the editor is requested. Column 1 contains the QIcon modes, whereas column 2 contains the QIcon states.

In addition, we connect the combobox's activated() signal to the emitCommitData() slot to emit the QAbstractItemDelegate::commitData() signal whenever the user chooses an item using the combobox. This ensures that the rest of the application notices the change and updates itself.

    void ImageDelegate::setEditorData(QWidget *editor,
                                      const QModelIndex &index) const
    {
        QComboBox *comboBox = qobject_cast<QComboBox *>(editor);
        if (!comboBox)
            return;

        int pos = comboBox->findText(index.model()->data(index).toString(),
                                     Qt::MatchExactly);
        comboBox->setCurrentIndex(pos);
    }

The QItemDelegate::setEditorData() function is used by QTableWidget to transfer data from a QTableWidgetItem to the editor. The data is stored as a string; we use QComboBox::findText() to locate it in the combobox.

Delegates work in terms of models, not items. This makes it possible to use them with any item view class (e.g., QListView, QListWidget, QTreeView, etc.). The transition between model and items is done implicitly by QTableWidget; we don't need to worry about it.

    void ImageDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
                                     const QModelIndex &index) const
    {
        QComboBox *comboBox = qobject_cast<QComboBox *>(editor);
        if (!comboBox)
            return;

        model->setData(index, comboBox->currentText());
    }

The QItemDelegate::setEditorData() function is used by QTableWidget to transfer data back from the editor to the QTableWidgetItem.

    void ImageDelegate::emitCommitData()
    {
        emit commitData(qobject_cast<QWidget *>(sender()));
    }

The emitCommitData() slot simply emit the QAbstractItemDelegate::commitData() signal for the editor that triggered the slot. This signal must be emitted when the editor widget has completed editing the data, and wants to write it back into the model.


Copyright © 2005 Trolltech Trademarks
Qt 4.1.0
Hosted by uCoz