qt workshop

Download Qt Workshop

If you can't read please download the document

Upload: johan-thelin

Post on 16-Apr-2017

4.429 views

Category:

Technology


1 download

TRANSCRIPT

.

An Introductionto Qtand Workshop

Qt in Education

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.

http://www.pelagicore.com/documents/2011-Apr-QtWorkshop.odp

The Presenter

Johan Thelin

Creates middleware for in-vehicle infotainment based on MeeGo

Thesis work within embedded Linux, open source and design

What is Qt?

C++ framework bindings for other languagesPython, Ruby, C#, etc.

Originally for user interfaces now for everythingDatabases, XML, WebKit, multimedia, networking, OpenGL, scripting, non-GUI...

Qt is a cross platform development
framework written in C++.

Qt, is a cross platform development framework written in C++.

This does not limit the languages used. Bindings are available for Python, Ruby, C# (mono), Ada, Pascal, Perl, PHP (see: http://qt.nokia.com/products/programming-language-support)

Most people know Qt for its cross platform user interface abilities. Cross platform development is about so much more. For instance, just compare file paths between Windows and Unix. Qt provides classes for almost all conceivable tasks.

What is Qt?

Qt is made up of modulesAll modules have a common scheme and are built from the same API design ideas

QtCorePhononQtXmlPatternsQtXmlQtWebKitQtSvgQtSqlQtScriptQtOpenVGQtOpenGLQtNetworkQtMultimediaQtGuiQt supports a multitude of functions in a cross platform manner. This means that Qt is a large package.

Qt is divided into modules, and when building and deploying, you can choose which module to use.

This helps reducing the number of bytes needed to deploy.

Also, there are a few platform specific modules (e.g. QtDBUS for inter process communication unix only, QtAxContainer and QtAxServer for building and using ActiveX components Windows only)

What is Qt?

Qt extends C++ with macros and introspection

All code is still plain C++

foreach (int value, intList) { }

QObject *o = new QPushButton;o->metaObject()->className(); // returns QPushButton

connect(button, SIGNAL(clicked()), window, SLOT(close()));

Qt extends C++ while sticking to pure C++.

Examples of what you get (there is much more):

foreach loops

Meta-information, great for casting, working with dynamic trees of classes, etc.

Using meta-information, dynamic connections such as the connect example is possible.

Desktop target platforms

Windows

Mac OS X

Linux/Unix X11

Qt is available for all major desktop platforms.

Windows XP/Vista/7 are officially supported

OS X, latest version of Qt supports at least down to 10.3 (10.4 or later is required for development)

Linux/Unix with X11, i.e. not tied to Linux. Official support for Linux, AIX, HPUX, Solaris. Community support for FreeBSD, OpenBSD, NetBSD, etc.

Notice that the X11 support is not focused to deploying KDE on all desktops. Instead, Qt aims to integrate as a native part of all desktops, including Gnome.

Embedded target platforms

Windows CE

Symbian

Maemo

Embedded LinuxDirect framebuffer access

Qt is also available for a number of embedded platforms.

Windows CE, versions 5 and 6.

Symbian S60, well tested with 3.1, 3.2 and 5.0.

Maemo, so you can use it on your N900 tablets

Embedded Linux, using the framebuffer directly, i.e. no X11 and a smaller footprint. Can accelerate on some platforms. A nice example is the beagleboard.

Tier 3 platforms: QNX, WxWorks. Supported by partner companies.

Community target platforms

Android Necessitas and Ministri

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

iOS, Kindle, QNX, wxWorks...

Qt is also available for a number of embedded platforms.

Windows CE, versions 5 and 6.

Symbian S60, well tested with 3.1, 3.2 and 5.0.

Maemo, so you can use it on your N900 tablets

Embedded Linux, using the framebuffer directly, i.e. no X11 and a smaller footprint. Can accelerate on some platforms. A nice example is the beagleboard.

Tier 3 platforms: QNX, WxWorks. Supported by partner companies.

Hello World

Walkthrough

The target of the project

This will be the starting point of the exercises for this lecture.

Hello World

#include #include

int main( int argc, char **argv ){ QApplication app( argc, argv ); QLabel l( "Hello World!" ); l.show(); return app.exec();}

