https://techbase.kde.org/api.php?action=feedcontributions&user=Battler&feedformat=atomKDE TechBase - User contributions [en]2024-03-28T10:30:34ZUser contributionsMediaWiki 1.40.2https://techbase.kde.org/index.php?title=Development/Tutorials/Saving_and_loading&diff=47958Development/Tutorials/Saving and loading2010-01-02T04:31:51Z<p>Battler: fixed a typo</p>
<hr />
<div>{{Template:I18n/Language Navigation Bar|Development/Tutorials/Saving_and_loading}}<br />
<br />
{{TutorialBrowser|<br />
<br />
series=Beginner Tutorial|<br />
<br />
name=Loading and saving files|<br />
<br />
pre=[[Development/Tutorials/Using_KActions|Tutorial 3 - KActions]]|<br />
<br />
next=[[Development/Tutorials/KCmdLineArgs|Tutorial 5 - Using KCmdLineArgs]]| <br />
<br />
reading=[[Development/Tutorials/KIO Slaves/Using KIO Slaves in your Program|Tutorial: Using KIO Slaves in your Program]] KIO::{{class|NetAccess}} {{qt|QFile}}<br />
}}<br />
<br />
==Abstract==<br />
<br />
Now that we have a basic text editor interface, it's time to make it do something useful. At the most basic, a text editor needs to be able to load files from disc, save files that you've created/edited and create new files.<br />
<br />
KDE provides a number of classes for working with files which make life a lot easier for developers. The KIO library allows you to easily access files through network-transparent protocols as well as providing standard file dialogs.<br />
<br />
[[image:introtokdetutorial4.png|frame|center]]<br />
<br />
== The Code ==<br />
<br />
===main.cpp===<br />
<code cppqt n><br />
#include <KApplication><br />
#include <KAboutData><br />
#include <KCmdLineArgs><br />
<br />
#include "mainwindow.h"<br />
<br />
int main (int argc, char *argv[])<br />
{<br />
KAboutData aboutData( "tutorial4", "tutorial4",<br />
ki18n("Tutorial 4"), "1.0",<br />
ki18n("A simple text area which can load and save."),<br />
KAboutData::License_GPL,<br />
ki18n("Copyright (c) 2007 Developer") );<br />
KCmdLineArgs::init( argc, argv, &aboutData );<br />
KApplication app;<br />
<br />
MainWindow* window = new MainWindow();<br />
window->show();<br />
return app.exec();<br />
}<br />
</code><br />
<tt>main.cpp</tt> hasn't changed from tutorial 3 except to change any reference from tutorial 3 to tutorial 4.<br />
<br />
===mainwindow.h===<br />
<code cppqt n><br />
#ifndef MAINWINDOW_H<br />
#define MAINWINDOW_H<br />
<br />
#include <KXmlGuiWindow><br />
#include <KTextEdit><br />
<br />
class MainWindow : public KXmlGuiWindow<br />
{<br />
Q_OBJECT //new from tutorial3<br />
<br />
public:<br />
MainWindow(QWidget *parent=0);<br />
<br />
private:<br />
KTextEdit* textArea;<br />
void setupActions();<br />
QString fileName; //new<br />
<br />
private slots: //new<br />
void newFile(); //new<br />
void openFile(); //new<br />
void saveFile(); //new<br />
void saveFileAs(); //new<br />
void saveFileAs(const QString &outputFileName); //new<br />
};<br />
<br />
#endif<br />
</code><br />
Since we want to add the ability to load and save files, we must add the functions which will do the work. Since the functions will be called through Qt's [http://doc.trolltech.com/latest/signalsandslots.html signal/slot] mechanism we must specify that these functions are slots as we do on line 19. Since we are using slots in this header file, we must also add the [http://doc.trolltech.com/latest/qobject.html#Q_OBJECT <tt>Q_OBJECT</tt>] macro.<br />
<br />
We also want to keep track of the filename of the currently opened file so we declare a <tt>{{qt|QString}} fileName</tt>.<br />
<br />
===mainwindow.cpp===<br />
<code cppqt n><br />
#include "mainwindow.h"<br />
<br />
#include <KApplication><br />
#include <KAction><br />
#include <KLocale><br />
#include <KActionCollection><br />
#include <KStandardAction><br />
#include <KFileDialog> //new<br />
#include <KMessageBox> //new<br />
#include <KIO/NetAccess> //new<br />
#include <KSaveFile> //new<br />
#include <QTextStream> //new<br />
<br />
MainWindow::MainWindow(QWidget *parent)<br />
: KXmlGuiWindow(parent),<br />
fileName(QString()) //new<br />
{<br />
textArea = new KTextEdit;<br />
setCentralWidget(textArea);<br />
<br />
setupActions();<br />
}<br />
<br />
void MainWindow::setupActions()<br />
{<br />
KAction* clearAction = new KAction(this);<br />
clearAction->setText(i18n("Clear"));<br />
clearAction->setIcon(KIcon("document-new"));<br />
clearAction->setShortcut(Qt::CTRL + Qt::Key_W);<br />
actionCollection()->addAction("clear", clearAction);<br />
connect(clearAction, SIGNAL(triggered(bool)),<br />
textArea, SLOT(clear()));<br />
<br />
KStandardAction::quit(kapp, SLOT(quit()),<br />
actionCollection());<br />
<br />
KStandardAction::open(this, SLOT(openFile()),<br />
actionCollection()); //new<br />
<br />
KStandardAction::save(this, SLOT(saveFile()),<br />
actionCollection()); //new<br />
<br />
KStandardAction::saveAs(this, SLOT(saveFileAs()),<br />
actionCollection()); //new<br />
<br />
KStandardAction::openNew(this, SLOT(newFile()),<br />
actionCollection()); //new<br />
<br />
setupGUI();<br />
}<br />
<br />
//New from here on<br />
<br />
void MainWindow::newFile()<br />
{<br />
fileName.clear();<br />
textArea->clear();<br />
}<br />
<br />
void MainWindow::saveFileAs(const QString &outputFileName)<br />
{<br />
KSaveFile file(outputFileName);<br />
file.open();<br />
<br />
QByteArray outputByteArray;<br />
outputByteArray.append(textArea->toPlainText().toUtf8());<br />
file.write(outputByteArray);<br />
file.finalize();<br />
file.close();<br />
<br />
fileName = outputFileName;<br />
}<br />
<br />
void MainWindow::saveFileAs()<br />
{<br />
saveFileAs(KFileDialog::getSaveFileName());<br />
}<br />
<br />
void MainWindow::saveFile()<br />
{<br />
if(!fileName.isEmpty())<br />
{<br />
saveFileAs(fileName);<br />
}<br />
else<br />
{<br />
saveFileAs();<br />
}<br />
}<br />
<br />
void MainWindow::openFile()<br />
{<br />
QString fileNameFromDialog = KFileDialog::getOpenFileName();<br />
<br />
QString tmpFile;<br />
if(KIO::NetAccess::download(fileNameFromDialog, tmpFile, <br />
this))<br />
{<br />
QFile file(tmpFile);<br />
file.open(QIODevice::ReadOnly);<br />
textArea->setPlainText(QTextStream(&file).readAll());<br />
fileName = fileNameFromDialog;<br />
<br />
KIO::NetAccess::removeTempFile(tmpFile);<br />
}<br />
else<br />
{<br />
KMessageBox::error(this, <br />
KIO::NetAccess::lastErrorString());<br />
}<br />
}<br />
</code><br />
<br />
===tutorial4ui.rc===<br />
<code xml n><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<gui name="tutorial4"<br />
version="1"<br />
xmlns="http://www.kde.org/standards/kxmlgui/1.0"<br />
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br />
xsi:schemaLocation="http://www.kde.org/standards/kxmlgui/1.0<br />
http://www.kde.org/standards/kxmlgui/1.0/kxmlgui.xsd" ><br />
<br />
<MenuBar><br />
<Menu name="file" ><br />
<Action name="clear" /><br />
</Menu><br />
</MenuBar><br />
<br />
<ToolBar name="mainToolBar" ><br />
<text>Main Toolbar</text><br />
<Action name="clear" /><br />
</ToolBar><br />
<br />
</gui><br />
</code><br />
This is identical to <tt>tutorial3ui.rc</tt> from tutorial 3 except the <tt>name</tt> has changed to 'tutorial4'. We do not need to add any information about any of the <tt>KStandardAction</tt>s since the placement of those actions is handled automatically by KDE.<br />
<br />
==Explanation==<br />
<br />
Okay, now to implement the code that will do the loading and saving. This will all be happening in <tt>mainwindow.cpp</tt><br />
<br />
The first thing we do is add<br />
<code cppqt><br />
fileName(QString())<br />
</code><br />
to the <tt>MainWindow</tt> constructor list on line 16. This makes sure that <tt>fileName</tt> is empty right from the beginning.<br />
<br />
===Adding the actions===<br />
<br />
The first thing we are going to do is provide the outward interface for the user so they can tell the application to load and save. Like with the <tt>quit</tt> action in tutorial 3, we will use <tt>KStandardActions</tt>. On lines 37 to 47 we add the actions in the same way as for the <tt>quit</tt> action. For each one, we connect it to the appropriate slot that we declared in the header file.<br />
<br />
===Creating a new document===<br />
<br />
The first function we create is the <tt>newFile()</tt> function.<br />
<code cppqt><br />
void MainWindow::newFile()<br />
{<br />
fileName.clear();<br />
textArea->clear();<br />
}<br />
</code><br />
<tt>fileName.clear()</tt> sets the <tt>fileName</tt> QString to be empty to reflect the fact that this document does not yet have a presence on disc. <tt>textArea->clear()</tt> then clears the central text area using the same function that we connected the <tt>clear</tt> <tt>KAction</tt> to in tutorial 3.<br />
<br />
===Saving a file===<br />
<br />
====saveFileAs(QString)====<br />
<br />
Now we get onto our first file handling code. We're going to implement a function which will save the contents of the text area to the file name given as a parameter. KDE provides a class for safely saving a file called {{class|KSaveFile}} which is derived from Qt's {{qt|QFile}}.<br />
<br />
The function's prototype is<br />
<code cppqt><br />
void MainWindow::saveFileAs(const QString &outputFileName)<br />
</code><br />
<br />
We then create our <tt>KSaveFile</tt> object and open it with<br />
<code cppqt><br />
KSaveFile file(outputFileName);<br />
file.open();<br />
</code><br />
<br />
Now that we have our file to write to, we need to format the text in the text area to a format which can be written to file. For this, we create a {{qt|QByteArray}} and fill it with the plain text version of whatever is in the text area:<br />
<code cppqt><br />
QByteArray outputByteArray;<br />
outputByteArray.append(textArea->toPlainText().toUtf8());<br />
</code><br />
Now that we have our <tt>QByteArray</tt>, we use it to write to the file with <tt>KSaveFile::write()</tt>. If we were using a normal <tt>QFile</tt>, this would make the changes immediately. However, if a problem occurred partway through writing, the file would become corrupted. For this reason, <tt>KSaveFile</tt> works by first writing to a temporary file and then, when you call <tt>KSaveFile::finalize()</tt> the changes are made to the actual file.<br />
<code cppqt><br />
file.write(outputByteArray);<br />
file.finalize();<br />
file.close();<br />
</code><br />
Finally, we set <tt>MainWindows</tt>'s <tt>fileName</tt> member to point to the file name we just saved to.<br />
<code cppqt><br />
fileName = outputFileName;<br />
</code><br />
<br />
====saveFileAs()====<br />
<br />
This is the function that the <tt>saveAs</tt> slot is connected to. It simply calls the generic <tt>saveFileAs(QString)</tt> function and passes the file name returned by <tt>{{class|KFileDialog}}::[http://api.kde.org/4.0-api/kdelibs-apidocs/kio/html/classKFileDialog.html#8891356c249c5911e1ab15cc2739a89b getSaveFileName()]</tt>.<br />
<br />
<code cppqt><br />
void MainWindow::saveFileAs()<br />
{<br />
saveFileAs(KFileDialog::getSaveFileName());<br />
}<br />
</code><br />
<br />
This is our first actual use of the KIO library. {{class|KFileDialog}} provides a number of static functions for displaying the common file dialog that is used by all KDE applications. Calling <tt>KFileDialog::getSaveFileName()</tt> will display a dialog where the user can select the name of the file to save to or choose a new name. The function returns the full file name, which we then pass to <tt>saveFileAs(QString)</tt>.<br />
<br />
====saveFile()====<br />
<br />
<code cppqt><br />
void MainWindow::saveFile()<br />
{<br />
if(!fileName.isEmpty())<br />
{<br />
saveFileAs(fileName);<br />
}<br />
else<br />
{<br />
saveFileAs();<br />
}<br />
}<br />
</code><br />
<br />
There's nothing exciting or new in this function, just the logic to decide whether or not to show the save dialog. If <tt>fileName</tt> is not empty, then the file is saved to <tt>fileName</tt>. But if it is, then the dialog is shown to allow the user to select a file name.<br />
<br />
===Loading a file===<br />
<br />
Finally, we get round to being able to load a file from disc. The code for this is all contained in <tt>MainWindow::openFile()</tt>.<br />
<br />
First we must ask the user for the name of the file they wish to open. We do this using another one of the <tt>KFileDialog</tt> functions, this time <tt>getOpenFileName()</tt>:<br />
<code cppqt><br />
QString fileNameFromDialog = KFileDialog::getOpenFileName();<br />
</code><br />
<br />
Then we use the KIO library to retrieve our file. This allows us to open the file with QFile even if it's stored in a remote location like an FTP site. We make the following call to {{class|NetAccess}}'s <tt>download()</tt> function<br />
<code cppqt><br />
KIO::NetAccess::download(fileNameFromDialog, tmpFile, this)<br />
</code><br />
The first argument is the name of the file you wish to download. The second is a QString which, after the download is complete, will contain the location of the temporary copy of the file. It is this <tt>tmpFile</tt> we will work with from now on.<br />
<br />
The function returns <tt>true</tt> or <tt>false</tt> depending on whether the transfer was successful. If it failed, we display a message box giving the error:<br />
<code cppqt><br />
KMessageBox::error(this, KIO::NetAccess::lastErrorString());<br />
</code><br />
<br />
Otherwise, we continue with opening the file.<br />
<br />
We create a QFile by passing the temporary file created by <tt>NetAccess::download()</tt> to its constructor and then open it in read-only mode<br />
<code cppqt><br />
QFile file(tmpFile);<br />
file.open(QIODevice::ReadOnly);<br />
</code><br />
<br />
In order to display the contents of the file, we must use a {{qt|QTextStream}}. We create one by passing the contents of our file to its constructor and then call QFile's <tt>readAll()</tt> function to get the text from the file. This is then passed to the <tt>setPlainText()</tt> function of our text area.<br />
<br />
<code cppqt><br />
textArea->setPlainText(QTextStream(&file).readAll());<br />
</code><br />
<br />
We then store the path of the file we just opened:<br />
<code cppqt><br />
fileName = fileNameFromDialog;<br />
</code><br />
and finally, we remove the temporary file that was created by <tt>NetAccess::download()</tt>:<br />
<code cppqt><br />
KIO::NetAccess::removeTempFile(tmpFile);<br />
</code><br />
<br />
==Make, Install And Run==<br />
<br />
===CMakeLists.txt===<br />
<code ini n><br />
project(tutorial4)<br />
<br />
find_package(KDE4 REQUIRED)<br />
include_directories(${KDE4_INCLUDES})<br />
<br />
set(tutorial4_SRCS <br />
main.cpp<br />
mainwindow.cpp<br />
)<br />
<br />
kde4_add_executable(tutorial4 ${tutorial4_SRCS})<br />
<br />
target_link_libraries(tutorial4 ${KDE4_KDEUI_LIBS} <br />
${KDE4_KIO_LIBS})<br />
<br />
install(TARGETS tutorial4 DESTINATION ${BIN_INSTALL_DIR})<br />
install(FILES tutorial4ui.rc <br />
DESTINATION ${DATA_INSTALL_DIR}/tutorial4)<br />
</code><br />
Since we are now using the KIO library, we must tell CMake to link against it. We do this by passing <tt>${KDE4_KIO_LIBS}</tt> to the <tt>target_link_libraries()</tt> function.<br />
<br />
With this file, the tutorial can be built and run in the same way as tutorial 3. For more information, see tutorial 3.<br />
<code><br />
mkdir build && cd build<br />
cmake .. -DCMAKE_INSTALL_PREFIX=$HOME<br />
make install<br />
$HOME/bin/tutorial4<br />
</code><br />
<br />
{{note|Changed settings are saved in your KDE directory, in this case into $HOME/.kde/share/apps/tutorial4.}}<br />
<br />
==Moving On==<br />
Now you can move on to the [[Development/Tutorials/KCmdLineArgs|KCmdLineArgs]] tutorial.<br />
<br />
[[Category:C++]]</div>Battler