Documentos de Académico
Documentos de Profesional
Documentos de Cultura
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.
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:
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.
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.
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.
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);
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");
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.
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.
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.
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:
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.
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.
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.
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 );
He olvidado mencionar que el constructor de QPixmap que obtiene un parámetro QString, carga el
mapa de pixels indicado en dicho parámetro.
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.
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 ...
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.
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).
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.
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(); };
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.
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;
// 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.
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>
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.
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.