You are on page 1of 98

Qt in Education

An Introduction to Qt and Workshop

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++.

C++ framework bindings for other languages

Python, Ruby, C#, etc.

Originally for user interfaces now for everything


Databases, XML, WebKit, multimedia, networking, OpenGL, scripting, non-GUI...

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?

Qt extends C++ with macros and introspection


foreach (int value, intList) { } QObject *o = new QPushButton; o->metaObject()->className(); // returns QPushButton connect(button, SIGNAL(clicked()), window, SLOT(close()));

All code is still plain C++

Desktop target platforms

Windows

Mac OS X

Linux/Unix X11

Embedded target platforms

Windows CE Symbian Maemo Embedded Linux

Direct framebuffer access

Community target platforms

Android Necessitas and Ministri

http://sourceforge.net/p/necessitas/home/

iOS, Kindle, QNX, wxWorks...

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

can have parent and children


*parent = *child1 = *child2 = *child1_1 *child1_2 new QObject(); new QObject(parent); new QObject(parent); = new QObject(child1); = new QObject(child1); parent child1 child1_1 child2 child1_2

When a parent object is deleted, it deletes its children


QObject QObject QObject QObject QObject

delete parent;

parent deletes child1 and child2 child1 deletes child1_1 and child1_2

Memory Management

This is used when implementing visual hierarchies.


QDialog *parent = new QDialog(); QGroupBox *box = new QGroupBox(parent); QPushButton *button = new QPushButton(parent); QRadioButton *option1 = new QRadioButton(box); QRadioButton *option2 = new QRadioButton(box); delete parent;

parent deletes box and button box deletes option1 and option2

Usage Patterns

Use the this-pointer as top level parent


Dialog::Dialog(QWidget *parent) : QDialog(parent) { QGroupBox *box = QGroupBox(this); QPushButton *button = QPushButton(this); QRadioButton *option1 = QRadioButton(box); QRadioButton *option2 = QRadioButton(box); ...

Allocate parent on the stack


void Widget::showDialog() { Dialog dialog; if (dialog.exec() == QDialog::Accepted) { ... dialog is deleted when } the scope ends

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
}

Stack and Heap

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(); }

MyMainWindow::MyMainWindow(... { new QLabel(this); new ... }

Signals and Slots

Dynamically and loosely tie together events and state changes with reactions What makes Qt tick

Signals and Slots in Action


emit clicked();

Signals and Slots in Action


2x connect(addButton,SIGNAL(clicked()),this,SLOT(...));

clear();

private slots: void on_addButton_clicked(); void on_deleteButton_clicked();

connect(clearButton,SIGNAL(clicked()),listWidget,SLOT(clear()));

Signals and Slots in Action


... emit clicked(); ...

QString newText = QInputDialog::getText(this, "Enter text", "Text:"); if( !newText.isEmpty() ) ui->listWidget->addItem(newText);

}
{ ... emit clicked(); ... }

}
{ ... emit clicked(); ... }

foreach (QListWidgetItem *item, ui->listWidget->selectedItems()) { delete item; }

clear();

What is a slot?

A slot is defined in one of the slots sections


public slots: void aPublicSlot(); protected slots: void aProtectedSlot(); private slots: void aPrivateSlot();

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()));

It is implemented as an ordinary method It can be called as an ordinary method

What is a signal?

A signal is defined in the signals section


signals: void aSignal();

A signal always returns void A signal must not be implemented

The moc provides an implementation

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();

Making the connection


QObject*

QObject::connect( src, SIGNAL( signature ), dest, SLOT( signature ) );

<function name> ( <arg type>... )

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)

Custom types reduces reusability.

Making the connection

Qt can ignore arguments, but not create values from nothing


Signals
rangeChanged(int,int) rangeChanged(int,int) rangeChanged(int,int) valueChanged(int) valueChanged(int) valueChanged(int) textChanged(QString) clicked() clicked()

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*)

Triggered by calling QMetaObject::connectSlotsByName Think about reuse when naming

Compare on_widget_signal to updatePageMargins

updatePageMargins can be connected to a number of signals or called directly.

Much more...

QtQuick cool animated user interfaces http://qt.nokia.com/qtquick/

Goal of the day

Build and entire application


Document window(s) Dialogs Menus, toolbars, statusbars Basic file management Open, Save and Save As

We will run out of time!

Goal of the day

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

The resulting project contains five files

TextEditor.pro the project

definition file

mainwindow.ui the user

interface of the main window

mainwindow.h/cpp the class

declaration and implementation of the main window

main.cpp the main function,

getting everything initialized and running

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!

Starting with the user interface

Add a QTextEdit widget to the main window form

Starting with the user interface

