Development/Tutorials/Saving and loading (ru)

From KDE TechBase


Загрузка и сохранение файлов
Tutorial Series   Beginner Tutorial
Previous   Урок 3 - KActions
What's Next   Tutorial 5 - Using KCmdLineArgs
Further Reading   Tutorial: Using KIO Slaves in your Program KIO::NetAccess QFile

Введение

Мы сделали интерфейс простого текстового редактора, самое время добавить к нему чего-нибудь полезного. Самое основное в чем нуждается текстовый редактор - это загрузка файлов с диска, сохранение файлов, которые были созданы/изменены и создание новых файлов.

KDE предоставляет набор классов для работы с файлами, которые делают жизнь разработчика намного легче. Библиотека KIO предоставляет вам легкий доступ к файлам через сеть, а так же стандартные диалоги выбора файла.

Листинг кода

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 практически не изменился с урока 3.

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <KXmlGuiWindow>
#include <KTextEdit>

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); //новое
};

#endif

Если мы хотим добавить возможность загружать и сохранять файлы, мы должны добавить функции которые будут делать это. Т.к. функции будут вызваны через механизм Qt сигнал/слот мы должны обозначить, что эти функции - слоты, что мы и делаем на строке 19. Раз мы используем слоты, мы должны добавить макрос Q_OBJECT.

Так же нам нужно хранить имя открытого файла, поэтому объявляем переменную QString fileName.

mainwindow.cpp

#include "mainwindow.h"

#include <KApplication>
#include <KAction>
#include <KLocale>
#include <KActionCollection>
#include <KStandardAction>
#include <KFileDialog> //новое
#include <KMessageBox> //новое
#include <KIO/NetAccess> //новое
#include <KSaveFile> //новое
#include <QTextStream> //новое
 
MainWindow::MainWindow(QWidget *parent)
    : KXmlGuiWindow(parent),
      fileName(QString()) //новое
{
  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()); //новое
 
  KStandardAction::save(this, SLOT(saveFile()),
                        actionCollection()); //новое
 
  KStandardAction::saveAs(this, SLOT(saveFileAs()),
                        actionCollection()); //новое
 
  KStandardAction::openNew(this, SLOT(newFile()),
                        actionCollection()); //новое
 
  setupGUI();
}

//Дальше все новое

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>

Этот файл идентичен tutorial3ui.rc из урока 3, только поле name изменилось на 'tutorial4'. Нам не нужно добавлять никакую информацию о любом KStandardAction т.к. их обработкой занимается KDE автоматически.

Объяснение

Хорошо, теперь напишем код, который будет загружать и сохранять. Все это мы допишем в mainwindow.cpp

Первая штука, которую добавим

fileName(QString())

в конструктор MainWindow на строке 16. Это очищает fileName с самого начала.

Добавление действий

Первое, что мы собираемся сделать, это предоставить пользователю интерфейс, что бы он мог сказать приложению "загрузить" и "сохранить". Как действие quit в уроке 3, мы будем использовать KStandardActions. На строках с 37 по 47 мы добавляем действия тем же путем, что и действие quit. Каждое из них мы соединяем с соответствующим слотом, который мы объявили в заголовочном файле.

Создание нового документа

Первая функция, которую мы создали - newFile():

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

fileName.clear() очищает переменную fileName типа QString, что отражает тот факт, что этого документа еще нет на диске. textArea->clear() очищает область текста, используя функцию clear, которую мы подключили к KAction в уроке 3.

Сохранение файла

saveFileAs(QString)

Теперь разберем наш первый обработчик. Мы собираемся реализовать функцию, которая сохраняет содержимое текстовой области в файл имя которого передается в функцию. KDE предоставляет класс для безопасного сохранения файла именуемый KSaveFile, который является производным от Qt'шного QFile.

Прототип функции:

void MainWindow::saveFileAs(const QString &outputFileName)

Мы создаем объект KSaveFile и открываем файл:

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

Теперь у нас есть файл для записи, нам нужно отформатировать текст в текстовой области в формат, который может быть записан в файл. Для этого, мы создаем QByteArray и заполняем его обычным текстом из текстовой области:

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

Теперь у нас есть QByteArray, запишем его в файл с помощью KSaveFile::write(). Если бы мы использовали QFile, он бы моментально начал бы изменения в файле. Однако, если возникнет проблема посреди записи, файл будет поврежден. По этой причине KSaveFile сначала записывает данные во временный файл, а потом, когда вы вызываете KSaveFile::finalize() изменения попадают в текущий файл.

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

Наконец, мы записываем в переменную MainWindow fileName имя файла в который только, что сохранили:

fileName = outputFileName;

saveFileAs()

Эта функция к которой подключек слот saveAs. Она просто вызывает saveFileAs(QString) и передает ей имя файла возвращенное KFileDialog::getSaveFileName().

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

Это наше первое использование библиотеки KIO. KFileDialog предоставляет набор функций для отображения файловых диалогов, которые используют все приложения KDE. Вызов KFileDialog::getSaveFileName() отобразит диалог, где пользователь может выбрать файл в который сохранить или создать новый файл для сохранения. Функция возвращает полный путь к файлу, который передается функции saveFileAs(QString).

saveFile()

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

Тут ничего сложного или нового в этой функции, просто условие показывать или нет диалог сохранения. Если fileName не пустой, тогда файл сохраняется под именем fileName. Но если это не так, то отображаем диалог сохранения, чтобы пользователь мог выбрать файл для сохранения.

Загрузка файла

Наконец, мы не можем обойтись без загрузки фала с диска. Код для этого содержится в MainWindow::openFile().

Сначала мы должны спросить пользователя, какой файл он хочет открыть. Это делается с помощью KFileDialog, на этот раз getOpenFileName():

QString fileNameFromDialog = KFileDialog::getOpenFileName();

Затем мы используем библиотеку KIO, чтобы загрузить файл. Это позволяет нам открывать файлы с помощью QFile даже если они расположены удаленно. Мы вызываем функцию NetAccess download()

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

Первый аргумент имя файла, который вы хотите загрузить. Второй -- QString, содержащий путь ко временной копии фала. Именно с tmpFile мы будем работать.

Функция возвращает true или false в зависимости от результата передачи (успешно/ошибка). Если произошла ошибка, мы отображаем сообщение с ошибкой:

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

В противном случае, мы продолжаем открывать файл.

Мы создаем QFile работая со временным файлом созданным NetAccess::download() и открываем его в режиме только для чтения:

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

Для отображения содержимого файла, мы используем QTextStream. Мы создаем QTextStream и передаем в его конструктор содержимое фала, вызываем функцию QFile readAll(), что бы прочитать текст из файла. И вот это передается в функцию setPlainText() нашей текстовой области.

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

Затем мы сохраняем путь к только что открытому файлу:

fileName = fileNameFromDialog;

и наконец, удаляем временный файл созданный NetAccess::download():

KIO::NetAccess::removeTempFile(tmpFile);

Сборка, Установка и Запуск

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)

Теперь мы используем библиотеку KIO, мы должны сказать CMake слинковать приложение с ней. Это делается путем передачи ${KDE4_KIO_LIBS} функции target_link_libraries().

С помощью этого файла, урок может быть собран и запущен тем же путем, что и урок 3. Подробнее см. урок 3.

mkdir build && cd build
cmake .. -DCMAKE_INSTALL_PREFIX=$HOME
make install
$HOME/bin/tutorial4
Note
Измененные настройки сохраняются в вашем каталоге KDE: $HOME/.kde/share/apps/tutorial4.


Moving On

Теперь вы можете переходить к уроку KCmdLineArgs.