You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
853 lines
95 KiB
853 lines
95 KiB
14 years ago
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
||
|
<!-- /home/espenr/tmp/qt-3.3.8-espenr-2499/qt-x11-free-3.3.8/tools/designer/book/chap-main-windows.leaf:3 -->
|
||
|
<html>
|
||
|
<head>
|
||
|
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||
|
<title>Creating a Main Window Application</title>
|
||
|
<style type="text/css"><!--
|
||
|
fn { margin-left: 1cm; text-indent: -1cm; }
|
||
|
a:link { color: #004faf; text-decoration: none }
|
||
|
a:visited { color: #672967; text-decoration: none }
|
||
|
body { background: #ffffff; color: black; }
|
||
|
--></style>
|
||
|
</head>
|
||
|
<body>
|
||
|
|
||
|
<table border="0" cellpadding="0" cellspacing="0" width="100%">
|
||
|
<tr bgcolor="#E5E5E5">
|
||
|
<td valign=center>
|
||
|
<a href="index.html">
|
||
|
<font color="#004faf">Home</font></a>
|
||
|
| <a href="classes.html">
|
||
|
<font color="#004faf">All Classes</font></a>
|
||
|
| <a href="mainclasses.html">
|
||
|
<font color="#004faf">Main Classes</font></a>
|
||
|
| <a href="annotated.html">
|
||
|
<font color="#004faf">Annotated</font></a>
|
||
|
| <a href="groups.html">
|
||
|
<font color="#004faf">Grouped Classes</font></a>
|
||
|
| <a href="functions.html">
|
||
|
<font color="#004faf">Functions</font></a>
|
||
|
</td>
|
||
|
<td align="right" valign="center"><img src="logo32.png" align="right" width="64" height="32" border="0"></td></tr></table><p align="right">[<a href="designer-manual-2.html">Prev: Quick Start</a>] [<a href="designer-manual.html">Home</a>] [<a href="designer-manual-4.html">Next: Creating Dialogs</a>]</p>
|
||
|
<h2 align="center">Creating a Main Window Application</h2>
|
||
|
<p>In this chapter and in chapter three we will create a small but complete Qt application called <tt>colortool</tt>. The <tt>colortool</tt> application is used to associate names with colors. It consists of a standard main window application with some custom dialogs to facilitate some of the user interaction.This chapter will cover the main window, and the next chapter covers the dialogs that complete the application.</p>
|
||
|
<h3><a name="1"></a>The Color Tool Application</h3>
|
||
|
<p align="center"><img align="middle" src="mw-colortool1.png" alt="The Color Tool application" width="605" height="501">
|
||
|
</p>
|
||
|
<p>The <tt>colortool</tt> application is a multiplatform application that allows users to create, edit and save lists of colors. Each color has a user defined name and an RGB (Red, Green, Blue) value.</p>
|
||
|
<p>This application presents the user with a view of a set of colors and their names. We will provide two views (using a <a href="qwidgetstack.html">QWidgetStack</a>) which the user can switch between. The tabular view will show each color as a small square followed by its name and hex value. It will also provide the option of an indicator to show whether or not the color is one of the 216 standard web colors. The iconic view will show each color as a circular color swatch with the name of the color beneath.</p>
|
||
|
<p>The application will read and write files in the format used by the X Consortium for the <tt>rgb.txt</tt> file. This will allow users to create their own color files and to load, edit and save <tt>rgb.txt</tt> format files.</p>
|
||
|
<p>We will provide a simple search option so that users can quickly locate a color; this is particularly useful when hundreds or thousands of colors are shown. The search will be provided in a modeless dialog so that the user can conduct a search but still interact with the main form. We will also enable the user to add and delete colors, and to set some user options. To provide these facilities, we must create some modal dialogs.</p>
|
||
|
<p>Finally, we must ensure that the application loads user options at start up and saves user options at termination. We will also include the view and the size and position of the main window with these options, so that the application will always start with the size, position and view it had when the user last used it.</p>
|
||
|
<p align="center"><img align="middle" src="mw-colortool2.png" alt="The Color Tool application" width="580" height="485">
|
||
|
</p>
|
||
|
<h3><a name="2"></a>Starting and Exiting <em>Qt Designer</em></h3>
|
||
|
<!-- index Starting Qt Designer --><p>To start <em>Qt Designer</em> under Windows click the <b>Start</b> button and click <b>Programs|Qt X.x.x|Designer</b>. (X.x.x is the Qt version number, e.g. 3.1.0.) If you're running a Unix or Linux operating system you can either double click the <em>Qt Designer</em> icon or enter <tt>designer &</tt> in an xterm.</p>
|
||
|
<p>When <em>Qt Designer</em> starts, it shows the <em>New/Open</em> dialog. If you prefer to not have this dialog appear the next time you open <em>Qt Designer</em>, check the "Don't show this dialog in the future" checkbox.</p>
|
||
|
<p>For this example, click <b>Cancel</b> to skip over the dialog.</p>
|
||
|
<p align="center"><img align="middle" src="mw-startdesign.png" width="584" height="446">
|
||
|
</p>
|
||
|
<!-- index Exiting Qt Designer --><!-- index Getting Help --><p>When you've finished using <em>Qt Designer</em> click <b>File|Exit</b>; you will be prompted to save any unsaved changes. Help is available by pressing <b>F1</b> or from the <b>Help</b> menu.</p>
|
||
|
<p>To get the most benefit from the tutorial chapters we recommend that you start <em>Qt Designer</em> now and create the <tt>colortool</tt> application as you read. Most of the work involves using <em>Qt Designer</em>'s menus, dialogs and editors. We also suggest that as you work through this manual you enter the code directly using <em>Qt Designer</em>'s code editor. You can cut and paste the code from the on-line version of this manual or copy it from the example source code.</p>
|
||
|
<p>When you start <em>Qt Designer</em>, by default, you will see a menu bar and various toolbars at the top.<!-- index Widgets and Source window!Object Explorer --><!-- index Object Explorer --><!-- index Pixmaps --> On the left is the widget Toolbox. Click the toolbox's buttons to reveal a particular set of tools. On the right there are three windows: the first is the Project Overview window, the second is the Object Explorer window, and the third is the Properties Editor/Signal Handlers window. The Project Overview window lists the files and images associated with the project; to open any form (<tt>.ui</tt> file), or the code associated with it (in the <tt>.ui.h</tt> file), simply single click it. The Object Explorer window lists the current form's widgets and members. The Properties Editor/Signal Handlers window is used to view and change the properties of forms and widgets. We will cover the use of <em>Qt Designer</em>'s windows, dialogs, menu options and tools as we create the example application.</p>
|
||
|
<a name="creating-the-project"></a><h3><a name="3"></a>Creating the Project</h3>
|
||
|
<p>Our <tt>colortool</tt> application is going to be a standard C++ application, so we need to create a C++ project and add our files and code to this project.</p>
|
||
|
<blockquote>
|
||
|
<p align="center"><b> Creating a Project</b></p>
|
||
|
<!-- index Projects --><!-- index Projects!Creating New --><!-- index Creating Projects!Projects --><!-- index Pixmaps!In Projects --><!-- index Pixmaps!Adding to Forms --><p>Whenever you create a new application we recommend that you create a project file and open the project rather than individual<!-- index .ui --> <tt>.ui</tt> files. Using a project has the advantage that all the forms you create for the project are available via a single mouse click rather than having to be loaded individually through file open dialogs. An additional benefit of using project files is that they allow you to store all your images in a single file rather than duplicate them in each form in which they appear. See <a href="designer-manual-5.html#the-designer-approach">The Designer Approach</a> chapter's <a href="designer-manual-5.html#2">Project management</a> section for detailed information on the benefits of using project files.</p>
|
||
|
<p>Project files use the <tt>.pro</tt> suffix and are used by the <tt>qmake</tt> tool to create makefiles for the relevant target platforms.</p>
|
||
|
</blockquote>
|
||
|
<p>Create a new project as follows:</p>
|
||
|
<ol type=1><li><p>Click <b>File|New</b> to invoke the <em>New File</em> dialog.</p>
|
||
|
<li><p>Click "C++ Project" to create a C++ project, then click <b>OK</b> to invoke the <em>Project Settings</em> dialog.</p>
|
||
|
<li><p>Click the ellipsis button to the right of the Project File line edit to invoke the <em>Save As</em> dialog. Use this dialog to navigate to where you want to create the new project, ideally creating a new folder for it (e.g. called "colortool"), using the <b>Create New Folder</b> toolbar button.</p>
|
||
|
<li><p>Enter "colortool.pro" as the file name, then click <b>Save</b>. The project's name will now be "colortool"; click <b>OK</b> to close the <em>Project Settings</em> dialog.</p>
|
||
|
<p align="center"><img align="middle" src="mw-projset.png" width="383" height="273">
|
||
|
</p>
|
||
|
<li><p>Click <b>File|Save</b> to save the project.</p>
|
||
|
</ol><p align="center"><img align="middle" src="mw-newfile.png" width="565" height="330">
|
||
|
</p>
|
||
|
<p>The <em>New File</em> dialog is used to create all the files that can be used in a <em>Qt Designer</em> project. This includes C++ source files, an automatically generated <tt>main.cpp</tt> file (if you are in a project), and a variety of forms based on pre-defined templates. (You can create your own templates too.)</p>
|
||
|
<p>For the <tt>colortool</tt> application we want to start with a main window form. When we create this form, <em>Qt Designer</em> will present a wizard which we can use to automatically create menu and toolbar options and automatically create the relevant signal/slot connections. For every menu option or toolbar button, <em>Qt Designer</em> will create a single <a href="qaction.html">QAction</a> (see the <a href="designer-manual-3.html#actions-and-action-groups-sidebar">Actions and Action Groups</a> sidebar).</p>
|
||
|
<blockquote>
|
||
|
<p align="center"><b> Actions and Action Groups</b></p>
|
||
|
<a name="actions-and-action-groups-sidebar"></a><p>An <em>action</em> is an operation that the user initiates through the user interface, for example, saving a file or changing some text's font weight to bold.</p>
|
||
|
<p>We often want the user to be able to perform an action using a variety of means. For example, to save a file we might want the user to be able to press <b>Ctrl+S</b>, or to click the <b>Save</b> toolbar button or to click the <b>File|Save</b> menu option. Although the means of invoking the action are all different, the underlying operation is the same and we don't want to duplicate the code that performs the operation. In Qt we can create an action (a <a href="qaction.html">QAction</a> object) which will call the appropriate function when the action is invoked. We can assign an accelerator, (e.g. <b>Ctrl+S</b>), to an action. We can also add an action to a menu and to a toolbar.</p>
|
||
|
<p>If the action has an on/off state, e.g. bold is on or off, when the user changes the state, for example by clicking a toolbar button, the state of everything associated with the action, e.g. menu items and toolbar buttons, is updated.</p>
|
||
|
<p>Some actions should operate together like radio buttons. For example, if we have left align, center align and right align actions, only one should be 'on' at any one time. An <em>action group</em> (a <a href="qactiongroup.html">QActionGroup</a> object) is used to group a set of actions together. If the action group's <tt>exclusive</tt> property is TRUE then only one of the actions in the group can be on at any one time. If the user changes the state of an action in an action group where <tt>exclusive</tt> is TRUE, everything associated with the actions in the action group, e.g. menu items and toolbar buttons, is updated.</p>
|
||
|
<!-- index Actions and Action Groups --><p><em>Qt Designer</em> can create actions and action groups visually, assign accelerators to them, and associate them with menu items and toolbar buttons.</p>
|
||
|
</blockquote>
|
||
|
<h3><a name="4"></a>Creating the Main Window</h3>
|
||
|
<!-- index Main Window!Creating --><!-- index Creating Main Windows --><!-- index Main Window!Wizard --><!-- index Wizards!Main Window --><!-- index Creating Menus --><!-- index Menus!Adding --><!-- index Adding!Menus --><!-- index Creating Toolbars --><!-- index Toolbars, Creating --><!-- index Toolbar Buttons!Adding --><!-- index Adding!Toolbars --><!-- index Adding!Actions and Action Groups --><!-- index Adding!Toolbar Buttons --><p>We will use the Main Window Wizard to build a main window. The wizard allows us to create actions as well as a menu bar and a toolbar through which the user can invoke the actions. We will also create our own actions, menus and toolbar buttons, and add a main widget to the main window.</p>
|
||
|
<p>Click <b>File|New</b> to invoke the <em>New File</em> dialog, click "Main Window" to create a main window form, then click <b>OK</b>. A new <a href="qmainwindow.html">QMainWindow</a> form will be created and the <em>Main Window Wizard</em> will pop up.</p>
|
||
|
<h4><a name="4-1"></a>Using the Main Window Wizard</h4>
|
||
|
<ol type=1><li><p>The <em>Choose available menus and toolbars</em> page appears first. It presents three categories of default actions, File Actions, Edit Actions and Help Actions. For each category you can choose to have <em>Qt Designer</em> create menu items, toolbar buttons, and signal/slots connections for the relevant actions. You can always add or delete actions, menu items, toolbar buttons, and connections later.</p>
|
||
|
<!-- index Creating Menus --><!-- index Menus!Adding --><!-- index Creating Toolbars --><!-- index Toolbars, Creating --><!-- index Toolbar Buttons!Adding --><!-- index Adding!Menus --><!-- index Adding!Toolbars --><!-- index Adding!Actions and Action Groups --><!-- index Adding!Toolbar Buttons --><!-- index Signals and Slots!Connecting Actions --><p>We will accept the defaults for File Actions and for the Edit Actions, i.e. have menu items, toolbar buttons and the relevant connections created. In fact we'll be changing the Edit actions considerably later on, but it is still convenient to create them now. We won't have any Help Actions on the toolbar so uncheck the Help Action's Toolbar checkbox. Click <b>Next</b> to move on to the next wizard page.</p>
|
||
|
<p align="center"><img align="middle" src="mw-menuwiz.png" width="616" height="420">
|
||
|
</p>
|
||
|
<blockquote><p align="center"><em>Main Window Wizard- Choosing menus and toolbars</em></p></blockquote>
|
||
|
<li><!-- index Creating Toolbars --><!-- index Toolbars, Creating --><!-- index Toolbar Buttons!Adding --><!-- index Adding!Toolbar Buttons --><!-- index Separator!Menu item --><!-- index Separator!Toolbar button --><p>The <em>Setup Toolbar</em> wizard page is used to populate a toolbar with actions from each of the default action categories. The Category combobox is used to select which set of actions you wish to choose from. The Actions list box lists the actions available for the current category. The Toolbar listbox lists the toolbar buttons you want to create. The blue left and right arrow buttons are used to move actions into or out of the Toolbar list box. The blue up and down arrow buttons are used to move actions up and down within the Toolbar list box. Note that the '<Separator>' item in the Actions list box may be moved to the Toolbar list box as often as required and will cause a separator to appear in the finished toolbar.</p>
|
||
|
<p>Copy the New, Open, and Save Actions to the Toolbar list box. Copy a <Separator> to the Toolbar list box. Change the Category to Edit and copy the Cut, Copy, and Find actions to the Toolbar list box. Click <b>Next</b> and then click <b>Finish</b>.</p>
|
||
|
<p>Click <b>File|Save</b> and save the form as <tt>mainform.ui</tt>.</p>
|
||
|
<p align="center"><img align="middle" src="mw-settoolwiz.png" width="616" height="420">
|
||
|
</p>
|
||
|
<blockquote><p align="center"><em>Main Window Wizard- Setting up the toolbar</em></p></blockquote>
|
||
|
</ol><p>If you preview the form (<b>Ctrl+T</b>) the File and Edit menus will be available and you'll be able to drag the toolbar either into an independent window of its own, or to dock it to the left, right, bottom, or top of the window. The menus and toolbars are not yet functional, but we will rectify this as we progress. You leave preview mode by clicking the form's Close box (or the platform-specific equivalent).</p>
|
||
|
<p align="center"><img align="middle" src="mw-previewform.png" width="391" height="316">
|
||
|
</p>
|
||
|
<blockquote><p align="center"><em>Previewing the Form</em></p></blockquote>
|
||
|
<p>Now that we've created the form we will need to change some of its properties. (See the <a href="designer-manual-3.html#using-the-property-editor-sidebar">Using the Property Editor</a> sidebar.)</p>
|
||
|
<blockquote>
|
||
|
<p align="center"><b> Using the Property Editor</b></p>
|
||
|
<a name="using-the-property-editor-sidebar"></a><!-- index Using the Property Editor --><!-- index Properties --><p>The Property Editor has two columns, the Property column which lists property names and the Value column which lists the property values. Some property names have a plus sign '+' in a square to their left; this indicates that the property name is the collective name for a set of related properties. Click a form or widget to make the Property Editor show the form or widget's properties.</p>
|
||
|
<p>For example, click the <em>sizePolicy</em> property's plus sign; you will see four properties appear indented below sizePolicy: hSizeType, vSizeType, horizontalStretch and verticalStretch. These properties are edited in the same way as any other properties.</p>
|
||
|
<p>If you want to change the same property to the same value for a whole set of widgets, (e.g. to give them all a common cursor, tooltip, colors, etc.), <b>Click</b> one of the widgets, then <b>Shift+Click</b> the others to select them all. (Alternatively, click the first widget's name in Object Explorer, then <b>Shift+Click</b> all the others in Object Explorer: this technique is especially useful for forms with lots of nested widgets and layouts.) The properties they have in common will be shown in the property editor, and any change made to one property will be made to that same property for all the selected widgets.</p>
|
||
|
<p align="center"><img align="middle" src="mw-propedit.png" width="250" height="678">
|
||
|
</p>
|
||
|
<blockquote><p align="center"><em>Property Editor</em></p></blockquote>
|
||
|
<p>Some properties have simple values, for example, the <em>name</em> property has a text value, the <em>width</em> property (within <em>minimumSize</em>) has a numeric value. To change a text value click the existing text and type in your new text. To change a numeric value click the value and either type in a new number, or use the spin buttons to increase or decrease the existing number until it reaches the value you want. Some properties have a fixed list of values, for example the <em>mouseTracking</em> property is boolean and can take the values True or False. The <em>cursor</em> property also has a fixed list of values. If you click the cursor property or the <em>mouseTracking</em> property the value will be shown in a drop down combobox; click the down arrow to see what values are available. Some properties have complex sets of values or special values; for example the <em>font</em> property and the <em>iconSet</em> property. If you click the font property an ellipsis button (<b>...</b>) will appear; click this button and a <em>Select Font</em> dialog will pop up which you can use to change any of the font settings. Other properties have ellipsis buttons which lead to different dialogs depending on what settings the property can have. For example, if you have a lot of text to enter for a <em>text</em> property you could click the ellipsis button to invoke the <em>Multi-line Edit</em> dialog.</p>
|
||
|
<!-- index Properties!Reverting changes --><!-- index Properties!Initial values --><p>The names of properties which have changed are shown in bold. If you've changed a property but want to revert it to its default value click the property's value and then click the red 'X' button to the right of the value. Some properties have an <em>initial</em> value, e.g. 'TextEdit1', but no default value; if you revert a property that has an initial value but no default value (by clicking the red 'X') the value will become empty unless the property, e.g. name, is not allowed to be empty.</p>
|
||
|
<!-- index Undo and Redo!Properties --><!-- index Properties!Undo and Redo!Undo and Redo --><!-- index Redo!Undo and Redo --><p>The property editor fully supports Undo and Redo (<b>Ctrl+Z</b> and <b>Ctrl+Y</b>, also available from the <b>Edit</b> menu).</p>
|
||
|
</blockquote>
|
||
|
<h4><a name="4-2"></a>Setting the Form's Properties and Actions</h4>
|
||
|
<p>Click the form to make all of its properties appear in the <a href="designer-manual-3.html#using-the-property-editor-sidebar">Property Editor</a>. Change the form's <em>name</em> to "MainForm" and its <em>caption</em> to "Color Tool".</p>
|
||
|
<p>Now we'll need to delete some actions that the main window wizard created but that are not relevant to our application.</p>
|
||
|
<p>Click the Object Explorer's Members tab. Right click the filePrint() slot, then click Delete from the popup menu. In the same way delete the editUndo(), editRedo() and editPaste() slots. Later we'll see how to create new slots when we add further functionality to the application.</p>
|
||
|
<p>We also need to delete these actions in the Action Editor window. Right click the filePrintAction action, then click Delete Action from the popup menu. In the same way delete the editUndoAction, editRedoAction and editPasteAction actions.</p>
|
||
|
<p>Finally, we need to delete those separators in the form's menu that have become redundant because they separated actions that we've now deleted.</p>
|
||
|
<p>Note that the Action Editor window is dockable, so if you don't want it to float freely you can drag it to one of <em>Qt Designer</em>'s dock areas (top, left, right, bottom of the main window) if preferred.</p>
|
||
|
<p>Click the form's <b>File</b> menu. (Note, we're clicking our newly created form's <b>File</b> menu, not <em>Qt Designer</em>'s <b>File</b> menu!) There are <em>two</em> separators above the Exit menu option (the <b>File|Print</b> option was in-between until we deleted it). Click one of these separators, then press the <b>Delete</b> key. Don't worry if you miss and delete a menu option by accident: if you delete the wrong thing click <b>Edit|Undo</b> to undelete. The form's <b>Edit</b> menu has a redundant separator at the top (the undo and redo options were there). Delete this separator in the same way. Again, don't worry if you delete a menu option by mistake, just press <b>Ctrl+Z</b> to undo.</p>
|
||
|
<p>Click <b>File|Save</b> to save the form.</p>
|
||
|
<p>The form can now be previewed by clicking <b>Preview|Preview Form</b> (or press <b>Ctrl+T</b>).</p>
|
||
|
<blockquote>
|
||
|
<p align="center"><b> The Object Explorer</b></p>
|
||
|
<!-- index Object Explorer --><p>View the Object Explorer window by clicking <b>Window|Views|Object Explorer</b>. The Object Explorer has two tabs, the Objects tab which shows the object hierarchy, and the Members tab which shows the members you have added to the form. Click the name of a widget in the Objects tab to select the widget and show its properties in the <a href="designer-manual-3.html#using-the-property-editor-sidebar">Property Editor</a>. It is easy to see and select widgets in the Object Explorer which is especially useful for forms that have many widgets or which use layouts. Multiple widgets can be selected by <b>Click</b>ing the first one then <b>Shift+Click</b>ing the others.</p>
|
||
|
<p align="center"><img align="middle" src="mw-objexplor.png" width="328" height="465">
|
||
|
</p>
|
||
|
<blockquote><p align="center"><em>Object Explorer</em></p></blockquote>
|
||
|
<!-- index Code Editing --><!-- index Subclassing --><!-- index Forward declarations --><!-- index Includes --><!-- index Adding!Code --><!-- index Adding!Forward declarations --><!-- index Adding!Includes --><!-- index Adding!Class variables --><!-- index Class variables --><!-- index Deleting!Forward declarations --><!-- index Deleting!Includes --><!-- index Deleting!Class variables --><!-- index Forms!Forward declarations --><!-- index Forms!Class variables --><!-- index Forms!Code editing --><!-- index Forms!destructor --><!-- index Forms!constructor --><p>In the original version of <em>Qt Designer</em> if you wanted to provide code for a form you had to subclass the form and put your code in the subclass. This version fully supports the subclassing approach, but now provides an alternative: placing your code directly into forms. Writing code in <em>Qt Designer</em> is not quite the same as subclassing, for example you cannot get direct access to the form's constructor or destructor. If you need code to be executed by the constructor create a slot called<!-- index init() --> <tt>void init()</tt>; if it exists it will be called from the constructor. Similarly, if you need code to be executed before destruction create a slot called<!-- index destroy() --> <tt>void destroy()</tt>. You can also add your own class variables which will be put in the generated constructor's code, and you can add forward declarations and any includes you require. To add a variable or declaration, right click the appropriate item, e.g. Class Variables, then click <b>New</b> then enter your text, e.g. <tt>QString m_filename</tt>. If one or more items exist, right click to pop up a menu that has New, Edit and Delete options. If you want to enter multiple items, e.g. multiple include files or multiple data members, it is easiest to right click in the relevant section, then click <b>Edit</b> to invoke an Edit dialog. To edit code, just click the name of a function to invoke the code editor. Code editing and creating slots are covered later in the chapter.</p>
|
||
|
<!-- index Subclassing --><p>If you subclass the form you create your own<!-- index .cpp --> <tt>.cpp</tt> files which can contain your own constructor, destructor, functions, slots, declarations and variables as your requirements dictate. (See <a href="designer-manual-6.html#1">Subclassing</a> for more information.)</p>
|
||
|
</blockquote>
|
||
|
<h4><a name="4-3"></a>Adding Custom Actions</h4>
|
||
|
<p>We want to provide the user with actions that are specific to our application. We want to provide the ability to switch between the two views we will be offering, and allow the user to add colors and set their preferred options. We'll prepare the way by creating a new menu for the view options and by adding a separator to the toolbar.</p>
|
||
|
<p>Click "new menu" on the menu bar and type "&View" over the text. The & (ampersand) causes the following character to be underlined and to become an Alt-accelerator (i.e., in this case Alt+V will pop up the View menu).</p>
|
||
|
<p align="center"><img align="middle" src="mw-newmenuitem.png" width="251" height="27">
|
||
|
</p>
|
||
|
<blockquote><p align="center"><em>The Menu Bar</em></p></blockquote>
|
||
|
<blockquote>
|
||
|
<p align="center"><b> Duplicate Accelerators</b></p>
|
||
|
<p>In an application that has dialogs with lots of widgets it is easy to accidentally duplicate accelerators. <em>Qt Designer</em> provides the <b>Edit|Check Accelerators</b> menu option (<b>Alt+R</b>) which will highlight any two or more widgets which have the same accelerators, making it easy to spot the problem if it occurs.</p>
|
||
|
</blockquote>
|
||
|
<p>Drag the View menu to the left of the "Help" menu and release it there. (A vertical red line indicates its position.)</p>
|
||
|
<p align="center"><img align="middle" src="mw-dragviewitem.png" width="252" height="27">
|
||
|
</p>
|
||
|
<blockquote><p align="center"><em>Dragging the View Menu Item</em></p></blockquote>
|
||
|
<p>We could create a new toolbar for the View menu items, but instead we'll put a separator at the end of the existing toolbar and add the View options after the separator. Right click the right-most toolbar button ("Find"), then click <b>Insert Separator</b>. Alternatively, we could have created an entirely new toolbar. See <a href="designer-manual-3.html#creating-and-populating-toolbars-sidebar">Creating and Populating Toolbars</a> for more information on doing this.</p>
|
||
|
<p align="center"><img align="middle" src="mw-separator.png" width="326" height="83">
|
||
|
</p>
|
||
|
<blockquote><p align="center"><em>Insert Separator in the Toolbar Menu</em></p></blockquote>
|
||
|
<blockquote>
|
||
|
<p align="center"><b> Creating and Populating Toolbars</b></p>
|
||
|
<a name="creating-and-populating-toolbars-sidebar"></a><!-- index Creating Toolbars --><!-- index Adding!Toolbars --><!-- index Toolbars, Creating --><p>A new toolbar is created by clicking to the right of the existing toolbars, then clicking <b>Add Toolbar</b>. The new toolbar is empty and is visible only by its <em>toolbar handle</em>. (Toolbar handle's are usually represented as a gray area containing either two thick vertical lines or with many small pits).</p>
|
||
|
<p align="center"><img align="middle" src="mw-toolbarpits.png" width="107" height="31">
|
||
|
</p>
|
||
|
<blockquote><p align="center"><em>Toolbar Handle</em></p></blockquote>
|
||
|
<!-- index Actions and Action Groups!Adding to a Toolbar --><!-- index Separator!Toolbar button --><!-- index Adding!Actions and Action Groups to a Toolbar --><!-- index Adding!Toolbar Separators --><p>Actions are added to toolbars simply by dragging them from the Action Editor to the toolbar, and dropping them on the toolbar in the position we want them. (The position is indicated by a vertical red line.)</p>
|
||
|
<p align="center"><img align="middle" src="mw-dragaction.png" width="212" height="38">
|
||
|
</p>
|
||
|
<blockquote><p align="center"><em>Dragging the Action Group to the Toolbar</em></p></blockquote>
|
||
|
<p>All the actions in an action group are added to a toolbar in one go, simply by dragging the action group from the Action Editor and dropping it on the toolbar.</p>
|
||
|
<p>Since toolbar buttons normally only show an image, all actions that are to be used in toolbars should have their <em>iconSet</em> property set to a suitable image.</p>
|
||
|
<!-- index Separator!Toolbar button --><!-- index Deleting!Toolbar Separators --><!-- index Deleting!Toolbars --><p>Toolbar buttons and separators (usually represented as indented vertical gray lines), can be dragged and dropped into new positions in the toolbar at any time. Separators can be inserted by right clicking a toolbar button and clicking <b>Insert Separator</b>. Toolbar buttons and separators can be deleted by right clicking them and then clicking Delete Item. Toolbars can be deleted by right clicking their toolbar handle and then clicking Delete Toolbar.</p>
|
||
|
<!-- index Previewing!Toolbars --><p>If you preview an application you'll find that all the toolbars can be dragged to different docking points (top, left, right and bottom of a <a href="qmainwindow.html">QMainWindow</a> or subclass), or dragged out of the application as independent tool windows.</p>
|
||
|
</blockquote>
|
||
|
<blockquote>
|
||
|
<p align="center"><b> Adding Widgets to the Toolbar</b></p>
|
||
|
<!-- index Toolbars!Adding Widgets --><!-- index Widgets!Adding to Toolbars --><!-- index Adding!Widgets --><p>Sometimes a simple button is insufficient for our needs. For example, if we wanted the user to be able to choose a font name and font size from the toolbar we might want to provide a direct means rather than having a toolbar button launch a font dialog.</p>
|
||
|
<!-- index Widgets!ComboBox --><!-- index Widgets!SpinBox --><!-- index Separator!Toolbar button --><p>It is perfectly feasible to add <em>ComboBox</em>es and <em>SpinBox</em>es to toolbars. For example, a <em>ComboBox</em> could be used to list the available font names and the <em>SpinBox</em> used to select a font size.</p>
|
||
|
<p>Although you can put any widget into a toolbar we recommend that widgets which can be associated with an action should <em>not</em> be added to the toolbar directly. For these widgets, i.e. menu items, toolbar buttons and lists of items, you should create an action (drop down action for a list of items), associate the action with the widget, and add the action to the toolbar. Widgets that can sensibly be inserted directly into a toolbar are <em>ComboBox</em>es, <em>SpinBox</em>es and <em>Line Edit</em>s.</p>
|
||
|
</blockquote>
|
||
|
<h4><a name="4-4"></a>Adding the Options Action</h4>
|
||
|
<p>Right click the first action in the Action Editor, then click <b>New Action</b>. The <a href="designer-manual-3.html#using-the-property-editor-sidebar">Property Editor</a> now shows the new action's properties. Change the action's <em>name</em> property to "optionsAction". Click the ellipsis button on the <em>iconSet</em> property to pop up the <em>Choose an Image</em> dialog. Click the <b>Add</b> button to invoke the <em>Choose Images...</em> dialog. Navigate to <tt>/tools/designer/examples/colortool/images</tt>; click the <tt>tabwidget.png</tt> image. Click <b>Open</b> to use it and then click <b>OK</b> once you are in the <em>Choose an Image</em> dialog. Change the <em>text</em> property to "Options" and change the <em>menuText</em> property to "&Options...".</p>
|
||
|
<p>Click the Options action in the Action Editor and drag it to the Edit menu. The Edit menu will pop up; drag the Options action down the menu (a horizontal red line indicates its position), and drop it at the end after the "Find" item.</p>
|
||
|
<blockquote>
|
||
|
<p align="center"><b> Alternative Approach to Adding the Options Action</b></p>
|
||
|
<p>Click Edit on the menu bar and then click "new item", located after the Find menu item. Type "&Options" over "new item" to rename it and press <b>Enter</b>. Move the arrow key to the space to the left of the Options menu item (the pixmap field) and press <b>Enter</b>. The <em>Choose an Image</em> dialog pops up. Click the <b>Add</b> button to invoke the <em>Choose Images...</em> dialog. Navigate to <tt>/tools/designer/examples/colortool/images</tt>; click the <tt>tabwidget.png</tt> image. Click <b>Open</b> to use the image and then click <b>OK</b> once you are in the <em>Choose an Image</em> dialog. The pixmap now appears next to the Options item in the menu.</p>
|
||
|
</blockquote>
|
||
|
<p>The options action ought to be visually separated from the other Edit menu options. Click the form's Edit menu, then click and drag the "new separator" item to the space above the Options item.</p>
|
||
|
<p>Since we also want to make this option available from the toolbar, click the Options action in the Action Editor and drag it to the toolbar. Drop it to the right of the magnifying glass (Find) toolbar button (after the separator); a horizontal red line indicates its position during the drag.</p>
|
||
|
<p>We'll connect and code this action later.</p>
|
||
|
<h4><a name="4-5"></a>Adding the Add Action</h4>
|
||
|
<p>Right click the first action in the Action Editor, then click <b>New Action</b>. Change the action's <em>name</em> property to "editAddAction". Change its <em>iconSet</em> property to <tt>widgetstack.png</tt>. Change the <em>text</em> property to "Add" and the <em>menuText</em> property to "&Add...". Change the <em>accel</em> property to "Ctrl+A" (press <b>CTRL+A</b> and the key combination will automatically appear in the field).</p>
|
||
|
<p>Click the Add action and drag it to be the first item in the Edit menu. (Drag it to the edit menu and drop it when the horizontal red line is above the "Cut" menu item.)</p>
|
||
|
<blockquote>
|
||
|
<p align="center"><b> Alternative Approach to Adding the Add Action</b></p>
|
||
|
<p>Click Edit on the menu bar and then click "new item", located after the Find menu item. Type "&Add" over "new item" to rename it and press <b>Enter</b>. Move the arrow key to the space to the left of the Add item and press <b>Enter</b>. The <em>Choose an Image</em> dialog pops up.Click the <b>Add</b> button to invoke the <em>Choose Images...</em> dialog. Navigate to <tt>/tools/designer/examples/colortool/images</tt>; click the <tt>tabwidget.png</tt> image. Click <b>Open</b> to use the image and then click <b>OK</b> once you are in the <em>Choose an Image</em> dialog. The pixmap now appears next to the Options item in the menu. Finally, move the arrow key to the space to the right of the Add item and and press "Ctrl+A". The accelerator key combination now appears next to the Add menu item.</p>
|
||
|
</blockquote>
|
||
|
<h4><a name="4-6"></a>Tidying Up</h4>
|
||
|
<p>We're going to use "Cut" for deleting colors, so we'll change the user-visible name to "Delete" to make its meaning clearer. Click the editCutAction in the Action Editor to make its properties appear in the <a href="designer-manual-3.html#using-the-property-editor-sidebar">Property Editor</a>. Change its <em>text</em> property to "Delete" and change its <em>menuText</em> property to "&Delete".</p>
|
||
|
<p>A side-effect of the above change is that <b>Alt+C</b> (originally used for "Cut") is now unused. Click the editCopyAction action in the Action Editor, and change its <em>menuText</em> property to "&Copy".</p>
|
||
|
<blockquote>
|
||
|
<p align="center"><b> Alternative Approach to Renaming Actions</b></p>
|
||
|
<p>To change the name of the Cut action to "Delete", click Edit on the menu bar and then click "Cut". Type "&Delete" over "Cut" and press enter.</p>
|
||
|
<p>To change name of the Copy action to "&Copy", click Edit on the menu bar and then click "Copy". Type "&Copy" over "Copy" and press enter.</p>
|
||
|
</blockquote>
|
||
|
<p>We can always check to see if there are any accelerator conflicts by clicking <b>Edit|Check Accelerators</b> (or <b>Alt+R</b>).</p>
|
||
|
<h4><a name="4-7"></a>Adding an Action Group</h4>
|
||
|
<p>We want to provide the user with a choice of views, but since they can only use one view at a time we need to ensure that the menu options and toolbar buttons they use to switch between views always stay in sync. We don't have to write any code to achieve this: we simply put the relevant actions in an action group and let Qt take care of the details.</p>
|
||
|
<p>Right click an action in the Action Editor, then click <b>New Action Group</b>. The action group's properties are now showing in the <a href="designer-manual-3.html#using-the-property-editor-sidebar">Property Editor</a>. Change the action group's <em>name</em> property to "viewActionGroup", and change its <em>text</em> property to "View". We want the action group to be <em>exclusive</em>, i.e. for only one of its actions to be "on" at any one time; but there's no need to set the <em>exclusive</em> property since it defaults to True which is what we want.</p>
|
||
|
<p>We'll now create the view actions. The process is virtually the same as for actions that are not in an action group; the only difference is that when we right click to pop up the context menu, we <em>must</em> right click the relevant action group, not just anything in the Action Editor.</p>
|
||
|
<p>Right click the viewActionGroup, then click <b>New Action</b>. Change this action's <em>name</em> property to "viewTableAction". Set its <em>toggleAction</em> property to True and set its <em>on</em> property to True. We want it to be a toggle action because either the user is using this view (it is "on") or another view (it is "off"). We set this action to "on" because it will be the default view. Change its <em>iconSet</em> property to <tt>table.png</tt>. Change the <em>text</em> property to "View Table" and the <em>menuText</em> property to "View &Table". Change the <em>accel</em> property to "Ctrl+T", and set the <em>toolTip</em> property to "View Table (Ctrl+T)". When the user clicks the <b>View</b> menu and hovers the mouse over the "View Table" option the tool tip will appear in the status bar. Similarly when the user hovers the mouse over the "View Table" toolbar button, the tool tip text will appear both in the status bar and in a temporary yellow label next to the toolbar button.</p>
|
||
|
<p>Right click the viewActionGroup, then click <b>New Action</b>. Change this action's <em>name</em> property to "viewIconsAction". Set its <em>toggleAction</em> property to True. Change its <em>iconSet</em> property to <tt>iconview.png</tt>. Change the <em>text</em> property to "View Icons" and the <em>menuText</em> property to "View &Icons". Set the <em>accel</em> property to "Ctrl+I" and change the <em>toolTip</em> property to "View Icons (Ctrl+I)".</p>
|
||
|
<h4><a name="4-8"></a>Using an Action Group</h4>
|
||
|
<p>Now that we've created the view actions we need to make them available to the user.</p>
|
||
|
<p>Click the viewActionGroup action group in the Action Editor, and drag it to the View menu; drop it on this menu (when the horizontal red line appears beneath the View menu). Because we dragged the action group, <em>all</em> its actions (in our case the viewTableAction and viewIconsAction) are added to the relevant menu. We'll also make the view actions available on the toolbar. Click the viewActionGroup once again, and drag it to the toolbar; drop it the right of the separator at the far right of the toolbar, and drop it on the toolbar's edge. (Again, a vertical red line will indicate the position.)</p>
|
||
|
<p>Don't forget that you can preview to see things in action with <b>Ctrl+T</b>, and to click <b>File|Save</b> (or press <b>Ctrl+S</b>) regularly! If you preview now you will find that if you click the view toolbar buttons and menu options that both the toolbar buttons and the menu items automatically stay in sync.</p>
|
||
|
<h3><a name="5"></a>Creating the Main Widget</h3>
|
||
|
<p>Most main-window style applications consist of a menu bar, a toolbar, a status bar and a central widget. We've already created a menu bar and toolbar, and since we've created a <a href="qmainwindow.html">QMainWindow</a> (via the main window wizard), we also have a status bar. Widgets commonly used as an application's main widget are <a href="qlistview.html">QListView</a> (which provides a tree view), <a href="qtable.html">QTable</a> and <a href="qtextedit.html">QTextEdit</a>. Since we want to provide our users with two different views of the same data, we'll use a <a href="qwidgetstack.html">QWidgetStack</a> as our main widget. The <a href="qwidgetstack.html">QWidgetStack</a> has no visual representation of its own; you place one or more widgets on each <a href="qwidgetstack.html">QWidgetStack</a> "page", as if each page was a form in its own right, and then provide the user with some mechanism for switching between pages. (This is similar in principle to using a <a href="qtabwidget.html">QTabWidget</a>.) We want to provide our users with two views: a tabular view that lists colors and their names, and an icon-based view that shows color swatches. In our example we only place a single widget on each <a href="qwidgetstack.html">QWidgetStack</a> page; but this merely reflects the application's design -- we could have placed any number of widgets on each page.</p>
|
||
|
<p>Click the Toolbox's Containers button, then click WidgetStack. Click approximately in the middle of the form to place the widget stack. Change the widget stack's <em>name</em> property to "colorWidgetStack".</p>
|
||
|
<p align="center"><img align="middle" src="mw-addmainwidg.png" width="641" height="394">
|
||
|
</p>
|
||
|
<blockquote>
|
||
|
<p align="center"><b> Widget Placement</b></p>
|
||
|
<p>When placing widgets on forms using <em>Qt Designer</em>, you only need to place things in <em>approximately</em> the right place. And there is no need to worry about the size of the widgets placed. If, for example, you place a label and then change its text so that the text doesn't fit, this doesn't matter. The reason we don't have to care about precise positions and sizes is that <em>Qt Designer</em> uses Qt's layout classes to lay out forms automatically: we just have to select sets of widgets and tell <em>Qt Designer</em> how they should be laid out in relation to each other, e.g. vertically, one above the other, or horizontally, side by side, or in a grid, and <em>Qt Designer</em> will lay them out and size them appropriately.</p>
|
||
|
<p>In this chapter we only make the most minimal use of <em>Qt Designer</em>'s layout facilities. We make more use of layouts and provide more information in chapter two, <a href="designer-manual-4.html">Creating Dialogs</a>, where we create several dialogs.</p>
|
||
|
</blockquote>
|
||
|
<p>Click the form itself, then click the <b>Lay Out Vertically</b> toolbar button. The widget stack now fills the entire form. We're now ready to populate the widget stack's pages with widgets.</p>
|
||
|
<p align="center"><img align="middle" src="mw-laymainwidg.png" width="642" height="394">
|
||
|
</p>
|
||
|
<p>Click the Toolbox's Views button. Click Table, then click approximately in the middle of the widget stack. Change the table's <em>name</em> property to "colorTable", change its <em>numRows</em> property to "0", and its <em>readOnly</em> property to "True".</p>
|
||
|
<p>If you right click a widget to pop up its context menu, in most cases the first item will be an "Edit" option. The Table widget is no different in this respect, and its "Edit" option leads to a dialog through which columns and rows can have their titles changed, etc.</p>
|
||
|
<p>Right click the table, then click <b>Edit...</b> to invoke the <em>Edit Table</em> dialog. Change the Label for column 1 to "Name". Click "2" in the Columns list so that column 2's label is shown in the Label line edit. Change column 2's label to "Hex". In the same way change column 3's label to "Web". (The reference section provides full information on this dialog.) Click <b>OK</b> to close the dialog.</p>
|
||
|
<p align="center"><img align="middle" src="mw-colortable.png" width="644" height="396">
|
||
|
</p>
|
||
|
<p>Click the widget stack, then click the <b>Lay Out Vertically</b> toolbar button. The table now fits inside the widget stack, and will resize with the widget stack (which in turn will resize with the form: try clicking <b>Ctrl+T</b> to preview and resize the previewed form).</p>
|
||
|
<p align="center"><img align="middle" src="mw-laycolortable.png" width="643" height="395">
|
||
|
</p>
|
||
|
<p>Click the "WStackPage" object in Object Explorer. Change its <em>name</em> property to "tablePage".</p>
|
||
|
<p>We're now ready to create the next page. Right click the widget stack, then click <b>Add Page</b> on the context menu. The table has "disappeared", or rather the new widget stack page obscures the first widget stack page which contains the table. Click IconView in the Toolbox, then click approximately in the middle of the widget stack. Change the IconView's <em>name</em> property to "colorIconView" and change its <em>resizeMode</em> property to "Adjust". We want our color swatches to appear in neat columns so change the <em>gridX</em> property to "100".</p>
|
||
|
<p align="center"><img align="middle" src="mw-coloriconv.png" width="643" height="397">
|
||
|
</p>
|
||
|
<p>It is often useful to create IconView items during design, but it isn't appropriate for our application. Right click the IconView to pop up its context menu, then click <b>Edit...</b> to invoke the <em>Edit IconView</em> dialog. Click <b>Delete Item</b> to delete the default item, then click <b>OK</b>.</p>
|
||
|
<p>Click the widget stack, then click the <b>Lay Out Vertically</b> toolbar button. The icon view now fits inside the widget stack.</p>
|
||
|
<p align="center"><img align="middle" src="mw-laycoloriconv.png" width="642" height="396">
|
||
|
</p>
|
||
|
<p>Click the "WStackPage" object in Object Explorer. Change its name to "iconsPage".</p>
|
||
|
<p>Right click the widget stack, then click <b>Previous Page</b>.</p>
|
||
|
<p>That completes the user interface design for our application's main window. Note that if you preview the form clicking the "View" menu options and toolbar buttons has no effect. This is because we haven't written any code to be executed when the actions triggered by these menu options and toolbar buttons occur. We'll write the necessary code in the next section.</p>
|
||
|
<h3><a name="6"></a>Writing the Code</h3>
|
||
|
<p>There are two approaches that can be taken when it comes to writing code for forms designed with <em>Qt Designer</em>. The original approach is to create a subclass of every form you create and put all your code in the subclass. Since Qt 3.0, <em>Qt Designer</em> has provided an alternative: you can write your code directly in <em>Qt Designer</em> using the code editor. See <a href="designer-manual-5.html#the-designer-approach">The Designer Approach</a> for a comparative review. For this example we will write all the code inside <em>Qt Designer</em>; for an example of the subclassing approach see <a href="designer-manual-6.html">Subclassing and Dynamic Dialogs</a>.</p>
|
||
|
<p>Before we launch into writing code we need to create some form variables. For example, we need to keep track of whether a view needs updating (because the user loaded a new set of colors, or added or deleted colors in the other view).</p>
|
||
|
<h4><a name="6-1"></a>Adding Member Variables</h4>
|
||
|
<p>Click Object Explorer's Members tab. Right click "Class Variables" (towards the bottom), then click <b>Edit</b>. The <em>Edit Class Variables</em> dialog appears. Click the <b>Add</b> button, and type in "QMap<QString,QColor> m_colors". We will use this map to relate user color names to colors. Click the <b>Add</b> button again, and type in "bool m_changed". We'll use this variable to keep track of whether the data has changed or not; this is useful for offering the user a prompt to save unsaved changes when they exit or open a new file, for example.</p>
|
||
|
<p align="center"><img align="middle" src="mw-editvar.png" width="324" height="346">
|
||
|
</p>
|
||
|
<p>In the same way add "QString m_filename" so that we can keep track of the file the user has open. Add "bool m_table_dirty" and "bool m_icons_dirty". If the user adds a color when viewing the table we'll mark the icons as 'dirty' so that the icon view will be updated if the user changes to view the icons, and vice versa. Add "bool m_show_web" -- we'll use this to record whether or not the user wants a column in the table to indicate which colors are web colors. Add "int m_clip_as" -- we'll use this to choose what to put on the clipboard when the user clicks <b>File|Copy</b>. We'll keep a pointer to the global clipboard, so add "QClipboard *clipboard". Finally add "QStringList m_comments". This is used for loading and saving color files and is explained later.</p>
|
||
|
<p>You should now have the following variables:</p>
|
||
|
<ul><li><p>QMap<QString,QColor> m_colors;</p>
|
||
|
<li><p>bool m_changed;</p>
|
||
|
<li><p>QString m_filename;</p>
|
||
|
<li><p>bool m_table_dirty;</p>
|
||
|
<li><p>bool m_icons_dirty;</p>
|
||
|
<li><p>bool m_show_web;</p>
|
||
|
<li><p>int m_clip_as;</p>
|
||
|
<li><p>QClipboard *clipboard;</p>
|
||
|
<li><p>QStringList m_comments;</p>
|
||
|
</ul><p>Press <b>Enter</b>, to confirm the last variable, then click <b>OK</b> to close the dialog. All the variables now appear in Object Explorer's Members tab.</p>
|
||
|
<h4><a name="6-2"></a>Adding Forward Declarations</h4>
|
||
|
<p>Some of the variables we've created are of classes that need forward declarations. Right click Forward Declarations (in Object Explorer's Members tab), then click <b>Edit</b>. This pops up the <em>Edit Forward Declarations</em> dialog. This dialog works the same way as the <em>Edit Class Variables</em> dialog that we've just used. Add the following forward declarations: "class QString;" and "class QColor;". Close the dialog and the forward declarations appear in Object Explorer.</p>
|
||
|
<p align="center"><img align="middle" src="mw-editforw.png" width="339" height="327">
|
||
|
</p>
|
||
|
<p>You should now have the following forward declarations:</p>
|
||
|
<ul><li><p>class QString;</p>
|
||
|
<li><p>class QColor;</p>
|
||
|
</ul><h4><a name="6-3"></a>Adding Includes</h4>
|
||
|
<p>Our form will also need some included files. Includes may be added in the declaration, or (for preference) in the implementation. Right click "Includes (in Implementation)", then click <b>Edit</b>. Use the dialog that pops up to enter "qcolor.h" and "qstring.h". Since we're going to use the clipboard we'll need access to the global clipboard object via <a href="qapplication.html">QApplication</a>, so also add "qapplication.h" and "qclipboard.h". We'll also be doing some drawing (e.g. the color swatches), so add "qpainter.h" too, then close the dialog.</p>
|
||
|
<p align="center"><img align="middle" src="mw-editincimp.png" width="339" height="327">
|
||
|
</p>
|
||
|
<p>When entering include files you can include double quotes or angle brackets if you wish; if you don't use either <em>Qt Designer</em> will put in double quotes automatically.</p>
|
||
|
<p>You should now have added the following includes (in implementation):</p>
|
||
|
<ul><li><p>"qcolor.h"</p>
|
||
|
<li><p>"qstring.h"</p>
|
||
|
<li><p>"qapplication.h"</p>
|
||
|
<li><p>"qclipboard.h"</p>
|
||
|
<li><p>"qpainter.h"</p>
|
||
|
</ul><h4><a name="6-4"></a>Signals and Slots Connections</h4>
|
||
|
<p>Most of the signals and slots connections were created automatically by the main window wizard when we created the main form. We have added some new actions since then, and we need to ensure that they are connected to slots so that we can code their behavior.</p>
|
||
|
<p align="center"><img align="middle" src="mw-conn1.png" width="608" height="341">
|
||
|
</p>
|
||
|
<blockquote>
|
||
|
<p align="center"><b> Creating Signals and Slots Connections</b></p>
|
||
|
<a name="creating-signals-and-slots-connections-sidebar"></a><p>Click <b>Edit|Connections</b> to invoke the <em>View and Edit Connections</em> dialog.</p>
|
||
|
<p>The use of this dialog usually follows the same pattern. We click <b>New</b> to create a new connection, then we select the Sender widget, the sender's Signal and the Receiver (usually the form). If we want to use a pre-defined slot, we select that slot; otherwise we click <b>Edit Slots...</b> create a new slot on-the-fly, and select the newly created slot. (The old method of clicking and dragging to create connections is still supported, but the new method is a lot faster and easier, especially for creating lots of connections in one go.)</p>
|
||
|
</blockquote>
|
||
|
<p>We want to update the status bar so that the user can see information about the color they're on. Click <b>Edit|Connections</b> to invoke the <em>View and Edit Connections</em> dialog. Click <b>New</b> to create a new connection. Change the Sender to "colorTable" and the Signal to "currentChanged(int,int)". Change the Receiver to "MainForm".</p>
|
||
|
<p align="center"><img align="middle" src="mw-conn2.png" width="608" height="391">
|
||
|
</p>
|
||
|
<p>We want to connect to our own custom slot which we haven't yet created. Click the <b>Edit Slots...</b> button to invoke the <em>Edit Functions</em> dialog. Click <b>New Function</b> and change the slot name to "changedTableColor(int,int)". Click <b>OK</b> to close the dialog.</p>
|
||
|
<p align="center"><img align="middle" src="mw-editfunc.png" width="608" height="397">
|
||
|
</p>
|
||
|
<p>Now change the Slot in the <em>View and Edit Connections</em> dialog to our newly created "changedTableColor(int,int)" slot.</p>
|
||
|
<p align="center"><img align="middle" src="mw-conn3.png" width="600" height="343">
|
||
|
</p>
|
||
|
<p>Click <b>New</b> to create a new connection. Change the Sender to "colorIconView" and the Signal to "currentChanged(QIconViewItem*)". Change the Receiver to "MainForm". Click the <b>Edit Slots...</b> button to invoke the <em>Edit Functions</em> dialog. Click <b>New Function</b> and change the slot name to "changedIconColor(QIconViewItem*)". Click <b>OK</b> to close the dialog. Now change the Slot in the <em>View and Edit Connections</em> dialog to "changedIconColor(QIconViewItem*)".</p>
|
||
|
<p>Now we can implement our <tt>changedTableColor()</tt> and <tt>changedIconColor()</tt> slots to update the status bar with details about the current color.</p>
|
||
|
<p>We also want to ensure that when the user changes view, the colors shown in the view are correct. For example, if the user deleted a color in the table view and changed to the icon view, we must ensure that the icon view does not show the deleted color.</p>
|
||
|
<p>Click <b>New</b> to create a new connection. Change the Sender to "colorWidgetStack", the Signal to "aboutToShow(int)", and the Receiver to "MainForm". Create a new slot called "aboutToShow()" and make this the Slot that the widget stack's "aboutToShow(int)" signal connects to. The signal includes the ID of the widget that is about to be shown; but we don't need it so we create a slot that doesn't take any parameters.</p>
|
||
|
<p>Once crucial piece of functionality is to allow the user to switch between views. We could connect each of the view actions separately, but it is more convenient (and easier to extend) if we connect the action group as a whole.</p>
|
||
|
<p>Create a new connection with the "viewActionGroup" as the Sender. Change the Signal to "selected(QAction*)" and change the Receiver to "MainForm". Create a slot called "changeView(QAction*)" and make this the slot that the signal connects to.</p>
|
||
|
<p>Click <b>OK</b> to close the <em>View and Edit Connections</em> dialog. We are now ready to write the code.</p>
|
||
|
<p align="center"><img align="middle" src="mw-conn4.png" width="600" height="407">
|
||
|
</p>
|
||
|
<h4><a name="6-5"></a>Editing the Code: Setting Up</h4>
|
||
|
<p>There is quite a lot of code to include in the application, but this does not mean that a lot of typing is required! All the code is reproduced here so, if you're reading an electronic copy, you can simply cut and paste. If you're reading a print copy, all the code is provided in <tt>/tools/designer/examples/colortool</tt>; simply open the relevant <tt>.ui.h</tt> files and copy and paste from there into your own version of the project.</p>
|
||
|
<blockquote>
|
||
|
<p align="center"><b> Cutting & Pasting Into the Code Editor</b></p>
|
||
|
<p>If you cut and paste code from this manual, because we've indented the code for readability, the code will be over-indented in <em>Qt Designer</em>. This is easily solved. Simply select the function containing the pasted code (either with the mouse, or <b>Shift+Arrow</b>s) and press <b>Tab</b>: this will make <em>Qt Designer</em> fix the indentation. Note that you must select the <em>entire</em> function, including its name and parameters.</p>
|
||
|
<p>Remember that if you copy and paste just the body of functions into the skeletons <em>Qt Designer</em> provides, you must manually enter the names of the arguments in the functions' parameter lists.</p>
|
||
|
</blockquote>
|
||
|
<p>Click <tt>mainform.ui.h</tt> in the Project Overview window. A code editor window showing the empty slots appears.</p>
|
||
|
<h4><a name="6-6"></a>Adding Constants</h4>
|
||
|
<pre> const int CLIP_AS_HEX = 0;
|
||
|
const int CLIP_AS_NAME = 1;
|
||
|
const int CLIP_AS_RGB = 2;
|
||
|
const int COL_NAME = 0;
|
||
|
const int COL_HEX = 1;
|
||
|
const int COL_WEB = 2;
|
||
|
const <a href="qstring.html">QString</a> WINDOWS_REGISTRY = "/QtExamples";
|
||
|
const <a href="qstring.html">QString</a> APP_KEY = "/ColorTool/";
|
||
|
</pre>
|
||
|
<p>We define some useful constants for our form since it's easier to remember "CLIP_AS_RGB" than "2". The two <a href="qstring.html">QString</a>s are used by <a href="qsettings.html">QSettings</a> when we come to load and save user preferences; they're explained when we cover <tt>loadOptions()</tt> and <tt>saveOptions()</tt>. Note that we can insert any valid C++ into a <tt>.ui.h</tt> file including constant declarations as we've done here and <tt>#include</tt>s, etc.</p>
|
||
|
<p>Since we're not subclassing if we want to have code executed during construction we must create an <tt>init()</tt> function; this will be called at the end of the form's constructor.</p>
|
||
|
<h4><a name="6-7"></a>init()</h4>
|
||
|
<pre> void MainForm::init()
|
||
|
{
|
||
|
clipboard = QApplication::<a href="qapplication.html#clipboard">clipboard</a>();
|
||
|
if ( clipboard->supportsSelection() )
|
||
|
clipboard->setSelectionMode( TRUE );
|
||
|
|
||
|
findForm = 0;
|
||
|
loadSettings();
|
||
|
m_filename = "";
|
||
|
m_changed = FALSE;
|
||
|
m_table_dirty = TRUE;
|
||
|
m_icons_dirty = TRUE;
|
||
|
clearData( TRUE );
|
||
|
}
|
||
|
</pre>
|
||
|
<p>The first thing we do is take a pointer to the global clipboard object. The <tt>setSelectionMode()</tt> call ensures that the clipboard works as expected on all platforms. The "findForm" and "loadSettings()" lines will be covered later; if you're entering the code, comment them out for now. We set the filename to be empty because the user hasn't opened a file. We set changed to false since no changes have taken place yet. But we mark both the table and the icon view as dirty since we want these to be drawn straight away. We call the <tt>clearData()</tt> function that we'll write next; this function clears all the color data, and if called with "TRUE", it creates new colors with default values.</p>
|
||
|
<h4><a name="6-8"></a>clearData()</h4>
|
||
|
<pre> void MainForm::clearData( bool fillWithDefaults )
|
||
|
{
|
||
|
<a href="qwidget.html#setCaption">setCaption</a>( "Color Tool" );
|
||
|
|
||
|
m_colors.clear();
|
||
|
m_comments.clear();
|
||
|
|
||
|
if ( fillWithDefaults ) {
|
||
|
m_colors["black"] = Qt::black;
|
||
|
m_colors["blue"] = Qt::blue;
|
||
|
m_colors["cyan"] = Qt::cyan;
|
||
|
m_colors["darkblue"] = Qt::darkBlue;
|
||
|
m_colors["darkcyan"] = Qt::darkCyan;
|
||
|
m_colors["darkgray"] = Qt::darkGray;
|
||
|
m_colors["darkgreen"] = Qt::darkGreen;
|
||
|
m_colors["darkmagenta"] = Qt::darkMagenta;
|
||
|
m_colors["darkred"] = Qt::darkRed;
|
||
|
m_colors["darkyellow"] = Qt::darkYellow;
|
||
|
m_colors["gray"] = Qt::gray;
|
||
|
m_colors["green"] = Qt::green;
|
||
|
m_colors["lightgray"] = Qt::lightGray;
|
||
|
m_colors["magenta"] = Qt::magenta;
|
||
|
m_colors["red"] = Qt::red;
|
||
|
m_colors["white"] = Qt::white;
|
||
|
m_colors["yellow"] = Qt::yellow;
|
||
|
}
|
||
|
|
||
|
populate();
|
||
|
}
|
||
|
</pre>
|
||
|
<p>This function is used when we start the application and when the user creates a new file or loads an existing file. It clears out the data and optionally inserts default colors. We set the application's caption because when we load and save files we add the filename to the caption, so when we clear we need to remove any filename from the caption. We clear the colors map and the comments string list, then optionally fill the colors map with some standard colors. Finally we call <tt>populate()</tt> which is the function we'll create next to fill the table and icon view with data.</p>
|
||
|
<h4><a name="6-9"></a>populate()</h4>
|
||
|
<pre> void MainForm::populate()
|
||
|
{
|
||
|
if ( m_table_dirty ) {
|
||
|
for ( int r = 0; r < colorTable->numRows(); ++r ) {
|
||
|
for ( int c = 0; c < colorTable->numCols(); ++c ) {
|
||
|
colorTable->clearCell( r, c );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
colorTable->setNumRows( m_colors.count() );
|
||
|
if ( ! m_colors.isEmpty() ) {
|
||
|
<a href="qpixmap.html">QPixmap</a> pixmap( 22, 22 );
|
||
|
int row = 0;
|
||
|
QMap<QString,QColor>::ConstIterator it;
|
||
|
for ( it = m_colors.constBegin(); it != m_colors.constEnd(); ++it ) {
|
||
|
<a href="qcolor.html">QColor</a> color = it.data();
|
||
|
pixmap.<a href="qpixmap.html#fill">fill</a>( color );
|
||
|
colorTable->setText( row, COL_NAME, it.key() );
|
||
|
colorTable->setPixmap( row, COL_NAME, pixmap );
|
||
|
colorTable->setText( row, COL_HEX, color.<a href="qcolor.html#name">name</a>().upper() );
|
||
|
if ( m_show_web ) {
|
||
|
<a href="qchecktableitem.html">QCheckTableItem</a> *item = new <a href="qchecktableitem.html">QCheckTableItem</a>( colorTable, "" );
|
||
|
item-><a href="qchecktableitem.html#setChecked">setChecked</a>( isWebColor( color ) );
|
||
|
colorTable->setItem( row, COL_WEB, item );
|
||
|
}
|
||
|
row++;
|
||
|
}
|
||
|
colorTable->setCurrentCell( 0, 0 );
|
||
|
}
|
||
|
colorTable->adjustColumn( COL_NAME );
|
||
|
colorTable->adjustColumn( COL_HEX );
|
||
|
if ( m_show_web ) {
|
||
|
colorTable->showColumn( COL_WEB );
|
||
|
colorTable->adjustColumn( COL_WEB );
|
||
|
}
|
||
|
else
|
||
|
colorTable->hideColumn( COL_WEB );
|
||
|
m_table_dirty = FALSE;
|
||
|
}
|
||
|
|
||
|
if ( m_icons_dirty ) {
|
||
|
colorIconView->clear();
|
||
|
|
||
|
QMap<QString,QColor>::ConstIterator it;
|
||
|
for ( it = m_colors.constBegin(); it != m_colors.constEnd(); ++it )
|
||
|
(void) new <a href="qiconviewitem.html">QIconViewItem</a>( colorIconView, it.key(),
|
||
|
colorSwatch( it.data() ) );
|
||
|
m_icons_dirty = FALSE;
|
||
|
}
|
||
|
}
|
||
|
</pre>
|
||
|
<p>This function is at the heart of the application. It visually presents the data to the user. If the table is "dirty" (e.g. if the user has added or deleted colors in the icon view, or has opened a color file) we will populate the table. We start by deleting the contents of every cell. Next we change the number of rows to equal the number of colors in the colors map. For each color we want to display a little square that shows the color, so we create a pixmap of the required size.</p>
|
||
|
<p>We now create an iterator for our colors map, and iterate over every color. The colors map has the user's color names as its keys, and <a href="qcolor.html">QColor</a> instances as values. We retrieve the color and fill our pixmap with that color. We then set the "Name" column (column <tt>COL_NAME</tt>), to have the color's name (<tt>it.key()</tt>) and the pixmap we've just filled with that color. <a href="qcolor.html">QColor</a>'s <tt>name()</tt> function returns a string that is the hex representation of a color, e.g. "#12AB2F"; we retrieve this and set the second ("Hex") column to this value.</p>
|
||
|
<p>If the user wants to see if which colors are web colors we create a <a href="qchecktableitem.html">QCheckTableItem</a>, and check it if it is a web color. (We'll cover <tt>isWebColor()</tt> shortly.) We then insert this <a href="qchecktableitem.html">QCheckTableItem</a> into the "Web" column.</p>
|
||
|
<p>Having populated the table we call <tt>adjustColumn()</tt> to ensure that each column is just wide enough to show its widest entry, and show or hide the "Web" column depending on the user's preference.</p>
|
||
|
<p>Finally we set <tt>m_table_dirty</tt> to FALSE, since it is now up-to-date.</p>
|
||
|
<p>If the icon view is "dirty" we <tt>clear()</tt> it of any existing data. We then iterate over each color in our colors map. For each color we create a new <a href="qiconviewitem.html">QIconViewItem</a>; we label the item with the user's color name and provide a pixmap (generated by <tt>colorSwatch()</tt>, covered shortly) in the relevant color. Finally we set <tt>m_icons_dirty</tt> to "FALSE", since it is now up-to-date.</p>
|
||
|
<h4><a name="6-10"></a>isWebColor()</h4>
|
||
|
<pre> bool MainForm::isWebColor( <a href="qcolor.html">QColor</a> color )
|
||
|
{
|
||
|
int r = color.<a href="qcolor.html#red">red</a>();
|
||
|
int g = color.<a href="qcolor.html#green">green</a>();
|
||
|
int b = color.<a href="qcolor.html#blue">blue</a>();
|
||
|
|
||
|
return ( ( r == 0 || r == 51 || r == 102 ||
|
||
|
r == 153 || r == 204 || r == 255 ) &&
|
||
|
( g == 0 || g == 51 || g == 102 ||
|
||
|
g == 153 || g == 204 || g == 255 ) &&
|
||
|
( b == 0 || b == 51 || b == 102 ||
|
||
|
b == 153 || b == 204 || b == 255 ) );
|
||
|
}
|
||
|
</pre>
|
||
|
<p>The 216 web colors are those colors whose RGB (Red, Green, Blue) values are all in the set (0, 51, 102, 153, 204, 255).</p>
|
||
|
<h4><a name="6-11"></a>colorSwatch()</h4>
|
||
|
<pre> QPixmap MainForm::colorSwatch( const <a href="qcolor.html">QColor</a> color )
|
||
|
{
|
||
|
<a href="qpixmap.html">QPixmap</a> pixmap( 80, 80 );
|
||
|
pixmap.<a href="qpixmap.html#fill">fill</a>( white );
|
||
|
<a href="qpainter.html">QPainter</a> painter;
|
||
|
painter.<a href="qpainter.html#begin">begin</a>( &pixmap );
|
||
|
painter.<a href="qpainter.html#setPen">setPen</a>( <a href="qt.html#PenStyle-enum">NoPen</a> );
|
||
|
painter.<a href="qpainter.html#setBrush">setBrush</a>( color );
|
||
|
painter.<a href="qpainter.html#drawEllipse">drawEllipse</a>( 0, 0, 80, 80 );
|
||
|
painter.<a href="qpainter.html#end">end</a>();
|
||
|
return pixmap;
|
||
|
}
|
||
|
</pre>
|
||
|
<p>We create a pixmap of a suitable size and fill it with white. We then create a <a href="qpainter.html">QPainter</a> which we'll use to paint on the pixmap. We don't want a pen because we don't want an outline around the shape we draw. We draw an ellipse (which will be circular since we draw in an 80 x 80 pixel square). We return the resultant pixmap.</p>
|
||
|
<h4><a name="6-12"></a>Creating main.cpp</h4>
|
||
|
<p>Now that we've entered some of the code it would be nice to build and run the application to get a feel for the progress we've made. To do this we need to create a <tt>main()</tt> function. In Qt we typically create a small <tt>main.cpp</tt> file for the <tt>main()</tt> function. We can ask <em>Qt Designer</em> to create this file for us.</p>
|
||
|
<p>Click <b>File|New</b> to invoke the <em>New File</em> dialog. Click "C++ Main-File", then click OK. The <em>Configure Main-File</em> dialog appears, listing the all the forms in the project. We've only got one form, "MainForm", so it is already highlighted. Click <b>OK</b> to create a <tt>main.cpp</tt> file that loads our MainForm.</p>
|
||
|
<pre> #include <<a href="qapplication-h.html">qapplication.h</a>>
|
||
|
#include "mainform.h"
|
||
|
|
||
|
int main( int argc, char ** argv )
|
||
|
{
|
||
|
<a href="qapplication.html">QApplication</a> a( argc, argv );
|
||
|
MainForm *w = new MainForm;
|
||
|
w->show();
|
||
|
return a.<a href="qapplication.html#exec">exec</a>();
|
||
|
}
|
||
|
</pre>
|
||
|
<p>When <em>Qt Designer</em> generates a <tt>main.cpp</tt> file it includes this line:</p>
|
||
|
<pre>
|
||
|
a.connect( &a, SIGNAL( lastWindowClosed() ), &a, SLOT( quit() ) );
|
||
|
</pre>
|
||
|
<p>If we left this code as-is, the user could by-pass our own termination code by clicking the main window's close (X) button. Since we want to give the user the option to save any unsaved changes we need to ensure that we intercept any attempt to close the application. To achieve this we delete the connection and add a new slot, <tt>closeEvent()</tt> which will intercept attempts to close the application and call our <tt>fileExit()</tt> function.</p>
|
||
|
<p>Click <tt>main.cpp</tt> in the Project Overview window. The file will appear in an editing window. Delete the connect line.</p>
|
||
|
<p>Click <tt>mainform.ui.h</tt> in the Project Overview window; (you may need to click <tt>mainform.ui</tt> first to reveal <tt>mainform.ui.h</tt>). Right click "fileExit()" in Object Explorer's Members list (under Slots, public), then click <b>Goto Implementation</b>. Add the following slot above the <tt>fileExit()</tt> slot:</p>
|
||
|
<pre> void MainForm::closeEvent( <a href="qcloseevent.html">QCloseEvent</a> * )
|
||
|
{
|
||
|
fileExit();
|
||
|
}
|
||
|
</pre>
|
||
|
<p>Now, whatever the user clicks to close the application, our <tt>fileExit()</tt> slot will be called. We'll code the <tt>fileExit()</tt> slot right now:</p>
|
||
|
<pre> void MainForm::fileExit()
|
||
|
{
|
||
|
QApplication::<a href="qapplication.html#exit">exit</a>( 0 );
|
||
|
}
|
||
|
</pre>
|
||
|
<p>This ensures that our application will cleanly terminate. Later we'll revise this function to give the user the opportunity to save any unsaved data.</p>
|
||
|
<h4><a name="6-13"></a>Building and Running</h4>
|
||
|
<p>We now have some code in the application and a <tt>main.cpp</tt> containing the <tt>main()</tt> function, so we should be able to compile, link and run the application.</p>
|
||
|
<p>Click <b>File|Save</b> to ensure that all our work is saved to disk. Open a console (e.g. an xterm or DOS window), change directory to where you have saved the <tt>colortool</tt> project, and run <tt>qmake</tt> to generate a Makefile:</p>
|
||
|
<pre>
|
||
|
qmake -o Makefile colortool.pro
|
||
|
</pre>
|
||
|
<p>Now make the project (run <tt>nmake</tt> on Windows, <tt>make</tt> on other platforms). Providing you commented out the "findForm" and "loadSettings" lines in the <tt>init()</tt> function, the program should build. (If it doesn't build see the <a href="designer-manual-4.html#6">Troubleshooting</a> section.)</p>
|
||
|
<p>Once the make has finished, run the program. You still can't change views since we haven't written the code for that yet, but it does create a default set of colors. You can terminate the application by clicking the close (X) button or by clicking <b>File|Exit</b>.</p>
|
||
|
<h4><a name="6-14"></a>Editing the Code: Updating the Status Bar</h4>
|
||
|
<p>We want to show information about the current color in the status bar, and we want to ensure that when the user changes their view or loads in a color file, the relevant view is updated.</p>
|
||
|
<h4><a name="6-15"></a>aboutToShow()</h4>
|
||
|
<pre> void MainForm::aboutToShow()
|
||
|
{
|
||
|
populate();
|
||
|
}
|
||
|
</pre>
|
||
|
<p>We could have made <tt>populate()</tt> a slot and connected directly to it. We've used the indirection because it's clearer and in a real application there would probably be more to do in this slot.</p>
|
||
|
<h4><a name="6-16"></a>changedTableColor()</h4>
|
||
|
<pre> void MainForm::changedTableColor( int row, int )
|
||
|
{
|
||
|
changedColor( colorTable->text( row, COL_NAME ) );
|
||
|
}
|
||
|
</pre>
|
||
|
<p>We connected to this slot so that we'd know whenever the user moved or clicked in the table view. We call the <tt>changedColor()</tt> function (which we'll see in a moment) with the name of the current color. Note that we don't care about the column argument, so we could have left it out. Don't forget to name the changedTableColor parameter to "int row".</p>
|
||
|
<h4><a name="6-17"></a>changedIconColor()</h4>
|
||
|
<pre> void MainForm::changedIconColor( <a href="qiconviewitem.html">QIconViewItem</a> *item )
|
||
|
{
|
||
|
changedColor( item-><a href="qtableitem.html#text">text</a>() );
|
||
|
}
|
||
|
</pre>
|
||
|
<p>This slot is connected for the same purpose as <tt>changedTableColor()</tt>, above. It also calls <tt>changedColor()</tt> with the name of the current color. (If you're cutting and pasting the code don't forget to name the <a href="qiconviewitem.html">QIconViewItem</a> parameter "item".)</p>
|
||
|
<h4><a name="6-18"></a>changedColor()</h4>
|
||
|
<p>This is a function that we need to write from scratch. Simply enter its code into <em>Qt Designer</em>'s code editor and it will automatically appear in Object Explorer's Members tab (under Functions, public).</p>
|
||
|
<p>By default any function that it typed directly into the code editor becomes a public function. To change this, right click the function's name in Object Explorer's Members list, and click <b>Properties</b> to invoke the <em>Edit Functions</em> dialog. This dialog can be used to change various attributes of the function, including changing it into a slot.</p>
|
||
|
<pre> void MainForm::changedColor( const <a href="qstring.html">QString</a>& name )
|
||
|
{
|
||
|
<a href="qcolor.html">QColor</a> color = m_colors[name];
|
||
|
int r = color.<a href="qcolor.html#red">red</a>();
|
||
|
int g = color.<a href="qcolor.html#green">green</a>();
|
||
|
int b = color.<a href="qcolor.html#blue">blue</a>();
|
||
|
statusBar()->message( QString( "%1 \"%2\" (%3,%4,%5)%6 {%7 %8 %9}" ).
|
||
|
arg( <a href="qobject.html#name-prop">name</a> ).
|
||
|
arg( color.<a href="qcolor.html#name">name</a>().upper() ).
|
||
|
arg( r ).arg( g ).arg( b ).
|
||
|
arg( isWebColor( color ) ? " web" : "" ).
|
||
|
arg( r / 255.0, 1, 'f', 3 ).
|
||
|
arg( g / 255.0, 1, 'f', 3 ).
|
||
|
arg( b / 255.0, 1, 'f', 3 )
|
||
|
);
|
||
|
}
|
||
|
</pre>
|
||
|
<p>This function looks up the color name in the colors map and retrieves the color the name refers to. It then displays the name, hex value and whether the color is a web color in the status bar.</p>
|
||
|
<p>Note that <a href="qmainwindow.html">QMainWindow</a> only creates a status bar if you actually use one. Since we haven't used one up until now we've had no problem, but if we were to try compiling we'd get an error because we're now using a status bar but haven't declared the relevant header. Click Object Explorer's Members tab and add a "qstatusbar.h" to the "Includes (In Implementation)" section. (Right click "Includes (In Implementation)", click <b>New</b>, enter "qstatusbar.h" then press <b>Enter</b>.)</p>
|
||
|
<p>You should now have added the following declaration to your includes (in implementation):</p>
|
||
|
<ul><li><p>"qstatusbar.h"</p>
|
||
|
</ul><p>Try saving (press <b>Ctrl+S</b>), making and running the application. Move to different colors and see the status bar indicating the color you are on. (If it doesn't build see the <a href="designer-manual-4.html#6">Troubleshooting</a> section.)</p>
|
||
|
<h4><a name="6-19"></a>Changing Views</h4>
|
||
|
<p>Up to now we have not yet been able to see the icon view in action because there's been no code in place to switch views. We'll address this issue now.</p>
|
||
|
<p>We have already created a <tt>changeView()</tt> slot that is called when the user clicks one of the view toolbar buttons or menu options, so we just need to write in the code.</p>
|
||
|
<pre> void MainForm::changeView(QAction* action)
|
||
|
{
|
||
|
if ( action == viewTableAction )
|
||
|
colorWidgetStack->raiseWidget( tablePage );
|
||
|
else
|
||
|
colorWidgetStack->raiseWidget( iconsPage );
|
||
|
}
|
||
|
</pre>
|
||
|
<p>(If you're cutting and pasting the code don't forget to name the <a href="qaction.html">QAction</a> parameter "action".)</p>
|
||
|
<h4><a name="6-20"></a>Editing the Code: File Handling</h4>
|
||
|
<p>Since the X Consortium has already defined a file format for relating colors to color names we will use their format rather than creating one specially for the application. This has the advantage that we will be able to read and write <tt>rgb.txt</tt>, and that our format will be familiar to many users.</p>
|
||
|
<h4><a name="6-21"></a>fileNew()</h4>
|
||
|
<pre> void MainForm::fileNew()
|
||
|
{
|
||
|
if ( okToClear() ) {
|
||
|
m_filename = "";
|
||
|
m_changed = FALSE;
|
||
|
m_table_dirty = TRUE;
|
||
|
m_icons_dirty = TRUE;
|
||
|
clearData( FALSE );
|
||
|
}
|
||
|
}
|
||
|
</pre>
|
||
|
<p>This function doesn't load or save any data; it simply checks to see if it is okay to clear the existing data (with the call to <tt>okToClear()</tt> which we'll look at next), and if it is okay, it initializes the form.</p>
|
||
|
<h4><a name="6-22"></a>okToClear()</h4>
|
||
|
<p>Before we can create a new set of colors, or load an existing set, we must check to see if there are any unsaved changes. If there are, we must give the user the opportunity of saving their data. That's what this function does.</p>
|
||
|
<pre> bool MainForm::okToClear()
|
||
|
{
|
||
|
if ( m_changed ) {
|
||
|
<a href="qstring.html">QString</a> msg;
|
||
|
if ( m_filename.isEmpty() )
|
||
|
msg = "Unnamed colors ";
|
||
|
else
|
||
|
msg = QString( "Colors '%1'\n" ).arg( m_filename );
|
||
|
msg += QString( "has been changed." );
|
||
|
int ans = QMessageBox::<a href="qmessagebox.html#information">information</a>(
|
||
|
this,
|
||
|
"Color Tool -- Unsaved Changes",
|
||
|
msg, "&Save", "Cancel", "&Abandon",
|
||
|
0, 1 );
|
||
|
if ( ans == 0 )
|
||
|
fileSave();
|
||
|
else if ( ans == 1 )
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
</pre>
|
||
|
<p>If the data has changed (<tt>m_changed</tt> is TRUE), we present the user with a message box offering the option of saving their data, or cancelling the current operation (e.g. not loading a new file, or not creating a new set of colors), or abandoning their changes and continuing. We make the <b>Save</b> button the default button (pressed by <b>Enter</b>) and the <b>Cancel</b> button the escape button (pressed by <b>Esc</b>).</p>
|
||
|
<p>Since we're using a <a href="qmessagebox.html">QMessageBox</a> we need to include the relevant header. (Right click "Includes (in Implementation)", then click <b>New</b>. Type "qmessagebox.h" and press <b>Enter</b>.)</p>
|
||
|
<p>You should now have added the following declaration to your includes (in implementation):</p>
|
||
|
<ul><li><p>"qmessagebox.h"</p>
|
||
|
</ul><h4><a name="6-23"></a>fileOpen()</h4>
|
||
|
<pre> void MainForm::fileOpen()
|
||
|
{
|
||
|
if ( ! okToClear() )
|
||
|
return;
|
||
|
|
||
|
<a href="qstring.html">QString</a> filename = QFileDialog::<a href="qfiledialog.html#getOpenFileName">getOpenFileName</a>(
|
||
|
QString::null, "Colors (*.txt)", this,
|
||
|
"file open", "Color Tool -- File Open" );
|
||
|
if ( ! filename.<a href="qstring.html#isEmpty">isEmpty</a>() )
|
||
|
load( filename );
|
||
|
else
|
||
|
statusBar()->message( "File Open abandoned", 2000 );
|
||
|
}
|
||
|
</pre>
|
||
|
<p>If it isn't okay to clear the data (i.e. the user has unsaved changes and clicked <b>Cancel</b> in the message box popped up by <tt>okToClear()</tt>), we simply return. Otherwise we ask the user for a filename using one of <a href="qfiledialog.html">QFileDialog</a>'s static functions, and if we got the filename we attempt to load the file.</p>
|
||
|
<p>Since we're using a <a href="qfiledialog.html">QFileDialog</a> we need to include the relevant header. (Right click "Includes (in Implementation)", then click <b>New</b>. Type "qfiledialog.h" and press <b>Enter</b>.)</p>
|
||
|
<p>You should now have added the following declaration to your includes (in implementation):</p>
|
||
|
<ul><li><p>"qfiledialog.h"</p>
|
||
|
</ul><h4><a name="6-24"></a>load()</h4>
|
||
|
<pre> void MainForm::load( const <a href="qstring.html">QString</a>& filename )
|
||
|
{
|
||
|
clearData( FALSE );
|
||
|
m_filename = filename;
|
||
|
<a href="qregexp.html">QRegExp</a> regex( "^\\s*(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\S+.*)$" );
|
||
|
<a href="qfile.html">QFile</a> file( filename );
|
||
|
if ( file.<a href="qfile.html#open">open</a>( IO_ReadOnly ) ) {
|
||
|
statusBar()->message( QString( "Loading '%1'..." ).
|
||
|
arg( filename ) );
|
||
|
<a href="qtextstream.html">QTextStream</a> stream( &file );
|
||
|
<a href="qstring.html">QString</a> line;
|
||
|
while ( ! stream.<a href="qtextstream.html#eof">eof</a>() ) {
|
||
|
line = stream.<a href="qtextstream.html#readLine">readLine</a>();
|
||
|
if ( regex.<a href="qregexp.html#search">search</a>( line ) == -1 )
|
||
|
m_comments += line;
|
||
|
else
|
||
|
m_colors[regex.<a href="qregexp.html#cap">cap</a>( 4 )] = QColor(
|
||
|
regex.<a href="qregexp.html#cap">cap</a>( 1 ).toInt(),
|
||
|
regex.<a href="qregexp.html#cap">cap</a>( 2 ).toInt(),
|
||
|
regex.<a href="qregexp.html#cap">cap</a>( 3 ).toInt() );
|
||
|
}
|
||
|
file.<a href="qfile.html#close">close</a>();
|
||
|
m_filename = filename;
|
||
|
<a href="qwidget.html#setCaption">setCaption</a>( QString( "Color Tool -- %1" ).arg( m_filename ) );
|
||
|
statusBar()->message( QString( "Loaded '%1'" ).
|
||
|
arg( m_filename ), 3000 );
|
||
|
<a href="qwidget.html">QWidget</a> *visible = colorWidgetStack->visibleWidget();
|
||
|
m_icons_dirty = ! ( m_table_dirty = ( <a href="qwidget.html#visible-prop">visible</a> == tablePage ) );
|
||
|
populate();
|
||
|
m_icons_dirty = ! ( m_table_dirty = ( visible != tablePage ) );
|
||
|
m_changed = FALSE;
|
||
|
}
|
||
|
else
|
||
|
statusBar()->message( QString( "Failed to load '%1'" ).
|
||
|
arg( m_filename ), 3000 );
|
||
|
}
|
||
|
</pre>
|
||
|
<p>Before loading new data, we clear out any existing data. The format of an <tt>rgb.txt</tt> file is:</p>
|
||
|
<pre>
|
||
|
RED WHITESPACE GREEN WHITESPACE BLUE WHITESPACE NAME
|
||
|
</pre>
|
||
|
<p>Where RED, GREEN and BLUE are decimal numbers in the range 0..255 taking up three characters padded with leading spaces where necessary. The WHITESPACE between the colors is usually a single space, and between BLUE and the NAME two tabs. The NAME may include whitespace. For example:</p>
|
||
|
<pre>
|
||
|
0 191 255 deep sky blue
|
||
|
176 48 96 maroon
|
||
|
199 21 133 medium violet red
|
||
|
</pre>
|
||
|
<p>The file may also include comment lines; these begin with '!' for example.</p>
|
||
|
<p>There are numerous approaches we could have taken to parsing these files, but we've opted for a simple regular expression (regex). The regex is more "liberal" regarding the whitespace in the input than the format demands.</p>
|
||
|
<p>If a line matches the regex we create a new entry in the <tt>m_colors</tt> <a href="qmap.html">QMap</a>, setting its text to be the name of the color (<tt>regex.cap( 4 )</tt>), and its value to be a new <a href="qcolor.html">QColor</a> created from the red, green and blue values. Lines that don't match the regex are treated as comments and are stored in the <tt>m_comments</tt> string list. (When we save the file we write all the comments out first even if they appeared in the middle of the file.)</p>
|
||
|
<p>Once we've populated the <tt>m_colors</tt> map we mark the visible view as "dirty" and call <tt>populate()</tt> to update it. We then mark the visible view as not dirty and the non-visible view as dirty. This ensures that when user changes the view, the view they switch to will be updated. We could have simply marked both views as dirty and updated them both, but it is more efficient to update "lazily", after all the user may only ever use one view, so why waste their time updating the other one.</p>
|
||
|
<p>Since we're using <a href="qfile.html">QFile</a> and <a href="qregexp.html">QRegExp</a> we need to include the relevant headers. (Right click "Includes (in Implementation)", then click <b>New</b>. Type "qfile.h" and press <b>Enter</b>. Repeat this process to add "qregexp.h".)</p>
|
||
|
<p>You should now have added the following declarations to your includes (in implementation):</p>
|
||
|
<ul><li><p>"qfile.h"</p>
|
||
|
<li><p>"qregexp.h"</p>
|
||
|
</ul><blockquote>
|
||
|
<p align="center"><b> The Regular Expression</b></p>
|
||
|
<p>The regex we've used can be broken up into the following pieces:</p>
|
||
|
<pre>
|
||
|
Regex: ^ \\s* (\\d+) \\s+ (\\d+) \\s+ (\\d+) \\s+ (\\S+.*) $
|
||
|
Pieces: A B C D C D C D E F
|
||
|
Captures: cap(1) cap(2) cap(3) cap(4)
|
||
|
</pre>
|
||
|
<p>Piece A says the regex must match from the beginning of the string, and piece F says the regex must match to the end of the string: so the regex must match the whole string or not match at all. The 'B' piece matches zero or more whitespaces (i.e. any leading whitespace), and the D pieces match one or more whitespaces (i.e. the gaps between each number). The 'C' pieces match one or more digits, i.e. the numbers. Piece E matches one or more non-whitespace followed by anything else, i.e. the name of the color.</p>
|
||
|
<p>The parentheses are used to <em>capture</em> the parts of the match that they enclose. The captured parts are numbered from 1.</p>
|
||
|
<p>For more information on regexes see the <a href="qregexp.html">QRegExp</a> documentation.</p>
|
||
|
</blockquote>
|
||
|
<h4><a name="6-25"></a>fileSaveAs()</h4>
|
||
|
<pre> void MainForm::fileSaveAs()
|
||
|
{
|
||
|
<a href="qstring.html">QString</a> filename = QFileDialog::<a href="qfiledialog.html#getSaveFileName">getSaveFileName</a>(
|
||
|
QString::null, "Colors (*.txt)", this,
|
||
|
"file save as", "Color Tool -- File Save As" );
|
||
|
if ( ! filename.<a href="qstring.html#isEmpty">isEmpty</a>() ) {
|
||
|
int ans = 0;
|
||
|
if ( QFile::<a href="qfile.html#exists">exists</a>( filename ) )
|
||
|
ans = QMessageBox::<a href="qmessagebox.html#warning">warning</a>(
|
||
|
this, "Color Tool -- Overwrite File",
|
||
|
QString( "Overwrite\n'%1'?" ).
|
||
|
arg( filename ),
|
||
|
"&Yes", "&No", QString::null, 1, 1 );
|
||
|
if ( ans == 0 ) {
|
||
|
m_filename = filename;
|
||
|
fileSave();
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
statusBar()->message( "Saving abandoned", 2000 );
|
||
|
}
|
||
|
</pre>
|
||
|
<p>If the user attempts to save data that has been edited but not saved previously, or if they want to save some existing data under a new name, this slot is called. The user is presented with a standard file dialog which they can use to choose a filename. If the filename already exists they are given the option of continuing (overwriting) or cancelling. If the filename doesn't exist or does but the user has elected to continue the <tt>m_filename</tt> member is set and <tt>fileSave()</tt> is called.</p>
|
||
|
<h4><a name="6-26"></a>fileSave()</h4>
|
||
|
<pre> void MainForm::fileSave()
|
||
|
{
|
||
|
if ( m_filename.isEmpty() ) {
|
||
|
fileSaveAs();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
<a href="qfile.html">QFile</a> file( m_filename );
|
||
|
if ( file.<a href="qfile.html#open">open</a>( IO_WriteOnly ) ) {
|
||
|
<a href="qtextstream.html">QTextStream</a> stream( &file );
|
||
|
if ( ! m_comments.isEmpty() )
|
||
|
stream << m_comments.join( "\n" ) << "\n";
|
||
|
QMap<QString,QColor>::ConstIterator it;
|
||
|
for ( it = m_colors.constBegin(); it != m_colors.constEnd(); ++it ) {
|
||
|
<a href="qcolor.html">QColor</a> color = it.data();
|
||
|
stream << QString( "%1 %2 %3\t\t%4" ).
|
||
|
arg( color.<a href="qcolor.html#red">red</a>(), 3 ).
|
||
|
arg( color.<a href="qcolor.html#green">green</a>(), 3 ).
|
||
|
arg( color.<a href="qcolor.html#blue">blue</a>(), 3 ).
|
||
|
arg( it.key() ) << "\n";
|
||
|
}
|
||
|
file.<a href="qfile.html#close">close</a>();
|
||
|
<a href="qwidget.html#setCaption">setCaption</a>( QString( "Color Tool -- %1" ).arg( m_filename ) );
|
||
|
statusBar()->message( QString( "Saved %1 colors to '%2'" ).
|
||
|
arg( m_colors.count() ).
|
||
|
arg( m_filename ), 3000 );
|
||
|
m_changed = FALSE;
|
||
|
}
|
||
|
else
|
||
|
statusBar()->message( QString( "Failed to save '%1'" ).
|
||
|
arg( m_filename ), 3000 );
|
||
|
|
||
|
}
|
||
|
</pre>
|
||
|
<p>If there is no current filename we call <tt>fileSaveAs()</tt>; that function will call this one if the user provides a filename.</p>
|
||
|
<p>We write out any comment lines first. This means that a file that we load and then save may not be the same (e.g. if the original had comments scattered throughout, since our saved version will have all the comments at the beginning). We then iterate over every color in the <tt>m_colors</tt> map, writing them out in the <tt>rgb.txt</tt> file format.</p>
|
||
|
<h4><a name="6-27"></a>fileExit()</h4>
|
||
|
<pre> void MainForm::fileExit()
|
||
|
{
|
||
|
if ( okToClear() ) {
|
||
|
QApplication::<a href="qapplication.html#exit">exit</a>( 0 );
|
||
|
}
|
||
|
}
|
||
|
</pre>
|
||
|
<p>This is the second revision of this function. Now we only exit if the user has had the opportunity to save any unsaved changes. (We'll make a third and final version of this function later, when we deal with saving user settings.)</p>
|
||
|
<p>Try making and running the program. If you have <tt>rgb.txt</tt> on your system try loading it and saving it under a new name for testing purposes. If you don't have this file, save the standard colors and use those. In the next section we'll cover adding and deleting colors so that you can create your own color files. (If it doesn't build see the <a href="designer-manual-4.html#6">Troubleshooting</a> section.)</p>
|
||
|
<h4><a name="6-28"></a>Editing the Code: The Edit Options</h4>
|
||
|
<p>Adding a new color, finding a color and handling user options all require custom dialogs, so we'll defer them until chapter three when we deal with dialogs.</p>
|
||
|
<h4><a name="6-29"></a>editCut()</h4>
|
||
|
<pre> void MainForm::editCut()
|
||
|
{
|
||
|
<a href="qstring.html">QString</a> name;
|
||
|
<a href="qwidget.html">QWidget</a> *visible = colorWidgetStack->visibleWidget();
|
||
|
statusBar()->message( QString( "Deleting '%1'" ).arg( <a href="qobject.html#name-prop">name</a> ) );
|
||
|
|
||
|
if ( <a href="qwidget.html#visible-prop">visible</a> == tablePage && colorTable->numRows() ) {
|
||
|
int row = colorTable->currentRow();
|
||
|
<a href="qobject.html#name-prop">name</a> = colorTable->text( row, 0 );
|
||
|
colorTable->removeRow( colorTable->currentRow() );
|
||
|
if ( row < colorTable->numRows() )
|
||
|
colorTable->setCurrentCell( row, 0 );
|
||
|
else if ( colorTable->numRows() )
|
||
|
colorTable->setCurrentCell( colorTable->numRows() - 1, 0 );
|
||
|
m_icons_dirty = TRUE;
|
||
|
}
|
||
|
else if ( <a href="qwidget.html#visible-prop">visible</a> == iconsPage && colorIconView->currentItem() ) {
|
||
|
<a href="qiconviewitem.html">QIconViewItem</a> *item = colorIconView->currentItem();
|
||
|
<a href="qobject.html#name-prop">name</a> = item-><a href="qtableitem.html#text">text</a>();
|
||
|
if ( colorIconView->count() == 1 )
|
||
|
colorIconView->clear();
|
||
|
else {
|
||
|
<a href="qiconviewitem.html">QIconViewItem</a> *current = item-><a href="qiconviewitem.html#nextItem">nextItem</a>();
|
||
|
if ( ! current )
|
||
|
current = item-><a href="qiconviewitem.html#prevItem">prevItem</a>();
|
||
|
delete item;
|
||
|
if ( current )
|
||
|
colorIconView->setCurrentItem( current );
|
||
|
colorIconView->arrangeItemsInGrid();
|
||
|
}
|
||
|
m_table_dirty = TRUE;
|
||
|
}
|
||
|
|
||
|
if ( ! name.<a href="qstring.html#isNull">isNull</a>() ) {
|
||
|
m_colors.remove( <a href="qobject.html#name-prop">name</a> );
|
||
|
m_changed = TRUE;
|
||
|
statusBar()->message( QString( "Deleted '%1'" ).arg( <a href="qobject.html#name-prop">name</a> ), 5000 );
|
||
|
}
|
||
|
else
|
||
|
statusBar()->message( QString( "Failed to delete '%1'" ).arg( <a href="qobject.html#name-prop">name</a> ), 5000 );
|
||
|
}
|
||
|
</pre>
|
||
|
<p>If the user is viewing the table view we delete the current row. We set the new current cell to be the one following the deleted row, or if the one we deleted was last, its predecessor. We mark the <em>other</em> view (the icon view) as dirty, to make sure that it is updated if the user switches views. Similarly, if the user is viewing the icon view, we make the next (or previous if there is no next) item current and delete the one they were on. We then mark the table view as dirty. If we deleted a color (i.e. there was a current color in one of the views), we remove it from the <tt>m_colors</tt> map and mark the data as changed.</p>
|
||
|
<h4><a name="6-30"></a>editCopy()</h4>
|
||
|
<pre> void MainForm::editCopy()
|
||
|
{
|
||
|
<a href="qstring.html">QString</a> text;
|
||
|
<a href="qwidget.html">QWidget</a> *visible = colorWidgetStack->visibleWidget();
|
||
|
|
||
|
if ( <a href="qwidget.html#visible-prop">visible</a> == tablePage && colorTable->numRows() ) {
|
||
|
int row = colorTable->currentRow();
|
||
|
text = colorTable->text( row, 0 );
|
||
|
}
|
||
|
else if ( <a href="qwidget.html#visible-prop">visible</a> == iconsPage && colorIconView->currentItem() ) {
|
||
|
<a href="qiconviewitem.html">QIconViewItem</a> *item = colorIconView->currentItem();
|
||
|
text = item-><a href="qtableitem.html#text">text</a>();
|
||
|
}
|
||
|
if ( ! text.<a href="qstring.html#isNull">isNull</a>() ) {
|
||
|
<a href="qcolor.html">QColor</a> color = m_colors[text];
|
||
|
switch ( m_clip_as ) {
|
||
|
case CLIP_AS_HEX: text = color.<a href="qcolor.html#name">name</a>(); break;
|
||
|
case CLIP_AS_NAME: break;
|
||
|
case CLIP_AS_RGB:
|
||
|
text = QString( "%1,%2,%3" ).
|
||
|
arg( color.<a href="qcolor.html#red">red</a>() ).
|
||
|
arg( color.<a href="qcolor.html#green">green</a>() ).
|
||
|
arg( color.<a href="qcolor.html#blue">blue</a>() );
|
||
|
break;
|
||
|
}
|
||
|
clipboard->setText( text );
|
||
|
statusBar()->message( "Copied '" + text + "' to the clipboard" );
|
||
|
}
|
||
|
}
|
||
|
</pre>
|
||
|
<p>In this function we retrieve the name of the color from the current table row (or current icon, depending on the view). We then set a <a href="qstring.html">QString</a> to the text we want to copy into the clipboard and copy it.</p>
|
||
|
<h3><a name="7"></a>Summary</h3>
|
||
|
<p>In this chapter we have created a standard main-window style application. We have implemented menus, a toolbar and a main widget (a QWidgetStack). We've also created signal and slot connections and implemented many custom slots. In the following chapter we will complete the application by implementing custom dialogs, and by making use of common dialogs where appropriate.</p>
|
||
|
<!-- eof -->
|
||
|
<p align="right">[<a href="designer-manual-2.html">Prev: Quick Start</a>] [<a href="designer-manual.html">Home</a>] [<a href="designer-manual-4.html">Next: Creating Dialogs</a>]</p>
|
||
|
<p><address><hr><div align=center>
|
||
|
<table width=100% cellspacing=0 border=0><tr>
|
||
|
<td>Copyright © 2007
|
||
|
<a href="troll.html">Trolltech</a><td align=center><a href="trademarks.html">Trademarks</a>
|
||
|
<td align=right><div align=right>Qt 3.3.8</div>
|
||
|
</table></div></address></body>
|
||
|
</html>
|