qt workshop
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!