Walkthrough

The entire source, focus on:- simplicity- small code

Hello World

#include #include

int main( int argc, char **argv ){ QApplication app( argc, argv ); QLabel l( "Hello World!" ); l.show(); return app.exec();}

Walkthrough

Focus on includes. All Qt classes are included by name, compare with iostream, etc. No .h ending, capitalization.

Hello World

#include #include

int main( int argc, char **argv ){ QApplication app( argc, argv ); QLabel l( "Hello World!" ); l.show(); return app.exec();}

Walkthrough

One QApplication object, drives the application, manages global settings.

There must always be a QApplication object.

You can always access the QApplication object through the qApp pointer. You can look at this as a singleton, but the instantiation must be made explicitly from the main function.

Hello World

#include #include

int main( int argc, char **argv ){ QApplication app( argc, argv ); QLabel l( "Hello World!" ); l.show(); return app.exec();}

Walkthrough

QLabel, is a widget. The text is passed to the constructor before the widget is shown.

Elaborate, everything is built from widgets. Widgets can be labels (as here), buttons, sliders, group boxes, windows, etc.

As the label does not have a parent widget, i.e. it is not contained by another widget, it is a top-level widget. This means that it will result in a new window that is decorated by the native window manager.

Hello World

#include #include

int main( int argc, char **argv ){ QApplication app( argc, argv ); QLabel l( "Hello World!" ); l.show(); return app.exec();}

Walkthrough

Calling exec start the event loop. This gets everything running.

The event loop ends when last window closes (can be turned off).

Having started the event loop, you must change mind-set. Everything from here on is event driven, be it user interaction (keys or mouse), network or timer.

The QObject

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?

QObject instances are individuals!

So, why cannot QChar be a QObject. QObjects are individuals!

This means that you cannot write obj1 = obj2, copy is not a valid operation.

Why?

QObjects have names, for instance addButton, deleteButton, etc (from the first lecture's demo).How do you copy this? You don't want two addButtons?

QObjects are structured in hierarchies. How do you copy that context? Do you want them at the same level? Leaf level? Root level?

QObjects can be interconnected (add calls a slot)How do you copy this? Do you want to duplicate connections?

Meta data

Qt implements introspection in C++

Every QObject has a meta object

The meta object knows aboutclass name (QObject::className)

inheritance (QObject::inherits)

properties

signals and slots

general information (QObject::classInfo)

QObjects carry meta-data, that is data about the object itself.

This makes it possible to add introspection to Qt, for instance to ask a class which methods it has

Every QObject has a meta object which can be retreived using the metaObject method.

The meta object knows about the class, its name, base class, properties, methods, slots and signals.

Continues

Memory Management

QObject can have parent and children

When a parent object is deleted, it deletes its children

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

delete parent;

parentchild1child2child1_1child1_2

parent deletes child1 and child2child1 deletes child1_1 and child1_2

QObjects can be used in a way that takes care of all the dirty parts of memory management. It becomes almost as easy as working with a garbage collector, but you are still in full control.

The trick is that all QObjects have a parent, or an owner. When the parent is deleted, it deletes all its children.

This reduces the number of objects that you need to keep track of to one, in the ideal case.

Continues

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 buttonbox deletes option1 and option2The very same parent-child relationship is used to represent visual hierarchies.

Refer to the tree structure, the box contains the radio buttons (option1/2). The parent contains the box and the button. Compare to the previous slide.

Continues

Usage Patterns

Use the this-pointer as top level parent

Allocate parent on the stack

void Widget::showDialog(){ Dialog dialog;

if (dialog.exec() == QDialog::Accepted) { ... }}

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

dialog is deleted whenthe scope ends

So, how does this make memory management easy. I still need to keep track of an object and make sure that I delete it?

No, not if you use the stack cleverly.

First of all, the example from the previous slide would probably have been implemented in the parent's constructor, i.e. this is the top-level parent.

Second, when using the dialog, you allocate it on the stack. This means that the dialog, along with all its children, will be deleted when the scope ends.

Continues

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

delete

Construction

Destruction

These slides intend to jog the students' memory, not explain the stack vs heap decision in full.

The heap is used when you allocate memory dynamically. In C++, that means new/delete. In C you have used malloc and free.

Heap memory must be explicitly freed, i.e. you must call delete on everything that you allocate.

If you do not do so, you will leak memory. This will, eventually, lead to memory shortage and a crash.

Dynamically allocated objects live until you delete them, so you have full control of when something is constructed or destructed.

Continues

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

The stack is used for automatic memory allocations (as opposed to dynamic memory).

The stack grows and shrinks when you make function calls. It is used for local variables, function arguments and return addresses.

Objects allocated on the stack are destructed when they go out of scope.

The scope ends with a }, or return, or for single-line scopes, at the end of the line.

