Development/Tutorials/Saving and loading/pt-br: Difference between revisions

From KDE TechBase
No edit summary
(Created page with "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 co...")
Line 287: Line 287:
====saveFileAs(QString)====
====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. Qt provides a class for safely saving a file called [http://doc.qt.io/qt-5/qsavefile.html QSaveFile].
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 [http://doc.qt.io/qt-5/qsavefile.html QSaveFile].


The function's prototype is
The function's prototype is

Revision as of 17:40, 19 November 2019

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.

The function's prototype is

void MainWindow::saveFileAs(const QString &outputFileName)

We then create our QSaveFile object and open it with

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

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 QSaveFile::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, QSaveFile works by first writing to a temporary file and then, when you call QSaveFile::commit() the changes are made to the actual file. commit() also closes the file.

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

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 QFileDialog::getSaveFileName().

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

QFileDialog provides a number of static functions for displaying the common file dialog that is used by all KDE applications. Calling QFileDialog::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 local storage or from a remote location like an FTP server. 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 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.