Está en la página 1de 38

El tutorial del desarrollo visual

Antonio Larrosa Jiménez

Traductor: Miguel Revilla Rodríguez


Revision 1.00.01 (2003-12-27)

Copyright © 2003 Antonio Larrosa

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU
Free Documentation License, Version 1.1 or any later version published by the Free Software
Foundation; with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A
copy of the license is included in the section entitled "GNU Free Documentation License".

Visual Tutorial es un tutorial que mostrará al lector la forma de crear aplicaciones visualmente
utilizando KDevelop y Qt Designer. Crearemos una aplicación que nos permitirá abrir una imagen y
teñirla de diferentes colores. Posteriormente cambiaremos el interfaz para permitir al usuario
seleccionar también la intensidad de un canal alfa.

Table of Contents

1. Introducción
2. Cómo descargar e iniciar KDevelop y Designer
Requisitos
Cómo ejecutar KDevelop
3. Creación de la aplicación
Creación de un nuevo proyecto
Cómo compilar lo generado por KDevelop
Comprensión de la estructura básica
4. Primeros cambios al código fuente
Eliminación de las partes que no utilizaremos
5. Uso del diseñador
Cómo añadir un archivo .ui
Uso del diseñador
Integración del diseñador con KDevelop
6. Cómo hacer que el widget generado por el diseñador funcione
Añadir nombres, slots y conexiones
Implementación de los slots
7. Eliminación del parpadeo utilizando un temporizador
8. Carga y manipulación de un mapa de pixels
Carga de un mapa de pixels
Mezcla de colores
9. Modificación del interfaz de usuario existente
Añadir el deslizador de alfa
Añadir el botón de selección de color
10. Añadir las implementaciones de los nuevos métodos
Soporte para el deslizador alfa
Soporte para el botón de color
11. Algunos cambios aquí y allá
Corrección del botón de color.
Corrección de mapa de pixels inicial
12. Añadir más características
Posibilidad de guardar el resultado
Añadir soporte para arratrar y soltar
A. Créditos y licencia

Chapter 1. Introducción
Visual Tutorial es un tutorial que mostrará al lector la forma de crear aplicaciones visualmente
utilizando KDevelop y Qt Designer. Crearemos una aplicación que nos permitirá abrir una imagen y
teñirla de diferentes colores. Posteriormente cambiaremos el interfaz para permitir al usuario
seleccionar también la intensidad de un canal alfa.

Este tutorial complementa mi otro trabajo sobre desarrollo en KDE, que puede encontrar en
developer.kde.org. Además hay otro excelente tutorial sobre KDevelop y Designer, realizado por
Anne-Marie Mahfouf que se encuentra en women.kde.org. Sobre el orden en el que se deberían leer
los tutoriales mencionados y estos otros, yo únicamente diré una cosa: cuantos más lea, mejor :), y si
también consulta la documentación de las clases que utiliza, mejor que mejor.

Si desea descargar el código de este tutorial junto a la documentación y demás elementos, puede
hacerlo en la página web. Puede seguir este tutorial haciendolo todo usted mismo de la forma que se
indica, o puede descargar cada paso del tutorial de esa página. En el segundo caso, obtendrá cada paso
en un directorio diferente y tendrá que abrir diferentes proyectos de KDevelop para cada uno de ellos.

¿Listo? Vamos a empezar.

Chapter 2. Cómo descargar e iniciar KDevelop y Designer


Requisitos
En este tutorial necesitaremos, además del compilador, alguna otra aplicación como KDevelop o
Qt/Designer. Se dará cuenta de que KDevelop tiene una lista de requisitos y/o aplicaciones
recomendadas, así es que posible que también tenga que instalar esas.

Puede descargar KDevelop de www.kdevelop.org y muy probablemente su distribución incluya


Qt/Designer en algún paquete, normalmente denominado qt-devel, qt3-devel o qt-designer. Si no lo
encuentra, puede descargarlo de trolltech.com

Necesitará KDE 3.x, lo que significa que también le hará falta Qt 3.x. La última versión estable de
KDevelop en el momento de redacción de este texto es la 2.1.5, mientras que la versión de desarrollo
de KDevelop 3.0 (con nombre clave gideon) se encuentra en estado alpha3. Este documento trata
mayormente sobre gideon, pero también se puede utilizar como referencia en el manejo de KDevelop
2.1.5.
Cómo ejecutar KDevelop
Una vez que haya instalado todos los paquetes, puede iniciar gideon (kdevelop) desde el menú K o
desde una ventana de konsole. Es necesario configurar elementos básicos (como la ubicación de la
documentación, etc.). Para hacerlo, basta con que siga las instrucciones en pantalla.

Una vez terminada la configuración, podrá ver una ventana similar a esta:

Ahora ya estamos listos para desarrollar una aplicación.

Chapter 3. Creación de la aplicación


Creación de un nuevo proyecto
Ahora vamos a crear un nuevo proyecto para nuestra aplicación. Para hacerlo, seleccionamos
Proyecto/Nuevo proyecto. Se abrirá un asistente en el que seleccionaremos las opciones del proyecto.
Podemos utilizar KDevelop para desarrollar en C++, C, Java, Python o PHP. El asistente de "nuevo
proyecto" se puede utilizar para crear una aplicación sencilla que sirve como estructura de base para la
aplicación final. Si decide crear una aplicación C++, puede generar (en el momento de redacción de
este tutorial): una aplicación de KDE, una aplicación de KDE que utilice KParts, un módulo del
Centro de control, un servicio DCOP, un salvapantallas, una extensión KHTMLPart, un esclavo KIO
(implementación de un protocolo), un módulo de KOffice, una extensión de Kate, un applet de Kicker,
una extensión del panel de navegación de Konqueror, una aplicación KDE sencilla, un módulo de
KDevelop, varios tipos de aplicaciones Qt y una aplicación para terminal basada en C++.

