Development/Tutorials/Saving and loading: Difference between revisions

    From KDE TechBase
    (How to open files. This tutorial is structurally complete now!)
    No edit summary
    (15 intermediate revisions by 10 users not shown)
    Line 1: Line 1:
    {{Template:I18n/Language Navigation Bar|Development/Tutorials/Saving_and_loading}}
     


    {{TutorialBrowser|
    {{TutorialBrowser|
    Line 5: Line 5:
    series=Beginner Tutorial|
    series=Beginner Tutorial|


    name=Loading and saving files (Under construction [[User:milliams]])|
    name=Loading and saving files|


    pre=[[Development/Tutorials/Using_KActions|Tutorial 3 - KActions]]|
    pre=[[Development/Tutorials/Using_KActions|Tutorial 3 - KActions]]|


    next=KCmdLineArgs (TODO [[User:milliams]])|  
    next=[[Development/Tutorials/KCmdLineArgs|Tutorial 5 - Using KCmdLineArgs]]|  


    reading=KIO::{{class|NetAccess}} {{qt|QFile}}
    reading=[[Development/Tutorials/KIO Slaves/Using KIO Slaves in your Program|Tutorial: Using KIO Slaves in your Program]] KIO::{{class|NetAccess}} {{qt|QFile}}
    }}
    }}


    Line 21: Line 21:


    [[image:introtokdetutorial4.png|frame|center]]
    [[image:introtokdetutorial4.png|frame|center]]
    {{Attention||The source code on this page applies only to KDE Development Platform 4 ("KDE 4"). For the KDE Frameworks 5 ("KF5") version, see [[Development/Tutorials/Saving_and_loading/KF5]]}}


    == The Code ==
    == The Code ==


    ===main.cpp===
    ===main.cpp===
    <code cppqt n>
    <syntaxhighlight lang="cpp-qt">
    #include <KApplication>
    #include <KApplication>
    #include <KAboutData>
    #include <KAboutData>
    Line 46: Line 49:
       return app.exec();
       return app.exec();
    }
    }
    </code>
    </syntaxhighlight>
    <tt>main.cpp</tt> hasn't changed from tutorial 3 except to change any reference to tutorial 3 to tutorial 4.
    <tt>main.cpp</tt> hasn't changed from tutorial 3 except to change any reference from tutorial 3 to tutorial 4.


    ===mainwindow.h===
    ===mainwindow.h===
    <code cppqt n>
    <syntaxhighlight lang="cpp-qt">
    #ifndef MAINWINDOW_H
    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    #define MAINWINDOW_H
    Line 78: Line 81:


    #endif
    #endif
    </code>
    </syntaxhighlight>
    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.
    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.


    Line 84: Line 87:


    ===mainwindow.cpp===
    ===mainwindow.cpp===
    <code cppqt n>
    <syntaxhighlight lang="cpp-qt">
    #include "mainwindow.h"
    #include "mainwindow.h"


    Line 150: Line 153:
        
        
       QByteArray outputByteArray;
       QByteArray outputByteArray;
       outputByteArray.append(textArea->toPlainText());
       outputByteArray.append(textArea->toPlainText().toUtf8());
       file.write(outputByteArray);
       file.write(outputByteArray);
       file.finalize();
       file.finalize();
    Line 180: Line 183:


       QString tmpFile;
       QString tmpFile;
       if(KIO::NetAccess::download(fileNameFromDialog, tmpFile,  
       if(KIO::NetAccess::download(fileNameFromDialog, tmpFile, this))
            this))
       {
       {
         QFile file(tmpFile);
         QFile file(tmpFile);
    Line 196: Line 198:
       }
       }
    }
    }
    </code>
    </syntaxhighlight>


    ===tutorial4ui.rc===
    ===tutorial4ui.rc===
    <code xml n>
    <syntaxhighlight lang="xml">
    <?xml version="1.0" encoding="UTF-8"?>
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
    <gui name="tutorial4"
    <gui name="tutorial4" version="1">
        version="1"
      <ToolBar name="mainToolBar" >
        xmlns="http://www.kde.org/standards/kxmlgui/1.0"
        <text>Main Toolbar</text>
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        <Action name="clear" />
        xsi:schemaLocation="http://www.kde.org/standards/kxmlgui/1.0
      </ToolBar>
                            http://www.kde.org/standards/kxmlgui/1.0/kxmlgui.xsd" >
     
       <MenuBar>
       <MenuBar>
         <Menu name="file" >
         <Menu name="file" >
    Line 212: Line 215:
         </Menu>
         </Menu>
       </MenuBar>
       </MenuBar>
      <ToolBar name="mainToolBar" >
        <text>Main Toolbar</text>
        <Action name="clear" />
      </ToolBar>
    </gui>
    </gui>
    </code>
    </syntaxhighlight>
    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.
    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.


    Line 220: Line 229:
    Okay, now to implement the code that will do the loading and saving. This will all be happening in <tt>mainwindow.cpp</tt>
    Okay, now to implement the code that will do the loading and saving. This will all be happening in <tt>mainwindow.cpp</tt>


    The first thing we do is add
    The first thing we do is add <tt>fileName(QString())</tt> to the <tt>MainWindow</tt> constructor list on line 16. This makes sure that <tt>fileName</tt> is empty right from the beginning.
    <code cppqt>
    fileName(QString())
    </code>
    to the <tt>MainWindow</tt> constructor list on line 16. This makes sure that <tt>fileName</tt> is empty right from the beginning.


    ===Adding the actions===
    ===Adding the actions===
    Line 233: Line 238:


    The first function we create is the <tt>newFile()</tt> function.
    The first function we create is the <tt>newFile()</tt> function.
    <code cppqt>
    <syntaxhighlight lang="cpp-qt">
    void MainWindow::newFile()
    void MainWindow::newFile()
    {
    {
    Line 239: Line 244:
       textArea->clear();
       textArea->clear();
    }
    }
    </code>
    </syntaxhighlight>
    <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.
    <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.


    Line 249: Line 254:


    The function's prototype is
    The function's prototype is
    <code cppqt>
    <syntaxhighlight lang="cpp-qt">
    void MainWindow::saveFileAs(const QString &outputFileName)
    void MainWindow::saveFileAs(const QString &outputFileName)
    </code>
    </syntaxhighlight>


    We then create our <tt>KSaveFile</tt> object and open it with
    We then create our <tt>KSaveFile</tt> object and open it with
    <code cppqt>
    <syntaxhighlight lang="cpp-qt">
    KSaveFile file(outputFileName);
    KSaveFile file(outputFileName);
    file.open();
    file.open();
    </code>
    </syntaxhighlight>


    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:
    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:
    <code cppqt>
    <syntaxhighlight lang="cpp-qt">
    QByteArray outputByteArray;
    QByteArray outputByteArray;
    outputByteArray.append(textArea->toPlainText());
    outputByteArray.append(textArea->toPlainText().toUtf8());
    </code>
    </syntaxhighlight>
    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.
    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.
    <code cppqt>
    <syntaxhighlight lang="cpp-qt">
    file.write(outputByteArray);
    file.write(outputByteArray);
    file.finalize();
    file.finalize();
    file.close();
    file.close();
    </code>
    </syntaxhighlight>
    Finally, we set <tt>MainWindows</tt>'s <tt>fileName</tt> member to point to the file name we just saved to.
    Finally, we set <tt>MainWindows</tt>'s <tt>fileName</tt> member to point to the file name we just saved to.
    <code cppqt>
    <syntaxhighlight lang="cpp-qt">
    fileName = outputFileName;
    fileName = outputFileName;
    </code>
    </syntaxhighlight>


    ====saveFileAs()====
    ====saveFileAs()====
    Line 279: Line 284:
    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>.
    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>.


    <code cppqt>
    <syntaxhighlight lang="cpp-qt">
    void MainWindow::saveFileAs()
    void MainWindow::saveFileAs()
    {
    {
       saveFileAs(KFileDialog::getSaveFileName());
       saveFileAs(KFileDialog::getSaveFileName());
    }
    }
    </code>
    </syntaxhighlight>


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


    ====saveFile()====
    ====saveFile()====


    <code cppqt>
    <syntaxhighlight lang="cpp-qt">
    void MainWindow::saveFile()
    void MainWindow::saveFile()
    {
    {
    Line 302: Line 307:
       }
       }
    }
    }
    </code>
    </syntaxhighlight>


    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.
    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.
    Line 311: Line 316:


    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>:
    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>:
    <code cppqt>
    <syntaxhighlight lang="cpp-qt">
    QString fileNameFromDialog = KFileDialog::getOpenFileName();
    QString fileNameFromDialog = KFileDialog::getOpenFileName();
    </code>
    </syntaxhighlight>


    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
    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
    <code cppqt>
    <syntaxhighlight lang="cpp-qt">
    KIO::NetAccess::download(fileNameFromDialog, tmpFile, this)
    KIO::NetAccess::download(fileNameFromDialog, tmpFile, this)
    </code>
    </syntaxhighlight>
    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.
    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.


    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:
    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:
    <code cppqt>
    <syntaxhighlight lang="cpp-qt">
    KMessageBox::error(this, KIO::NetAccess::lastErrorString());
    KMessageBox::error(this, KIO::NetAccess::lastErrorString());
    </code>
    </syntaxhighlight>


    Otherwise, we continue with opening the file.
    Otherwise, we continue with opening the file.


    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
    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
    <code cppqt>
    <syntaxhighlight lang="cpp-qt">
    QFile file(tmpFile);
    QFile file(tmpFile);
    file.open(QIODevice::ReadOnly);
    file.open(QIODevice::ReadOnly);
    </code>
    </syntaxhighlight>


    In order to display the contents of the file, we must use a {{class|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.
    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.


    <code cppqt>
    <syntaxhighlight lang="cpp-qt">
    textArea->setPlainText(QTextStream(&file).readAll());
    textArea->setPlainText(QTextStream(&file).readAll());
    </code>
    </syntaxhighlight>


    We then store the path of the file we just opened:
    We then store the path of the file we just opened:
    <code cppqt>
    <syntaxhighlight lang="cpp-qt">
    fileName = fileNameFromDialog;
    fileName = fileNameFromDialog;
    </code>
    </syntaxhighlight>
    and finally, we remove the temporary file that was created by <tt>NetAccess::download()</tt>:
    and finally, we remove the temporary file that was created by <tt>NetAccess::download()</tt>:
    <code cppqt>
    <syntaxhighlight lang="cpp-qt">
    KIO::NetAccess::removeTempFile(tmpFile);
    KIO::NetAccess::removeTempFile(tmpFile);
    </code>
    </syntaxhighlight>


    ==Make, Install And Run==
    ==Make, Install And Run==


    ===CMakeLists.txt===
    ===CMakeLists.txt===
    <code ini n>
    <syntaxhighlight lang="cmake">
    project(tutorial4)
    project(tutorial4)
       
       
    Line 371: Line 376:
    install(FILES tutorial4ui.rc  
    install(FILES tutorial4ui.rc  
             DESTINATION ${DATA_INSTALL_DIR}/tutorial4)
             DESTINATION ${DATA_INSTALL_DIR}/tutorial4)
    </code>
    </syntaxhighlight>
    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.
    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.


    With this file, the tutorial can built and run in the same way as tutorial 3. For more information, see tutorial 3.
    With this file, the tutorial can be built and run in the same way as tutorial 3. For more information, see tutorial 3.
    <syntaxhighlight lang="bash">
    mkdir build && cd build
    cmake .. -DCMAKE_INSTALL_PREFIX=$HOME
    make install
    $HOME/bin/tutorial4
    </syntaxhighlight>


    mkdir build && cd build
    {{note|Changed settings are saved in your KDE directory, in this case into $HOME/.kde/share/apps/tutorial4.}}
    cmake .. -DCMAKE_INSTALL_PREFIX=$HOME
    make install
    $HOME/bin/tutorial4


    ==Moving On==
    ==Moving On==
    Now you can move on to KCmdLineArgs (TODO [[User:milliams]]).
    Now you can move on to the [[Development/Tutorials/KCmdLineArgs|KCmdLineArgs]] tutorial.


    [[Category:C++]]
    [[Category:C++]]

    Revision as of 21:58, 18 August 2015


    Loading and saving files
    Tutorial Series   Beginner Tutorial
    Previous   Tutorial 3 - KActions
    What's Next   Tutorial 5 - Using KCmdLineArgs
    Further Reading   Tutorial: Using KIO Slaves in your Program KIO::NetAccess QFile

    Abstract

    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.

    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.

    The source code on this page applies only to KDE Development Platform 4 ("KDE 4"). For the KDE Frameworks 5 ("KF5") version, see Development/Tutorials/Saving_and_loading/KF5


    The Code

    main.cpp

    #include <KApplication>
    #include <KAboutData>
    #include <KCmdLineArgs>
     
    #include "mainwindow.h"
     
    int main (int argc, char *argv[])
    {
      KAboutData aboutData( "tutorial4", "tutorial4",
          ki18n("Tutorial 4"), "1.0",
          ki18n("A simple text area which can load and save."),
          KAboutData::License_GPL,
          ki18n("Copyright (c) 2007 Developer") );
      KCmdLineArgs::init( argc, argv, &aboutData );
      KApplication app;
     
      MainWindow* window = new MainWindow();
      window->show();
      return app.exec();
    }
    

    main.cpp hasn't changed from tutorial 3 except to change any reference from tutorial 3 to tutorial 4.

    mainwindow.h

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <KXmlGuiWindow>
    #include <KTextEdit>
    
    class MainWindow : public KXmlGuiWindow
    {
      Q_OBJECT //new from tutorial3
      
      public:
        MainWindow(QWidget *parent=0);
      
      private:
        KTextEdit* textArea;
        void setupActions();
        QString fileName; //new
    
      private slots: //new
        void newFile(); //new
        void openFile(); //new
        void saveFile(); //new
        void saveFileAs(); //new
        void saveFileAs(const QString &outputFileName); //new
    };
    
    #endif
    

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

    We also want to keep track of the filename of the currently opened file so we declare a QString fileName.

    mainwindow.cpp

    #include "mainwindow.h"
    
    #include <KApplication>
    #include <KAction>
    #include <KLocale>
    #include <KActionCollection>
    #include <KStandardAction>
    #include <KFileDialog> //new
    #include <KMessageBox> //new
    #include <KIO/NetAccess> //new
    #include <KSaveFile> //new
    #include <QTextStream> //new
     
    MainWindow::MainWindow(QWidget *parent)
        : KXmlGuiWindow(parent),
          fileName(QString()) //new
    {
      textArea = new KTextEdit;
      setCentralWidget(textArea);
     
      setupActions();
    }
     
    void MainWindow::setupActions()
    {
      KAction* clearAction = new KAction(this);
      clearAction->setText(i18n("Clear"));
      clearAction->setIcon(KIcon("document-new"));
      clearAction->setShortcut(Qt::CTRL + Qt::Key_W);
      actionCollection()->addAction("clear", clearAction);
      connect(clearAction, SIGNAL(triggered(bool)),
              textArea, SLOT(clear()));
     
      KStandardAction::quit(kapp, SLOT(quit()),
                            actionCollection());
     
      KStandardAction::open(this, SLOT(openFile()),
                            actionCollection()); //new
     
      KStandardAction::save(this, SLOT(saveFile()),
                            actionCollection()); //new
     
      KStandardAction::saveAs(this, SLOT(saveFileAs()),
                            actionCollection()); //new
     
      KStandardAction::openNew(this, SLOT(newFile()),
                            actionCollection()); //new
     
      setupGUI();
    }
    
    //New from here on
    
    void MainWindow::newFile()
    {
      fileName.clear();
      textArea->clear();
    }
    
    void MainWindow::saveFileAs(const QString &outputFileName)
    {
      KSaveFile file(outputFileName);
      file.open();
      
      QByteArray outputByteArray;
      outputByteArray.append(textArea->toPlainText().toUtf8());
      file.write(outputByteArray);
      file.finalize();
      file.close();
      
      fileName = outputFileName;
    }
    
    void MainWindow::saveFileAs()
    {
      saveFileAs(KFileDialog::getSaveFileName());
    }
    
    void MainWindow::saveFile()
    {
      if(!fileName.isEmpty())
      {
        saveFileAs(fileName);
      }
      else
      {
        saveFileAs();
      }
    }
    
    void MainWindow::openFile()
    {
      QString fileNameFromDialog = KFileDialog::getOpenFileName();
    
      QString tmpFile;
      if(KIO::NetAccess::download(fileNameFromDialog, tmpFile, this))
      {
        QFile file(tmpFile);
        file.open(QIODevice::ReadOnly);
        textArea->setPlainText(QTextStream(&file).readAll());
        fileName = fileNameFromDialog;
    
        KIO::NetAccess::removeTempFile(tmpFile);
      }
      else
      {
        KMessageBox::error(this, 
            KIO::NetAccess::lastErrorString());
      }
    }
    

    tutorial4ui.rc

    <?xml version="1.0" encoding="UTF-8"?>
    <gui name="tutorial4"
         version="1"
         xmlns="http://www.kde.org/standards/kxmlgui/1.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://www.kde.org/standards/kxmlgui/1.0
                             http://www.kde.org/standards/kxmlgui/1.0/kxmlgui.xsd" >
    
      <MenuBar>
        <Menu name="file" >
          <Action name="clear" />
        </Menu>
      </MenuBar>
    
      <ToolBar name="mainToolBar" >
        <text>Main Toolbar</text>
        <Action name="clear" />
      </ToolBar>
    
    </gui>
    

    This is identical to tutorial3ui.rc from tutorial 3 except the name has changed to 'tutorial4'. We do not need to add any information about any of the KStandardActions since the placement of those actions is handled automatically by KDE.

    Explanation

    Okay, now to implement the code that will do the loading and saving. This will all be happening in mainwindow.cpp

    The first thing we do is add fileName(QString()) to the MainWindow constructor list on line 16. This makes sure that fileName is empty right from the beginning.

    Adding the actions

    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 quit action in tutorial 3, we will use KStandardActions. On lines 37 to 47 we add the actions in the same way as for the quit action. For each one, we connect it to the appropriate slot that we declared in the header file.

    Creating a new document

    The first function we create is the newFile() function.

    void MainWindow::newFile()
    {
      fileName.clear();
      textArea->clear();
    }
    

    fileName.clear() sets the fileName QString to be empty to reflect the fact that this document does not yet have a presence on disc. textArea->clear() then clears the central text area using the same function that we connected the clear KAction to in tutorial 3.

    Saving a file

    saveFileAs(QString)

    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 KSaveFile which is derived from Qt's QFile.

    The function's prototype is

    void MainWindow::saveFileAs(const QString &outputFileName)
    

    We then create our KSaveFile object and open it with

    KSaveFile file(outputFileName);
    file.open();
    

    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 QByteArray and fill it with the plain text version of whatever is in the text area:

    QByteArray outputByteArray;
    outputByteArray.append(textArea->toPlainText().toUtf8());
    

    Now that we have our QByteArray, we use it to write to the file with KSaveFile::write(). If we were using a normal QFile, this would make the changes immediately. However, if a problem occurred partway through writing, the file would become corrupted. For this reason, KSaveFile works by first writing to a temporary file and then, when you call KSaveFile::finalize() the changes are made to the actual file.

    file.write(outputByteArray);
    file.finalize();
    file.close();
    

    Finally, we set MainWindows's fileName member to point to the file name we just saved to.

    fileName = outputFileName;
    

    saveFileAs()

    This is the function that the saveAs slot is connected to. It simply calls the generic saveFileAs(QString) function and passes the file name returned by KFileDialog::getSaveFileName().

    void MainWindow::saveFileAs()
    {
      saveFileAs(KFileDialog::getSaveFileName());
    }
    

    This is our first actual use of the KIO library. KFileDialog provides a number of static functions for displaying the common file dialog that is used by all KDE applications. Calling KFileDialog::getSaveFileName() 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 saveFileAs(QString).

    saveFile()

    void MainWindow::saveFile()
    {
      if(!fileName.isEmpty())
      {
        saveFileAs(fileName);
      }
      else
      {
        saveFileAs();
      }
    }
    

    There's nothing exciting or new in this function, just the logic to decide whether or not to show the save dialog. If fileName is not empty, then the file is saved to fileName. But if it is, then the dialog is shown to allow the user to select a file name.

    Loading a file

    Finally, we get round to being able to load a file from disc. The code for this is all contained in MainWindow::openFile().

    First we must ask the user for the name of the file they wish to open. We do this using another one of the KFileDialog functions, this time getOpenFileName():

    QString fileNameFromDialog = KFileDialog::getOpenFileName();
    

    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 NetAccess's download() function

    KIO::NetAccess::download(fileNameFromDialog, tmpFile, this)
    

    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 tmpFile we will work with from now on.

    The function returns true or false depending on whether the transfer was successful. If it failed, we display a message box giving the error:

    KMessageBox::error(this, KIO::NetAccess::lastErrorString());
    

    Otherwise, we continue with opening the file.

    We create a QFile by passing the temporary file created by NetAccess::download() to its constructor and then open it in read-only mode

    QFile file(tmpFile);
    file.open(QIODevice::ReadOnly);
    

    In order to display the contents of the file, we must use a QTextStream. We create one by passing the contents of our file to its constructor and then call QFile's readAll() function to get the text from the file. This is then passed to the setPlainText() function of our text area.

    textArea->setPlainText(QTextStream(&file).readAll());
    

    We then store the path of the file we just opened:

    fileName = fileNameFromDialog;
    

    and finally, we remove the temporary file that was created by NetAccess::download():

    KIO::NetAccess::removeTempFile(tmpFile);
    

    Make, Install And Run

    CMakeLists.txt

    project(tutorial4)
     
    find_package(KDE4 REQUIRED)
    include_directories(${KDE4_INCLUDES})
     
    set(tutorial4_SRCS 
      main.cpp
      mainwindow.cpp
    )
     
    kde4_add_executable(tutorial4 ${tutorial4_SRCS})
     
    target_link_libraries(tutorial4 ${KDE4_KDEUI_LIBS} 
                                    ${KDE4_KIO_LIBS})
     
    install(TARGETS tutorial4 DESTINATION ${BIN_INSTALL_DIR})
    install(FILES tutorial4ui.rc 
            DESTINATION ${DATA_INSTALL_DIR}/tutorial4)
    

    Since we are now using the KIO library, we must tell CMake to link against it. We do this by passing ${KDE4_KIO_LIBS} to the target_link_libraries() function.

    With this file, the tutorial can be built and run in the same way as tutorial 3. For more information, see tutorial 3.

    mkdir build && cd build
    cmake .. -DCMAKE_INSTALL_PREFIX=$HOME
    make install
    $HOME/bin/tutorial4
    
    Note
    Changed settings are saved in your KDE directory, in this case into $HOME/.kde/share/apps/tutorial4.


    Moving On

    Now you can move on to the KCmdLineArgs tutorial.