blob: 7a8aa2037a43881bd2ca7de549115f2c0d17f460 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the documentation of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:FDL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Free Documentation License Usage
** Alternatively, this file may be used under the terms of the GNU Free
** Documentation License version 1.3 as published by the Free Software
** Foundation and appearing in the file included in the packaging of
** this file. Please review the following information to ensure
** the GNU Free Documentation License version 1.3 requirements
** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
** $QT_END_LICENSE$
**
****************************************************************************/
/*!
\example drilldown
\title Drill Down Example
\ingroup sql_examples
\brief The Drill Down example shows how to read data from a database as
well as submit changes, using the QSqlRelationalTableModel and
QDataWidgetMapper classes.
\borderedimage drilldown-example.png Screenshot of the Drill Down Example
When running the example application, a user can retrieve
information about each item by clicking the corresponding image.
The application pops up an information window displaying the data,
and allows the users to alter the description as well as the image.
The main view will be updated when the users submit their changes.
The example consists of three classes:
\list
\li \c ImageItem is a custom graphics item class used to
display the images.
\li \c View is the main application widget allowing the user to
browse through the various items.
\li \c InformationWindow displays the requested information,
allowing the users to alter it and submit their changes to the
database.
\endlist
We will first take a look at the \c InformationWindow class to see
how you can read and modify data from a database. Then we will
review the main application widget, i.e., the \c View class, and
the associated \c ImageItem class.
\section1 InformationWindow Class Definition
The \c InformationWindow class is a custom widget inheriting
QWidget:
\snippet drilldown/informationwindow.h 0
When we create an information window, we pass the associated
item ID, a parent, and a pointer to the database, to the
constructor. We will use the database pointer to populate our
window with data, while passing the parent parameter on to the
base class. The ID is stored for future reference.
Once a window is created, we will use the public \c id() function
to locate it whenever information for the given location is
requested. We will also use the ID to update the main application
widget when the users submit their changes to the database, i.e.,
we will emit a signal carrying the ID and file name as parameters
whenever the users changes the associated image.
\snippet drilldown/informationwindow.h 1
Since we allow the users to alter some of the data, we
must provide functionality for reverting and submitting their
changes. The \c enableButtons() slot is provided for convenience
to enable and disable the various buttons when required.
\snippet drilldown/informationwindow.h 2
The \c createButtons() function is also a convenience function,
provided to simplify the constructor. As mentioned above we store
the item ID for future reference. We also store the name of
the currently displayed image file to be able to determine when to
emit the \c imageChanged() signal.
The information window uses the QLabel class to display the name of
an item. The associated image file is displayed using a QComboBox
instance while the description is displayed using QTextEdit. In
addition, the window has three buttons to control the data flow and
whether the window is shown or not.
Finally, we declare a \e mapper. The QDataWidgetMapper class
provides mapping between a section of a data model to widgets. We
will use the mapper to extract data from the given database,
updating the database whenever the user modifies the data.
\section1 InformationWindow Class Implementation
The constructor takes three arguments: an item ID, a database
pointer and a parent widget. The database pointer is actually a
pointer to a QSqlRelationalTableModel object providing an editable
data model (with foreign key support) for our database table.
\snippet drilldown/informationwindow.cpp 0
\snippet drilldown/informationwindow.cpp 1
First we create the various widgets required to display the data
contained in the database. Most of the widgets are created in a
straight forward manner. But note the combobox displaying the
name of the image file:
\snippet drilldown/informationwindow.cpp 2
In this example, information about the items are stored in a
database table called "items". When creating the model,
we will use a foreign key to establish a relation between this
table and a second data base table, "images", containing the names
of the available image files. We will get back to how this is done
when reviewing the \c View class. The rationale for creating such
a relation though, is that we want to ensure that the user only
can choose between predefined image files.
The model corresponding to the "images" database table, is
available through the QSqlRelationalTableModel's \l
{QSqlRelationalTableModel::}{relationModel()} function, requiring
the foreign key (in this case the "imagefile" column number) as
argument. We use QComboBox's \l {QComboBox::}{setModel()} function
to make the combobox use the "images" model. And, since this model
has two columns ("itemid" and "file"), we also specify which
column we want to be visible using the QComboBox::setModelColumn()
function.
\snippet drilldown/informationwindow.cpp 3
Then we create the mapper. The QDataWidgetMapper class allows us
to create data-aware widgets by mapping them to sections of an
item model.
The \l {QDataWidgetMapper::}{addMapping()} function adds a mapping
between the given widget and the specified section of the
model. If the mapper's orientation is horizontal (the default) the
section is a column in the model, otherwise it is a row. We call
the \l {QDataWidgetMapper::}{setCurrentIndex()} function to
initialize the widgets with the data associated with the given
item ID. Every time the current index changes, all the widgets
are updated with the contents from the model.
We also set the mapper's submit policy to
QDataWidgetMapper::ManualSubmit. This means that no data is
submitted to the database until the user expliclity requests a
submit (the alternative is QDataWidgetMapper::AutoSubmit,
automatically submitting changes when the corresponding widget
loses focus). Finally, we specify the item delegate the mapper
view should use for its items. The QSqlRelationalDelegate class
represents a delegate that unlike the default delegate, enables
combobox functionality for fields that are foreign keys into other
tables (like "imagefile" in our "items" table).
\snippet drilldown/informationwindow.cpp 4
Finally, we connect the "something's changed" signals in the
editors to our custom \c enableButtons slot, enabling the users
to either submit or revert their changes.
We need to use lambdas for connecting the \c enableButtons slot
because its signature does not match \c QTextEdit::textChanged
and \c QComboBox::currentIndexChanged.
Since the latter has another overload with the signature
\c {const QString &} and the selected signal would be ambiguous,
we need to use \c QOverload<int>::of to select a specific overload
for \c currentIndexChanged.
We add all the widgets into a layout, store the item ID and the
name of the displayed image file for future reference, and set
the window title and initial size.
Note that we also set the Qt::Window window flag to indicate that
our widget is in fact a window, with a window system frame and a
title bar.
\snippet drilldown/informationwindow.cpp 5
When a window is created, it is not deleted until the main
application exits (i.e., if the user closes the information
window, it is only hidden). For this reason we do not want to
create more than one \c InformationWindow object for each
item, and we provide the public \c id() function to be able to
determine whether a window already exists for a given location
when the user requests information about it.
\snippet drilldown/informationwindow.cpp 6
The \c revert() slot is triggered whenever the user hits the \uicontrol
Revert button.
Since we set the QDataWidgetMapper::ManualSubmit submit policy,
none of the user's changes are written back to the model unless
the user expliclity choose to submit all of them. Nevertheless, we
can use the QDataWidgetMapper's \l {QDataWidgetMapper::}{revert()}
slot to reset the editor widgets, repopulating all widgets with
the current data of the model.
\snippet drilldown/informationwindow.cpp 7
Likewise, the \c submit() slot is triggered whenever the users
decide to submit their changes by pressing the \uicontrol Submit button.
We use QDataWidgetMapper's \l {QDataWidgetMapper::}{submit()} slot
to submit all changes from the mapped widgets to the model,
i.e. to the database. For every mapped section, the item delegate
will then read the current value from the widget and set it in the
model. Finally, the \e model's \l {QAbstractItemModel::}{submit()}
function is invoked to let the model know that it should submit
whatever it has cached to the permanent storage.
Note that before any data is submitted, we check if the user has
chosen another image file using the previously stored \c
displayedImage variable as reference. If the current and stored
file names differ, we store the new file name and emit the \c
imageChanged() signal.
\snippet drilldown/informationwindow.cpp 8
The \c createButtons() function is provided for convenience, i.e.,
to simplify the constructor.
We make the \uicontrol Close button the default button, i.e., the button
that is pressed when the user presses \uicontrol Enter, and connect its
\l {QPushButton::}{clicked()} signal to the widget's \l
{QWidget::}{close()} slot. As mentioned above closing the window
only hides the widget; it is not deleted. We also connect the \uicontrol
Submit and \uicontrol Revert buttons to the corresponding \c submit()
and \c revert() slots.
\snippet drilldown/informationwindow.cpp 9
The QDialogButtonBox class is a widget that presents buttons in a
layout that is appropriate to the current widget style. Dialogs
like our information window, typically present buttons in a layout
that conforms to the interface guidelines for that
platform. Invariably, different platforms have different layouts
for their dialogs. QDialogButtonBox allows us to add buttons,
automatically using the appropriate layout for the user's desktop
environment.
Most buttons for a dialog follow certain roles. We give the \uicontrol
Submit and \uicontrol Revert buttons the \l
{QDialogButtonBox::ButtonRole}{reset} role, i.e., indicating that
pressing the button resets the fields to the default values (in
our case the information contained in the database). The \l
{QDialogButtonBox::ButtonRole}{reject} role indicates that
clicking the button causes the dialog to be rejected. On the other
hand, since we only hide the information window, any changes that
the user has made will be preserved until the user explicitly
reverts or submits them.
\snippet drilldown/informationwindow.cpp 10
The \c enableButtons() slot is called to enable the buttons
whenever the user changes the presented data. Likewise, when the
user chooses to submit the changes, the buttons are disabled to
indicate that the current data is stored in the database.
This completes the \c InformationWindow class. Let's take a look
at how we have used it in our example application.
\section1 View Class Definition
The \c View class represents the main application window and
inherits QGraphicsView:
\snippet drilldown/view.h 0
\codeline
\snippet drilldown/view.h 1
The QGraphicsView class is part of the \l {Graphics View
Framework} which we will use to display the images. To be able to
respond to user interaction by displaying the appropriate
information window when the image is clicked, we reimplement
QGraphicsView's \l{QGraphicsView::}{mouseReleaseEvent()} function.
Note that the constructor expects the names of two database
tables: One containing the detailed information about the items,
and another containing the names of the available image files. We
also provide a private \c updateImage() slot to catch \c
{InformationWindow}'s \c imageChanged() signal that is emitted
whenever the user changes an image associated with the item.
\snippet drilldown/view.h 2
The \c addItems() function is a convenience function provided to
simplify the constructor. It is called only once, creating the
various items and adding them to the view.
The \c findWindow() function, on the other hand, is frequently
used. It is called from the \c showInformation() function to
detemine whether a window is already created for the given
item (whenever we create an \c InformationWindow object, we
store a reference to it in the \c informationWindows list). The
latter function is in turn called from our custom \c
mouseReleaseEvent() implementation.
\snippet drilldown/view.h 3
Finally, we declare a QSqlRelationalTableModel pointer. As
previously mentioned, the QSqlRelationalTableModel class provides
an editable data model with foreign key support. There are a
couple of things you should keep in mind when using the
QSqlRelationalTableModel class: The table must have a primary key
declared and this key cannot contain a relation to another table,
that is, it cannot be a foreign key. Also note that if a relational
table contains keys that refer to non-existent rows in the
referenced table, the rows containing the invalid keys will not be
exposed through the model. It is the user's or the database's
responsibility to maintain referential integrity.
\section1 View Class Implementation
Although the constructor requests the names of both the table
containing office details as well as the table containing the
names of the available image files, we only have to create a
QSqlRelationalTableModel object for the "items" table:
\snippet drilldown/view.cpp 0
The reason is that once we have a model with the item details,
we can create a relation to the available image files using
QSqlRelationalTableModel's \l
{QSqlRelationalTableModel::}{setRelation()} function. This
function creates a foreign key for the given model column. The key
is specified by the provided QSqlRelation object constructed by
the name of the table the key refers to, the field the key is
mapping to and the field that should be presented to the user.
Note that setting the table only specifies which table the model
operates on, i.e., we must explicitly call the model's \l
{QSqlRelationalTableModel::}{select()} function to populate our
model.
\snippet drilldown/view.cpp 1
Then we create the contents of our view, i.e., the scene and its
items. The labels are regular QGraphicsTextItem objects, whereas
the images are instances of the \c ImageItem class, derived from
QGraphicsPixmapItem. We will get back to this shortly when reviewing
the \c addItems() function.
Finally, we set the main application widget's size constraints and
window title.
\snippet drilldown/view.cpp 3
The \c addItems() function is called only once when creating the main
application window. For each row in the database table, we first
extract the corresponding record using the model's
\l {QSqlRelationalTableModel::}{record()} function. The QSqlRecord
class encapsulates both the functionality and characteristics of a
database record, and supports adding and removing fields as well
as setting and retrieving field values. The QSqlRecord::value()
function returns the value of the field with the given name or
index as a QVariant object.
For each record, we create a label item as well as an image item,
calculate their position and add them to the scene. The image
items are represented by instances of the \c ImageItem class. The
reason we must create a custom item class is that we want to catch
the item's hover events, animating the item when the mouse cursor
is hovering over the image (by default, no items accept hover
events). Please see the \l{Graphics View Framework} documentation
and the \l{Graphics View Examples} for more details.
\snippet drilldown/view.cpp 5
We reimplement QGraphicsView's \l
{QGraphicsView::}{mouseReleaseEvent()} event handler to respond to
user interaction. If the user clicks any of the image items, this
function calls the private \c showInformation() function to pop up
the associated information window.
The \l {Graphics View Framework} provides the qgraphicsitem_cast()
function to determine whether the given QGraphicsItem instance is
of a given type. Note that if the event is not related to any of
our image items, we pass it on to the base class implementation.
\snippet drilldown/view.cpp 6
The \c showInformation() function is given an \c ImageItem object
as argument, and starts off by extracting the item's item ID.
Then it determines if there already is created an information
window for this location.
If no window for the given location exists, we create one by
passing the item ID, a pointer to the model, and our view as a
parent, to the \c InformationWindow constructor. Note that we
connect the information window's \c imageChanged() signal to \e
this widget's \c updateImage() slot, before we give it a suitable
position and add it to the list of existing windows.
If there is a window for the given location, and that window is
visible, it ensures that the window is raised to the top of the
widget stack and activated. If it is hidden, calling its \l
{QWidget::}{show()} slot gives the same result.
\snippet drilldown/view.cpp 7
The \c updateImage() slot takes an item ID and the name of an
image file as arguments. It filters out the image items, and
updates the one that correspond to the given item ID, with the
provided image file.
\snippet drilldown/view.cpp 8
The \c findWindow() function simply searches through the list of
existing windows, returning a pointer to the window that matches
the given item ID, or 0 if the window doesn't exists.
Finally, let's take a quick look at our custom \c ImageItem class:
\section1 ImageItem Class Definition
The \c ImageItem class is provided to facilitate animation of the
image items. It inherits QGraphicsPixmapItem and reimplements its
hover event handlers:
\snippet drilldown/imageitem.h 0
In addition, we implement a public \c id() function to be able to
identify the associated location and a public \c adjust() function
that can be called to ensure that the image item is given the
preferred size regardless of the original image file.
The animation is implemented using the QTimeLine class together
with the event handlers and the private \c setFrame() slot: The
image item will expand when the mouse cursor hovers over it,
returning back to its original size when the cursor leaves its
borders.
Finally, we store the item ID that this particular record is
associated with as well as a z-value. In the \l {Graphics View
Framework}, an item's z-value determines its position in the item
stack. An item of high z-value will be drawn on top of an item
with a lower z-value if they share the same parent item. We also
provide an \c updateItemPosition() function to refresh the view
when required.
\section1 ImageItem Class Implementation
The \c ImageItem class is really only a QGraphicsPixmapItem with
some additional features, i.e., we can pass most of the
constructor's arguments (the pixmap, parent and scene) on to the
base class constructor:
\snippet drilldown/imageitem.cpp 0
Then we store the ID for future reference, and ensure that our
image item will accept hover events. Hover events are delivered
when there is no current mouse grabber item. They are sent when the
mouse cursor enters an item, when it moves around inside the item,
and when the cursor leaves an item. As we mentioned earlier, none
of the \l {Graphics View Framework}'s items accept hover
event's by default.
The QTimeLine class provides a timeline for controlling
animations. Its \l {QTimeLine::}{duration} property holds the
total duration of the timeline in milliseconds. By default, the
time line runs once from the beginning and towards the end. The
QTimeLine::setFrameRange() function sets the timeline's frame
counter; when the timeline is running, the \l
{QTimeLine::}{frameChanged()} signal is emitted each time the
frame changes. We set the duration and frame range for our
animation, and connect the time line's \l
{QTimeLine::}{frameChanged()} and \l {QTimeLine::}{finished()}
signals to our private \c setFrame() and \c updateItemPosition()
slots.
Finally, we call \c adjust() to ensure that the item is given the
preferred size.
\snippet drilldown/imageitem.cpp 1
\codeline
\snippet drilldown/imageitem.cpp 2
Whenever the mouse cursor enters or leaves the image item, the
corresponding event handlers are triggered: We first set the time
line's direction, making the item expand or shrink,
respectively. Then we alter the item's z-value if it is not already
set to the expected value.
In the case of hover \e enter events, we immediately update the
item's position since we want the item to appear on top of all
other items as soon as it starts expanding. In the case of hover
\e leave events, on the other hand, we postpone the actual update
to achieve the same result. But remember that when we constructed
our item, we connected the time line's \l
{QTimeLine::}{finished()} signal to the \c updateItemPosition()
slot. In this way the item is given the correct position in the
item stack once the animation is completed. Finally, if the time
line is not already running, we start it.
\snippet drilldown/imageitem.cpp 3
When the time line is running, it triggers the \c setFrame() slot
whenever the current frame changes due to the connection we
created in the item constructor. It is this slot that controls the
animation, expanding or shrinking the image item step by step.
We first call the \c adjust() function to ensure that we start off
with the item's original size. Then we scale the item with a
factor depending on the animation's progress (using the \c frame
parameter). Note that by default, the transformation will be
relative to the item's top-left corner. Since we want the item to
be transformed relative to its center, we must translate the
coordinate system before we scale the item.
In the end, only the following convenience functions remain:
\snippet drilldown/imageitem.cpp 4
\codeline
\snippet drilldown/imageitem.cpp 5
\codeline
\snippet drilldown/imageitem.cpp 6
The \c adjust() function defines and applies a transformation
matrix, ensuring that our image item appears with the preferred
size regardless of the size of the source image. The \c id()
function is trivial, and is simply provided to be able to identify
the item. In the \c updateItemPosition() slot we call the
QGraphicsItem::setZValue() function, setting the elevation of the
item.
*/