Si decide crear una aplicación en C, KDevelop podrá generar la estructura para una aplicación para
GameBoyAdvance, una aplicación GNOME (sí, es posible utilizar KDevelop para crear aplicaciones
para GNOME), o símplemente una aplicación para terminal basada en C.
Si prefiere utilizar Java, KDevelop podrá ayudarle con las estructuras de un proyecto de Ant, una
aplicación de KDE o una extensión de KDevelop, todas ellas con Java (incluso podrá utilizar el
depurador de aplicaciones Java integrado). Por último, también puede utilizar el asistente para crear un
simple guión de PHP, una aplicación Python Qt o un sencillo guión de Python.

Al pinchar en el botón Siguiente, se mostrarán las opciones sobre el sistema de control de versiones
que utilizaremos en el proyecto. Para simplificar nuestro ejemplo, seleccionaremos Ninguno y
continuaremos en las siguientes páginas, donde podremos cambiar la plantilla de la cabecera de los
archivos .h y .cpp. Fíjese en que KDevelop ha utilizado el año actual, su nombre y su dirección de
correo electrónico completos, por lo que probablemente no necesitará modificar nada. Al menos
nosotros no modificaremos nada, así que bastará con pinchar en el botón Finalizar.

Ahora KDevelop generará el proyecto, y estaremos listos para comenzar a desarrollar nuestra
aplicación sobre la estructura básica.

La siguiente instantánea muestra una ventana vacía de KDevelop con el interfaz IDEAL. Puede
cambiar el interfaz de usuario y poner alguno de los modos clásicos a través del menú
Preferencias/Configurar Gideon, en la sección Interfaz de usuario, pero en este tutorial utilizaremos el
interfaz IDEAL. Tenga en cuenta que las características de KDevelop son independientes del interfaz
que se utilice, así que puede seleccionar aquel en el que se encuentre más cómodo.

Cómo compilar lo generado por KDevelop


Ahora que ya está hecha una buena parte de la aplicación, vamos a compilarla. Pinche en
Construir/Construir proyecto, tras una corta espera (y en el caso de que no se produzca ningún error
debido a la falta de alguno de los paquetes necesarios), obtendrá una aplicación compilada que podrá
ejecutar por medio de Construir/Ejecutar progama, de forma similar a lo mostrado en la siguiente
instantánea.
La aplicación también tiene los menús estándar, y un díalogo de preferencias preparado para que añada
las opciones que considere oportuno. Algo parecido a esto:

Comprensión de la estructura básica


Vamos a revisar ahora el código utilizado por KDevelop. Pinche en la etiqueta Clases que podrá
encontrar a la izquierda para abrir la lista de clases de nuestro proyecto.
Puede ver que nuestro proyecto tiene 5 clases, TheTinter (que hereda de KMainWindow),
TheTinterView (que es el widget central de la aplicación donde se mostrarán nuestros
documentos), TheTinterPreferences (el diálogo de preferencias), y
TheTinterPrefPageOne y TheTinterPrefPageTwo (que son dos páginas de configuración
para diferentes opciones del diálogo de preferencias).

En primer lugar, vamos a echar un vistazo a la clase TheTinter. Esta clase es la ventana principal
de nuestra aplicación y contiene el menú, las barras de herramientas, etc. TheTinter contiene
algunos métodos interesantes que sin duda utilizaremos:

load(const KURL &url) es el método al que se llamará cuando el usuario quiera cargar un
documento. Tenga en cuenta que en KDE se recomienda utilizar la clase KURL para almacenar la
url de un documento. De esta forma, no nos preocupará si el documento es local, está en un
servidor ftp, si se accede por ssh, etc. La biblioteca KIO se ocupará de todo.

readProperties(KConfig *) y saveProperties(KConfig *) se utilizan para


cargar y guardar el estado de la aplicación (por ejemplo, los documentos cargados) cuando se
abre o se cierra la sesión de KDE. De esa forma, los usuarios podrán cerrar la sesión y, cuando
vuelvan a acceder al sistema, encontrarán la aplicación en el mismo estado en que la dejaron.

setupActions() se utiliza para crear las acciones y conectarlas con nuestros slots (si no le
resultan familiares conceptos como señales o slots, puede consultar la documentación de Qt o mi
otro tutorial). Tenga en cuenta que la mayoría de las acciones utilizadas en la aplicación de
plantilla son estándar, de forma que el método contiene código del tipo
KStdAction::open(this, SLOT(fileOpen()), actionCollection()); con el
fin de crear una acción estándar para la entrada de menú Archivo/Abrir que, al ser activada, emite
una señal que se conecta a nuestro slot fileOpen.
dragEnterEvent(QDragEnterEvent *event) se utiliza para indicarle al subsistema de
arrastrar y soltar si aceptamos o no un evento de arrastrar y soltar que el usuario está arrastrando
(todavía no soltando) sobre nuestra ventana, mientras que se llama
adropEvent(QDropEvent *event) cuando el usuario suelta algo sobre nuestra ventana,
por lo que es ahí donde debemos procesar el evento de soltar.

TheTinterView es el widget central de nuestra aplicación, es decir, el widget que se coloca debajo
de la barra de herramientas y sobre la barra de estado. En la plantilla de KDevelop, esta clase crea un
componente KPart capaz de leer documentos HTML, pero eliminaremos la mayor parte de esto en el
siguiente paso del tutorial, ya que no nos resulta necesaria tanta complejidad.

Chapter 4. Primeros cambios al código fuente


Eliminación de las partes que no utilizaremos
Lo primero que haremos será eliminar el código que no resulte necesario para nuestra aplicación. Por
ejemplo, no utilizaremos el comando de impresión, con lo que podemos quitar la linea #include
<kprinter.h> la inicialización de m_printer(0) en el constructor de TheTinter, la linea
KStdAction::print(this, SLOT(filePrint()), actionCollection()); que
añadía una acción estandar para imprimir, y la función TheTinter::filePrint, tanto del fichero
.cpp como del .h así como la variable m_printer de la misma clase y la función
TheTinterView::print .

