Task-number: QTBUG-115174 Change-Id: Idd8ba8504efb17f0e4a11b3c36b991739251f2ef Reviewed-by: Venugopal Shivashankar <Venugopal.Shivashankar@qt.io> (cherry picked from commit c07a3f10ef27b42ab2f87bde74d86831c79317cf) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
524 lines
24 KiB
Plaintext
524 lines
24 KiB
Plaintext
// Copyright (C) 2016 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
|
|
|
/*!
|
|
\example drilldown
|
|
\title Drill Down Example
|
|
\ingroup sql_examples
|
|
\examplecategory {Data Processing & I/O}
|
|
|
|
\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
|
|
determine 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 \nullptr 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
|
|
|
|
We declare a \c Type enum value for our custom item and reimplement
|
|
\l{QGraphicsItem::}{type()}. This is done so we can safely use
|
|
qgraphicsitem_cast().
|
|
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.
|
|
*/
|