Development/Tutorials/Saving and loading (es)

From KDE TechBase


Cargar y guardar archivos
Serie   Tutorial para principiantes
Requisitos previos   Tutorial 3 - KActions
Siguiente   Tutorial 5 - Usar KCmdLineArgs
Lectura avanzada   KIO::NetAccess QFile

Resumen

Ahora que tenemos una interfaz básica del editor de textos, es hora de añadirle funcionalidad. Básicamente, un editor de textos debe abrir archivos desde disco, guardar archivos que has creado/editado y crear nuevos archivos.

KDE proporciona algunas clases para trabajar con archivos que hacen la vida mucho mas fácil a los desarrolladores. La biblioteca KIO te permite fácilmente tener acceso a archivos a través de protocolos de red transparentes, así como mediante cuadros de dialogo estándar.

El código

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) 2008 Developer") );
  KCmdLineArgs::init( argc, argv, &aboutData );
  KApplication app;
 
  MainWindow* window = new MainWindow();
  window->show();
  return app.exec();
}

main.cpp no ha cambiado desde el tutorial 3, excepto el cambio de cualquier referencia de tutorial 3 a tutorial 4.

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <KXmlGuiWindow>
#include <KTextEdit>

class MainWindow : public KXmlGuiWindow
{
	Q_OBJECT //nuevo respecto al tutorial3

	public:
		MainWindow(QWidget *parent=0);

	private:
		KTextEdit* textArea;
		void setupActions();
		QString fileName; //nuevo

	private slots: //nuevo
		void newFile(); //nuevo
		void openFile(); //nuevo
		void saveFile(); //nuevo
		void saveFileAs(); //nuevo
		void saveFileAs(const QString &outputFileName); //nuevo 
};

#endif

Como queremos añadir la posibilidad de cargar y guardar archivos, debemos añadir las funciones que realizarán el trabajo. Ya que invocaremos a las funciones mediante el mecanismo de Qt signal/slot, debemos especificar que dichas funciones son slots, por lo que lo hacemos en la linea 19. Como estamos usando slots en el archivo cabecera, debemos también añadir la macro Q_OBJECT.

También queremos llevar un registro del nombre del archivo abierto actual, por lo que declaramos QString fileName.

mainwindow.cpp

#include "mainwindow.h"

#include <KApplication>
#include <KAction>
#include <KLocale>
#include <KActionCollection>
#include <KStandardAction>
#include <KFileDialog> //nuevo
#include <KMessageBox> //nuevo
#include <KIO/NetAccess> //nuevo
#include <KSaveFile> //nuevo
#include <QTextStream> //nuevo
 
MainWindow::MainWindow(QWidget *parent)
	: KXmlGuiWindow(parent),
		fileName(QString()) //nuevo
{
	textArea = new KTextEdit;
	setCentralWidget(textArea);
 
	setupActions();
}
 
void MainWindow::setupActions()
{
	KAction* clearAction = new KAction(this);
	clearAction->setText(i18n("Limpiar"));
	clearAction->setIcon(KIcon("document-new"));
	clearAction->setShortcut(Qt::CTRL + Qt::Key_W);
	actionCollection()->addAction("limpiar", clearAction);
	connect(clearAction, SIGNAL(triggered(bool)), 
			textArea, SLOT(clear()));

	KStandardAction::quit(kapp, SLOT(quit()), 
						actionCollection());

	KStandardAction::open(this, SLOT(openFile()),
						actionCollection()); //nuevo

	KStandardAction::save(this, SLOT(saveFile()),
						actionCollection()); //nuevo

	KStandardAction::saveAs(this, SLOT(saveFileAs()),
						actionCollection()); //nuevo

	KStandardAction::openNew(this, SLOT(newFile()),
						actionCollection()); //nuevo

	setupGUI();
}

//Nuevo de aqui en adelante

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="limpiar" />
    </Menu>
  </MenuBar>

  <ToolBar name="mainToolBar" >
    <text>Main Toolbar</text>
    <Action name="limpiar" />
  </ToolBar>

</gui>

Es idéntico a tutorial3ui.rc del tutorial 3 excepto name, que ha cambiado a 'tutorial4'. No necesitamos añadir ninguna información de las KStandardAction ya que la posición de esas acciones las maneja KDE automaticamente.

Explicación

Bien, ahora vamos a implementar el código que realizará el trabajo de cargar y guardar. Esto se lleva a cabo en mainwindow.cpp

Lo primero que hacemos es añadir

fileName(QString())

al constructor de MainWindow en la linea 16. Esto asegura que fileName esté vacio desde el principio.

Añadir las acciones

La primera cosa que vamos a hacer es proporcionar al usuario la interfaz para decirle a la aplicación que cargue o guarde un archivo. Como ya hicimos con la acción quit en el tutorial 3, usaremos KStandardActions. En las lineas 37-47 añadimos las acciones de la misma manera que la acción quit. Para cada una de ellas, la conectamos que el slot apropiado que hemos declarado en el archivo cabecera.