Apply a layout to the central widget

Starting with the user interface

You have to deselect and reselect the central widget to see these properties after you have applied the layout

Set the layout margin properties to zero

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

Many user interface elements refer to the same action

Action

Ctrl+S

Do not forget tooltips, statusbar tips, etc.

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

the current check status

toolTip/statusTip tips text for tool tips (hover

and wait) and status bar tips (hover, no wait)

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

Designer has an action editor at the bottom of the screen


Tools Form Editor Views Action Editor

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

Creating a resource file

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...

Pick the icon from the resource directory tree

Adding actions

When you have added your actions to the action editor, you need to add them to the form

To add them to the toolbar, simply drag and drop

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

Typing &File, makes F a keyboard shortcut

To enter & in a menu, type && as the text.

Adding actions

Text New Close Exit

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(); }

Closing and Exiting


Setting the WA_DeleteOnClose attribute makes the widget delete itself when it is closed
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); connect(ui->actionClose, SIGNAL(triggered()), this, SLOT(close())); connect(ui->actionExit, SIGNAL(triggered()), qApp, SLOT(closeAllWindows()));

All widgets has a close slot out-of-the-box

The qApp pointer refers to the current QApplication instance

The QApplication class has a slot for closing all windows

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)

Do not close what is modified

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

The modified flag

Declaration and initialization

class MainWindow : public QMainWindow { ... private: bool m_modified; };

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), m_modified(false) { ...

The modified flag

Keeping track of the document


MainWindow::MainWindow(QWidget *parent) ... { ... connect(ui->textEdit, SIGNAL(textChanged()), this, SLOT(documentModified())); } void MainWindow::documentModified() { m_modified = true; }

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(); } }

Unmodified windows are always closed

QMessageBox

The QMessageBox class can be used to show


question

dialogs dialogs
Roughly the same type of dialogs with a title, a message and one or more buttons.

information warning

dialogs dialogs

critical about

dialogs for both the current application and Qt itself


The green words are function names for static members. To show an information dialog, use QMessageBox::information, etc.

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 message of the dialog. Notice the use of '\n'

The default button

The return value is the button used for closing the dialog

When is a document modified?

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

void MainWindow::updateWindowTitle() { setWindowTitle(tr("%1[*]").arg(qApp->applicationName())); setWindowModified(m_modified); }

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

Text Select Font...

Icon

Short-cut

Tool-tip Select the display font

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

Back to the font

Saving the font

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); } }

Restoring the 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

User configurable fonts Persistent user settings

The edit menu

The actions cut, copy and paste are easy to add


Name actionCut actionCopy actionPaste Icon Short-cut Ctrl+X Ctrl+C Ctrl+V Cut Copy Paste Tool-tip

Text Cut Copy Paste

The actions are added to both the tool bar and menu

Right click on the tool bar to add separators

The edit 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

Text Undo Redo

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

This means that we must implement file operations

Interfacing files

Qt file handing is too large a subject to cover here!


Loading

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;

The document file name

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

Updating the window title

The window title must reflect the filename


void MainWindow::updateWindowTitle() { setWindowTitle(tr("%1[*] - %2") .arg(m_fileName.isNull()?"untitled":QFileInfo(m_fileName).fileName()) .arg(QApplication::applicationName())); setWindowModified(m_modified);

Extracts the file name part of a file name with a path

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

Now, we can load files on start-up


int main(int argc, char *argv[]) { QApplication a(argc, argv); ... MainWindow *w; if(a.arguments().length()>1) w = new MainWindow(a.arguments().last()); else w = new MainWindow(); w->show(); return a.exec(); }

You can specify arguments in the Run Settings, under the project tab in QtCreator

Adding file actions

Actions to let the user open and save documents


Text Name actionOpen actionSave actionSaveAs Icon Short-cut Ctrl+O Ctrl+S
Ctrl+Shift+S

Tool-tip Open a document Save the current document Save current document as

Open... Save Save As...

The actions are added to the File menu and tool bar

Opening documents

QFileDialog::getOpenFileName(this, Initial directory

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

Putting all together opens 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

Saving documents is slightly more complicated than loading


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();

If the name is null, we need to save as

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;

Save the document

and set the file name to null

Update the modified flag and title

Return true on success

Saving documents
bool MainWindow::saveFileAs() { QString fileName = QFileDialog::getSaveFileName(...); if(!fileName.isNull()) { m_fileName = fileName; return saveFile(); } return false;

If a name is given, attempt to save If no name is given, the save is a failure

Ask the user for a file name

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

The final piece of the puzzle is the close event

saved? Y Do not close Close

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

Can load documents Can save documents

Thank you for your attention! johan.thelin@pelagicore.com

You might also like