Desenvolvimento/Tutoriais/Salvando e Carregando

    From KDE TechBase
    Revision as of 19:15, 19 November 2019 by Juliano Assis (talk | contribs) (Created page with "Por fim, somos capazes de carregar um arquivo, do local de armazenamento ou de um local remoto como um servidor FTP. O código para isso está todo contido em <tt>MainWindow::...")
    Other languages:
    Carregando e salvando arquivos
    Tutorial Series   Tutorial para Iniciantes
    Previous   Tutorial 3 - Actions
    What's Next   Tutorial 5 - Using Command Line Arguments
    Further Reading   Tutorial: Using KIO Slaves in your Program KIO::NetAccess QFile

    Resumo

    Agora que temos uma interface básica de editor de texto, é hora de fazer algo útil. No mais básico, um editor de texto precisa ser capaz de carregar arquivos do armazenamento de dados, salvar arquivos que você criou/editou e criar novos arquivos.

    O KDE Frameworks fornece várias classes para trabalhar com arquivos que facilitam muito a vida dos desenvolvedores. A KIO framework permite que você acesse arquivos facilmente através de protocolos transparentes de rede. Ao mesmo tempo, o Qt também fornece caixas de diálogo de arquivo padrão para abrir e salvar arquivos.

    O Código

    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;
        aboutData.setupCommandLine(&parser);
        parser.process(app);
        aboutData.processCommandLine(&parser);
        
        MainWindow* window = new MainWindow();
        window->show();
        
        return app.exec();
    }
    

    main.cpp não mudou do tutorial 3, exceto para alterar qualquer referência do tutorial 3 para o tutorial 4.

    mainwindow.h

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
     
    #include <KXmlGuiWindow>
    
    class KTextEdit;
    class KJob;
     
    class MainWindow : public KXmlGuiWindow
    {
        Q_OBJECT
        
      public:
        explicit MainWindow(QWidget *parent = nullptr);
     
      private:
        void setupActions();
     
      private slots:
        void newFile();
        void openFile();
        void saveFile();
        void saveFileAs();
        void saveFileAs(const QString &outputFileName);
        
        void downloadFinished(KJob* job);
    
      private:
        KTextEdit* textArea;
        QString fileName;
    };
     
    #endif
    

    Como queremos adicionar a capacidade de carregar e salvar arquivos, precisamos adicionar as funções que farão o trabalho. Como as funções serão chamadas pelo mecanismo do Qt signal/slot, devemos especificar que essas funções são slots. Como estamos usando slots neste arquivo de cabeçalho, também devemos adicionar a macro Q_OBJECT.

    Também queremos acompanhar o nome de arquivo do arquivo aberto no momento, por isso declaramos umQString fileName.

    mainwindow.cpp

    #include <QApplication>
    #include <QAction>
    #include <QSaveFile>
    #include <QFileDialog>
    #include <QTextStream>
    #include <QByteArray>
    
    #include <KTextEdit>
    #include <KLocalizedString>
    #include <KActionCollection>
    #include <KStandardAction>
    #include <KMessageBox>
    #include <KIO/Job>
    
    #include "mainwindow.h"
    
    MainWindow::MainWindow(QWidget *parent) : KXmlGuiWindow(parent), fileName(QString())
    {
      textArea = new KTextEdit();
      setCentralWidget(textArea);
      
      setupActions();
    }
    
    void MainWindow::setupActions()
    {
        QAction* clearAction = new QAction(this);
        clearAction->setText(i18n("&Clear"));
        clearAction->setIcon(QIcon::fromTheme("document-new"));
        actionCollection()->setDefaultShortcut(clearAction, Qt::CTRL + Qt::Key_W);
        actionCollection()->addAction("clear", clearAction);
        connect(clearAction, SIGNAL(triggered(bool)), textArea, SLOT(clear()));
        
        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()
    {
        QUrl fileNameFromDialog = QFileDialog::getOpenFileUrl(this, i18n("Open File"));
        
        if (!fileNameFromDialog.isEmpty())
        {
            KIO::Job* job = KIO::storedGet(fileNameFromDialog);
            fileName = fileNameFromDialog.toLocalFile();
    
            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());
    }
    

    Entraremos em detalhes do mainwindow.cpp daqui a pouco.

    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>
    

    Isso é idêntico ao tutorial3ui.rc do tutorial 3, exceto que o name foi alterado para 'tutorial4'. Não precisamos adicionar nenhuma informação sobre nenhum dos KStandardActions, pois o posicionamento dessas actions é tratado automaticamente pelo KDE.

    Explicação

    Ok, agora para implementar o código que fará o carregamento e o salvamento. Tudo isso estará acontecendo em mainwindow.cpp

    A primeira coisa que fazemos é adicionar fileName(QString()) à lista de construtores MainWindow para garantir que fileName esteja vazio desde o início.

    Adicionando os actions

    A primeira coisa que faremos é fornecer a interface externa para o usuário, para que ele possa dizer ao aplicativo para carregar e salvar. Assim como na action quit no tutorial 3, usaremos KStandardActions. Nós adicionamos as actions da mesma maneira que para a action quit e, para cada uma, nós a conectamos ao slot apropriado que declaramos no arquivo de cabeçalho.

    Criando um novo documento

    A primeira função que criamos é a função newFile().

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

    fileName.clear() define a QString fileName como vazio para refletir o fato de que este documento ainda não tem presença no armazenamento. textArea->clear() limpa a área de texto central usando a mesma função à qual conectamos o clear KQction no tutorial 3.

    Aviso
    Este exemplo simples apenas limpa a área de texto sem verificar se o arquivo foi salvo primeiro. É apenas uma demonstração de E/S de arquivo e não um exemplo de boas práticas de programação.


    Salvando um arquivo

    Nota
    Para simplificar este tutorial, este exemplo de programa pode salvar apenas no armazenamento local, mesmo que possa abrir qualquer arquivo de qualquer local, mesmo de fontes remotas.


    saveFileAs(QString)

    Agora chegamos a nossa primeira manipulação de código de arquivos. Vamos implementar uma função que salvará o conteúdo da área de texto no arquivo de nome fornecido como parâmetro. O Qt fornece uma classe para salvar com segurança um arquivo chamada QSaveFile.

    O protótipo da função é

    void MainWindow::saveFileAs(const QString &outputFileName)
    

    Em seguida, criamos nosso objeto QSaveFile e o abrimos com

    QSaveFile file(outputFileName);
    file.open(QIODevice::WriteOnly);
    

    Agora que temos nosso arquivo para gravar, precisamos formatar o texto na área de texto para um formato que possa ser gravado em arquivo. Para isso, criamos um QByteArray e o preenchemos com a versão em texto sem formatação do que estiver na área de texto:

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

    Agora que temos nosso QByteArray, usamos o para gravar no arquivo com QSaveFile::write(). Se estivéssemos usando um QFile normal, isso faria as alterações imediatamente. No entanto, se um problema ocorresse parcialmente durante a gravação, o arquivo seria corrompido. Por esse motivo, QSaveFile funciona primeiro escrevendo em um arquivo temporário e, em seguida, quando você chama QSaveFile::commit() as alterações são feitas no arquivo real. commit() também fecha o arquivo.

    file.write(outputByteArray);
    file.commit();
    

    Por fim, definimos fileName do membro MainWindows para apontar para o nome do arquivo no qual acabamos de salvar.

    fileName = outputFileName;
    

    saveFileAs()

    Esta é a função à qual o slot saveAs está conectado. Ele simplesmente chama a função genérica saveFileAs(QString) e passa o nome do arquivo retornado por QFileDialog::getSaveFileName().

    void MainWindow::saveFileAs()
    {
      saveFileAs(QFileDialog::getSaveFileName(this, i18n("Save File As")));
    }
    

    QFileDialog fornece várias funções estáticas para exibir a caixa de diálogo de arquivo comum usada por todos os aplicativos do KDE. Chamar QFileDialog::getSaveFileName() exibirá uma caixa de diálogo onde o usuário pode selecionar o nome do arquivo para salvar ou escolher um novo nome. A função retorna o nome completo do arquivo, que passamos para saveFileAs(QString).

    saveFile()

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

    Não há nada interessante ou novo nessa função, apenas a lógica para decidir se deve ou não mostrar a caixa de diálogo Salvar. Se fileName não estiver vazio, o arquivo será salvo em fileName. Mas, se estiver, a caixa de diálogo é mostrada para permitir que o usuário selecione um nome de arquivo.

    Carregando um arquivo

    Por fim, somos capazes de carregar um arquivo, do local de armazenamento ou de um local remoto como um servidor FTP. O código para isso está todo contido em 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 QFileDialog functions, this time getOpenFileName():

    QUrl fileNameFromDialog = QFileDialog::getOpenFileUrl(this, i18n("Open File"));
    

    Here we use the QUrl class to handle files from remote locations.

    Then we use the KIO library to retrieve our file. This allows us to open the file normally even if it's stored in a remote location like an FTP site. We make the following call to the KIO::storedGet() function with an argument for the file you wish to open or download:

    KIO::Job* job = KIO::storedGet(fileNameFromDialog);
    

    The function returns a handle to a KIO::Job, which we first connect to our downloadFinished() slot before "running" the job.

    connect(job, SIGNAL(result(KJob*)), this, SLOT(downloadFinished(KJob*)));
    
    job->exec();
    

    The rest of the work happens in the downloadFinished() slot. First, the job is checked for errors. If it failed, we display a message box giving the error. We also make sure to clear the fileName, since the file wasn't opened successfully:

    KMessageBox::error(this, job->errorString());
    fileName.clear();
    

    Otherwise, we continue with opening the file.

    The data that storedGet() successfully downloaded, in this case the contents of our text file, is stored in the data member of a KIO::StoredTransferJob class. But in order to display the contents of the file at text, we must use a QTextStream. We create one by passing the the data of the StoredTransferJob to its constructor and then call its readAll() function to get the text from the file. This is then passed to the setPlainText() function of our text area.

    KIO::StoredTransferJob* storedJob = (KIO::StoredTransferJob*)job;
    textArea->setPlainText(QTextStream(storedJob->data(), QIODevice::ReadOnly).readAll());
    

    {

    Note
    Again, for simplicity's sake, this tutorial only saves text files to local disk. When you open a remote file for viewing and try to save it, the program will behave as if you were calling Save As on a completely new file.


    Make, Install, and Run

    CMakeLists.txt

    cmake_minimum_required(VERSION 3.0)
    
    project (tutorial4)
    
    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 NO_POLICY_SCOPE)
    include(FeatureSummary)
    
    find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS 
        Core    # QCommandLineParser, QStringLiteral, QSaveFile, QTextStream, QByteArray
        Widgets # QApplication, QAction, QFileDialog
    )
    
    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)
    
    add_executable(tutorial4 ${tutorial4_SRCS})
    
    target_link_libraries(tutorial4
        Qt5::Widgets
        KF5::CoreAddons
        KF5::I18n
        KF5::XmlGui
        KF5::TextWidgets
        KF5::ConfigWidgets
        KF5::WidgetsAddons
        KF5::KIOCore
    )
    
    install(TARGETS tutorial4  ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
    
    install(FILES tutorial4ui.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/tutorial4)
    

    Since we are now using the KIO library, we must tell CMake to link against it. We do this by passing KIO to the find_package() function and KF5::KIOCore to 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
    XDG_DATA_DIRS=$HOME/share:$XDG_DATA_DIRS $HOME/bin/tutorial4
    

    Moving On

    Now you can move on to the command line arguments tutorial.