diff -r dee5afe5301f -r 3f74d0d4af4c doc/src/examples/drilldown.qdoc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/src/examples/drilldown.qdoc Thu Apr 08 14:19:33 2010 +0300 @@ -0,0 +1,550 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** 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 Technology Preview License Agreement accompanying +** this package. +** +** 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.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $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{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. +*/