Tampoco abriremos páginas HTML, con lo que podemos eliminar el código que busca una
componente para tal fin y la utiliza. Este código está en el constructor de TheTinterView, que será
suficiente con dejarlo con las tres primeras lineas: .
// setup our layout manager to automatically add our widgets
QHBoxLayout *top_layout = new QHBoxLayout(this);
top_layout->setAutoAdd(true);

Además, podemos eliminar la variable m_html y algunas funciones que ya no usaremos de


TheTinterView, como son signalChangeStatusbar, signalChangeCaption,
slotOnURL y slotSetTitle (recuerda eliminarlas tanto del .h como del .cpp). Al eliminar la
variable m_html, las funciones currentURL y openURL ya no compilarán, no te preocupes, pues
las vamos a reimplementar dentro de poco, simplemente déjalas vacías (o con un
return "";

en el caso de currentURL).

Como hemos eliminado las señales de TheTinterView, podemos también eliminar el código que
las conectaba en el constructor de TheTinter:
// allow the view to change the statusbar and caption
connect(m_view, SIGNAL(signalChangeStatusbar(const QString&)),
this, SLOT(changeStatusbar(const QString&)));
connect(m_view, SIGNAL(signalChangeCaption(const QString&)),
this, SLOT(changeCaption(const QString&)));

Tampoco vamos a crear un nuevo documento, con lo que podemos eliminar la creación de la acción
estandar openNew con la llamada a KStdAction::openNew igual que hicimos con print, junto
con la función fileNew tanto del .h como del .cpp .
Para terminar, podemos eliminar el siguiente trozo de código ya que como el comentario que lo
acompaña dice, no hace nada útil y está sólo para ilustrar como insertar un menú propio de nuestra
aplicación.
// this doesn’t do anything useful. it’s just here to illustrate
// how to insert a custom menu and menu item
KAction *custom = new KAction(i18n("Cus&tom Menuitem"), 0,
this, SLOT(optionsPreferences()),
actionCollection(), "custom_action");

El resultado es el paso 1, s1, del tutorial.

En primer lugar, hemos eliminado la mayor parte del constructor de TheTinterView, ya que no lo
vamos a utilizar para ver HTML. Además, hemos eliminado los métodos innecesarios de esa clase,
junto con las señales y los slots. Si posteriormente necesitamos más señales o slots, los añadiremos
individualmente.

Chapter 5. Uso del diseñador


Cómo añadir un archivo .ui
En primer lugar, añadiremos un nuevo archivo .ui (interfaz de usuario) a nuestro proyecto. Los
archivos .ui se crean con el diseñador de Trolltech para generar interfaces de usuario para nuestra
aplicación. Para hacerlo, abra la pestaña Nuevo archivo en la parte izquierda, y seleccione Un nuevo
widget. El nombre del archivo que añadiremos es centralviewbase.ui.

Se le mostrará un diálogo en el que configurar el nuevo archivo que se ha añadido al proyecto


(utilizando el Gestor de automake). Seleccionaremos Añadir nuevos archivos a nuestro destino activo,
y a continuación marcaremos la casilla de la parte inferior para utilizar siempre el destino activo sin
necesidad de más preguntas.
Esto abrirá una ventana del diseñador con un widget vacío, dispuesto para comenzar el diseño.
Uso del diseñador
En este punto tenemos que diseñar el aspecto de nuestra aplicación. Haremos que la imagen aparezca
en la parte superior de la ventana, y habrá tres deslizadores en la parte inferior, para seleccionar los
componentes rojo, verde y azul del color con el que teñiremos el mapa de pixels. Así que nuestro
primer paso será seleccionar un Pixmap label (de la caja de herramientas Display) y crearlo de forma
que cubra la parte superior de nuestra ventana.

A continuación, añadiremos, en la parte de abajo, tres deslizadores y sus correspondientes etiquetas.


Las etiquetas contendrán el texto Rojo:, Verde: y Azul:.

En el caso de que se esté preguntando por qué cada etiqueta aparece en el color indicado por su texto,
de diré que (obviamente) no es algo automático. Dejo como ejercicio para lector la búsqueda en los
menús del diálogo Edit Text de la forma de cambiar el color. Además, con la intención de embellecer
un poco el widget, he añadido un Marco (en la pestaña Containers) que contiene los deslizadores y las
etiquetas de texto.
Como ya habrá notado, los elementos no han quedado correctamente alineados. Aunque usted crea que
puede hacerlo mejor que yo, compruebe el aspecto del resultado mediante la opción Preview Form en
el menú Preview y redimensione la ventana. Los elementos dentro de nuestro widget están colocados
en una posición fija y con un tamaño fijo. Está claro que esa no es nuestra intención, así que
añadiremos una disposición que se ocupará de la ubicación y el tamaño correctos de los elementos.

En primer lugar, pinche en el marco que contiene los deslizadores y las etiquetas, y pinche en el botón
Lay out in a grid (el que tiene una rejilla verde de 3x3). De esta forma se creará una disposición de
rejilla para el contenedor seleccionado actualmente. El resultado es:

Intente redimensionar el marco contenedor y comprobará que ahora los elementos tienen siempre su
posición y tamaño correctos.

Bien, la parte de los botones ya está prácticamente acabada, ahora debemos darnos cuenta de que en el
nivel más alto sólo hay dos elementos, el widget gráfico y el marco contenedor. Estos dos elementos
deben estar dispuestos verticalmente, así que pincharemos en una zona vacía del widget principal, y
seleccionaremos Lay out vertically en el menú (o el botón con tres rectángulos verdes apilados en la
barra de herramientas). El resultado es:
Esta vez seguro que pensará que el resultado es extraño, ¿verdad? Bueno, en realidad no lo es. Ambos
elementos deben estar uno sobre el otro, pero el subsistema de disposición no tiene forma de saber cuál
de los elementos debe ocupar más espacio que el otro, así que los distribuye de forma proporcional.

