summaryrefslogtreecommitdiffstats
path: root/doc/src/examples/drilldown.qdoc
blob: abc3d6df270bc0f353bd4575e39bdc4784e228ab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the documentation of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the either Technology Preview License Agreement or the
** Beta Release License Agreement.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain
** additional rights. These rights are described in the Nokia Qt LGPL
** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
** package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://www.qtsoftware.com/contact.
** $QT_END_LICENSE$
**
****************************************************************************/

/*!
    \example sql/drilldown
    \title Drill Down Example

    The Drill Down example shows how to read data from a database as
    well as submit changes, using the QSqlRelationalTableModel and
    QDataWidgetMapper classes.

    \image drilldown-example.png Screenshot of the Drill Down Example

    When running the example application, a user can retrieve
    information about each of Nokia's Qt offices by clicking the
    corresponding image. The application pops up an information window
    displaying the data, and allows the users to alter the location
    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
        \o \c ImageItem is a custom graphics item class used to
        display the office images.

        \o \c View is the main application widget allowing the user to
        browse through the various locations.

        \o \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 examples/sql/drilldown/informationwindow.h 0

    When we create an information window, we pass the associated
    location 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 examples/sql/drilldown/informationwindow.h 1

    Since we allow the users to alter some of the location 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 examples/sql/drilldown/informationwindow.h 2

    The \c createButtons() function is also a convenience function,
    provided to simplify the constructor. As mentioned above we store
    the location 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 office
    location and the country. 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: a location 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 examples/sql/drilldown/informationwindow.cpp 0
    \snippet examples/sql/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 examples/sql/drilldown/informationwindow.cpp 2

    In this example, the information about the offices are stored in a
    database table called "offices". 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 ("locationid" and "file"), we also specify which
    column we want to be visible using the QComboBox::setModelColumn()
    function.

    \snippet examples/sql/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
    location 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
    looses 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 "trolltechoffices" table).

    \snippet examples/sql/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 add all the widgets
    into a layout, store the location 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 examples/sql/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
    location, 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 examples/sql/drilldown/informationwindow.cpp 6

    The \c revert() slot is triggered whenever the user hits the \gui
    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 examples/sql/drilldown/informationwindow.cpp 7

    Likewise, the \c submit() slot is triggered whenever the users
    decide to submit their changes by pressing the \gui 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 examples/sql/drilldown/informationwindow.cpp 8

    The \c createButtons() function is provided for convenience, i.e.,
    to simplify the constructor.

    We make the \gui Close button the default button, i.e., the button
    that is pressed when the user presses \gui 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 \gui
    Submit and \gui Revert buttons to the corresponding \c submit()
    and \c revert() slots.

    \snippet examples/sql/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 \gui
    Submit and \gui 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 wil be preserved until the user expliclity
    revert or submit them.

    \snippet examples/sql/drilldown/informationwindow.cpp 10

    The \c enableButtons() slot is called to enable the buttons
    whenever the user changes the presented data. Likewise, when the
    data the user choose 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 examples/sql/drilldown/view.h 0
    \codeline
    \snippet examples/sql/drilldown/view.h 1

    The QGraphicsView class is part of the \l {The Graphics View
    Framework} which we will use to display the images of Nokia's
    Qt offices. To be able to respond to user interaction;
    i.e., showing the
    appropriate information window whenever the user clicks one of the
    office images, 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 offices,
    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 a location's image.

    \snippet examples/sql/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
    location (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 examples/sql/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,
    i.e., it cannot be a foreign key. Note also 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 office table:

    \snippet examples/sql/drilldown/view.cpp 0

    The reason is that once we have a model with the office 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 examples/sql/drilldown/view.cpp 1

    Then we create the contents of our view, i.e., the scene and its
    items. The location labels are regular QGraphicsTextItem objects,
    and the "Qt" logo is represented by a QGraphicsPixmapItem
    object. The images, on the other hand, 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 examples/sql/drilldown/view.cpp 3

    The \c addItems() function is called only once, i.e., 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{The Graphics View Framework}
    documentation and the
    \l{Qt Examples#Graphics View}{Graphics View examples} for more
    details.

    \snippet examples/sql/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.

    \l {The 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 examples/sql/drilldown/view.cpp 6

    The \c showInformation() function is given an \c ImageItem object
    as argument, and starts off by extracting the item's location
    ID. Then it determines if there already is created an information
    window for this location. If it is, and the window is visible, it
    ensures that the window is raised to the top of the widget stack
    and activated. If the window exists but is hidden, calling its \l
    {QWidget::}{show()} slot gives the same result.

    If no window for the given location exists, we create one by
    passing the location 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.

    \snippet examples/sql/drilldown/view.cpp 7

    The \c updateImage() slot takes a location ID and the name of an
    image files as arguments. It filters out the image items, and
    updates the one that correspond to the given location ID, with the
    provided image file.

    \snippet examples/sql/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 location 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 examples/sql/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 orignal size when the cursor leaves its
    borders.

    Finally, we store the location ID that this particular record is
    associated with as well as a z-value. In the \l {The 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 examples/sql/drilldown/imageitem.cpp 0

    Then we store the ID for future reference, and ensure that our
    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 {The 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 examples/sql/drilldown/imageitem.cpp 1
    \codeline
    \snippet examples/sql/drilldown/imageitem.cpp 2

    Whenever the mouse cursor enters or leave 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 examples/sql/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 examples/sql/drilldown/imageitem.cpp 4
    \codeline
    \snippet examples/sql/drilldown/imageitem.cpp 5
    \codeline
    \snippet examples/sql/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 (i.e.,
    the position) of the item.
*/