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.
Arriba

Añadiendo una barra de herramientas

La barra de herramientas es un control que además de darle vistosidad a nuestro programa le permite al usuario ejecutar las acciones más usada de manera rápida. KDE facilita mucho su codificación al permitir incrustar una acción usada en el menú en una barra de herramientas con solo usar una línea de código, llamando al método plug(). En nuestro caso, usaremos las cinco acciones ya creadas y las colocaremos también en la barra de herramientas, también modificaremos el procedimiento void slotSalir(void) para que termine la aplicación al hacer clic en el menú salir o en la barra de herramientas. Esto a manera de ejemplo para que se entienda el uso de los slots, pues el programador decidirá qué código colocar en el slot según las necesidades de su programa.

A partir de ahora cambiaremos un poco el código hecho para tratar de ser más ordenado con el código pues a medida que un programa o módulo va creciendo, el desorden es causa de errores. Por eso se ha creado un procedimiento para cada cosa: crear las acciones, crear los menúes y crear la barra de herramientas. También se tratará de comentar más el código para que se entiendan algunas cosas incluidas en el mismo; lo que no se entienda y no aparezca explicado en el artículo deberá ser consultado en las documentación de KDE, pues no es el objetivo de este blog sustituir dicha documentación sino explicar como usarla de manera sencilla. Para usar la barra de herramientas debemos añadir el encabezado ktoolbar.h. Aquí solo se muestra el caso más sencillo: añadir la barra, colocar una acción y usar el slot, suficiente para la mayoría de los programas. Como se dijo, para otras cosas se debe revisar la documentación de la API de KDE, referenciada en la sección de enlaces de este blog. Los archivos modificados quedarían de la siguiente manera:

Archivo KMiMainWindow.h

#include <kmainwindow.h> class KAction; 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 *actPegar; KAction *actSalir; // Funciones Privadas void CrearAcciones(void); void CrearMenu(void); void CrearToolbar(void); private slots: void slotAbrir(void); void slotCopiar(void); void slotCortar(void); void slotPegar(void); void slotSalir(void); };

Ahora hemos tenido que modificar la declaración de la clase para incluir las acciones y los procedimientos añadidos. Antes no hizo falta porque se procesaban directamente; ahora una acción va a ser incluida en dos objetos, por lo que se necesita almacenar su dirección al ser creada para luego poder llamar al procedimiento plug() de la clase, que se encarga de añadir la acción al objeto deseado.

Archivo KMiMainWindow.cpp

#include "KMiMainWindow.h" #include <kaction.h> #include <kmenubar.h> #include <kmessagebox.h> #include <kpopupmenu.h> #include <kstdaction.h> // Encabezados añadidos #include <ktoolbar.h> KMiMainWindow::KMiMainWindow(QWidget *parent, const char *name) : KMainWindow(parent, name) { setGeometry(0, 0, 400, 400); setCaption("MiMainWindow"); 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()); 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) { // Creamos la barra de menú KMenuBar *mb = new KMenuBar(this, "MenuBar"); // Creamos el menú Archivo, lo llenamos y lo añadimos a la barra de menú KPopupMenu *p = new KPopupMenu(this, "PopupArchivo"); actAbrir->plug(p); p->insertSeparator(); actSalir->plug(p); mb->insertItem("&Archivo", p); // Creamos el menú Edición, lo llenamos y lo añadimos a la barra de menú 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); 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) { KMessageBox::information(this, "Se ha presionado Abrir", "KMiMainWindow"); } // SLOT para procesar la acción Copiar void KMiMainWindow::slotCopiar(void) { KMessageBox::information(this, "Se ha presionado Copiar", "KMiMainWindow"); } // SLOT para procesar la acción Cortar void KMiMainWindow::slotCortar(void) { KMessageBox::information(this, "Se ha presionado Cortar", "KMiMainWindow"); } // SLOT para procesar la acción Pegar void KMiMainWindow::slotPegar(void) { KMessageBox::information(this, "Se ha presionado Pegar", "KMiMainWindow"); } // SLOT para procesar la acción Salir void KMiMainWindow::slotSalir(void) { close(); }

Aquí vemos como el constructor de la clase ha sido modificado para incluir tres procedimientos que llevan a cabo una tarea específica, haciendo más estructurado nuestro programa y más fácil de ser modificado. Cuando se crea la barra de herramientas se debe indicar si vas a estar acoplada en la parte superior, inferior, derecha o izquierda. En nuestro caso estará en la parte superior, lo cual se le indica al constructor con la constante QMainWindow::Top. Los dos últimos parámetros pasados al constructor de KToolBar indican si se debe respetar la configuración global para los iconos y textos de las barras de herramienta, y para indicar si se debe obtener la configuración de la barra de herramientas de un archivo de configuración específico de nuestra aplicación KDE creada.

Ahora lo compilamos y lo ejecutamos como de costumbre:

make && ./kaplicacion

A continuación el screenshot de nuestra aplicación, ahora con una barra herramientas añadida.

Programa MiMainWindow compilado y corriendo con una barra de herramientas añadida
Arriba

Mejorando los menúes

