Development/Tutorials/Saving and loading/KF5

    From KDE TechBase


    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.

    The KDE Frameworks provides a number of classes for working with files which make life a lot easier for developers. The KIO framework allows you to easily access files through network-transparent protocols. At the same time, Qt also provides standard file dialogs for opening and saving files.

    The Code

    main.cpp

    #include <cstdlib>
     
    #include <QApplication>
    #include <QCommandLineParser>
    
    #include <KAboutData>
    #include <KLocalizedString>
    
    #include "mainwindow.h"
     
    int main (int argc, char *argv[])
    {
        QApplication app(argc, argv);
        
        KLocalizedString::setApplicationDomain("tutorial4");
        
        KAboutData aboutData(
                             // The program name used internally. (componentName)
                             QStringLiteral("tutorial4"),
                             // A displayable program name string. (displayName)
                             i18n("Tutorial 4"),
                             // The program version string. (version)
                             QStringLiteral("1.0"),
                             // Short description of what the app does. (shortDescription)
                             i18n("A simple text area which can load and save."),
                             // The license this code is released under
                             KAboutLicense::GPL,
                             // Copyright Statement (copyrightStatement = QString())
                             i18n("(c) 2015"),
                             // Optional text shown in the About box.
                             // Can contain any information desired. (otherText)
                             i18n("Some text..."),
                             // The program homepage string. (homePageAddress = QString())
                             QStringLiteral("http://example.com/"),
                             // The bug report email address
                             // (bugsEmailAddress = QLatin1String("[email protected]")
                             QStringLiteral("[email protected]"));
        aboutData.addAuthor(i18n("Name"), i18n("Task"), QStringLiteral("[email protected]"),
                            QStringLiteral("http://your.website.com"), QStringLiteral("OSC Username"));
        KAboutData::setApplicationData(aboutData);
     
        QCommandLineParser parser;
        parser.addHelpOption();
        parser.addVersionOption();
        aboutData.setupCommandLine(&parser);
        parser.process(app);
        aboutData.processCommandLine(&parser);
        
        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.

    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>
    

    mainwindow.h

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
     
    #include <KXmlGuiWindow>
    
    class KTextEdit;
    class KJob;
     
    class MainWindow : public KXmlGuiWindow
    {
        Q_OBJECT
        
      public:
        MainWindow(QWidget *parent=0);
     
      private:
        KTextEdit* textArea;
        void setupActions();
        
        QString fileName;
     
      private slots:
        void newFile();
        void openFile();
        void saveFile();
        void saveFileAs();
        void saveFileAs(const QString &outputFileName);
        
        void downloadFinished(KJob* job);
    };
     
    #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 <QApplication>
    #include <QAction>
    // 1
    #include <QSaveFile>
    // 2
    #include <QFileDialog>
    #include <QTextStream>
    #include <QByteArray>
    
    #include <KTextEdit>
    #include <KLocalizedString>
    #include <KActionCollection>
    #include <KStandardAction>
    #include <KMessageBox>
    // 3
    #include <KIO/Job>
    
    #include "mainwindow.h"
    
    MainWindow::MainWindow(QWidget *parent) : KXmlGuiWindow(parent)
    {
      textArea = new KTextEdit();
      setCentralWidget(textArea);
      
      setupActions();
    }
    
    void MainWindow::setupActions()
    {
        QAction* clearAction = new QAction(this);
        clearAction->setText(i18n("&Clear"));
        // 4
        clearAction->setIcon(QIcon::fromTheme("document-new"));
        // 5
        actionCollection()->setDefaultShortcut(clearAction, Qt::CTRL + Qt::Key_W);
        actionCollection()->addAction("clear", clearAction);
        connect(clearAction, SIGNAL(triggered(bool)), textArea, SLOT(clear()));
        
        // 6
        KStandardAction::quit(qApp, SLOT(quit()), actionCollection());
        
        KStandardAction::open(this, SLOT(openFile()), actionCollection());
     
        KStandardAction::save(this, SLOT(saveFile()), actionCollection());
     
        KStandardAction::saveAs(this, SLOT(saveFileAs()), actionCollection());
     
        KStandardAction::openNew(this, SLOT(newFile()), actionCollection());
        
        setupGUI(Default, "tutorial4ui.rc");
    }
    
    void MainWindow::newFile()
    {
        fileName.clear();
        textArea->clear();
    }
    
    void MainWindow::saveFileAs(const QString &outputFileName)
    {
        if (!outputFileName.isNull())
        {
            QSaveFile file(outputFileName);
            file.open(QIODevice::WriteOnly);
            
            QByteArray outputByteArray;
            outputByteArray.append(textArea->toPlainText().toUtf8());
            file.write(outputByteArray);
            file.commit();
    
            fileName = outputFileName;
        }
    }
    
    void MainWindow::saveFileAs()
    {
        saveFileAs(QFileDialog::getSaveFileName(this, i18n("Save File As")));
    }
    
    void MainWindow::saveFile()
    {
        if (!fileName.isEmpty())
        {
            saveFileAs(fileName);
        }
        else
        {
            saveFileAs();
        }
    }
    
    
    void MainWindow::openFile()
    {
        QString fileNameFromDialog = QFileDialog::getOpenFileName(this, i18n("Open File"));
        
        if (!fileNameFromDialog.isNull())
        {
            // 4
            KIO::Job* job = KIO::storedGet(QUrl::fromUserInput(fileNameFromDialog));
            fileName = fileNameFromDialog;
    
            // 5
            connect(job, SIGNAL(result(KJob*)), this, SLOT(downloadFinished(KJob*)));
            
            job->exec();
        }
    }
    
    void MainWindow::downloadFinished(KJob* job)
    {
        if (job->error())
        {
            KMessageBox::error(this, job->errorString());
            fileName.clear();
            return;
        }
        
        KIO::StoredTransferJob* storedJob = (KIO::StoredTransferJob*)job;
        textArea->setPlainText(QTextStream(storedJob->data(), QIODevice::ReadOnly).readAll());
    }
    

    Notes

    1. KSaveFile has been deprecated, use QSaveFile instead - https://community.kde.org/Frameworks/Porting_Notes#General
    2. KFileDialog has been deprecated, use QFileDialog instead - http://api.kde.org/frameworks-api/frameworks5-apidocs/kdelibs4support/html/classKFileDialog.html
    3. KIO::NetAccess has been deprecated, use KIO::get() instead - https://community.kde.org/Frameworks/Porting_Notes#KIO_Changes
    4. Replace KIO::NetAccess::download() with KIO::storedGet() and job->exec() - http://api.kde.org/frameworks-api/frameworks5-apidocs/kdelibs4support/html/classKIO_1_1NetAccess.html#ade6208bc67d6a808af98efadf74b53cc
    5. Call KIO::StoredTransferJob::data() only from a lost connected to result signal - http://api.kde.org/frameworks-api/frameworks5-apidocs/kio/html/classKIO_1_1StoredTransferJob.html#a9b60239d0e01bc994c8d1473d0a288eb

    Make, Install, and Run

    CMakeLists.txt

    project (tutorial4)
    
    cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR)
    set(QT_MIN_VERSION "5.3.0")
    set(KF5_MIN_VERSION "5.2.0")
    
    find_package(ECM 1.0.0 REQUIRED NO_MODULE)
    set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
    
    include(KDEInstallDirs)
    include(KDECMakeSettings)
    include(KDECompilerSettings)
    include(FeatureSummary)
    
    # Find Qt modules
    find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS 
        Core    # QCommandLineParser, QStringLiteral, QSaveFile, QTextStream, QByteArray
        Widgets # QApplication, QAction, QFileDialog
    )
    
    # Find KDE modules
    find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS
        CoreAddons      # KAboutData
        I18n            # KLocalizedString
        XmlGui          # KXmlGuiWindow, KActionCollection
        TextWidgets     # KTextEdit
        ConfigWidgets   # KStandardActions
        WidgetsAddons   # KMessageBox
        KIO             # KIO
    )
        
    
    feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
        
    set(tutorial4_SRCS main.cpp mainwindow.cpp)
    
    # just plain add_executable
    add_executable(tutorial4 ${tutorial4_SRCS})
    
    # module-based linking
    target_link_libraries(tutorial4
        Qt5::Widgets
        KF5::CoreAddons
        KF5::I18n
        KF5::XmlGui
        KF5::TextWidgets
        KF5::ConfigWidgets
        KF5::WidgetsAddons
        KF5::KIOCore
    )
    
    install(TARGETS tutorial4  ${INSTALL_TARGETS_DEFAULT_ARGS})
    
    install(FILES tutorial4ui.rc DESTINATION ${KXMLGUI_INSTALL_DIR}/tutorial4)