Para solucionar esto, cambiaremos la política de tamaño del elemento gráfico, de forma que su tipo de
tamaño vertical sea MinimumExpanding. Vaya a la ventana del editor de propiedades, pinche en el
botón "más" a la izquierda de la propiedad sizePolicy y cambie la opción vSizeType a
MinimumExpanding. El resulta ahora es exactamente lo que buscábamos:
Ya estamos preparados para volver a KDevelop y comerzar a utilizar esta clase. Pero, primero,
debemos darle un nombre. Pinche en Object Explorer del widget principal o en cualquier parte vacía
del mismo (cerca de los bordes suele haber un pequeño margen que puede utilizar). Después edite la
propiedad name y utilice algo como CentralViewBase.

El diseñador se utiliza normalmente para crear un widget que es la clase base de otra clase en la que se
sobrecargan los métodos importantes. Normalmente se utilizan dos esquemas de nomenclatura, llamar
a la clase base XXXBase y a la clase de implementación XXX o llamar a la clase base XXX y a la clase
de implementación XXXImpl. Prefiero la primera forma, así que esa es la que utilizaremos.

Integración del diseñador con KDevelop


KDevelop generará automáticamente las opciones necesarias del Makefile para que uic (compilador de
interfaz de usuario) produzca los archivos centralviewbase.h y centralviewbase.cpp
con el código fuente necesario. Todo lo que hay que hacer ahora es añadir una nueva clase (Proyecto |
Nueva clase...). En el diálogo que aparecerá, el nombre de la nueva clase será CentralView y
activaremos la casilla Generar clase hija de QWidget, como la clase heredará (indirectamente) de
QWidget, de hecho, vamos a cambiar QWidget (que KDevelop había añadido como Clase base) a
CentralViewBase.
Dependiendo de la versión de Gideon que esté utilizando, es posible que necesite ir a la pestaña
Información avanzada y modificar manualmente el constructor para que la llamada sea a
CentralViewBase en vez de a QWidget (sustituyendo el uno por el otro).

Ahora añadiremos una variable protegida class CentralView * m_view; a


TheTinterView, y después una línea m_view=new CentralView(this,
"centralview"); al final del constructor, para tener un widget CentralView como único hijo
de TheTinterView. El primer parámetro (this) significa que el widget CentralView será un
hijo del widget this, lo que significa que aparecerá en la pantalla dentro del widget this. El segundo
parámetro es un nombre de metadatos para el objeto, y es posible utilizar el nombre que se quiera.

En este momento estamos preparados para compilar s2 y obtener una aplicación que utilice el widget
que hemos creado en el diseñador.
Chapter 6. Cómo hacer que el widget generado por el
diseñador funcione
Como ya habrá notado, el widget se muestra pero no hace nada, así que le haremos trabajar un poco.

Añadir nombres, slots y conexiones


En primer lugar, abra con el diseñador el archivo centralviewbase.ui, utilizando la pestaña
Grupos de archivos (a la izquierda), dentro del bloque Interfaz de usuario.

Dentro del diseñador, seleccione cada uno de los widgets que se utilizarán desde la aplicación, y deles
un nombre. Yo he llamado m_label al elemento gráfico, m_red al deslizador del componente rojo,
y m_green y m_blue a los otros dos, respectivamente. En caso de que usted no esté familiarizado
con el sistema de nomenclatura m_nombre, puedo decirle que este sistema es el mejor invento desde el
pan. Hace que el código sea mucho más sencillo de leer y mucho más comprensible por otros
desarrolladores.

A modo de normas generales del esquema de nomenclatura que utilizo, cito algunas reglas:

1. Siempre se nombra a los componentes en la forma ?maximumSize?, y no como ?MaximumSize?


o ?maximum_size?. La única excepción es en los nombres de las clases, que deben empezar por
mayúscula (?MaximumSize?).

2. Siempre se coloca "m_" como prefijo de los nombres de las variables miembro. De esta forma
siempre se sabe si una variable es local a un método o si depende de clase en la que se encuentra.

3. No utilice nunca getXXX() como nombre para acceder a un método de una clase (lo métodos
normales que únicamente hacen { return m_XXX; };). Es mucho mejor utilizar siempre el
nombre de la propiedad, algo como QFont font() const { return m_font; };. De
esta forma, el método set correspondiente, debería ir precedido por ?set?, de forma que la pareja
get/set de una propiedad quede en la forma font() y setFont(const QFont &) en
nuestro ejemplo.

4. Las variables booleanas deben tener siempre nombres positivos, en vez de cosas como bool
m_noBackground. Es mucho más fácil entender el uso de la variable si se llama
m_background y después utilizar if (m_background) ... en vez de if
(!m_noBackground) ....

Bien, ahora nuestro widget tiene nombres que podremos utilizar en la implementación de
CentralView (ya que CentralView hereda de CentralViewBase), pero aún no es suficiente.
También debemos añadir slots a CentralViewBase, y, como son métodos virtuales, los
sobrecargaremos en CentralView. Vamos a hacerlo para que lo entienda mejor.

En primer lugar, abra el diálogo Slots del menú Edit o pulsando el botón derecho del ratón en el
widget principal. Pinche en New Function y dele el nombre setRed(int v), cree otros dos slots
llamados setGreen(int v) y setBlue(int v). Haremos que se llame a estos slots cada vez
que el usuario modifique los deslizadores rojo, verde o azul. Para hacerlo, salga del diálogo y
selecione Connect Signals/Slots en el menú Tools, en el icono de la flecha de la barra de herramientas,
o pulsando F3. Después pinche con el botón izquierdo del ratón sobre el deslizador rojo y, sin soltarlo,
muévalo hacia el widget principal (por ejemplo hacia uno de los bordes). Hay un rectángulo magenta
que muestra los widgets que serán conectados. Una vez que se ha determinado el deslizador rojo como
origen y el widget principal como destino, suelte el botón y se le mostrará un diálogo en el que
configurar qué señal del elemento de origen se conectará con qué slot del widget de destino.

En el diálogo, pinche en el campo No Signal y seleccione la señal valueChanged(int). Después,