KDE nos ofrece una clase llamada KStdAction para las acciones que son usadas por la gran mayoría de los programas; esta clase permite crear cada acción con su icono asociado según el tema escogido y la combinación de teclas usada como acelerador. Usando esta clase nos permite tener una unificación en el diseño de la interfaz de usuario entre diversos programas. Se recomienda que cada vez que necesitemos una acción para algo estándar se emplee esta clase. Ahora modificaremos el código para hacer uso de esta facilidad que nos brindan las clases de KDE. Recordando, tendremos los menúes Archivo {Abrir, Salir} y Edición {Copiar, Cortar, Pegar}. Para crearlos usamos los métodos estáticos de la clase dependiendo de la acción que querramos usar; estos métodos usan como mínimo dos parámetros: el objeto recibidor y el SLOT que recibirá el evento cuando se haga click en dicha acción. KDE usa el sistema SIGNAL/SLOT ideado por Qt. Un slot viene siendo un procedimiento que recibirá la señal emitida por un objeto. En el caso de un menú, cuando se hace clic se genera una señal y entonces se llama al slot asociado. El código modificado para mejorar cada menú sería el siguiente:

Archivo KMiMainWindow.cpp
#include "KMiMainWindow.h" #include <kmenubar.h> #include <kpopupmenu.h> // Encabezados añadidos #include <kaction.h> #include <kmessagebox.h> #include <kstdaction.h> KMiMainWindow::KMiMainWindow(QWidget *parent, const char *name) : KMainWindow(parent, name) { setGeometry(0, 0, 400, 400); setCaption("MiMainWindow"); popArchivo = new KPopupMenu(this, "PopupArchivo"); KStdAction::open (this, SLOT(slotAbrir()), 0)->plug(popArchivo); popArchivo->insertSeparator(); KStdAction::quit (this, SLOT(slotSalir()), 0)->plug(popArchivo); popEdicion = new KPopupMenu(this, "PopupEdicion"); KStdAction::cut (this, SLOT(slotCortar()), 0)->plug(popEdicion); KStdAction::copy (this, SLOT(slotCopiar()), 0)->plug(popEdicion); KStdAction::paste(this, SLOT(slotPegar()), 0)->plug(popEdicion); mb = new KMenuBar(this, "MenuBar"); mb->insertItem("&Archivo", popArchivo); mb->insertItem("&Edición", popEdicion); } // SLOT para procesar la acción Abrir void KMiMainWindow::slotAbrir(void) { KMessageBox::information(this, "Se ha presionado Abrir", "KMiMainWindow"); } // SLOT para procesar la acción Copiar void KMiMainWindow::slotCopiar(void) { KMessageBox::information(this, "Se ha presionado Copiar", "KMiMainWindow"); } // SLOT para procesar la acción Cortar void KMiMainWindow::slotCortar(void) { KMessageBox::information(this, "Se ha presionado Cortar", "KMiMainWindow"); } // SLOT para procesar la acción Pegar void KMiMainWindow::slotPegar(void) { KMessageBox::information(this, "Se ha presionado Pegar", "KMiMainWindow"); } // SLOT para procesar la acción Salir void KMiMainWindow::slotSalir(void) { KMessageBox::information(this, "Se ha presionado Salir", "KMiMainWindow"); }
Archivo KMiMainWindow.h
#include <kmainwindow.h> class KMenuBar; class KPopupMenu; class KMiMainWindow : public KMainWindow { Q_OBJECT public: KMiMainWindow(QWidget *parent = 0, const char *name = 0); private: KMenuBar *mb; KPopupMenu *popArchivo; KPopupMenu *popEdicion; private slots: void slotAbrir(void); void slotCopiar(void); void slotCortar(void); void slotPegar(void); void slotSalir(void); };
Archivo kapp.cpp
#include <kapp.h> #include "KMiMainWindow.h" // Encabezados añadidos #include <klocale.h> int main(int argc, char **argv ){ // Para traducir las etiquetas de los menúes según nuestra // i18n le decimos que cargue el catálogo de kdelibs. // Sin ésto, mostrará los objetos KStdAction en inglés. KLocale::setMainCatalogue("kdelibs"); KApplication kaplicacion(argc, argv, "Esquema Básico"); KMiMainWindow *mw = new KMiMainWindow(0, "KMiMainWindow"); kaplicacion.setMainWidget(mw); mw->show(); return kaplicacion.exec(); }

Algo importante a notar en el código del archivo kapp.cpp es el uso de la clase KLocale. Una de las funcionalidades de GNU/Linux es poder portar nuestros programas a otros idiomas sin mucho esfuerzo; para ello se usan los locales y los catálogos de idiomas. Como no hemos creado ningún catálogo para nuestra aplicación, KDE mostrará los textos de los menúes en inglés, pero para poner en el idioma local basta con indicarle que cargue como catálogo principal el de kdelibs que incluye la traducción de las acciones de KStdAction. Una vez cargado dicho catálogo notamos como nuestros textos han sido traducidos a nuestro idioma sin hacer más nada. Basta con comentar esa línea y compilar el programa para notar la diferencia. Posteriormente veremos como crear los catálogos para hacer aplicaciones que puedan ser traducidas a otros idiomas.

Ahora lo compilamos y lo ejecutamos:

make && ./kaplicacion

Y obtenemos nuestra aplicación con un aspecto más KDE y parecido a otras aplicaciones desarrolladas para este entorno. Hay que recordar que la apariencia varia según el tema de iconos, de color y de decoración de ventanas que se hayan escogido.

Menú MejoradoMenú Mejorado