Continues

Stack and Heap

To get automatic memory management, only the parent needs to be allocated on the stack.

MyMainWindow

QApplicationint main(int argc, char **argv){ QApplication a(argc, argv); MyMainWindow w; w.show(); return a.exec();}

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

To get almost automatic memory management using Qt, the trick is to allocate the outermost parents on the stack, and the rest on the heap.

For instance, the main function scope will be valid for as long as the application is running, so we allocate the application and window on the stack.

The window, in turn, creates a bunch of child widgets in its constructor. To avoid destruction when the scope of the constructor ends, they are allocated dynamically. But, they are given the window as their parent object and are thus also destructed when the main function scope ends.

Signals and Slots

Dynamically and loosely tie together events and state changes with reactions

What makes Qt tick

One of the key factors of Qt is the signals and slots mechanism.

It is a mechanism to dynamically and loosely tie together events and changes with reactions

Dynamically = at run-timeLoosely = sender and receiver do not know each otherevents = timer event, clicks, etc. Not to be confused with actual events (QEvent).state changes = size changed, text changed, value changed, etcreactions = source code that actually does something

This is what makes a Qt application tick

Continues

Signals and Slots in Action

emit clicked();

Looking at an example from the first lecture.

The three buttons all emit the clicked signal when they are clicked by the user (or activated by other means).

They do this regardless whether something is connected to them or not, and regardless of what is connected to them.

Continues

Signals and Slots in Action

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

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

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

2x

clear();

We choose to connect the buttons to the list widget's clear slot, and two custom slots in our custom code.

As said earlier, the buttons do not care what they are connected to, and the slots are equally independent of what triggers them. You can even call them programatically as ordinary functions.

Continues

Signals and Slots in Action

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

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

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

{ QString newText = QInputDialog::getText(this, "Enter text", "Text:");

if( !newText.isEmpty() ) ui->listWidget->addItem(newText);}

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

clear();

So, the add button emits clicked, ending up in the on_addButton_clicked slot resulting in the following code being run. It simply asks for input and then adds it to the list.

The delete button emits clicked, ending up in the on_deleteButton_clicked slot, where all selected items are deleted.

The clear button emits clicked, which ends up in the clear slot of the QListWidget, which to us is a black box that does what its documentation says.

What is a slot?

A slot is defined in one of the slots sections

A slot can return values, but not through connections

Any number of signals can be connected to a slot

It is implemented as an ordinary method

It can be called as an ordinary method

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

connect(src, SIGNAL(sig()), dest, SLOT(slt()));

A slot is an ordinary function, just that it can be connected to signals. They do not have to be connected, you can call a slot like any other function, and you implement it as usual.

Slots are declared in one of the sections public, protected and private slots. These access restrictions work as intended when calling the function, but a private or protected slot can be connected to any other signal, so they can be triggered from outside the class.

Slots can return values, but connections cannot carry return arguments.

Any number of signals can be connected to a single slot. This means that a single slot can serve several sources of events think keyboard shortcut, button, etc.

Continues

What is a signal?

A signal is defined in the signals section

A signal always returns void

A signal must not be implementedThe 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

signals: void aSignal();

emit aSignal();

Signals are defined in the signals section. This section can be considered protected, as a signal can only be emitted from within a class or its decendants.

Signals always return void, and must not be implemented. Instead, moc provides function bodies that trigger the actual slot-activation-code.

A signal can be connected to any number of slots, so a single event can trigger multiple reactions.