seleccione el slot setRed(int). Conecte también las señales correspondientes de m_green y
m_blue con setGreen(int) y setBlue(int).

Ahora, conviene recordar que los componentes de color van normalmente de 0 a 255 (y eso es lo que
dice la documentación de QColor), así que lo propio sería que el deslizador nos proporcionase
valores entre 0 y 255. Para hacerlo, seleccionamos cada deslizador y fijamos su propiedad maxValue
(en el editor de propiedades) a 255.
Guarde el interfaz de usuario y cierre el diseñador para volver a KDevelop.

Implementación de los slots


De vuelta en KDevelop, abra la pestaña Clases, pinche con el botón derecho sobre la clase
CentralView y seleccione Nuevo método en el menú emergente. Aquí vamos a reimplementar
setRed, de forma que se utilice este en vez del que aparece en CentralViewBase. Así que el
nombre es setRed, el tipo es void (hace referencia al tipo del valor devuelto), y le incluimos un
parámetro de tipo int, para hacerlo coincidir con el slot que hemos creado en el diseñador. Tendremos
que realizar la misma operación otras dos veces, para crear los slots de setGreen y setBlue.

Ahora vamos a añadir una nueva variable, que será el color utilizado en TheTinter, y que tendrá tipo
QColor. Para hacerlo, pinche nuevamente con el botón derecho en el nombre de la clase en la vista
de árbol y seleccione Añadir atributo. Utilice como tipo QColor y como nombre m_color. Para ser
correctos, también la haremos protegida.
Ahora vamos a implementar los slots que acabamos de añadir para modificar el valor de m_color.
Utilizaremos el siguiente código:
void CentralView::setRed(int v)
{
m_color.setRgb( v, m_color.green(), m_color.blue() );
updatePixmap();
}

que utiliza el nuevo valor del componente rojo y deja los otros dos sin cambios. También llamamos al
método updatePixmap() que actualizará el mapa de pixels con el color m_color. Ahora vamos a
añadir el método y a implementarlo.
void CentralView::updatePixmap()
{
QPixmap pixmap(128,128);
pixmap.fill(m_color);
m_label->setPixmap(pixmap);
}

La primera línea crea un mapa de pixels de 128x128, y en la segunda línea se rellena del color. La
tercera línea coloca el mapa de pixels en el elemento de imagen. El tamaño 128x128 es un tanto
arbitrario, ya que el elemento gráfico lo redimensionará para que llene todo el área (puede cambiar
esto, si lo desea, en las propiedades).

Este es nuestro código fuente para s3, pronto haremos que cargue una imagen y la tiña con el color, en
vez de que únicamente aparezca este rellenando un rectángulo, pero, antes, trataremos de hacer que no
parpadee (fíjese en que si mueve los deslizadores muy rápido, se produce mucho parpadeo). Ese será
nuestro siguiente objetivo.
Chapter 7. Eliminación del parpadeo utilizando un
temporizador
Los objetos QTimer se utilizan cuando se desea hacer algo en un momento determinado, o cada x
milisegundos. Para hacerlo, puede iniciar un temporizador que emita una señal en un momento
concreto. Después conecta esa señal a un slot para que realice el trabajo necesario.

