Professional Documents
Culture Documents
http://www.pelagicore.com/documents/2011-Apr-QtWorkshop.odp
2011 Nokia Corporation and its Subsidiary(-ies). The enclosed Qt Materials are provided under the Creative Commons Attribution-Share Alike 2.5 License Agreement.
The full license text is available here: http://creativecommons.org/licenses/by-sa/2.5/legalcode. Nokia, Qt and the Nokia and Qt logos are the registered trademarks of Nokia Corporation in Finland and other countries worldwide.
The Presenter
Johan Thelin <johan.thelin@pelagicore.com>
Creates middleware for in-vehicle infotainment based on MeeGo Thesis work within embedded Linux, open source and design
What is Qt?
Qt is a cross platform development framework written in C++.
What is Qt?
Qt is made up of modules
All modules have a common scheme and are built from the same API design ideas
QtCore QtGui
QtOpenGL QtWebKit QtOpenVG QtSql QtSvg QtScript QtXmlPatterns QtMultimedia QtNetwork QtXml Phonon
What is Qt?
Windows
Mac OS X
Linux/Unix X11
http://sourceforge.net/p/necessitas/home/
Hello World
Hello World
#include <QApplication> #include <QLabel> int main( int argc, char **argv ) { QApplication app( argc, argv ); QLabel l( "Hello World!" ); l.show(); return app.exec(); }
Hello World
#include <QApplication> #include <QLabel> int main( int argc, char **argv ) { QApplication app( argc, argv ); QLabel l( "Hello World!" ); l.show(); return app.exec(); }
Hello World
#include <QApplication> #include <QLabel> int main( int argc, char **argv ) { QApplication app( argc, argv ); QLabel l( "Hello World!" ); l.show(); return app.exec(); }
Hello World
#include <QApplication> #include <QLabel> int main( int argc, char **argv ) { QApplication app( argc, argv ); QLabel l( "Hello World!" ); l.show(); return app.exec(); }
Hello World
#include <QApplication> #include <QLabel> int main( int argc, char **argv ) { QApplication app( argc, argv ); QLabel l( "Hello World!" ); l.show(); return app.exec(); }
The QObject
QObject instances are individuals!
They can have a name (QObject::objectName) They are placed in a hierarchy of QObject instances They can have connections to other QObject instances Example: does it make sense to copy a widget at run-time?
Meta data
Qt implements introspection in C++ Every QObject has a meta object The meta object knows about
class name (QObject::className) inheritance (QObject::inherits) properties signals and slots general information (QObject::classInfo)
Memory Management
QObject
delete parent;
parent deletes child1 and child2 child1 deletes child1_1 and child1_2
Memory Management
parent deletes box and button box deletes option1 and option2
Usage Patterns
Heap
When using new and delete, memory is allocated on the heap. Heap memory must be explicitly freed using delete to avoid memory leaks. Objects allocated on the heap can live for as long as they are needed.
new
Construction
Destruction
delete
Stack
Local variables are allocated on the stack. Stack variables are automatically destructed when they go out of scope. Objects allocated on the stack are always destructed when they go out of scope.
int a
Construction
Destruction
}
To get automatic memory management, only the parent needs to be allocated on the stack.
MyMainWindow QApplication
int main(int argc, char **argv) { QApplication a(argc, argv); MyMainWindow w; w.show(); return a.exec(); }
Dynamically and loosely tie together events and state changes with reactions What makes Qt tick
clear();
connect(clearButton,SIGNAL(clicked()),listWidget,SLOT(clear()));
}
{ ... emit clicked(); ... }
}
{ ... emit clicked(); ... }
clear();
What is a slot?
A slot can return values, but not through connections Any number of signals can be connected to a slot
connect(src, SIGNAL(sig()), dest, SLOT(slt()));
What is a signal?
A signal can be connected to any number of slots Usually results in a direct call, but can be passed as events between threads, or even over sockets (using 3rd party classes) The slots are activated in arbitrary order A signal is emitted using the emit keyword
emit aSignal();
A signature consists of the function name and argument types. No variable names, nor values are allowed.
setTitle(QString text) setValue(42) setItem(ItemClass) clicked() toggled(bool) setText(QString) textChanged(QString) rangeChanged(int,int)
Slots
setRange(int,int) setValue(int) updateDialog() setRange(int,int) setValue(int) updateDialog() setValue(int) setValue(int) updateDialog()
Automatic Connections
When using Designer it is convenient to have automatic connections between the interface and your code
on_ object name _ signal name ( signal parameters )
on_addButton_clicked(); on_deleteButton_clicked(); on_listWidget_currentItemChanged(QListWidgetItem*,QListWidgetItem*)
Much more...
Document window(s) Dialogs Menus, toolbars, statusbars Basic file management Open, Save and Save As
Creating a project
In QtCreator, create a Qt4 Gui Application project Use the QtCore and QtGui modules Base the skeleton project on a QMainWindow
Creating a project
definition file
The plan
This lecture will build a text editor in an iterative process Features will be added to the application, while maintaining a functioning application throughout the process This is how most software is being built in real life!
You have to deselect and reselect the central widget to see these properties after you have applied the layout
Adding actions
With the text editing central widget in place, the next step will be to let the user create new documents, close the current document and to exit the application For each of these user actions, a QAction object will be created
Why QAction
Action
Ctrl+S
Why QAction
A QAction encapsulates all settings needed for menus, tool bars and keyboard shortcuts Commonly used properties are
text the text used everywhere icon icon to be used everywhere shortcut shortcut checkable/checked if the action is checkable and
Why QAction
QAction *action = new QAction(parent); action->setText("text"); action->setIcon(QIcon(":/icons/icon.png")); action->setShortcut(QKeySequence("Ctrl+G"));
Creating a new action Setting properties for text, icon and keyboard short-cut A QVariant can be associated with each action, to carry data associated with the given operation
action->setData(myDataQVariant);
Why QAction
Adding a new action brings up a dialog with the details (name, text, tips...) Action icons can be added either from files or resources
Icon resources
Putting icons in a resource file lets Qt embed them into the executable
Avoid having to deploy multiple files No need to trying to determine the path for the icons for each specific install type All fits neatly into the build system ... You can add anything into resources, not only icons
Adding a resource file to a project is just as adding any source file to a project When having added the resource file, make sure to close any Designer forms and re-open them for Designer to discover the resource
Adding resources
Opening the resource file gives you the resource file editor Before any resources can be added to the resource file, you must first create a directory in the resource file In this case we put icons into the /icons directory
Adding actions
From Designer, you can now pick an icon for the actions that you plan to show in toolbars Click ... next to the icon setting Pick Choose Resource...
Adding actions
When you have added your actions to the action editor, you need to add them to the form
Adding actions
To add them to a menu, double click on Type here and enter the menu title, then drag and drop action onto the menu
Adding actions
In this first iteration, we add the most basic actions Name Icon Short-cut Tool-tip
actionNew actionClose actionExit Ctrl+N Ctrl+W Create a new document Close current window Exit application
The name is automatically generated when entering the text for the action Close and Exit are added to the menu.
To add a separator, double click Add Separator then drag it into place.
Implementing new
By right clicking on the action in the action editor, you can go the corresponding slot Slots are generated on the fly in both header and implementation Implementing the new slot is easy just create a new MainWindow and show it
void MainWindow::on_actionNew_triggered() { MainWindow *w = new MainWindow(); w->show(); }
Status update
Now, we have a window with a working text editing widget New windows can be created Windows can be closed The application can exit (i.e. close all windows)
Now, the windows close regardless of their contents The expected behavior would be to ask the user before close a modified document To implement this, we add a modified flag which is set whenever the text of the QTextEdit is changed We also intercept close events and ask the user before closing modified events
Closing safely
When a window is closed, the top-level widget can accept or ignore the close event
Accept means that the window will close (and be deleted in this case) Ignore means that the window will remain open
All events are handled in the QObject::event method provides virtual methods for the most common events, e.g. QWidget::closeEvent
QWidget
Closing safely
Re-implementing the closeEvent method to ask the user before closing a window with a modified document
void MainWindow::closeEvent(QCloseEvent *e) { if(m_modified) { if(QMessageBox::warning(this, "Document Modified", "The document has been modified, do you want to close it?\n" "You will lose all your changes.", QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { e->accept(); } else { Depending on the e->ignore(); user's answer, the } event is accepted } else { or ignored e->accept(); } }
QMessageBox
dialogs dialogs
Roughly the same type of dialogs with a title, a message and one or more buttons.
information warning
dialogs dialogs
critical about
QMessageBox
Parent window Dialog title
QMessageBox::warning(this, "Document Modified", "The document has been modified, do you want to close it?\n" "You will lose all your changes.", QMessageBox::Yes | QMessageBox::No,
The buttons to use. Pick from Ok, Open, Save, Cancel, Close, Discard, Apply, Reset, Restore defaults, Help, Save all, Yes, Yes to all, No, No to all, Abort, Retry and Ignore
QMessageBox::No) == QMessageBox::Yes
The return value is the button used for closing the dialog
The user expects to see if a document is modified or not from the title of the window By setting a window title with the sub-string [*], the modified indication can be activated using setWindowModified. This function is called
from the constructor as well as from the documentModified slot
Application name
When building the title, we used the QApplication::applicationName method The QApplication object keeps track of the application's name, version, producer, etc. This is used for window titles, etcetera
a.setApplicationName("Text Editor"); a.setApplicationVersion("0.1"); a.setOrganizationName("ExampleSoft"); a.setOrganizationDomain("example.com"); Application name and version Producer name and domain Application icon
a.setWindowIcon(QIcon(":/icons/new.png"));
Status update
Now, we have a window with a working text editing widget New windows can be created Windows can be closed The application can exit (i.e. close all windows)
We can keep track of document modifications A window cannot be closed without accepting the loss of document modifications
Selecting a font
The standard font of the text editor is very plain. To remedy, we will let the user pick the font We add an action, add a View menu and put the action in the menu
Name
actionSelectFont
Icon
Short-cut
New menus are always placed to the right, but they can be dragged in to place afterwards
Selecting a font
In the slot, a new QFont value is requested using the QFontDialog::getFont method
void MainWindow::on_actionSelectFont_triggered() { bool ok; QFont font = QFontDialog::getFont(&ok, ui->textEdit->font(), this); if(ok) ui->textEdit->setFont(font);
The boolean, ok, is used to determine if the user actually picked a font. This is because QFont values always are valid.
More on fonts
Using the current implementation, each window pops up with the default font and the user has to pick a new font The user expects settings to stick, i.e. use the last value when opening new windows
QSettings
Settings are stored differently on all major platforms By using a QSettings object, you get a cross platform interface for handing settings
QSettings settings; myString = settings.value("key","default").toString(); settings.setValue("key", myString); settings.remove("key");
Any QVariant can be stored but think about readability for advanced users
void MainWindow::on_actionSelectFont_triggered() { bool ok; QFont font = QFontDialog::getFont(&ok, ui->textEdit->font(), this); if(ok) { QSettings settings; settings.setValue("viewFont", font); ui->textEdit->setFont(font); } }
MainWindow::MainWindow(QWidget *parent) : ... { ... Default value QSettings settings; ui->textEdit->setFont( settings.value("viewFont", QApplication::font()).value<QFont>()); ...
Custom fonts!
Status update
Now, we have a window with a working text editing widget New windows can be created Windows can be closed The application can exit (i.e. close all windows) We can keep track of document modifications A window cannot be closed without accepting the loss of document modifications
The actions are added to both the tool bar and menu
Slots for the new actions are implemented in the text editing widget
connect(ui->actionCut, SIGNAL(triggered()), ui->textEdit, SLOT(cut())); connect(ui->actionCopy, SIGNAL(triggered()), ui->textEdit, SLOT(copy())); connect(ui->actionPaste, SIGNAL(triggered()), ui->textEdit, SLOT(paste()));
To avoid trying to copy in vain, let's add connections for enabling and disabling the actions depending on the selection
connect(ui->textEdit, SIGNAL(copyAvailable(bool)), ui->actionCut, SLOT(setEnabled(bool))); connect(ui->textEdit, SIGNAL(copyAvailable(bool)), ui->actionCopy, SLOT(setEnabled(bool)));
Undoing
Adding support for undo and redo is just as easy as for the clipboard operations
Name actionUndo actionRedo Icon Short-cut Ctrl+Z Ctrl+Y Tool-tip Undo the last action Redo the last action
connect(ui->actionUndo, SIGNAL(triggered()), ui->textEdit, SLOT(undo())); connect(ui->actionRedo, SIGNAL(triggered()), ui->textEdit, SLOT(redo())); connect(ui->textEdit, SIGNAL(undoAvailable(bool)), ui->actionUndo, SLOT(setEnabled(bool))); connect(ui->textEdit, SIGNAL(redoAvailable(bool)), ui->actionRedo, SLOT(setEnabled(bool)));
Status update
Now, we have a window with a working text editing widget New windows can be created Windows can be closed The application can exit (i.e. close all windows) We can keep track of document modifications A window cannot be closed without accepting the loss of document modifications User configurable fonts Persistent user settings
The expected clipboard actions: cut, copy and paste Undo and redo
Unmodifying documents
Until now, documents can be modified, but never unmodified When is the modified flag cleared?
In the constructor, i.e. new documents are not modified When a document is loaded When a document is saved
Interfacing files
QFile file(m_fileName); if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) qFatal("Error opening file for reading"); QTextStream in(&file); in >> textString;
Saving
QFile file(m_fileName); if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) qFatal("Error opening file for writing"); QTextStream out(&file); out << textString;
If order to implement file operations, each document window must have a file name As a start, it is set in the constructor
MainWindow::MainWindow(const QString &fileName, QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), m_modified(false), m_fileName(fileName) { ... if(!m_fileName.isNull()) loadFile(); updateWindowTitle();
The name can be QString(), i.e. null, as new, unnamed documents needs this
Results in:
untitled* - Text Editor testing.txt - Text Editor
Loading documents
Documents are loaded from the loadFile member function It attempts to load the current file If it fails, it sets the current filename to null
void MainWindow::loadFile() { QFile file(m_fileName); if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) { QMessageBox::warning(this, QApplication::applicationName(), tr("Could not open file %1.\n%2") .arg(QFileInfo(m_fileName).fileName()) .arg(file.errorString())); m_fileName = QString(); } else { QTextStream in(&file); ui->textEdit->setText(in.readAll()); } m_modified = false; updateWindowTitle(); }
Loading documents
Documents are loaded from the loadFile member function It attempts to load the current file If it fails, it sets the current filename to null
void MainWindow::loadFile() { QFile file(m_fileName); if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) { QMessageBox::warning(this, QApplication::applicationName(), tr("Could not open file %1.\n%2") .arg(QFileInfo(m_fileName).fileName()) .arg(file.errorString())); m_fileName = QString(); } else { QTextStream in(&file); ui->textEdit->setText(in.readAll()); } m_modified = false; updateWindowTitle(); }
Loading documents
Documents are loaded from the loadFile member function It attempts to load the current file If it fails, it sets the current filename to null
void MainWindow::loadFile() { QFile file(m_fileName); if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) { QMessageBox::warning(this, QApplication::applicationName(), tr("Could not open file %1.\n%2") .arg(QFileInfo(m_fileName).fileName()) .arg(file.errorString())); m_fileName = QString(); } else { QTextStream in(&file); ui->textEdit->setText(in.readAll()); } m_modified = false; updateWindowTitle(); }
Loading documents
Documents are loaded from the loadFile member function It attempts to load the current file If it fails, it sets the current filename to null
void MainWindow::loadFile() { QFile file(m_fileName); if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) { QMessageBox::warning(this, QApplication::applicationName(), tr("Could not open file %1.\n%2") .arg(QFileInfo(m_fileName).fileName()) .arg(file.errorString())); m_fileName = QString(); } else { QTextStream in(&file); ui->textEdit->setText(in.readAll()); } m_modified = false; updateWindowTitle(); }
Opening documents
You can specify arguments in the Run Settings, under the project tab in QtCreator
Tool-tip Open a document Save the current document Save current document as
The actions are added to the File menu and tool bar
Opening documents
Asking for file names, we use the QFileDialog::getOpen/SaveFileName Parent window methods
Dialog title "Open document", QDir::currentPath(), "Text documents (*.txt)"); File type filter
File type filters can contain several file extensions: "Document (*.txt *.rtf *.doc)" But also several document types: "Document (*.txt);;Images (*.png)"
Opening documents
void MainWindow::on_actionOpen_triggered() { QString fileName = QFileDialog::getOpenFileName(this, "Open document", QDir::currentPath(), "Text documents (*.txt)"); if(!fileName.isNull()) { MainWindow *w = new MainWindow(fileName); w->show(); }
Opening documents
We can avoid opening a new window if the current one is unmodified and without file name
void MainWindow::on_actionOpen_triggered() { QString fileName = QFileDialog::getOpenFileName(...); if(!fileName.isNull()) { if(!m_modified && m_fileName.isNull()) { m_fileName = fileName; loadFile(); } else { MainWindow *w = new MainWindow(fileName); w->show(); } } }
Saving documents
Save without a name leads to Save As Save As sets a name and attempts to save Save is one of the options when a modified document is closed Save As can be canceled Saving can fail
Saving documents
bool MainWindow::saveFile() { if(m_fileName.isNull()) return saveFileAs();
QFile file(m_fileName); if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::warning(...); m_fileName = QString(); If the file cannot be updateWindowTitle(); return false; opened for writing, } we display a warning QTextStream out(&file); out << ui->textEdit->toPlainText(); m_modified = false; updateWindowTitle(); return true;
Saving documents
bool MainWindow::saveFileAs() { QString fileName = QFileDialog::getSaveFileName(...); if(!fileName.isNull()) { m_fileName = fileName; return saveFile(); } return false;
The method saveFileAs never calls saveFile unlese !fileName.isNull() thus there is no risk for infinite recursion
Saving documents
As save and save as not are automatically connected slots, they need to be connected to the corresponding actions in the constructor
connect(ui->actionSave, SIGNAL(triggered()), this, SLOT(saveFile())); connect(ui->actionSaveAs, SIGNAL(triggered()), this, SLOT(saveFileAs()));
Saving documents
Saving documents
void MainWindow::closeEvent(QCloseEvent *e) { if(m_modified) { switch(QMessageBox::warning(this, "Document Modified", "The document has ...", QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Cancel)) { case QMessageBox::Yes: The user wants to save if(saveFile()) e->accept(); else Depending on the save, close or ignore e->ignore(); break; case QMessageBox::No: The user do not want to save e->accept(); break; case QMessageBox::Cancel: Cancel the closing e->ignore(); break; } } else { Close unmodified documents e->accept(); } }
Status update
Now, we have a window with a working text editing widget New windows can be created Windows can be closed The application can exit (i.e. close all windows) We can keep track of document modifications A window cannot be closed without accepting the loss of document modifications User configurable fonts Persistent user settings The expected clipboard actions: cut, copy and paste Undo and redo