Crear un nuevo documento

La primera función que hemos definido es newFile().

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

Con fileName.clear() ponemos la QString como "vacia" para reflejar el hecho de que el documento aún no está presente en disco. A continuación, textArea->clear() limpia el area de texto usando la misma función que conectaba con la KAction clear en el tutorial 3.

Guardar un archivo

saveFileAs(QString)

Ahora vamos a escribir nuestro primer código que maneje archivos. Vamos a implementar una función que guardará el contenido del área de texto en un archivo con un nombre pasado como parámetro. KDE proporciona una clase llamada KSaveFile para guardar archivos de manera segura, que hereda de la clase de Qt QFile.

El prototipo de la función es:

void MainWindow::saveFileAs(const QString &outputFileName)

A continuación creamos nuestro objeto KSaveFile y lo abrimos con:

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

Ahora que tenemos nuestro "file" para escribir, necesitamos convertir el texto del area de texto a un formato que pueda ser escrito en el archivo. Para hacer esto, creamos un QByteArray y lo rellenamos con la versión en texto plano de lo que haya en el area de texto:

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

Ahora que tenemos nuestro QByteArray, lo usamos para escribir el archivo con KSaveFile::write(). Si estuvieramos usando un QFile, los cambios se reflejarían inmediatamente. Sin embargo, si se ha producido un problema parcial en la escritura, el archivo podría estar corrupto. Por esta razón, KSaveFile escribe primero en un archivo temporal y luego, en la llamada KSaveFile::Finalize, refleja los cambios en el archivo actual.

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

Por último, establecemos que fileName apunte al nombre del archivo que acabamos de guardar.

fileName = outputFileName;

saveFileAs()

Esta es la función a la que está conectado el slot saveAs. Simplemente llama a la función genérica saveFileAs(QString) pasandole el nombre del fichero devuelto por KFileDialog::getSaveFileName().

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

Este es nuestro primer uso de la biblioteca KIO. La clase KFileDialog proporciona diferentes funciones estáticas para mostrar cuadros de dialgo estandar, usados en todas las aplicaciones de KDE. La llamada a KFileDialog::getSaveFileName() mostrará un diálogo donde el usuario puede seleccionar en nombre del archivo a guardar o elegir un nuevo nombre. La función devuelve el nombre del archivo, con el cual luego llamamos a saveFileAs(QString).

saveFile()

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

No hay nada destacable o nuevo en esta función, solamente la lógica para decidir si se muestra el dialogo para guardar el archivo. Si fileName no está vacio, el archivo es guardado como fileName. Si lo está, se muestra el diálogo para permitir al usuario elegir el nombre del archivo.

Cargar un archivo

Por último, el tutorial 4 debe ser capaz de cargar archivos desde disco. El código para llevarlo a cabo se encuentra en la función MainWindow::openFile().

Primero debemos preguntar al usuario por en nombre del archivo que desea abrir. Esto lo hacemos usando otra de las funciones de KFileDialog, getOpenFileName():

QString fileNameFromDialog = KFileDialog::getOpenFileName();

Luego usamos la biblioteca KIO para recuperar nuestro archivo. Esto nos permite abrir el archivo con QFile incluso si está almacenado en una ubicación remota, como un sitio FTP. Hacemos la siguiente llamada a la función download() de la clase NetAccess:

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

El primer argumento es el nombre del archivo que queremos descargar. El segundo es un QString el cual, una vez se haya completado la descarga, contendrá la localización de la copia temporal del archivo. Es con tmpFile con quien trabajaremos a partir de ahora.

La función devuelve true o false dependiendo si la transferencia tuvo exito. Si falló, mostraremos un mensaje informando del error:

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

En caso contrario, continuamos con la apertura del archivo.

Creamos un QFile pasando a su constructor el archivo temporal creado por NetAccess::download() y lo abrirmos en modo de solo lectura:

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

Para mostrar el contenido del archivo debemos usar un QTextStream. Creamos uno pasandole el contenido de nuestro archivo a su constructor, y llamamos a la función de QFile readAll() para obtener el texto del archivo. Luego este texto se le pasa a la función setPlainText() de nuestra área de texto.

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

A continuación almacenamos la ruta de acceso del archivo que acabamos de abrir:

fileName = fileNameFromDialog;

y por último, eliminamos el archivo temporal que fué creado por NetAccess::download():

KIO::NetAccess::removeTempFile(tmpFile);

Make, instalar y ejecutar

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)

Como ahora estamos usando la biblioteca KIO, debemos decirselo a CMAKE para que la enlace. Hacemos esto pasándole ${KDE4_KIO_LIBS} a la función target_link_libraries()

Con este archivo, puede construirse y ejecutar el tutorial de la misma forma que el tutorial 3. Para mas información, ve al tutorial 3.

mkdir build && cd build
cmake .. -DCMAKE_INSTALL_PREFIX=$HOME
make install
$HOME/bin/tutorial4

Avanzando

Ahora puedes continuar con el Tutorial 5 - KCmdLineArgs