En primer lugar, añadimos una variable protegida QTimer m_timer a CentralView (no olvide
la línea de inclusión #include <qtimer.h> correspondiente). En el constructor de
CentralView, conectamos la señal timeout() que emite m_timer al slot updatePixmap()
que hemos creado en s3. Para hacerlo, utilizaremos connect(&m_timer,
SIGNAL(timeout()), this, SLOT(updatePixmap()));.

Ahora cambiamos los tres slots, setRed, setGreen y setBlue para que inicien el temporizador
en vez de que llamen directamente a updatePixmap. m_timer.start(200,true); inicia un
temporizador de un único ciclo (que únicamente emitirá una señal y se detendrá) con un tiempo de
espera de 200 ms. Cada vez que iniciemos un temporizador mientras haya otro funcionando (iniciamos
el segundo temporizador antes de que hayan pasado los 200 ms), se detendrá el más antiguo. Eso
significa que si el usuario mueve el deslizador muy rápido, en vez de producirse muchas llamadas a
updatePixmap(), esperaremos a que el usuario deje de modificar los valores durante 200 ms antes
de hacer el cambio real.

Eso nos lleva a s4. Ahora que hemos corregido el parpadeo (al menos en gran parte, ya que su
corrección completa no entra dentro del objetivo de este tutorial), podemos buscar experiencias más
avanzadas.

Chapter 8. Carga y manipulación de un mapa de pixels


Carga de un mapa de pixels
En primer lugar, añadiremos una variable miembro de QPixmap llamada m_pixmap a
CentralView, además de un método setPixmap que únicamente hará:
void CentralView::setPixmap(const QPixmap &pixmap)
{
m_pixmap=pixmap;
updatePixmap();
}

Ahora vamos a implementar el método que KDevelop creó como futura ubicación de un método de
carga de un documento en TheTinterView.
void TheTinterView::openURL(const KURL& url)
{
QString tmpFile;
if (KIO::NetAccess::download(url, tmpFile))
{
// cargar el archivo (el destino es siempre local)
m_view->setPixmap( tmpFile );

// y eliminar el archivo temporal


KIO::NetAccess::removeTempFile(tmpFile);
}
}

KIO::NetAccess::download se utiliza para descargar la URL url y guardarla en un archivo


temporal local cuya ruta completa se almacena en tmpFile. Si tiene éxito, devolverá true y el
documento será cargado.

Habrá notado que CentralView::setPixmap admite un parámetro QPixmap, mientras que


nosotros hemos utilizado como parámetro aquí un QString, ¿por qué? Si tiene experiencia en C++ y
ha leido un poco la documentación de QPixmap, sabrá que QPixmap tiene un constructor que admite
un único parámetro QString, y, por lo tanto, esa línea es equivalente a:
QPixmap pixmap( tmpFile );
m_view->setPixmap( pixmap );

He olvidado mencionar que el constructor de QPixmap que obtiene un parámetro QString, carga el
mapa de pixels indicado en dicho parámetro.

Por último, KIO::NetAccess::removeTempFile(tmpFile); hace algo muy interesante.


Elimina el archivo tmpFile únicamente si se trata realmente de un archivo temporal. Conviene
reseñar que en caso de que esté tratando de acceder a un archivo local,
KIO::NetAccess::download devuelve el archivo real en vez de copiarlo a una ubicación
temporal (lo que sería absurdo) y KIO::NetAccess::removeTempFile no hace nada,por lo que
es seguro llamarlo aunque el archivo no sea temporal, porque KIO::NetAccess guarda una lista de
los archivos que se han descargado (y son realmente temporales) y los archivos reales que no deben
ser eliminados.

Mezcla de colores
Ahora vamos a hacer que CentralView realice operaciones sobre el mapa de pixels. Cambiaremos
la implementación de updatePixmap a:
void CentralView::updatePixmap()
{
QImage image= m_pixmap.convertToImage();
KImageEffect::blend(m_color, image, 0.5);
QPixmap pixmap;
pixmap.convertFromImage(image);
m_label->setPixmap(pixmap);
}

La primera línea convierte el mapa de pixels a una imagen, que es otra clase que puede albergar
imágenes. La diferencia es que QImage almacena las imágenes localmente, mientras que QPixmap
lo hace en el servidor X, de forma que habrá que utilizar uno un otro dependiendo del objetivo que se
quiera lograr. En este caso resultará más sencillo teñir una imagen.
KImageEffect::blend(m_color, image, 0.5); utiliza KImageEffect (de
kdelibs/kdefx) para mezclar un color con una imagen. KImageEffect es una clase que contiene
métodos muy útiles para aplicar efectos a imágenes. El primer parámetro es el color, el segundo la
imagen (que será modificada por este método) y el tercero es la intensidad de opacidad.

Continuamos volviendo a convertir la imagen a un mapa de pixels y colocándola, al igual que antes, en
el elemento gráfico.

Esto significa que hemos terminado con s5, y ya podemos abrir imágenes y teñirlas, pero aún no
hemos acabado.

Chapter 9. Modificación del interfaz de usuario existente


Ahora vamos a cambiar el interfaz de nuestra aplicación para añadirle un nuevo deslizador que nos
permita modificar el componente de opacidad (o alfa) del tinte.
Añadir el deslizador de alfa
En primer lugar, en la pestaña Grupos de archivos, seleccionamos el archivo
centralviewbase.ui, y KDevelop abrirá el diseñador. Lo primero que haremos es romper las
disposiciones. Usted se preguntará, ¿romper las disposiciones? Sí, eso se hace para poder decir: "Deja
de manejar las posiciones actuales de los elementos, porque vamos a añadir otros nuevos".

Pinche para seleccionar el widget principal, y después pinche en el icono de la barra de herramientas
Break Layout (también disponible en el menú Layout). Ahora seleccione la caja de agrupación que
contiene los deslizadores y rompa también la disposición de estos. Debe darse cuenta de que las dos
disposiciones son independientes entre sí. La disposición del widget principal es una disposición
vertical, que maneja m_label y la caja de agrupación, y la disposición de dicha caja, maneja las
etiquetas y los deslizadores.

Si intenta añadir un elemento dentro de otro que tiene una disposición, se le preguntará si desea
romper antes la disposición para poder añadirlo. Pero eso no debería ocurrir ahora puesto que hemos
roto las disposiciones manualmente :).
Ahora que hemos roto la disposición, puede mover hacia arriba los deslizadores y las etiquetas para
dejar espacio para el nuevo deslizador que añadiremos. Añada una etiqueta de texto Alfa: y un
deslizador al que llamaremos m_alpha (que el usuario podrá utilizar para establecer el alfa u
opacidad de la mezcla de colores) en la parte de abajo de la caja de agrupación. Cambie sus
propiedades para que el rango de valores de m_alpha vaya de 0 a 100. Seleccione la caja de
agrupación ...

... y pinche en la barra de herramientas para añadir una disposición de rejilla.


Ahora puede seleccionar el widget principal y añadir la disposición vertical.

Añadir el botón de selección de color


Si queremos mejor todavía más el aspecto de nuestra aplicacióm, podemos añadir un botón
KColorButton que utilizaremos como forma alternativa de selección del color mediante un diálogo
de color estándar.
Selecciónelo y añádalo en la parte derecha de la caja de agrupación (pero dentro de ella). ¿Reconoce
ese diálogo? Sí, está preguntando si se debe romper la disposición o cancelar la operación.
Seleccionaremos romper la disposición y a continuación la volveremos a crear.
Podemos añadir una disposición de rejilla a la caja de agrupación tal y como está, y el diseñador lo
colocará correctamente de forma automática, pero vamos a utilizar otra técnica con la intención de
aprender algo más. Deseleccione cualquier widget que pudiera estar seleccionado, y pulsando la tecla
?Ctrl? en el teclado, dibuje un rectángulo que cubra las etiquetas y los deslizadores (pero no la caja de
agrupación entera, ni el KColorButton), de esa forma quedarán seleccionados. Una vez que tenga los 8
elementos seleccionados, pinche en la disposición de rejilla, y el diseñador generará una disposición de
rejilla para eso elementos. Ahora esta disposición maneja a sus hijos, pero (aún) no está manejada por
nadie, así que resulta un tanto inútil.
Para solucionarlo, seleccionaremos la caja de agrupación y añadiremos una disposición horizontal.
Esta nueva disposición manejará la disposición de rejilla y el widget KColorButton, colocándolos
uno junto al otro.

Note la diferencia existente entre seleccionar la caja de agrupación y añadir una disposición horizontal
y seleccionar la disposición de rejilla y el KColorButton y añadir entonces esa disposición horizontal.
En el primer caso, la disposición pertenecerá a la caja de agrupación, de forma que administrará las
políticas de tamaño y (directa o indirectamente) a todos sus hijos. En el segundo caso, añadiremos una
disposición horizontal independiente. Puede realizar pruebas de estas dos formas de actuar y utilizar la
opción Preview Form para comprobar las diferencias que se produce al redimensionar la ventana.