It is fully possible to connect signals and slots across threads. Third party libraries such as Qxt (http://doc.libqxt.org/0.5.0/classQxtRPCPeer.html).

Inside a signal emitting class, you use the emit keyword to emit a signal. The emit keyword is defined as nothing, what actually takes place is a call to the signal function which calls the slots.

Continues

Making the connection

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

( ... )

clicked()toggled(bool)setText(QString)textChanged(QString)rangeChanged(int,int)

setTitle(QString text)setValue(42)

A signature consists of the function name
and argument types. No variable names,
nor values are allowed.

Custom types reduces reusability.

QObject*setItem(ItemClass)

You can make signals to slots connections between any two QObjects.

Qt verifies that the signatures of the signal and slot match. The signature consists of the name of the signal or slot followed by the argument types.

There must be no values nor variable names in the signature.

It is also recommended to stick to using standard types, e.g. the ItemClass custom type reduces the reusability and should thus be avoided.

Continues

Making the connection

Qt can ignore arguments, but not create values from nothing

SignalsrangeChanged(int,int)rangeChanged(int,int)rangeChanged(int,int)

valueChanged(int)valueChanged(int)valueChanged(int)

textChanged(QString)

clicked()clicked()

SlotssetRange(int,int)setValue(int)updateDialog()

setRange(int,int)setValue(int)updateDialog()

setValue(int)

setValue(int)updateDialog()

When matching signatures, Qt is very forgiving. The basic rule is that Qt cannot create or convert values, but apart from that anything is allowed (i.e. skipping arguments).

The examples on the slide demonstrate this. The errors are (from the top):missing the last int (cannot create)

QString does not match int (cannot convert)

missing the only int (cannot create)

Continues

Automatic Connections

When using Designer it is convenient to have automatic connections between the interface and your code

Triggered by calling QMetaObject::connectSlotsByName

Think about reuse when namingCompare on_widget_signal to updatePageMargins

on_ object name _ signal name ( signal parameters )

on_addButton_clicked();

on_deleteButton_clicked();

on_listWidget_currentItemChanged(QListWidgetItem*,QListWidgetItem*)

updatePageMargins
can be connected to
a number of signals
or called directly.When making connections from Designer to your own source, Qt uses the automatic connection mechanism.

It lets signals automatically connect to slots with the corresponding name (structure and examples on the slide).

The automatic connections are made when connectSlotsByName is called. That is done at the end of the setupUi function generated by Designer.

When using this mechanism, think about reusability. Sometimes handwriting a couple of connect statements can greatly improve readability of the code.

Continues

Much more...

QtQuick cool animated user interfaces

http://qt.nokia.com/qtquick/

Goal of the day

Build and entire applicationDocument 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 filesTextEditor.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 through-out 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

Set the layout margin properties to zero

You have to deselect andreselect thecentral widgetto see theseproperties 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

Many user interface elements refer to the same action

Do not forget tooltips, statusbar tips, etc.

Ctrl+S

Action

Why QAction

A QAction encapsulates all settings needed for menus, tool bars and keyboard shortcuts

Commonly used properties aretext 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"));

action->setData(myDataQVariant);

Setting propertiesfor text, icon and keyboard short-cutCreating a new actionA QVariant can beassociated with eachaction, to carry dataassociated with thegiven operation

Why QAction

Designer has an
action editor at the
bottom of the screenTools 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 executableAvoid 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, makesF a keyboard shortcutTo enter & in a menu,type && as the text.

Adding actions

In this first iteration, we add the most basic actions

TextNameIconShort-cutTool-tip

NewactionNewCtrl+NCreate a new document

CloseactionCloseCtrl+WClose current window

ExitactionExitExit application

The name is automaticallygenerated when enteringthe text for the action

Close and Exit areadded 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

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

Setting the WA_DeleteOnCloseattribute makes the widgetdelete itself when it is closedAll widgets has a closeslot out-of-the-boxThe QApplication class has a slot for closing all windowsThe qApp pointer refers to the current QApplication instanceRead clockwise

The application stops executing with the last window has been closed...

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

QWidget provides virtual methods for the most common events, e.g. QWidget::closeEvent

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 { e->ignore(); } } else { e->accept(); }}

