|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (qt-info@nokia.com) |
|
6 ** |
|
7 ** This file is part of the documentation of the Qt Toolkit. |
|
8 ** |
|
9 ** $QT_BEGIN_LICENSE:LGPL$ |
|
10 ** No Commercial Usage |
|
11 ** This file contains pre-release code and may not be distributed. |
|
12 ** You may use this file in accordance with the terms and conditions |
|
13 ** contained in the Technology Preview License Agreement accompanying |
|
14 ** this package. |
|
15 ** |
|
16 ** GNU Lesser General Public License Usage |
|
17 ** Alternatively, this file may be used under the terms of the GNU Lesser |
|
18 ** General Public License version 2.1 as published by the Free Software |
|
19 ** Foundation and appearing in the file LICENSE.LGPL included in the |
|
20 ** packaging of this file. Please review the following information to |
|
21 ** ensure the GNU Lesser General Public License version 2.1 requirements |
|
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
23 ** |
|
24 ** In addition, as a special exception, Nokia gives you certain additional |
|
25 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
27 ** |
|
28 ** If you have questions regarding the use of this file, please contact |
|
29 ** Nokia at qt-info@nokia.com. |
|
30 ** |
|
31 ** |
|
32 ** |
|
33 ** |
|
34 ** |
|
35 ** |
|
36 ** |
|
37 ** |
|
38 ** $QT_END_LICENSE$ |
|
39 ** |
|
40 ****************************************************************************/ |
|
41 |
|
42 /*! |
|
43 \page tutorials-addressbook.html |
|
44 |
|
45 \startpage {index.html}{Qt Reference Documentation} |
|
46 \contentspage Tutorials |
|
47 \nextpage {tutorials/addressbook/part1}{Chapter 1} |
|
48 |
|
49 \title Address Book Tutorial |
|
50 \brief An introduction to GUI programming, showing how to put together a |
|
51 simple yet fully-functioning application. |
|
52 |
|
53 This tutorial gives an introduction to GUI programming using the Qt |
|
54 cross-platform framework. |
|
55 |
|
56 \image addressbook-tutorial-screenshot.png |
|
57 |
|
58 \omit |
|
59 It doesn't cover everything; the emphasis is on teaching the programming |
|
60 philosophy of GUI programming, and Qt's features are introduced as needed. |
|
61 Some commonly used features are never used in this tutorial. |
|
62 \endomit |
|
63 |
|
64 In the process, we will learn about some basic technologies provided by Qt, |
|
65 such as |
|
66 |
|
67 \list |
|
68 \o Widgets and layout managers |
|
69 \o Container classes |
|
70 \o Signals and slots |
|
71 \o Input and output devices |
|
72 \endlist |
|
73 |
|
74 If you are completely new to Qt, please read \l{How to Learn Qt} if you |
|
75 have not already done so. |
|
76 |
|
77 The tutorial's source code is located in Qt's \c examples/tutorials/addressbook |
|
78 directory. |
|
79 |
|
80 Tutorial chapters: |
|
81 |
|
82 \list 1 |
|
83 \o \l{tutorials/addressbook/part1}{Designing the User Interface} |
|
84 \o \l{tutorials/addressbook/part2}{Adding Addresses} |
|
85 \o \l{tutorials/addressbook/part3}{Navigating between Entries} |
|
86 \o \l{tutorials/addressbook/part4}{Editing and Removing Addresses} |
|
87 \o \l{tutorials/addressbook/part5}{Adding a Find Function} |
|
88 \o \l{tutorials/addressbook/part6}{Loading and Saving} |
|
89 \o \l{tutorials/addressbook/part7}{Additional Features} |
|
90 \endlist |
|
91 |
|
92 Although this little application does not look much like a fully-fledged |
|
93 modern GUI application, it uses many of the basic techniques that are used |
|
94 in more complex applications. After you have worked through it, we |
|
95 recommend checking out the \l{mainwindows/application}{Application} |
|
96 example, which presents a small GUI application, with menus, toolbars, a |
|
97 status bar, and so on. |
|
98 */ |
|
99 |
|
100 /*! |
|
101 \page tutorials-addressbook-part1.html |
|
102 \contentspage {Address Book Tutorial}{Contents} |
|
103 \nextpage {tutorials/addressbook/part2}{Chapter 2} |
|
104 \example tutorials/addressbook/part1 |
|
105 \title Address Book 1 - Designing the User Interface |
|
106 |
|
107 The first part of this tutorial covers the design of the basic graphical |
|
108 user interface (GUI) we use for the Address Book application. |
|
109 |
|
110 The first step to creating a GUI program is to design the user interface. |
|
111 In this chapter, our goal is to set up the labels and input fields needed |
|
112 to implement a basic address book application. The figure below is a |
|
113 screenshot of our expected output. |
|
114 |
|
115 \image addressbook-tutorial-part1-screenshot.png |
|
116 |
|
117 We require two QLabel objects, \c nameLabel and \c addressLabel, as well |
|
118 as two input fields, a QLineEdit object, \c nameLine, and a QTextEdit |
|
119 object, \c addressText, to enable the user to enter a contact's name and |
|
120 address. The widgets used and their positions are shown in the figure |
|
121 below. |
|
122 |
|
123 \image addressbook-tutorial-part1-labeled-screenshot.png |
|
124 |
|
125 There are three files used to implement this address book: |
|
126 |
|
127 \list |
|
128 \o \c{addressbook.h} - the definition file for the \c AddressBook |
|
129 class, |
|
130 \o \c{addressbook.cpp} - the implementation file for the |
|
131 \c AddressBook class, and |
|
132 \o \c{main.cpp} - the file containing a \c main() function, with |
|
133 an instance of \c AddressBook. |
|
134 \endlist |
|
135 |
|
136 \section1 Qt Programming - Subclassing |
|
137 |
|
138 When writing Qt programs, we usually subclass Qt objects to add |
|
139 functionality. This is one of the essential concepts behind creating |
|
140 custom widgets or collections of standard widgets. Subclassing to |
|
141 extend or change the behavior of a widget has the following advantages: |
|
142 |
|
143 \list |
|
144 \o We can write implementations of virtual or pure virtual functions to |
|
145 obtain exactly what we need, falling back on the base class's implementation |
|
146 when necessary. |
|
147 \o It allows us to encapsulate parts of the user interface within a class, |
|
148 so that the other parts of the application don't need to know about the |
|
149 individual widgets in the user interface. |
|
150 \o The subclass can be used to create multiple custom widgets in the same |
|
151 application or library, and the code for the subclass can be reused in other |
|
152 projects. |
|
153 \endlist |
|
154 |
|
155 Since Qt does not provide a specific address book widget, we subclass a |
|
156 standard Qt widget class and add features to it. The \c AddressBook class |
|
157 we create in this tutorial can be reused in situations where a basic address |
|
158 book widget is needed. |
|
159 |
|
160 \section1 Defining the AddressBook Class |
|
161 |
|
162 The \l{tutorials/addressbook/part1/addressbook.h}{\c addressbook.h} file is |
|
163 used to define the \c AddressBook class. |
|
164 |
|
165 We start by defining \c AddressBook as a QWidget subclass and declaring |
|
166 a constructor. We also use the Q_OBJECT macro to indicate that the class |
|
167 uses internationalization and Qt's signals and slots features, even |
|
168 if we do not use all of these features at this stage. |
|
169 |
|
170 \snippet tutorials/addressbook/part1/addressbook.h class definition |
|
171 |
|
172 The class holds declarations of \c nameLine and \c addressText, the |
|
173 private instances of QLineEdit and QTextEdit mentioned earlier. |
|
174 You will see, in the coming chapters, that data stored in \c nameLine and |
|
175 \c addressText is needed for many of the address book's functions. |
|
176 |
|
177 We do not need to include declarations of the QLabel objects we will use |
|
178 because we will not need to reference them once they have been created. |
|
179 The way Qt tracks the ownership of objects is explained in the next section. |
|
180 |
|
181 The Q_OBJECT macro itself implements some of the more advanced features of Qt. |
|
182 For now, it is useful to think of the Q_OBJECT macro as a shortcut which allows |
|
183 us to use the \l{QObject::}{tr()} and \l{QObject::}{connect()} functions. |
|
184 |
|
185 We have now completed the \c addressbook.h file and we move on to |
|
186 implement the corresponding \c addressbook.cpp file. |
|
187 |
|
188 \section1 Implementing the AddressBook Class |
|
189 |
|
190 The constructor of \c AddressBook accepts a QWidget parameter, \a parent. |
|
191 By convention, we pass this parameter to the base class's constructor. |
|
192 This concept of ownership, where a parent can have one or more children, |
|
193 is useful for grouping widgets in Qt. For example, if you delete a parent, |
|
194 all of its children will be deleted as well. |
|
195 |
|
196 \snippet tutorials/addressbook/part1/addressbook.cpp constructor and input fields |
|
197 |
|
198 Within this constructor, we declare and instantiate two local QLabel objects, |
|
199 \c nameLabel and \c addressLabel, as well as instantiate \c nameLine and |
|
200 \c addressText. The |
|
201 \l{QObject::tr()}{tr()} function returns a translated version of the |
|
202 string, if there is one available; otherwise, it returns the string itself. |
|
203 Think of this function as an \c{<insert translation here>} marker to mark |
|
204 QString objects for translation. You will notice, in the coming chapters as |
|
205 well as in the \l{Qt Examples}, that we include it whenever we use a |
|
206 translatable string. |
|
207 |
|
208 When programming with Qt, it is useful to know how layouts work. |
|
209 Qt provides three main layout classes: QHBoxLayout, QVBoxLayout |
|
210 and QGridLayout to handle the positioning of widgets. |
|
211 |
|
212 \image addressbook-tutorial-part1-labeled-layout.png |
|
213 |
|
214 We use a QGridLayout to position our labels and input fields in a |
|
215 structured manner. QGridLayout divides the available space into a grid and |
|
216 places widgets in the cells we specify with row and column numbers. The |
|
217 diagram above shows the layout cells and the position of our widgets, and |
|
218 we specify this arrangement using the following code: |
|
219 |
|
220 \snippet tutorials/addressbook/part1/addressbook.cpp layout |
|
221 |
|
222 Notice that \c addressLabel is positioned using Qt::AlignTop as an |
|
223 additional argument. This is to make sure it is not vertically centered in |
|
224 cell (1,0). For a basic overview on Qt Layouts, refer to the |
|
225 \l{Layout Management} documentation. |
|
226 |
|
227 In order to install the layout object onto the widget, we have to invoke |
|
228 the widget's \l{QWidget::setLayout()}{setLayout()} function: |
|
229 |
|
230 \snippet tutorials/addressbook/part1/addressbook.cpp setting the layout |
|
231 |
|
232 Lastly, we set the widget's title to "Simple Address Book". |
|
233 |
|
234 \section1 Running the Application |
|
235 |
|
236 A separate file, \c main.cpp, is used for the \c main() function. Within |
|
237 this function, we instantiate a QApplication object, \c app. QApplication |
|
238 is responsible for various application-wide resources, such as the default |
|
239 font and cursor, and for running an event loop. Hence, there is always one |
|
240 QApplication object in every GUI application using Qt. |
|
241 |
|
242 \snippet tutorials/addressbook/part1/main.cpp main function |
|
243 |
|
244 We construct a new \c AddressBook widget on the stack and invoke |
|
245 its \l{QWidget::show()}{show()} function to display it. |
|
246 However, the widget will not be shown until the application's event loop |
|
247 is started. We start the event loop by calling the application's |
|
248 \l{QApplication::}{exec()} function; the result returned by this function |
|
249 is used as the return value from the \c main() function. At this point, |
|
250 it becomes apparent why we instanciated \c AddressBook on the stack: It |
|
251 will now go out of scope. Therefore, \c AddressBook and all its child widgets |
|
252 will be deleted, thus preventing memory leaks. |
|
253 */ |
|
254 |
|
255 /*! |
|
256 \page tutorials-addressbook-part2.html |
|
257 \previouspage Address Book 1 - Designing the User Interface |
|
258 \contentspage {Address Book Tutorial}{Contents} |
|
259 \nextpage {tutorials/addressbook/part3}{Chapter 3} |
|
260 \example tutorials/addressbook/part2 |
|
261 \title Address Book 2 - Adding Addresses |
|
262 |
|
263 The next step to creating our basic address book application is to allow |
|
264 a little bit of user interaction. |
|
265 |
|
266 \image addressbook-tutorial-part2-add-contact.png |
|
267 |
|
268 We will provide a push button that the user can click to add a new contact. |
|
269 Also, some form of data structure is needed to store these contacts in an |
|
270 organized way. |
|
271 |
|
272 \section1 Defining the AddressBook Class |
|
273 |
|
274 Now that we have the labels and input fields set up, we add push buttons to |
|
275 complete the process of adding a contact. This means that our |
|
276 \c addressbook.h file now has three QPushButton objects declared and three |
|
277 corresponding public slots. |
|
278 |
|
279 \snippet tutorials/addressbook/part2/addressbook.h slots |
|
280 |
|
281 A slot is a function that responds to a particular signal. We will discuss |
|
282 this concept in further detail when implementing the \c AddressBook class. |
|
283 However, for an overview of Qt's signals and slots concept, you can refer |
|
284 to the \l{Signals and Slots} document. |
|
285 |
|
286 Three QPushButton objects: \c addButton, \c submitButton and |
|
287 \c cancelButton, are now included in our private variable declarations, |
|
288 along with \c nameLine and \c addressText from the last chapter. |
|
289 |
|
290 \snippet tutorials/addressbook/part2/addressbook.h pushbutton declaration |
|
291 |
|
292 We need a container to store our address book contacts, so that we can |
|
293 traverse and display them. A QMap object, \c contacts, is used for this |
|
294 purpose as it holds a key-value pair: the contact's name as the \e key, |
|
295 and the contact's address as the \e{value}. |
|
296 |
|
297 \snippet tutorials/addressbook/part2/addressbook.h remaining private variables |
|
298 |
|
299 We also declare two private QString objects, \c oldName and \c oldAddress. |
|
300 These objects are needed to hold the name and address of the contact that |
|
301 was last displayed, before the user clicked \gui Add. So, when the user clicks |
|
302 \gui Cancel, we can revert to displaying the details of the last contact. |
|
303 |
|
304 \section1 Implementing the AddressBook Class |
|
305 |
|
306 Within the constructor of \c AddressBook, we set the \c nameLine and |
|
307 \c addressText to read-only, so that we can only display but not edit |
|
308 existing contact details. |
|
309 |
|
310 \dots |
|
311 \snippet tutorials/addressbook/part2/addressbook.cpp setting readonly 1 |
|
312 \dots |
|
313 \snippet tutorials/addressbook/part2/addressbook.cpp setting readonly 2 |
|
314 |
|
315 Then, we instantiate our push buttons: \c addButton, \c submitButton, and |
|
316 \c cancelButton. |
|
317 |
|
318 \snippet tutorials/addressbook/part2/addressbook.cpp pushbutton declaration |
|
319 |
|
320 The \c addButton is displayed by invoking the \l{QPushButton::show()} |
|
321 {show()} function, while the \c submitButton and \c cancelButton are |
|
322 hidden by invoking \l{QPushButton::hide()}{hide()}. These two push |
|
323 buttons will only be displayed when the user clicks \gui Add and this is |
|
324 handled by the \c addContact() function discussed below. |
|
325 |
|
326 \snippet tutorials/addressbook/part2/addressbook.cpp connecting signals and slots |
|
327 |
|
328 We connect the push buttons' \l{QPushButton::clicked()}{clicked()} signal |
|
329 to their respective slots. The figure below illustrates this. |
|
330 |
|
331 \image addressbook-tutorial-part2-signals-and-slots.png |
|
332 |
|
333 Next, we arrange our push buttons neatly to the right of our address book |
|
334 widget, using a QVBoxLayout to line them up vertically. |
|
335 |
|
336 \snippet tutorials/addressbook/part2/addressbook.cpp vertical layout |
|
337 |
|
338 The \l{QBoxLayout::addStretch()}{addStretch()} function is used to ensure |
|
339 the push buttons are not evenly spaced, but arranged closer to the top of |
|
340 the widget. The figure below shows the difference between using |
|
341 \l{QBoxLayout::addStretch()}{addStretch()} and not using it. |
|
342 |
|
343 \image addressbook-tutorial-part2-stretch-effects.png |
|
344 |
|
345 We then add \c buttonLayout1 to \c mainLayout, using |
|
346 \l{QGridLayout::addLayout()}{addLayout()}. This gives us nested layouts |
|
347 as \c buttonLayout1 is now a child of \c mainLayout. |
|
348 |
|
349 \snippet tutorials/addressbook/part2/addressbook.cpp grid layout |
|
350 |
|
351 Our layout coordinates now look like this: |
|
352 |
|
353 \image addressbook-tutorial-part2-labeled-layout.png |
|
354 |
|
355 In the \c addContact() function, we store the last displayed contact |
|
356 details in \c oldName and \c oldAddress. Then we clear these input |
|
357 fields and turn off the read-only mode. The focus is set on \c nameLine |
|
358 and we display \c submitButton and \c cancelButton. |
|
359 |
|
360 \snippet tutorials/addressbook/part2/addressbook.cpp addContact |
|
361 |
|
362 The \c submitContact() function can be divided into three parts: |
|
363 |
|
364 \list 1 |
|
365 \o We extract the contact's details from \c nameLine and \c addressText |
|
366 and store them in QString objects. We also validate to make sure that the |
|
367 user did not click \gui Submit with empty input fields; otherwise, a |
|
368 QMessageBox is displayed to remind the user for a name and address. |
|
369 |
|
370 \snippet tutorials/addressbook/part2/addressbook.cpp submitContact part1 |
|
371 |
|
372 \o We then proceed to check if the contact already exists. If it does not |
|
373 exist, we add the contact to \c contacts and we display a QMessageBox to |
|
374 inform the user that the contact has been added. |
|
375 |
|
376 \snippet tutorials/addressbook/part2/addressbook.cpp submitContact part2 |
|
377 |
|
378 If the contact already exists, again, we display a QMessageBox to inform |
|
379 the user about this, preventing the user from adding duplicate contacts. |
|
380 Our \c contacts object is based on key-value pairs of name and address, |
|
381 hence, we want to ensure that \e key is unique. |
|
382 |
|
383 \o Once we have handled both cases mentioned above, we restore the push |
|
384 buttons to their normal state with the following code: |
|
385 |
|
386 \snippet tutorials/addressbook/part2/addressbook.cpp submitContact part3 |
|
387 |
|
388 \endlist |
|
389 |
|
390 The screenshot below shows the QMessageBox object we use to display |
|
391 information messages to the user. |
|
392 |
|
393 \image addressbook-tutorial-part2-add-successful.png |
|
394 |
|
395 The \c cancel() function restores the last displayed contact details and |
|
396 enables \c addButton, as well as hides \c submitButton and |
|
397 \c cancelButton. |
|
398 |
|
399 \snippet tutorials/addressbook/part2/addressbook.cpp cancel |
|
400 |
|
401 The general idea behind adding a contact is to give the user the |
|
402 flexibility to click \gui Submit or \gui Cancel at any time. The flowchart below |
|
403 further explains this concept: |
|
404 |
|
405 \image addressbook-tutorial-part2-add-flowchart.png |
|
406 */ |
|
407 |
|
408 /*! |
|
409 \page tutorials-addressbook-part3.html |
|
410 \previouspage Address Book 2 - Adding Addresses |
|
411 \contentspage {Address Book Tutorial}{Contents} |
|
412 \nextpage {tutorials/addressbook/part4}{Chapter 4} |
|
413 \example tutorials/addressbook/part3 |
|
414 \title Address Book 3 - Navigating between Entries |
|
415 |
|
416 The address book application is now half complete. We need to add some |
|
417 functions to navigate between contacts. But first, we have to decide |
|
418 what sort of a data structure we would like to use to hold these contacts. |
|
419 |
|
420 In Chapter 2, we used a QMap of key-value pairs with the contact's name |
|
421 as the \e key, and the contact's address as the \e value. This works well |
|
422 for our case. However, in order to navigate and display each entry, a |
|
423 little bit of enhancement is needed. |
|
424 |
|
425 We enhance the QMap by making it replicate a data structure similar to a |
|
426 circularly-linked list, where all elements are connected, including the |
|
427 first element and the last element. The figure below illustrates this data |
|
428 structure. |
|
429 |
|
430 \image addressbook-tutorial-part3-linkedlist.png |
|
431 |
|
432 \section1 Defining the AddressBook Class |
|
433 |
|
434 In order to add navigation functions to the address book application, we |
|
435 need to add two more slots to our \c AddressBook class: \c next() and |
|
436 \c previous(). These are added to our \c addressbook.h file: |
|
437 |
|
438 \snippet tutorials/addressbook/part3/addressbook.h navigation functions |
|
439 |
|
440 We also require another two QPushButton objects, so we declare \c nextButton |
|
441 and \c previousButton as private variables: |
|
442 |
|
443 \snippet tutorials/addressbook/part3/addressbook.h navigation pushbuttons |
|
444 |
|
445 \section1 Implementing the AddressBook Class |
|
446 |
|
447 In the \c AddressBook constructor in \c addressbook.cpp, we instantiate |
|
448 \c nextButton and \c previousButton and disable them by default. This is |
|
449 because navigation is only enabled when there is more than one contact |
|
450 in the address book. |
|
451 |
|
452 \snippet tutorials/addressbook/part3/addressbook.cpp navigation pushbuttons |
|
453 |
|
454 We then connect these push buttons to their respective slots: |
|
455 |
|
456 \snippet tutorials/addressbook/part3/addressbook.cpp connecting navigation signals |
|
457 |
|
458 The image below is our expected graphical user interface. Notice that it |
|
459 is getting closer to our final application. |
|
460 |
|
461 \image addressbook-tutorial-part3-screenshot.png |
|
462 |
|
463 We follow basic conventions for \c next() and \c previous() functions by |
|
464 placing the \c nextButton on the right and the \c previousButton on the |
|
465 left. In order to achieve this intuitive layout, we use QHBoxLayout to |
|
466 place the widgets side-by-side: |
|
467 |
|
468 \snippet tutorials/addressbook/part3/addressbook.cpp navigation layout |
|
469 |
|
470 The QHBoxLayout object, \c buttonLayout2, is then added to \c mainLayout. |
|
471 |
|
472 \snippet tutorials/addressbook/part3/addressbook.cpp adding navigation layout |
|
473 |
|
474 The figure below shows the coordinates of the widgets in \c mainLayout. |
|
475 \image addressbook-tutorial-part3-labeled-layout.png |
|
476 |
|
477 Within our \c addContact() function, we have to disable these buttons so |
|
478 that the user does not attempt to navigate while adding a contact. |
|
479 |
|
480 \snippet tutorials/addressbook/part3/addressbook.cpp disabling navigation |
|
481 |
|
482 Also, in our \c submitContact() function, we enable the navigation |
|
483 buttons, \c nextButton and \c previousButton, depending on the size |
|
484 of \c contacts. As mentioned earlier, navigation is only enabled when |
|
485 there is more than one contact in the address book. The following lines |
|
486 of code demonstrates how to do this: |
|
487 |
|
488 \snippet tutorials/addressbook/part3/addressbook.cpp enabling navigation |
|
489 |
|
490 We also include these lines of code in the \c cancel() function. |
|
491 |
|
492 Recall that we intend to emulate a circularly-linked list with our QMap |
|
493 object, \c contacts. So, in the \c next() function, we obtain an iterator |
|
494 for \c contacts and then: |
|
495 |
|
496 \list |
|
497 \o If the iterator is not at the end of \c contacts, we increment it |
|
498 by one. |
|
499 \o If the iterator is at the end of \c contacts, we move it to the |
|
500 beginning of \c contacts. This gives us the illusion that our QMap is |
|
501 working like a circularly-linked list. |
|
502 \endlist |
|
503 |
|
504 \snippet tutorials/addressbook/part3/addressbook.cpp next() function |
|
505 |
|
506 Once we have iterated to the correct object in \c contacts, we display |
|
507 its contents on \c nameLine and \c addressText. |
|
508 |
|
509 Similarly, for the \c previous() function, we obtain an iterator for |
|
510 \c contacts and then: |
|
511 |
|
512 \list |
|
513 \o If the iterator is at the end of \c contacts, we clear the |
|
514 display and return. |
|
515 \o If the iterator is at the beginning of \c contacts, we move it to |
|
516 the end. |
|
517 \o We then decrement the iterator by one. |
|
518 \endlist |
|
519 |
|
520 \snippet tutorials/addressbook/part3/addressbook.cpp previous() function |
|
521 |
|
522 Again, we display the contents of the current object in \c contacts. |
|
523 |
|
524 */ |
|
525 |
|
526 /*! |
|
527 \page tutorials-addressbook-part4.html |
|
528 \previouspage Address Book 3 - Navigating between Entries |
|
529 \contentspage {Address Book Tutorial}{Contents} |
|
530 \nextpage {tutorials/addressbook/part5}{Chapter 5} |
|
531 \example tutorials/addressbook/part4 |
|
532 \title Address Book 4 - Editing and Removing Addresses |
|
533 |
|
534 In this chapter, we look at ways to modify the contents of contacts stored |
|
535 in the address book application. |
|
536 |
|
537 \image addressbook-tutorial-screenshot.png |
|
538 |
|
539 We now have an address book that not only holds contacts in an organized |
|
540 manner, but also allows navigation. It would be convenient to include |
|
541 edit and remove functions so that a contact's details can be changed |
|
542 when needed. However, this requires a little improvement, in the form of |
|
543 enums. In our previous chapters, we had two modes: \c{AddingMode} and |
|
544 \c{NavigationMode} - but they were not defined as enums. Instead, we |
|
545 enabled and disabled the corresponding buttons manually, resulting in |
|
546 multiple lines of repeated code. |
|
547 |
|
548 In this chapter, we define the \c Mode enum with three different values: |
|
549 |
|
550 \list |
|
551 \o \c{NavigationMode}, |
|
552 \o \c{AddingMode}, and |
|
553 \o \c{EditingMode}. |
|
554 \endlist |
|
555 |
|
556 \section1 Defining the AddressBook Class |
|
557 |
|
558 The \c addressbook.h file is updated to contain the \c Mode enum: |
|
559 |
|
560 \snippet tutorials/addressbook/part4/addressbook.h Mode enum |
|
561 |
|
562 We also add two new slots, \c editContact() and \c removeContact(), to |
|
563 our current list of public slots. |
|
564 |
|
565 \snippet tutorials/addressbook/part4/addressbook.h edit and remove slots |
|
566 |
|
567 In order to switch between modes, we introduce the \c updateInterface() function |
|
568 to control the enabling and disabling of all QPushButton objects. We also |
|
569 add two new push buttons, \c editButton and \c removeButton, for the edit |
|
570 and remove functions mentioned earlier. |
|
571 |
|
572 \snippet tutorials/addressbook/part4/addressbook.h updateInterface() declaration |
|
573 \dots |
|
574 \snippet tutorials/addressbook/part4/addressbook.h buttons declaration |
|
575 \dots |
|
576 \snippet tutorials/addressbook/part4/addressbook.h mode declaration |
|
577 |
|
578 Lastly, we declare \c currentMode to keep track of the enum's current mode. |
|
579 |
|
580 \section1 Implementing the AddressBook Class |
|
581 |
|
582 We now have to implement the mode-changing features of the address book |
|
583 application. The \c editButton and \c removeButton are instantiated and |
|
584 disabled by default, as the address book starts up with zero contacts in |
|
585 memory. |
|
586 |
|
587 \snippet tutorials/addressbook/part4/addressbook.cpp edit and remove buttons |
|
588 |
|
589 These buttons are then connected to their respective slots, \c editContact() |
|
590 and \c removeContact(), and we add them to \c buttonLayout1. |
|
591 |
|
592 \snippet tutorials/addressbook/part4/addressbook.cpp connecting edit and remove |
|
593 \dots |
|
594 \snippet tutorials/addressbook/part4/addressbook.cpp adding edit and remove to the layout |
|
595 |
|
596 The \c editContact() function stores the contact's old details in |
|
597 \c oldName and \c oldAddress, before switching the mode to \c EditingMode. |
|
598 In this mode, the \c submitButton and \c cancelButton are both enabled, |
|
599 hence, the user can change the contact's details and click either button. |
|
600 |
|
601 \snippet tutorials/addressbook/part4/addressbook.cpp editContact() function |
|
602 |
|
603 The \c submitContact() function has been divided in two with an \c{if-else} |
|
604 statement. We check \c currentMode to see if it's in \c AddingMode. If it is, |
|
605 we proceed with our adding process. |
|
606 |
|
607 \snippet tutorials/addressbook/part4/addressbook.cpp submitContact() function beginning |
|
608 \dots |
|
609 \snippet tutorials/addressbook/part4/addressbook.cpp submitContact() function part1 |
|
610 |
|
611 Otherwise, we check to see if \c currentMode is in \c EditingMode. If it |
|
612 is, we compare \c oldName with \c name. If the name has changed, we remove |
|
613 the old contact from \c contacts and insert the newly updated contact. |
|
614 |
|
615 \snippet tutorials/addressbook/part4/addressbook.cpp submitContact() function part2 |
|
616 |
|
617 If only the address has changed (i.e., \c oldAddress is not the same as \c address), |
|
618 we update the contact's address. Lastly, we set \c currentMode to |
|
619 \c NavigationMode. This is an important step as it re-enables all the |
|
620 disabled push buttons. |
|
621 |
|
622 To remove a contact from the address book, we implement the |
|
623 \c removeContact() function. This function checks to see if the contact |
|
624 exists in \c contacts. |
|
625 |
|
626 \snippet tutorials/addressbook/part4/addressbook.cpp removeContact() function |
|
627 |
|
628 If it does, we display a QMessageBox, to confirm the removal with the |
|
629 user. Once the user has confirmed, we call \c previous() to ensure that the |
|
630 user interface shows another contact, and we remove the contact using \l{QMap}'s |
|
631 \l{QMap::remove()}{remove()} function. As a courtesy, we display a QMessageBox |
|
632 to inform the user. Both the message boxes used in this function are shown below: |
|
633 |
|
634 \image addressbook-tutorial-part4-remove.png |
|
635 |
|
636 \section2 Updating the User Interface |
|
637 |
|
638 We mentioned the \c updateInterface() function earlier as a means to |
|
639 enable and disable the push buttons depending on the current mode. |
|
640 The function updates the current mode according to the \c mode argument |
|
641 passed to it, assigning it to \c currentMode before checking its value. |
|
642 |
|
643 Each of the push buttons is then enabled or disabled, depending on the |
|
644 current mode. The code for \c AddingMode and \c EditingMode is shown below: |
|
645 |
|
646 \snippet tutorials/addressbook/part4/addressbook.cpp update interface() part 1 |
|
647 |
|
648 For \c NavigationMode, however, we include conditions within the parameters |
|
649 of the QPushButton::setEnabled() function. This is to ensure that |
|
650 \c editButton and \c removeButton are enabled when there is at least one |
|
651 contact in the address book; \c nextButton and \c previousButton are only |
|
652 enabled when there is more than one contact in the address book. |
|
653 |
|
654 \snippet tutorials/addressbook/part4/addressbook.cpp update interface() part 2 |
|
655 |
|
656 By performing the task of setting the mode and updating the user interface in |
|
657 the same function, we avoid the possibility of the user interface getting "out |
|
658 of sync" with the internal state of the application. |
|
659 */ |
|
660 |
|
661 /*! |
|
662 \page tutorials-addressbook-part5.html |
|
663 \previouspage Address Book 4 - Editing and Removing Addresses |
|
664 \contentspage {Address Book Tutorial}{Contents} |
|
665 \nextpage {tutorials/addressbook/part6}{Chapter 6} |
|
666 \example tutorials/addressbook/part5 |
|
667 \title Address Book 5 - Adding a Find Function |
|
668 |
|
669 In this chapter, we look at ways to locate contacts and addresses in |
|
670 the address book application. |
|
671 |
|
672 \image addressbook-tutorial-part5-screenshot.png |
|
673 |
|
674 As we keep adding contacts to our address book application, it becomes |
|
675 tedious to navigate them with the \e Next and \e Previous buttons. In this |
|
676 case, a \e Find function would be more efficient in looking up contacts. |
|
677 The screenshot above shows the \e Find button and its position on the panel |
|
678 of buttons. |
|
679 |
|
680 When the user clicks on the \e Find button, it is useful to display a |
|
681 dialog that can prompt the user for a contact's name. Qt provides QDialog, |
|
682 which we subclass in this chapter, to implement a \c FindDialog class. |
|
683 |
|
684 \section1 Defining the FindDialog Class |
|
685 |
|
686 \image addressbook-tutorial-part5-finddialog.png |
|
687 |
|
688 In order to subclass QDialog, we first include the header for QDialog in |
|
689 the \c finddialog.h file. Also, we use forward declaration to declare |
|
690 QLineEdit and QPushButton since we will be using those widgets in our |
|
691 dialog class. |
|
692 |
|
693 As in our \c AddressBook class, the \c FindDialog class includes |
|
694 the Q_OBJECT macro and its constructor is defined to accept a parent |
|
695 QWidget, even though the dialog will be opened as a separate window. |
|
696 |
|
697 \snippet tutorials/addressbook/part5/finddialog.h FindDialog header |
|
698 |
|
699 We define a public function, \c getFindText(), to be used by classes that |
|
700 instantiate \c FindDialog. This function allows these classes to obtain the |
|
701 search string entered by the user. A public slot, \c findClicked(), is also |
|
702 defined to handle the search string when the user clicks the \gui Find |
|
703 button. |
|
704 |
|
705 Lastly, we define the private variables, \c findButton, \c lineEdit |
|
706 and \c findText, corresponding to the \gui Find button, the line edit |
|
707 into which the user types the search string, and an internal string |
|
708 used to store the search string for later use. |
|
709 |
|
710 \section1 Implementing the FindDialog Class |
|
711 |
|
712 Within the constructor of \c FindDialog, we set up the private variables, |
|
713 \c lineEdit, \c findButton and \c findText. We use a QHBoxLayout to |
|
714 position the widgets. |
|
715 |
|
716 \snippet tutorials/addressbook/part5/finddialog.cpp constructor |
|
717 |
|
718 We set the layout and window title, as well as connect the signals to their |
|
719 respective slots. Notice that \c{findButton}'s \l{QPushButton::clicked()} |
|
720 {clicked()} signal is connected to to \c findClicked() and |
|
721 \l{QDialog::accept()}{accept()}. The \l{QDialog::accept()}{accept()} slot |
|
722 provided by QDialog hides the dialog and sets the result code to |
|
723 \l{QDialog::}{Accepted}. We use this function to help \c{AddressBook}'s |
|
724 \c findContact() function know when the \c FindDialog object has been |
|
725 closed. We will explain this logic in further detail when discussing the |
|
726 \c findContact() function. |
|
727 |
|
728 \image addressbook-tutorial-part5-signals-and-slots.png |
|
729 |
|
730 In \c findClicked(), we validate \c lineEdit to ensure that the user |
|
731 did not click the \gui Find button without entering a contact's name. Then, we set |
|
732 \c findText to the search string, extracted from \c lineEdit. After that, |
|
733 we clear the contents of \c lineEdit and hide the dialog. |
|
734 |
|
735 \snippet tutorials/addressbook/part5/finddialog.cpp findClicked() function |
|
736 |
|
737 The \c findText variable has a public getter function, \c getFindText(), |
|
738 associated with it. Since we only ever set \c findText directly in both the |
|
739 constructor and in the \c findClicked() function, we do not create a |
|
740 setter function to accompany \c getFindText(). |
|
741 Because \c getFindText() is public, classes instantiating and using |
|
742 \c FindDialog can always access the search string that the user has |
|
743 entered and accepted. |
|
744 |
|
745 \snippet tutorials/addressbook/part5/finddialog.cpp getFindText() function |
|
746 |
|
747 \section1 Defining the AddressBook Class |
|
748 |
|
749 To ensure we can use \c FindDialog from within our \c AddressBook class, we |
|
750 include \c finddialog.h in the \c addressbook.h file. |
|
751 |
|
752 \snippet tutorials/addressbook/part5/addressbook.h include finddialog's header |
|
753 |
|
754 So far, all our address book features have a QPushButton and a |
|
755 corresponding slot. Similarly, for the \gui Find feature we have |
|
756 \c findButton and \c findContact(). |
|
757 |
|
758 The \c findButton is declared as a private variable and the |
|
759 \c findContact() function is declared as a public slot. |
|
760 |
|
761 \snippet tutorials/addressbook/part5/addressbook.h findContact() declaration |
|
762 \dots |
|
763 \snippet tutorials/addressbook/part5/addressbook.h findButton declaration |
|
764 |
|
765 Lastly, we declare the private variable, \c dialog, which we will use to |
|
766 refer to an instance of \c FindDialog. |
|
767 |
|
768 \snippet tutorials/addressbook/part5/addressbook.h FindDialog declaration |
|
769 |
|
770 Once we have instantiated a dialog, we will want to use it more than once; |
|
771 using a private variable allows us to refer to it from more than one place |
|
772 in the class. |
|
773 |
|
774 \section1 Implementing the AddressBook Class |
|
775 |
|
776 Within the \c AddressBook class's constructor, we instantiate our private |
|
777 objects, \c findButton and \c findDialog: |
|
778 |
|
779 \snippet tutorials/addressbook/part5/addressbook.cpp instantiating findButton |
|
780 \dots |
|
781 \snippet tutorials/addressbook/part5/addressbook.cpp instantiating FindDialog |
|
782 |
|
783 Next, we connect the \c{findButton}'s |
|
784 \l{QPushButton::clicked()}{clicked()} signal to \c findContact(). |
|
785 |
|
786 \snippet tutorials/addressbook/part5/addressbook.cpp signals and slots for find |
|
787 |
|
788 Now all that is left is the code for our \c findContact() function: |
|
789 |
|
790 \snippet tutorials/addressbook/part5/addressbook.cpp findContact() function |
|
791 |
|
792 We start out by displaying the \c FindDialog instance, \c dialog. This is |
|
793 when the user enters a contact name to look up. Once the user clicks |
|
794 the dialog's \c findButton, the dialog is hidden and the result code is |
|
795 set to QDialog::Accepted. This ensures that |
|
796 our \c if statement is always true. |
|
797 |
|
798 We then proceed to extract the search string, which in this case is |
|
799 \c contactName, using \c{FindDialog}'s \c getFindText() function. If the |
|
800 contact exists in our address book, we display it immediately. Otherwise, |
|
801 we display the QMessageBox shown below to indicate that their search |
|
802 failed. |
|
803 |
|
804 \image addressbook-tutorial-part5-notfound.png |
|
805 */ |
|
806 |
|
807 /*! |
|
808 \page tutorials-addressbook-part6.html |
|
809 \previouspage Address Book 5 - Adding a Find Function |
|
810 \contentspage {Address Book Tutorial}{Contents} |
|
811 \nextpage {tutorials/addressbook/part7}{Chapter 7} |
|
812 \example tutorials/addressbook/part6 |
|
813 \title Address Book 6 - Loading and Saving |
|
814 |
|
815 This chapter covers the file handling features of Qt that we use to write |
|
816 loading and saving routines for the address book application. |
|
817 |
|
818 \image addressbook-tutorial-part6-screenshot.png |
|
819 |
|
820 Although browsing and searching for contacts are useful features, our |
|
821 address book is not ready for use until we can save existing contacts and |
|
822 load them again at a later time. |
|
823 |
|
824 Qt provides a number of classes for \l{Input/Output and Networking} |
|
825 {input and output}, but we have chosen to use two which are simple to use |
|
826 in combination: QFile and QDataStream. |
|
827 |
|
828 A QFile object represents a file on disk that can be read from and written |
|
829 to. QFile is a subclass of the more general QIODevice class which |
|
830 represents many different kinds of devices. |
|
831 |
|
832 A QDataStream object is used to serialize binary data so that it can be |
|
833 stored in a QIODevice and retrieved again later. Reading from a QIODevice |
|
834 and writing to it is as simple as opening the stream - with the respective |
|
835 device as a parameter - and reading from or writing to it. |
|
836 |
|
837 |
|
838 \section1 Defining the AddressBook Class |
|
839 |
|
840 We declare two public slots, \c saveToFile() and \c loadFromFile(), as well |
|
841 as two QPushButton objects, \c loadButton and \c saveButton. |
|
842 |
|
843 \snippet tutorials/addressbook/part6/addressbook.h save and load functions declaration |
|
844 \dots |
|
845 \snippet tutorials/addressbook/part6/addressbook.h save and load buttons declaration |
|
846 |
|
847 \section1 Implementing the AddressBook Class |
|
848 |
|
849 In our constructor, we instantiate \c loadButton and \c saveButton. |
|
850 Ideally, it would be more user-friendly to set the push buttons' labels |
|
851 to "Load contacts from a file" and "Save contacts to a file". However, due |
|
852 to the size of our other push buttons, we set the labels to \gui{Load...} |
|
853 and \gui{Save...}. Fortunately, Qt provides a simple way to set tooltips with |
|
854 \l{QWidget::setToolTip()}{setToolTip()} and we use it in the following way |
|
855 for our push buttons: |
|
856 |
|
857 \snippet tutorials/addressbook/part6/addressbook.cpp tooltip 1 |
|
858 \dots |
|
859 \snippet tutorials/addressbook/part6/addressbook.cpp tooltip 2 |
|
860 |
|
861 Although it is not shown here, just like the other features we implemented, |
|
862 we add the push buttons to the layout panel on the right, \c button1Layout, |
|
863 and we connect the push buttons' \l{QPushButton::clicked()}{clicked()} |
|
864 signals to their respective slots. |
|
865 |
|
866 For the saving feature, we first obtain \c fileName using |
|
867 QFileDialog::getSaveFileName(). This is a convenience function provided |
|
868 by QFileDialog, which pops up a modal file dialog and allows the user to |
|
869 enter a file name or select any existing \c{.abk} file. The \c{.abk} file |
|
870 is our Address Book extension that we create when we save contacts. |
|
871 |
|
872 \snippet tutorials/addressbook/part6/addressbook.cpp saveToFile() function part1 |
|
873 |
|
874 The file dialog that pops up is displayed in the screenshot below: |
|
875 |
|
876 \image addressbook-tutorial-part6-save.png |
|
877 |
|
878 If \c fileName is not empty, we create a QFile object, \c file, with |
|
879 \c fileName. QFile works with QDataStream as QFile is a QIODevice. |
|
880 |
|
881 Next, we attempt to open the file in \l{QIODevice::}{WriteOnly} mode. |
|
882 If this is unsuccessful, we display a QMessageBox to inform the user. |
|
883 |
|
884 \snippet tutorials/addressbook/part6/addressbook.cpp saveToFile() function part2 |
|
885 |
|
886 Otherwise, we instantiate a QDataStream object, \c out, to write the open |
|
887 file. QDataStream requires that the same version of the stream is used |
|
888 for reading and writing. We ensure that this is the case by setting the |
|
889 version used to the \l{QDataStream::Qt_4_5}{version introduced with Qt 4.5} |
|
890 before serializing the data to \c file. |
|
891 |
|
892 \snippet tutorials/addressbook/part6/addressbook.cpp saveToFile() function part3 |
|
893 |
|
894 For the loading feature, we also obtain \c fileName using |
|
895 QFileDialog::getOpenFileName(). This function, the counterpart to |
|
896 QFileDialog::getSaveFileName(), also pops up the modal file dialog and |
|
897 allows the user to enter a file name or select any existing \c{.abk} file |
|
898 to load it into the address book. |
|
899 |
|
900 \snippet tutorials/addressbook/part6/addressbook.cpp loadFromFile() function part1 |
|
901 |
|
902 On Windows, for example, this function pops up a native file dialog, as |
|
903 shown in the following screenshot. |
|
904 |
|
905 \image addressbook-tutorial-part6-load.png |
|
906 |
|
907 If \c fileName is not empty, again, we use a QFile object, \c file, and |
|
908 attempt to open it in \l{QIODevice::}{ReadOnly} mode. Similar to our |
|
909 implementation of \c saveToFile(), if this attempt is unsuccessful, we |
|
910 display a QMessageBox to inform the user. |
|
911 |
|
912 \snippet tutorials/addressbook/part6/addressbook.cpp loadFromFile() function part2 |
|
913 |
|
914 Otherwise, we instantiate a QDataStream object, \c in, set its version as |
|
915 above and read the serialized data into the \c contacts data structure. |
|
916 The \c contacts object is emptied before data is read into it to simplify |
|
917 the file reading process. A more advanced method would be to read the |
|
918 contacts into a temporary QMap object, and copy over non-duplicate contacts |
|
919 into \c contacts. |
|
920 |
|
921 \snippet tutorials/addressbook/part6/addressbook.cpp loadFromFile() function part3 |
|
922 |
|
923 To display the contacts that have been read from the file, we must first |
|
924 validate the data obtained to ensure that the file we read from actually |
|
925 contains address book contacts. If it does, we display the first contact; |
|
926 otherwise, we display a QMessageBox to inform the user about the problem. |
|
927 Lastly, we update the interface to enable and disable the push buttons |
|
928 accordingly. |
|
929 */ |
|
930 |
|
931 /*! |
|
932 \page tutorials-addressbook-part7.html |
|
933 \previouspage Address Book 6 - Loading and Saving |
|
934 \contentspage {Address Book Tutorial}{Contents} |
|
935 \example tutorials/addressbook/part7 |
|
936 \title Address Book 7 - Additional Features |
|
937 |
|
938 This chapter covers some additional features that make the address book |
|
939 application more convenient for everyday use. |
|
940 |
|
941 \image addressbook-tutorial-part7-screenshot.png |
|
942 |
|
943 Although our address book application is useful in its own right, it would |
|
944 be useful if we could exchange contact data with other applications. |
|
945 The vCard format is a popular file format that can be used for this purpose. |
|
946 In this chapter, we extend our address book client to allow contacts to |
|
947 be exported to vCard \c{.vcf} files. |
|
948 |
|
949 \section1 Defining the AddressBook Class |
|
950 |
|
951 We add a QPushButton object, \c exportButton, and a corresponding public |
|
952 slot, \c exportAsVCard() to our \c AddressBook class in the |
|
953 \c addressbook.h file. |
|
954 |
|
955 \snippet tutorials/addressbook/part7/addressbook.h exportAsVCard() declaration |
|
956 \dots |
|
957 \snippet tutorials/addressbook/part7/addressbook.h exportButton declaration |
|
958 |
|
959 \section1 Implementing the AddressBook Class |
|
960 |
|
961 Within the \c AddressBook constructor, we connect \c{exportButton}'s |
|
962 \l{QPushButton::clicked()}{clicked()} signal to \c exportAsVCard(). |
|
963 We also add this button to our \c buttonLayout1, the layout responsible |
|
964 for our panel of buttons on the right. |
|
965 |
|
966 In our \c exportAsVCard() function, we start by extracting the contact's |
|
967 name into \c name. We declare \c firstName, \c lastName and \c nameList. |
|
968 Next, we look for the index of the first white space in \c name. If there |
|
969 is a white space, we split the contact's name into \c firstName and |
|
970 \c lastName. Then, we replace the space with an underscore ("_"). |
|
971 Alternately, if there is no white space, we assume that the contact only |
|
972 has a first name. |
|
973 |
|
974 \snippet tutorials/addressbook/part7/addressbook.cpp export function part1 |
|
975 |
|
976 As with the \c saveToFile() function, we open a file dialog to let the user |
|
977 choose a location for the file. Using the file name chosen, we create an |
|
978 instance of QFile to write to. |
|
979 |
|
980 We attempt to open the file in \l{QIODevice::}{WriteOnly} mode. If this |
|
981 process fails, we display a QMessageBox to inform the user about the |
|
982 problem and return. Otherwise, we pass the file as a parameter to a |
|
983 QTextStream object, \c out. Like QDataStream, the QTextStream class |
|
984 provides functionality to read and write plain text to files. As a result, |
|
985 the \c{.vcf} file generated can be opened for editing in a text editor. |
|
986 |
|
987 \snippet tutorials/addressbook/part7/addressbook.cpp export function part2 |
|
988 |
|
989 We then write out a vCard file with the \c{BEGIN:VCARD} tag, followed by |
|
990 the \c{VERSION:2.1} tag. The contact's name is written with the \c{N:} |
|
991 tag. For the \c{FN:} tag, which fills in the "File as" property of a vCard, |
|
992 we have to check whether the contact has a last name or not. If the contact |
|
993 does, we use the details in \c nameList to fill it. Otherwise, we write |
|
994 \c firstName only. |
|
995 |
|
996 \snippet tutorials/addressbook/part7/addressbook.cpp export function part3 |
|
997 |
|
998 We proceed to write the contact's address. The semicolons in the address |
|
999 are escaped with "\\", the newlines are replaced with semicolons, and the |
|
1000 commas are replaced with spaces. Lastly, we write the \c{ADR;HOME:;} |
|
1001 tag, followed by \c address and then the \c{END:VCARD} tag. |
|
1002 |
|
1003 \snippet tutorials/addressbook/part7/addressbook.cpp export function part4 |
|
1004 |
|
1005 In the end, a QMessageBox is displayed to inform the user that the vCard |
|
1006 has been successfully exported. |
|
1007 |
|
1008 \e{vCard is a trademark of the \l{http://www.imc.org} |
|
1009 {Internet Mail Consortium}}. |
|
1010 */ |