Bien, ahora seleccionaremos el widget principal y añadiremos una disposición vertical y, de esta
forma, hemos hecho que todos los elementos estén manejados por una disposición, que es la forma
correcta de actuación (quizá debería decir que esta debe ser la forma correcta de actuación, para
resaltar que es como hay que hacerlo para que las aplicaciones funcionen correctamente).

Esto podría ser suficiente para la mayor parte de la gente, pero no para nosotros. Queremos que el
widget KColorButton (al que llamaremos m_colorButton) aumente hasta ocupar todo el
espacio de la caja de agrupación. Para ello, le cambiaremos la política de tamaño. Seleccione
m_colorButton y en el editor de propiedades, abra la sección sizePolicy y cambie vSizeType a
Expanding.
Vaya, probablemente no era esto lo que queríamos. Hemos observado anteriormente que m_label
tenía una política de tamaño de expansión (MinimumExpanding para ser precisos) mientras que la
caja de agrupación no nos importaba (únicamente tenía una política Preferred). Ahora la caja de
agrupación también se ha expandido porque la política de m_colorButton es Expanding (por lo
que trata de ocupar el mayor espacio vertical posible), y m_label también, así que el sistema de
disposición trata de darle a cada uno la mitad del espacio disponible, y eso no es lo que buscamos.
¿Hay una solución? Desde luego. En casos como este es posible darle a los elementos involucrados
distintos factores de expansión para que a uno de ellos se le asigne más espacio que al otro en caso de
que ambos quieran la mayor cantidad de espacio posible. Esto se hace gracias a la propiedad
verticalStretch. Así que incrementaremos el valor de la propiedad verticalStretch de
m_label para que éste sea más grande y se solucione el problema.
Bien, ahora que el aspecto vuelve a ser el correcto, podemos abrir el editor de slots y añadir dos
nuevos, uno para el deslizador alfa (llamado setAlpha(int)) y otro al que se llamará cuando el
usuario modifique el color en m_colorButton, al que llamaremos setColor(const QColor
&).

Es hora de hacer las conexiones. No voy a volver a explicar cómo se hace, puesto que es muy sencillo.
Tiene que conectar la señal valueChanged(int) de m_alpha con el slot setAlpha(int) del
widget principal. No olvide conectar también la señal changed(const QColor &) de
m_colorButton con el slot setColor(const QColor &) del widget principal.
Bueno, de momento ya es suficiente, así que guardaremos el archivo .ui, cerraremos el diseñador,
compilaremos el resultado y lo llamaremos s6. Fíjese que, en este caso, no hemos hecho nada con
KDevelop, únicamente hemos modificado el interfaz de usuario con el diseñador y el hecho de
compilar sin haber cambiado nada nos da el nuevo interfaz de usuario (con el código anterior
funcionando correctamente). Añadiremos el código del nuevo interfaz en el siguiente paso.

Chapter 10. Añadir las implementaciones de los nuevos


métodos
Soporte para el deslizador alfa
En primer lugar, añadiremos un slot público a CentralView llamado setAlpha(int v). La
implementación de este método únicamente llamará a m_timer.start(200,true); para iniciar
la actualización del mapa de pixels.
Entonces cambiaremos el método updatePixmap para que utilice el valor del deslizador alfa.
Cambiamos
KImageEffect::blend(m_color, image, 0.5 );

por
KImageEffect::blend(m_color, image, m_alpha->value()/100.0 );

De forma que el valor resultante esté en el intervalo [0,1] (ya que en el diseñador hemos establecido en
100 en valor máximo de m_alpha).

Soporte para el botón de color


Vamos a añadir un nuevo método a CentralView llamado setColor.
void CentralView::setColor(const QColor &color)
{
m_red->setValue(color.red());
m_green->setValue(color.green());
m_blue->setValue(color.blue());
}

Con esto es suficiente para establecer el color, ya que fija el valor de los tres componentes, emitiendo
las tres respectivas señales y llamando a setRed, setGreen y setBlue, y, además, no parpadeará
puesto que se llama al temporizador cada vez que cambia un valor (gracias a las señales que emiten los
deslizadores cada vez que cambia su valor) y, por lo tanto, updatePixmap recibirá una única
llamada.

Con este cambio tenemos una aplicación completamente operativa, a la que llamaremos s7.
Chapter 11. Algunos cambios aquí y allá
Corrección del botón de color.
Como habrá notado, cuando el usuario cambia el color en el botón de color, los deslizadores cambian
sus valores, pero esto no ocurre en la situación inversa. Vamos a arreglarlo.
Añadiremos m_colorButton->setColor(m_color); a CentralView::updatePixmap
y así haremos que m_colorButton actualice su valor al mismo tiempo que se actualiza el mapa de
pixels.

Quizá quiera actualizar m_colorButton cada vez que cambie uno de los deslizadores, pero si lo
hace, podría tener problemas y posiblemente tendría que utilizar QObject::blockSignals para
bloquear las señales de los deslizadores o de m_colorButton, así que lo actualizaremos en
updatePixmap para estar seguros del correcto funcionamiento.

Corrección de mapa de pixels inicial


En primer lugar, vamos a eliminar el logotipo de Qt que utiliza m_label de forma predeterminada.
Para hacerlo, abra centralviewbase.ui en el diseñador, seleccione la etiqueta y elimine el
contenido de la propiedad pixmap pinchando en la pequeña flecha roja (aparece al seleccionar la
propiedad).

En cualquier caso, queremos que haya algo dibujado al comenzar. Así que añadiremos una llamada a
updatePixmap(); en el constructor de CentralView.