Unmodified windowsare always closedDepending on theuser's answer, theevent is acceptedor ignored

QMessageBox

The QMessageBox class can be used to showquestion dialogsinformation dialogswarning dialogscritical dialogsabout dialogs for both the current application and Qt itself

Roughly the same type of dialogswith a title, a message and one ormore buttons.

The green words are functionnames for static members. Toshow an information dialog, useQMessageBox::information, etc.

QMessageBox

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

Parent windowDialog titleThe message of the dialog. Noticethe use of '\n'The buttons touse. 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 IgnoreThe default buttonThe return valueis 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.

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

This function is calledfrom the constructor aswell as from thedocumentModified slot

Mention that OS X removes the [*], instead a dot in the left-most ball is used to indicate if the document is modified or not.

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

a.setWindowIcon(QIcon(":/icons/new.png"));

Application iconProducer name and domainApplication name and versionwindowtitles should be interpreted as automatic window titles...

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

TextNameIconShort-cutTool-tip

Select Font...actionSelectFontSelect the display font

New menus are always placedto the right, but they can bedragged 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 todetermine if the user actuallypicked a font. This is becauseQFont 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

Any QVariant can be stored but think about readability for advanced users

QSettings settings;

myString = settings.value("key","default").toString();

settings.setValue("key", myString);

settings.remove("key");

Back to the font

Saving the font

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

MainWindow::MainWindow(QWidget *parent) : ...{ ... QSettings settings; ui->textEdit->setFont( settings.value("viewFont", QApplication::font()).value()); ...

Default value

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

The actions are added to both the tool bar and menu

TextNameIconShort-cutTool-tip

CutactionCutCtrl+XCut

CopyactionCopyCtrl+CCopy

PasteactionPasteCtrl+VPaste

Right click on thetool bar to addseparators

The edit menu

Slots for the new actions are implemented in the text editing widget

To avoid trying to copy in vain, let's add connections for enabling and disabling the actions depending on the selection

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

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

TextNameIconShort-cutTool-tip

UndoactionUndoCtrl+ZUndo the last action

RedoactionRedoCtrl+YRedo 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

This means that we must implement file operations

Interfacing files

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

Loading

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, unnameddocuments needs this

Updating the window title

The window title must reflect the filename

Results in:untitled* - Text Editortesting.txt - Text Editor

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

setWindowModified(m_modified);}

Extracts the filename part of a filename with a path

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 argumentsin the Run Settings, underthe project tab in QtCreator

Adding file actions

Actions to let the user open and save documents

The actions are added to the
File menu and tool bar

TextNameIconShort-cutTool-tip

Open...actionOpenCtrl+OOpen a document

SaveactionSaveCtrl+SSave the current document

Save As...actionSaveAsCtrl+Shift+SSave current document as

Opening documents

Asking for file names, we use the QFileDialog::getOpen/SaveFileName methods

QFileDialog::getOpenFileName(this, "Open document", QDir::currentPath(), "Text documents (*.txt)");

Parent windowDialog titleInitial directoryFile type filterFile 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(); }}

Putting all together opens documents

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 loadingSave 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(); updateWindowTitle(); return false; }

QTextStream out(&file); out textEdit->toPlainText();

m_modified = false; updateWindowTitle(); return true;}

If the name is null,we need to save asSave the documentIf the file cannot beopened for writing,we display a warningand set the file name to nullUpdate the modified flag and titleReturn true on success

Saving documents

bool MainWindow::saveFileAs(){ QString fileName = QFileDialog::getSaveFileName(...);

if(!fileName.isNull()) { m_fileName = fileName; return saveFile(); }

return false;}

Ask the user fora file nameIf a name isgiven, attemptto save

If no name isgiven, the saveis a failureThe method saveFileAs never callssaveFile 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

Do not closeClose

saved?

Y

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: if(saveFile()) e->accept(); else e->ignore(); break; case QMessageBox::No: e->accept(); break; case QMessageBox::Cancel: e->ignore(); break; } } else { e->accept(); }}

The user wants to saveThe user do not want to saveCancel the closingClose unmodified documents

Depending on the save, close or ignore

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!

[email protected]