Arriba

Un Editor Sencillo De Documentos

Hasta ahora no hemos hecho nada útil, sino aprender a colocar los menúes y barra de herramientas en nuestra aplicación. En este artículo tampoco vamos a hacer algo muy útil, solo vamos a ver cómo se usan las clases de KDE para navegar por nuestros archivos, cómo mostrar un archivo de texto en pantalla y cómo guardarlo si se han hecho modificaciones. En otras palabras, un editor de texto extremadamente rudimentario, solo para mostrar el uso de algunas clases de KDE que necesitaremos más adelante y el funcionamiento de los slots.

Las clases encargadas de manejar todo lo referente a entrada y salida se encuentra en la biblioteca libkio. En nuestro caso usaremos la clase KFileDialog encargada de mostrar un navegador de archivos para permitir al usuario seleccionar un archivo; una vez seleccionado guardamos su nombre y ubicación en la variable Archivo del tipo QString. Ahora que tenemos el nombre y ubicación del archivo, lo abrimos usando la clase QFile y lo mostramos en un objeto de la clase KTextEdit. Para leer el archivo hacemos uso de la clase QTextStream y asignamos el stream al texto del KTextEdit. Todo ésto lo realiza el slot encargado de procesar la señal emitida por la acción Abrir. La clase QTextStream tiene un funcionamiento parecido a la clase stringstream de C++ mientras que la clase QString es derivada de la clase string, ambas pertenecientes a la STL. Si el lector quiere comprender un poco su funcionamiento, se recomienda leer este enlace con la ayuda para estas clases.

Para guardar el archivo usamos un procedimiento inverso. Añadimos una nueva acción, llamada actGuardar usando la clase KStdAction y creamos el slot para procesar su señal cuando se haga clic en dicha acción; llamemos al slot slotGuardar. Luego se redirecciona el texto del KTextEdit al QTextStream para luego escribirlo en el archivo de salida. Ahora tenemos un sencillo editor de texto plano, pero que se carga muy rápido. Nuestro código quedaría ahora así:

Archivo KMiMainWindow.h
#include <kmainwindow.h> class KAction; class KTextEdit; class QString; class KMiMainWindow : public KMainWindow { Q_OBJECT public: KMiMainWindow(QWidget *parent = 0, const char *name = 0); private: // Variables privadas KAction *actAbrir; KAction *actCopiar; KAction *actCortar; KAction *actGuardar; KAction *actPegar; KAction *actSalir; KTextEdit *txtEditor; QString Archivo; // Funciones Privadas void CrearAcciones(void); void CrearMenu(void); void CrearToolbar(void); private slots: void slotAbrir(void); void slotCopiar(void); void slotCortar(void); void slotGuardar(void); void slotPegar(void); void slotSalir(void); };
Archivo KMiMainWindow.cpp
#include "KMiMainWindow.h" #include <kaction.h> #include <kmenubar.h> #include <kmessagebox.h> #include <kpopupmenu.h> #include <kstdaction.h> #include <ktoolbar.h> // Encabezados añadidos #include <kfiledialog.h> #include <ktextedit.h> #include <qfile.h> #include <qtextstream.h> /****************************************************************************** * Constructor de la clase. * ******************************************************************************/ KMiMainWindow::KMiMainWindow(QWidget *parent, const char *name) : KMainWindow(parent, name) { setGeometry(0, 0, 400, 400); setCaption("MiMainWindow"); txtEditor = new KTextEdit(this, "txtEditor"); setCentralWidget(txtEditor); CrearAcciones(); CrearMenu(); CrearToolbar(); } /****************************************************************************** * Procedimiento encargado de crear las acciones que usaremos en el programa * ******************************************************************************/ void KMiMainWindow::CrearAcciones(void) { actAbrir = KStdAction::open (this, SLOT(slotAbrir()), actionCollection()); actCopiar = KStdAction::copy (this, SLOT(slotCopiar()), actionCollection()); actCortar = KStdAction::cut (this, SLOT(slotCortar()), actionCollection()); actGuardar = KStdAction::save (this, SLOT(slotGuardar()), actionCollection()); actPegar = KStdAction::paste(this, SLOT(slotPegar()), actionCollection()); actSalir = KStdAction::quit (this, SLOT(slotSalir()), actionCollection()); } /****************************************************************************** * Procedimiento para crear el menú y añadir las acciones * ******************************************************************************/ void KMiMainWindow::CrearMenu(void) { KMenuBar *mb = new KMenuBar(this, "MenuBar"); KPopupMenu *p = new KPopupMenu(this, "PopupArchivo"); actAbrir->plug(p); actGuardar->plug(p); p->insertSeparator(); actSalir->plug(p); mb->insertItem("&Archivo", p); p = new KPopupMenu(this, "PopupEdicion"); actCopiar->plug(p); actCortar->plug(p); actPegar->plug(p); mb->insertItem("&Edición", p); } /****************************************************************************** * Procedimiento para crear la barra de herramientas y añadir las acciones. * ******************************************************************************/ void KMiMainWindow::CrearToolbar(void) { KToolBar *tb = new KToolBar(this, QMainWindow::Top, false, "Toolbar", true, false); actAbrir->plug(tb); actGuardar->plug(tb); tb->insertLineSeparator(); actCopiar->plug(tb); actCortar->plug(tb); actPegar->plug(tb); tb->insertLineSeparator(); actSalir->plug(tb); } /****************************************************************************** * SLOT para procesar la acción Abrir * ******************************************************************************/ void KMiMainWindow::slotAbrir(void) { // Primero obtenemos el nombre del archivo a abrir. Archivo = KFileDialog::getOpenFileName(); QFile ArchEntr(Archivo); // Lo abrimos en modo lectura if (ArchEntr.open(IO_ReadOnly)) { // Lo leemos QTextStream ts(&ArchEntr); // Llenamos el txtEditor con el contenido del archivo txtEditor->setText(ts.read()); // Actualizamos el título de la ventana setCaption("MiMainWindow - " + Archivo); } } /****************************************************************************** * SLOT para procesar la acción Copiar * ******************************************************************************/ void KMiMainWindow::slotCopiar(void) { txtEditor->copy(); } /****************************************************************************** * SLOT para procesar la acción Cortar * ******************************************************************************/ void KMiMainWindow::slotCortar(void) { txtEditor->cut(); } /****************************************************************************** * SLOT para procesar la acción Guardar * ******************************************************************************/ void KMiMainWindow::slotGuardar(void) { // Abrimos el archivo en modo escritura QFile ArchSal(Archivo); if (ArchSal.open(IO_WriteOnly)) { // Asignamos el stream de salida al archivo QTextStream ts(&ArchSal); // Guardamos en el archivo el contenido de txtEditor ts << txtEditor->text(); } } /****************************************************************************** * SLOT para procesar la acción Pegar * ******************************************************************************/ void KMiMainWindow::slotPegar(void) { txtEditor->paste(); } /****************************************************************************** * SLOT para procesar la acción Salir * ******************************************************************************/ void KMiMainWindow::slotSalir(void) { close(); }

Para poder compilar nuestro código debemos modificar nuestro archivo de proyecto para que se incluya la biblioteca libkio donde se encuentra la función getOpenFileName. Aprovechamos para modificar otras cositas más que le darán un poco de orden a nuestro proyecto, pues ahora le indicamos dónde se colocan los archivos .moc, .o, .h y .cpp. Ahora nuestro archivo de proyecto tiene el siguiente contenido:

# Opciones de compilación
INCLUDEPATH = /usr/kde/3.5/include/ ./include
LIBS        = -lkdeui -lkio -L/usr/kde/3.5/lib
TEMPLATE    = app
TARGET      = kaplicacion
DEPENDPATH  = .
# Archivos con código fuente
HEADERS    += include/KMiMainWindow.h
SOURCES    += src/KMiMainWindow.cpp               src/kapp.cpp
# Directorios de destino
DESTDIR     = bin
MOC_DIR     = moc
OBJECTS_DIR = obj

Esta vez para compilar y ejecutar nuestra aplicación escribimos:

qmake && make && bin/kaplicacion

Con nuestro nuevo archivo de proyecto, la estructura de directorio de los archivos de nuestro proyecto debe quedar así una vez compilado:

.
|-- Makefile
|-- bin
|   `-- kaplicacion
|-- include
|   `-- KMiMainWindow.h
|-- kapp.pro
|-- moc
|   `-- moc_KMiMainWindow.cpp
|-- obj
|   |-- KMiMainWindow.o
|   |-- kapp.o
|   `-- moc_KMiMainWindow.o
|-- src
|   |-- KMiMainWindow.cpp
|   `-- kapp.cpp

Es de hacer notar que para nuestro programa no hace falta tener los slots slotCopiar, slotCortar y slotPegar ya que la clase QTextEdit, de la cual deriva KTextEdit, tiene los slots para manejar esas señales: copy(), cut() y paste(), que son llamados dentro de nuestros slots, quedando nuestro código más simple al eliminar las funciones mencionadas anteriormente tanto del archivo .h como del archivo .cpp y modificando lo siguiente en nuestro archivo KMiMainWindow.cpp:

actCopiar  = KStdAction::copy (txtEditor, SLOT(copy()),  actionCollection());
actCortar  = KStdAction::cut  (txtEditor, SLOT(cut()),   actionCollection());
actPegar   = KStdAction::paste(txtEditor, SLOT(paste()), actionCollection());

Un screenshot de la aplicación se muestra a continuación:

Programa MiMainWindow compilado y corriendo como un bloc de notas simple.

4 comentarios:

  1. Anónimo

    Hola, realmente interesante. Podías incluirle una función de impresión. Te paso una aproximación extraida de los ejemplos de ayuda de la librería Qt.

    QPrinter printer;
    QPainter p( &printer );

    // Comprobamos que tenemos un dispositivo válido para imprimir
    if ( !p.device() ) return;

    // Ponemos el tipo y el tamaño de la fuente de impresión
    p.setFont( txtEditor->font() );

    // Necesario para coger width y height, ancho y alto, de la impresión
    QPaintDeviceMetrics metrics( &printer );
    int margin = 50; // Margen de impresión

    // Rectángulo de impresión: izquierda, arriba, ancho/derecha y largo/abajo
    // Un rectángulo con un margen igual en los cuatro lados
    QRect view( margin, margin, metrics.width() - 2*margin, metrics.height() - 2*margin );

    // Ponemos medidas cuadro texto, widget QTextEdit, iguales al rectángulo de impresión
    txtEditor->setMinimumWidth( view.width() );
    txtEditor->setMaximumWidth( view.width() );

    // Idem con el widget QMainWindow
    this->setMinimumWidth( view.width() + 20 );
    this->setMaximumWidth( view.width() + 20 );

    // Ahora imprimimos el texto
    QSimpleRichText richText( txtEditor->text(),
    txtEditor->font(),
    txtEditor->context(),
    txtEditor->styleSheet(),
    txtEditor->mimeSourceFactory(),
    view.height() );
    richText.setWidth( &p, view.width() );
    int page = 1;
    do{
    richText.draw( &p, margin, margin, view, colorGroup() );
    view.moveBy( 0, view.height() );
    p.translate( 0 , -view.height() );
    /* Imprimimos el número de página en la parte inferior central
    Posiciones de impresión X, posición vertical, e Y, posición horizontal, y texto a imprimir, el número de
    página en éste caso */
    p.drawText( view.right() / 2 - p.fontMetrics().width( QString::number(page) ),
    view.bottom() + p.fontMetrics().ascent() + 5,
    QString::number(page));
    if ( view.top() - margin >= richText.height() ) break;
    printer.newPage();
    page++;
    }while( true );

    PD. Éste ejemplo forma parte de un proyecto de programa que estoy haciendo y que en realidad sirve para mostrar una vista previa de cómo quedaría la impresión, de ahí la modificación del tamaño del widget de texto y de la ventana principal.

    Saludos. Ubaloie. ubaloie@hotmail.com

  2. Gracias por tu comentario, muy útil tu aporte. No incluí la función de impresión en el programa porque tendría que haber explicado para qué es el QPainter, QPrinter, QPaintDeviceMetrics, etc. Mi idea con este blog es ir incluyendo objetos de a poquito aunque me tome 1.000 artículos. E ir tratando de colocar en el título algo que indique lo que se está añadiendo.

  3. Acabo de descubrir este blog y es sencillamente lo que andaba buscando.

    Sencillo, muy paso a paso... genial.

    Muchas gracias y, como se diría en inglé, "keep up the good work". ;)

  4. Hombre, claro que hay que aplicarle algún estilo. Una cosa es como lo veas en tu IDE y otra como lo vaya a mostrar el navegador, hay que usar CSS para que se vea así. Si quieres curiosea el código fuente de la página para que te enteres ;)