Aun así, m_pixmap está vacío al iniciar TheTinter, y seguimos queriendo que aparezca algo. Vamos
a modificar la función updatePixmap para:
void CentralView::updatePixmap()
{
m_colorButton->setColor(m_color);
QPixmap pixmap;
if (!m_pixmap.isNull())
{
QImage image= m_pixmap.convertToImage();
KImageEffect::blend(m_color, image, m_alpha->value()/100.0 );
pixmap.convertFromImage(image);
}
else
{
pixmap = QPixmap(128,128);
pixmap.fill(m_color);
}
m_label->setPixmap(pixmap);
}

De esta forma, en primer lugar comprobamos si m_pixmap es válido (no es nulo) y, en ese caso,
continuamos con el funcionamiento normal. En caso de que m_pixmap no haya sido inicializado,
haremos lo que se vio en los primeros pasos del tutorial y pintaremos un color.
Chapter 12. Añadir más características
Posibilidad de guardar el resultado
Ahora queremos guardar la imagen teñida en un archivo. ¿Qué podemos hacer? En primer lugar
añadiremos un método de acceso a CentralView que devuelva el mapa de pixels teñido (el mapa de
pixels que se encuentra en m_label):
QPixmap *tintedPixmap() const { return m_label->pixmap(); };

En TheTinterView, añadiremos un nuevo método junto a openURL(const KURL &url),


¿adivina el nombre? Correcto, saveURL(const KURL &url) :) Este método hace:
void TheTinterView::saveURL(const KURL &url)
{
QString tmpFile;
KTempFile *temp=0;
if (url.isLocalFile())
tmpFile=url.path();
else
{
temp=new KTempFile;
tmpFile=temp->name();
}

m_view->tintedPixmap()->save( tmpFile, "PNG");

if (temp)
{
KIO::NetAccess::upload(tmpFile, url, this);
temp->unlink();
delete temp;
}
}

En caso de que la URL sea un archivo local, lo utilizaremos como archivo en el que guardar el mapa
de pixels teñido en formato PNG. Si no es un archivo local, crearemos un archivo temporal y
guardaremos ahí la imagen en formato PNG y, a continuación, cargaremos el archivo temporal en la
URL especificada por el usuario, eliminaremos el archivo temporal y destruiremos el objeto que
habíamos creado para ello.

Añadir soporte para arratrar y soltar


Aquí no hay mucho que hacer, ya que KDevelop añadió automáticamente desde el principio el código
necesario para aceptar operaciones de arrastrar y soltar con URIs (equivalentes, de momento, a URLs),
y como hemos utilizado KIO para cargar nuestras imágenes, ya es posible arrastrar una imagen desde
Konqueror a TheTinter y será descargada.

Para desarrollar algo sobre arrastrar y soltar, añadiremos soporte para imágenes. La diferencia con las
URIs es que los eventos de arrastrar y soltar de una imagen contienen la información de la propia
imagen, mientras que en el caso de las URLs, estas únicamente contienen la dirección de la imagen.
El código que utilizaremos es:
void TheTinter::dragEnterEvent(QDragEnterEvent *event)
{
// accept uri drops only
event->accept(QUriDrag::canDecode(event) || QImageDrag::canDecode(event));
}

Esto será llamado cada vez que recibamos un evento de arrastrar y soltar en nuestra aplicación. Le dice
al subsistema de arrastrar y soltar si se debe aceptar o rechazar la operación.
void TheTinter::dropEvent(QDropEvent *event)
{
// esta es una implementación muy simple de un evento de soltar. únicamente
// aceptaremos una URL. el código de arrastrar y soltar de Qt puede hacer
// *mucho* más, así que, por favor, consulte la documentación
QStrList uri;

// comprobar si se puede decodificar la URI. si no, ignorarla


if (QUriDrag::decode(event, uri))
{
// correcto, tenemos una URI. la procesamos
QString url, target;
url = uri.first();

// cargamos el archivo
load(KURL(url));
return;
}

QPixmap pixmap;
if (QImageDrag::decode(event, pixmap))
{
m_view->openPixmap(pixmap);
}
}

Este código se ejecutará cada vez que el usuario suelte sobre nuestra ventana cualquier cosa que esté
arrastrando. En primero lugar tratamos de comprobar si el evento es un evento URI. Si la primera parte
tiene éxito, extraemos la primera URL de la (posible) lista de URIs que contiene el evento, y
ejecutamos esa URL.

Si no se trataba de un evento URI, tratamos de decodificarlo como un evento de imagen (utilizando


QImageDrag::decode). Si es correcto, abrimos esa imagen. Hay que tener en cuenta que
TheTinterView::openPixmap todavía no existe, así que añadimos ese método y lo
implementamos de esta forma:
void TheTinterView::openPixmap(const QPixmap& pixmap)
{
m_view->setPixmap( pixmap );
}

Y esto es s9, nuestra aplicación final. Espero que haya disfrutado del tutorial y haya aprendido con él.
También deseo poder ver publicadas muy pronto sus aplicaciones de KDE :).

Un saludo,
Antonio Larrosa Jiménez <larrosa@kde.org>

Appendix A. Créditos y licencia


El tutorial de desarrollo visual en KDE y TheTinter tienen Copyright (c) 2003 Antonio Larrosa
Jiménez <larrosa@kde.org>

This documentation is licensed under the terms of the GNU Free Documentation License.

TheTinter está publicado bajo los términos de la Licencia pública general GNU.

Me gustaría agradecer a las siguientes personas su ayuda en la creación de este tutorial:

A Lauri Watts por explicar el docbook y crear un marco de trabajo excepcional para la
documentación de KDE (y por responder a mis preguntas sobre docbook hasta altas horas en el
IRC :) ).

A Anne-Marie Mahfouf por explicarme algunas etiquetas del docbook y por escribir otro
excelente tutorial sobre KDevelop y el diseñador, que podrá encontrar aquí.

A Harald Fernengel y Roberto Raggi del equipo de KDevelop por solucionar rápidamente
algunos problemas que he encontrado en Gideon al hacer este tutorial.

A Miguel Revilla, del equipo de traducción de KDE, por traducir este tutorial en menos un día
para que me diera tiempo a usar la versión en español en Imaginática.

También podría gustarte