<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Un principiante en las Artes Gráficas &#187; Programación</title>
	<atom:link href="http://www.pinelo.com/blog/category/programacion/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.pinelo.com/blog</link>
	<description>Una experiencia en un sector que nos envuelve... El blog personal de David Pinelo.</description>
	<lastBuildDate>Wed, 01 Dec 2010 09:05:38 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>Simulador de Quadrotor en Gazebo</title>
		<link>http://www.pinelo.com/blog/2010/04/27/simulador-de-quadrotor-en-gazebo/</link>
		<comments>http://www.pinelo.com/blog/2010/04/27/simulador-de-quadrotor-en-gazebo/#comments</comments>
		<pubDate>Tue, 27 Apr 2010 11:33:19 +0000</pubDate>
		<dc:creator>David Pinelo</dc:creator>
				<category><![CDATA[Librerías Qt]]></category>
		<category><![CDATA[Programación]]></category>

		<guid isPermaLink="false">http://www.pinelo.com/blog/?p=368</guid>
		<description><![CDATA[Nuevo proyecto extraño que traigo a estas páginas. Dentro del Máster que ya os he comentado estoy realizando, y para una asignatura he realizado una implementación de un simulador de vuelo de un Quadrotor. Para ello, he utilizado un simulador multirrobot ya existente, Gazebo, que forma parte del proyecto Player/Stage. A grandes rasgos, hay que [...]]]></description>
			<content:encoded><![CDATA[<p>Nuevo proyecto extraño que traigo a estas páginas. Dentro del Máster que ya os he comentado estoy realizando, y para una asignatura he realizado una implementación de un simulador de vuelo de un <a href="http://en.wikipedia.org/wiki/Quadrotor">Quadrotor</a>.</p>
<p>Para ello, he utilizado un simulador multirrobot ya existente, <a href="http://playerstage.sourceforge.net/index.php?src=gazebo">Gazebo</a>, que forma parte del proyecto <a href="http://playerstage.sourceforge.net/">Player/Stage</a>. A grandes rasgos, hay que implementar un Controlador que simule la física del quadrotor (para ello he utilizado el modelo desarrollado en la tesis doctoral de Samir Bouabdallah). Además, he desarrollado una pequeña interfaz en Qt, que utilizando libgazebo (librería de comunicación con Gazebo) permite interactuar con el quadrotor.</p>
<p>Al utilizar Gazebo, permite simulaciones multirrobot, adjuntar cámaras, mundos virtuales complejos...</p>
<p>Los fuentes, podéis descargarlos desde el proyecto que he abierto en <a href="http://sourceforge.net/projects/gazeboquadrotor">Sourceforge</a>.</p>
<p>Además, tenéis disponible la <a href="http://www.pinelo.com/blog/postimgs/2010/04/27/memoria.pdf">memoria del trabajo</a>, con una explicación de "cómo" serían las líneas básicas de construcción de un simulador robótico (de un quadrotor) y las líneas generales de implementación del quadrotor en Gazebo.</p>
<p>Ahora en inglés.</p>
<p>I have developed a Quadrotor simulator using Gazebo. Gazebo is a 3D multirrobot simulator system, that is part of Player/Stage project. So, I have developed a Controller for Gazebo, that implements the dynamic model of Samir Bouabdallah to simulate the flight. I need to make several changes on the Gazebo core to get some information of the body of Quadrotor, to implement a new data structure to interact with or to set the absolute force applied to.</p>
<p>I have developed a Qt client application too. It is an example of how interact with Gazebo using libgazebo, and implements several loopback controllers that set the angular velocities of each rotor to control the flight.</p>
<p>You can download from <a href="http://gazeboquadrotor.sourceforge.net/">Sourceforge</a>.</p>
<p>In next post I will try to explain with more details.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.pinelo.com/blog/2010/04/27/simulador-de-quadrotor-en-gazebo/feed/</wfw:commentRss>
		<slash:comments>13</slash:comments>
		</item>
		<item>
		<title>Programando con Qt: Creando un widget sobre el que poder dibujar</title>
		<link>http://www.pinelo.com/blog/2010/03/30/programando-con-qt-creando-un-widget-sobre-el-que-poder-dibujar/</link>
		<comments>http://www.pinelo.com/blog/2010/03/30/programando-con-qt-creando-un-widget-sobre-el-que-poder-dibujar/#comments</comments>
		<pubDate>Tue, 30 Mar 2010 15:03:48 +0000</pubDate>
		<dc:creator>David Pinelo</dc:creator>
				<category><![CDATA[Librerías Qt]]></category>
		<category><![CDATA[Programación]]></category>

		<guid isPermaLink="false">http://www.pinelo.com/blog/?p=352</guid>
		<description><![CDATA[Si algo distingue este blog, es el CAOS. Lo mismo hablo de AbanQ, que Qt, que Asterisk, que de la crisis, o me llevo dos meses sin escribir para de nuevo hacerlo sobre redes neuronales artificiales. Supongo que aquí plasmo un poco la realidad de mi día a día: Me enfrento a múltiples y diversas [...]]]></description>
			<content:encoded><![CDATA[<p>Si algo distingue este blog, es el CAOS. Lo mismo hablo de AbanQ, que Qt, que Asterisk, que de la crisis, o me llevo dos meses sin escribir para de nuevo hacerlo sobre redes neuronales artificiales. Supongo que aquí plasmo un poco la realidad de mi día a día: Me enfrento a múltiples y diversas tecnologías para resolver múltiples y diversos problemas... pero la tecnología es sólo una pequeña parte de mi trabajo.</p>
<p>Y dentro de las líneas de caos, voy a dedicarme hoy a hablar un poquito de las Qt, mostrando un ejemplo; vamos a crear un widget muy sencillito: En él, y con el ratón pulsado, podremos dibujar, al más puro estilo Paint. La idea es utilizar este widget para capturar trazos de letras. Es decir, el usuario escribirá en él una letra. El widget esperará un tiempo prudencial, y avisará al código principal de que ya tiene la letra capturada. Este código está sacado de mi proyecto <a href="http://sourceforge.net/projects/qthandwritrecog/">QtHandwriteRecog</a>.</p>
<p>Ojo, la versión que pongo aquí NO ES LA IDEAL utilizando las posibilidades gráficas de Qt. Es una BURDA aproximación, pero que permite introducirse en algunos aspectos de las Qt sin llegar a un nivel de complejidad excesiva. Los que os estéis iniciando en las Qt lo entenderéis. Los expertos lo detestaréis. Pero a los primeros, os ayudará a ser los segundos, y detestarlo en un futuro. ;-)</p>
<p><span id="more-352"></span>El widget que crearemos será una extensión de QLabel. Es decir, dibujaremos en un QLabel. La razón, es la habilidad de este último para mostrar imágenes (QPixmap) de una forma muy sencilla. Lo mejor es ver la definición del widget e ir comentado particularidades</p>
<pre>/***************************************************************************
 copyright            : (C) 2009 by David Pinelo
 email                : adpinelo@yahoo.es
***************************************************************************/
/***************************************************************************
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; version 2 of the License.               *
 ***************************************************************************/
/***************************************************************************
 Este  programa es software libre. Puede redistribuirlo y/o modificarlo
 bajo  los  trminos  de  la  Licencia  Pblica General de GNU   en  su
 versin 2, publicada  por  la  Free  Software Foundation.
 ***************************************************************************/
<span style="color: #339966;">#ifndef PAINTWIDGET_H
#define PAINTWIDGET_H

#include &lt;QLabel&gt;
#include &lt;QMouseEvent&gt;
#include &lt;QPainter&gt;
#include &lt;QTimer&gt;
#include &lt;QDebug&gt;

#define DELAY_BETWEEN_TRACES    500
#define IMG_WRITE_X    200
#define IMG_WRITE_Y    250
#define TRACE_WIDTH 5
</span>
<strong>class</strong> PaintWidget : <strong>public</strong> QLabel
{
Q_OBJECT
<strong>private:</strong>
 <span style="color: #0000ff;">/** Buffer intermedio que contendrá una copia de trabajo de la imágen dibujada. */
</span> QImage *m_img;
 <span style="color: #0000ff;">/** Chivato que indica si se está dibujando */
</span> bool m_draw;
 bool m_clear;
 <span style="color: #0000ff;">/** Para conseguir un trazo continuo, guardamos la posicion
     del ultimo punto dibujado en el evento */
</span> QPoint m_lastPoint;
 <span style="color: #0000ff;">/** Tras escribir un trazo, esperamos un tiempo prudencial por si
     se escribe otro trazo. Caso de no escribirse, se lanza la senial
     de trazo finalizado */</span>
 QTimer *m_timer;
 <span style="color: #0000ff;">/** Resolucion de la imagen */</span>
 int m_xRes;
 <span style="color: #0000ff;">/** Resolucion de la imagen */</span>
 int m_yRes;

 void viewImage();
 void paintPixel ( const QPoint &amp;pos );
 void drawLine ( const QPoint &amp;orig, const QPoint &amp;dest );
 QPoint scaledPoint ( const QPoint &amp; point );
 void createBufferImg();
 void normalizeImage( QImage &amp;imgNormalized );
 void deleteWhiteSpaces ( QImage &amp;image );

<strong>public</strong>:
 PaintWidget(QWidget *parent = 0);
 ~ PaintWidget();

 void getNormImage( QImage &amp;image );

 int getXRes();
 int getYRes();
 void setXRes(int x);
 void setYRes(int y);

<strong>signals</strong>:
 void traceFinish();

<strong>public slots:</strong>
 void clear();

<strong>private slots</strong>:
 void isTraceFinish();

protected:
 void mousePressEvent ( QMouseEvent * event );
 void mouseMoveEvent ( QMouseEvent * event );
 void mouseReleaseEvent ( QMouseEvent * event );
};

<span style="color: #339966;">#endif // PAINTWIDGET_H</span>
</pre>
<p>La secuencia de eventos para que el usuario escriba un trazo es sencilla:</p>
<ol>
<li>Sitúa el ratón encima del widget.</li>
<li>Pulsa y <em>deja</em> pulsado el botón izquierdo del ratón todo el tiempo que quiera dibujar.</li>
<li>Mueve el ratón por el widget.</li>
<li>Se escribe el trazo.</li>
<li>Suelta el ratón, y pasado un tiempo el widget indica que el trazo está finalizado. Este tiempo se establecer por</li>
</ol>
<pre>DELAY_BETWEEN_TRACES
</pre>
<p>Al realizar un trazo, encontramos algunas dificultades y presento algunas consideraciones:</p>
<ul>
<li>Una de ellas es que el trazo es discontínuo por la "coordinación" entre el tiempo de refresco en pantalla y los eventos de captura del ratón. En este widget implementaremos un truco para conseguir un trazo contínuo: Entre evento y evento capturado de movimiento del ratón sobre la imágen, dibujaremos una línea que una los puntos que se pintan con cada evento. Esto proporcionará continuidad al trazo.</li>
<li>Para no hacer un trazo muy fino, todo el dibujo se realizará con un pincel mayor.</li>
<li>Para simplificar la lectura de la imagen y tratamiento de la misma, asocio al widget un "buffer" intermedio, una imagen a través del objeto QImage.</li>
</ul>
<p>Con esto, la implementación en código de este widget sería como sigue:</p>
<pre>#include "paintwidget.h"

PaintWidget::PaintWidget(QWidget *parent) : m_img(NULL),
    QLabel(parent)
{
    m_draw = false;
    m_clear = true;

    createBufferImg();

    m_timer = new QTimer(this);
    m_timer-&gt;setInterval(DELAY_BETWEEN_TRACES);

    connect(m_timer, SIGNAL(timeout()), this, SLOT(isTraceFinish()));
    viewImage();
}

PaintWidget::~PaintWidget()
{
    delete m_img;
    delete m_timer;
}

void PaintWidget::mousePressEvent ( QMouseEvent * event )
{
    /* Acciones cuando se pulsa el botón:
    -Se activa el chivato de dibujando.
    -Se para el timer que controla si el usuario ha dejado de pulsar
    -Se dibuja el primer punto en la pantalla
    */
    m_draw = true;
    m_timer-&gt;stop();
    paintPixel ( event-&gt;pos() );
    m_clear = true;
}

void PaintWidget::mouseReleaseEvent( QMouseEvent * event )
{
    /* Al soltar el botón: Se indica que se ha dejado de dibujar. Se inicia el conteo
    de tiempo para saber si el usuario ha terminado de escribir, o es una pequeña pausa
    para "ponerle el punto a la i", por ejemplo */
    m_draw = false;
    m_timer-&gt;start();
}

void PaintWidget::mouseMoveEvent( QMouseEvent *event )
{
    if ( !m_draw )
        return;

    /* Al mover el ratón, con el botón pulsado, se pinta los píxeles, y el trazo contínuo */
    paintPixel ( event-&gt;pos() );
    // Esto dara continuidad al trazo
    drawLine ( m_lastPoint, event-&gt;pos() );
    m_lastPoint = event-&gt;pos();
}

void PaintWidget::viewImage()
{
    this-&gt;setPixmap(QPixmap::fromImage(*m_img));
}

void PaintWidget::paintPixel ( const QPoint &amp;pos )
{
	// Para que no haya problemas con el escalado, hacemos el trazo algo más grueso
	QPainter paint;
	paint.begin( m_img );
	paint.setBrush( Qt::black );
	paint.drawEllipse(pos, TRACE_WIDTH/2, TRACE_WIDTH/2);
	paint.end();
	viewImage();

}

void PaintWidget::drawLine ( const QPoint &amp;orig, const QPoint &amp;dest )
{
    // Esta funcion da algo de continuidad al trazo
    QPainter paint;
    if ( !orig.isNull() ) {
        paint.begin( m_img );
	for ( int i = 0 ; i &lt; ( TRACE_WIDTH / 2 ) ; i++ ) {
             QPoint pO1 ( orig.x() - i, orig.y() - i );
             QPoint pD1 ( dest.x() - i, dest.y() - i );
             paint.drawLine( pO1, pD1 );
             QPoint pO2( orig.x() + i, orig.y() + i );
             QPoint pD2( dest.x() + i, dest.y() + i );
             paint.drawLine( pO2, pD2 );
        }
        paint.end();
    }
 } 

void PaintWidget::isTraceFinish()
{
    if ( !m_draw &amp;&amp; m_clear ) {
        m_timer-&gt;stop();
        emit traceFinish();
    }
}

void PaintWidget::getNormImage( QImage ℑ )
{
	image = *m_img;
	normalizeImage ( image );
}

void PaintWidget::clear()
{
    QPoint pnull;
    m_img-&gt;fill(1);
    m_clear = true;
    m_lastPoint = pnull;
    viewImage();
}

int PaintWidget::getXRes()
{
	return m_xRes;
}

int PaintWidget::getYRes()
{
	return m_yRes;
}

void PaintWidget::setXRes(int x)
{
	m_xRes = x;
}

void PaintWidget::setYRes(int y)
{
	m_yRes = y;
}

/** \fn PaintWidget::createBufferImg()
  Crea el buffer de imagen intermedio que almacena la imagen
  */
void PaintWidget::createBufferImg()
{
	if ( m_img != NULL ) {
		delete m_img;
	}
	// Solo dos colores: Blanco y negro
	m_img = new QImage(IMG_WRITE_X, IMG_WRITE_Y, QImage::Format_Mono);
	m_img-&gt;fill(1);
}

/** \fn PaintWidget::normalizeImage( QImage &amp;imgNormalized )
  Esta función elimina los espacios en blanco alrededor del carácter escrito, y escala
  la imagen a la resolución necesaria
  */
void PaintWidget::normalizeImage( QImage &amp;imgNormalized )
{
	deleteWhiteSpaces ( imgNormalized );
	imgNormalized = imgNormalized.scaled(QSize( m_xRes, m_yRes ));
}

/** \fn PaintWidget::deleteWhiteSpace ( QImage ℑ )
  Esta función elimina los espacios en blanco alrededor del carácter escrito
  */
void PaintWidget::deleteWhiteSpaces ( QImage ℑ )
{
	// Recorremos la imagen, primero para encontrar la primera fila con el primer punto no blanco
	// Después la recorremos en sentido inverso para encontrar la última fila con el último punto no blanco
	// Se hace lo mismo, pero por columnas para la columna derecha e izquierda
	int firstRow = 0, lastRow = image.size().height(), firstCol = 0, lastCol = image.size().width();

	for ( int row = 0 ; row &lt; image.size().height() ; row ++ ) {
		if ( firstRow == 0 ) {
			for ( int col = 0 ; col &lt; image.size().width() ; col ++ ) {
                                if ( image.pixelIndex( col, row ) == 0 ) {
                                        firstRow = row;
                                        if ( firstRow &gt; 0 )
						firstRow --;
					break;
				}
			}
		}
	}
	for ( int row = image.size().height() - 1 ; row &gt; 0 ; row -- ) {
		if ( lastRow == image.size().height() ) {
			for ( int col = 0 ; col &lt; image.size().width() ; col ++ ) {
				if ( image.pixelIndex( col, row ) == 0 ) {
					lastRow = row;
					if ( lastRow &lt; image.size().height() - 1 )
						lastRow++;
					break;
				}
			}
		}
	}
	for ( int col = 0 ; col &lt; image.size().width() ; col++ ) {
		if ( firstCol == 0 ) {
			for ( int row = 0 ; row &lt; image.size().height() ; row ++ ) {
                              if ( image.pixelIndex( col, row ) == 0 ) {
                                    firstCol = col;
                                    if ( firstCol == 0 )
                                         firstCol--;
                                    break;
                              }
                       }
                }
        }
        for ( int col = image.size().width() - 1; col &gt; 0  ; col-- ) {
		if ( lastCol == image.size().width() ) {
			for ( int row = 0 ; row &lt; image.size().height() ; row ++ ) {
				if ( image.pixelIndex( col, row ) == 0 ) {
					lastCol = col;
					if ( lastCol &lt; image.size().width() - 1 )
						lastCol++;
					break;
				}
			}
		}
	}
	image = image.copy ( firstCol, firstRow, (lastCol - firstCol), (lastRow - firstRow));

}
</pre>
<p>Básicamente, este sería el código. La imagen se normaliza, esto es, una cosa es el tamaño al que se dibuja y otra cómo se quiere obtener la imagen para tratarla (en el caso del programa para el que se escribió este widget, para que la imagen sirva de entrada a una red neuronal).</p>
<div id="_mcePaste" style="position: absolute; left: -10000px; top: 874px; width: 1px; height: 1px; overflow: hidden;">
<pre> <span style="color: #0000ff;">/** Para conseguir un trazo continuo, guardamos la posicion
     del ultimo punto dibujado en el evento */
</span></pre>
</div>
]]></content:encoded>
			<wfw:commentRss>http://www.pinelo.com/blog/2010/03/30/programando-con-qt-creando-un-widget-sobre-el-que-poder-dibujar/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Trabajando con Redes Neuronales y Qt</title>
		<link>http://www.pinelo.com/blog/2010/03/16/trabajando-con-redes-neuronales-y-qt/</link>
		<comments>http://www.pinelo.com/blog/2010/03/16/trabajando-con-redes-neuronales-y-qt/#comments</comments>
		<pubDate>Tue, 16 Mar 2010 17:51:29 +0000</pubDate>
		<dc:creator>David Pinelo</dc:creator>
				<category><![CDATA[Librerías Qt]]></category>
		<category><![CDATA[Programación]]></category>

		<guid isPermaLink="false">http://www.pinelo.com/blog/?p=338</guid>
		<description><![CDATA[Si estaba poco liado, ahora algo más. Ando haciendo un Máster por la Universidad de Sevilla en Automática, Robótica y Telemática, y entre los múltiples requisitos, está el de realizar un trabajo por asignatura. Para el trabajo de la asignatura de Visión por Computador, escogí realizar una aplicación de reconocimiento de caracteres manuscritos utilizando redes [...]]]></description>
			<content:encoded><![CDATA[<p>Si estaba poco liado, ahora algo más. Ando haciendo un Máster por la Universidad de Sevilla en Automática, Robótica y Telemática, y entre los múltiples requisitos, está el de realizar un trabajo por asignatura. Para el trabajo de la asignatura de Visión por Computador, escogí realizar una aplicación de reconocimiento de caracteres manuscritos utilizando redes neuronales.</p>
<p>Concremente, escogí como implementación de redes neuronales la <a href="http://leenissen.dk/fann/">Fast Artificial Neural Network, FANN</a>, una excelente implementación realizada en C.  Evidentemente, escogí como librería GUI para realizar el frontend, las Qt.</p>
<p>Los fuentes de la aplicación podéis descargarlos del proyecto que he abierto en <a href="http://sourceforge.net/projects/qthandwritrecog/">Sourceforge, justo aquí</a>.</p>
<p>La idea de funcionamiento de la aplicación es la siguiente:</p>
<ol>
<li>Se le indica a la aplicación los caracteres que se van a reconocer y el número de patrones de ejemplo que se le van a proporcionar. Eso generará un pequeño entrenamiento.</li>
<li>Además se configuran algunos parámetros de la red neuronal (tipo de entrenamiento, tasa de aprendizaje, tasa de conexiones iniciales...). Los valores por defecto que vienen en la aplicación funcionan bastante bien.</li>
<li>Se entrena la aplicación (y por extensión, las redes neuronales que dan soporte a la aplicación) con tu propia letra (la parte cutre es si no tenéis un lápiz óptico o algo así, que tendréis que hacerlo con vuestro ratón).</li>
<li>A partir de aquí, la aplicación debe ser capaz de reconocer tu letra.</li>
</ol>
<p>Podéis ver un ejemplo de funcionamiento en el vídeo que pongo a continuación. Puede descargarse <a href="http://www.pinelo.com/blog/postimgs/2010/03/16/video-handwrite.mpeg">aquí</a>.</p>
<p><object classid="clsid:02bf25d5-8c17-4b23-bc80-d3488abddc6b" width="100" height="100" codebase="http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0"><param name="src" value="http://www.pinelo.com/blog/postimgs/2010/03/16/video-handwrite.mpeg" /><embed type="video/quicktime" width="500" height="512" src="http://www.pinelo.com/blog/postimgs/2010/03/16/video-handwrite.mpeg"></embed></object></p>
]]></content:encoded>
			<wfw:commentRss>http://www.pinelo.com/blog/2010/03/16/trabajando-con-redes-neuronales-y-qt/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
<enclosure url="http://www.pinelo.com/blog/postimgs/2010/03/16/video-handwrite.mpeg" length="2399940" type="video/mpeg" />
		</item>
		<item>
		<title>AbanQ: Integrando con JasperServer / JasperReports (II)</title>
		<link>http://www.pinelo.com/blog/2009/12/10/abanq-integrando-con-jasperserver-jasperreports-ii/</link>
		<comments>http://www.pinelo.com/blog/2009/12/10/abanq-integrando-con-jasperserver-jasperreports-ii/#comments</comments>
		<pubDate>Thu, 10 Dec 2009 10:27:55 +0000</pubDate>
		<dc:creator>David Pinelo</dc:creator>
				<category><![CDATA[ERP Open Source]]></category>
		<category><![CDATA[Librerías Qt]]></category>
		<category><![CDATA[Programación]]></category>

		<guid isPermaLink="false">http://www.pinelo.com/blog/?p=311</guid>
		<description><![CDATA[Bien, la idea es usar una librería C++ para interaccionar con webservices. Para ello, escogí el toolkit gSOAP. Librería en software libre que permite crear aplicaciones tanto clientes (consumidores) como servidores de webservices. Nosotros vamos a hacer un uso muy parcial de la misma, ya que no vamos a instalar ningún servidor ni nada por [...]]]></description>
			<content:encoded><![CDATA[<p>Bien, la idea es usar una librería C++ para interaccionar con webservices. Para ello, escogí el toolkit <a href="http://www.cs.fsu.edu/~engelen/soap.html">gSOAP</a>. Librería en software libre que permite crear aplicaciones tanto clientes (consumidores) como servidores de webservices.</p>
<p>Nosotros vamos a hacer un uso muy parcial de la misma, ya que no vamos a instalar ningún servidor ni nada por el estilo, y sólo vamos a crear un consumidor. No me voy a meter en detalles con gSOAP (ya que se me escapa de los artículos) y sólo comentaré aquellos aspectos que nos interesan para integrar Webservices.</p>
<p>Lo primero, es obtener el <a href="http://es.wikipedia.org/wiki/WSDL">WSDL</a> de JasperServer. Para ello, y desde una instalación del mismo, se puede obtener sin más que abrir un navegador y solicitar http://servidor:puerto_tomcat/jasperserver/services/repository?wsdl</p>
<p>Bien. gSOAP proporciona dos ejecutables wsdl2h y soapcpp2 para generar los "proxys", esto es, código C++ que encapsula el acceso por webservices. Los ejecutamos tal que así: (INCLUDEGSOAP es una variable que apunta al directorio de includes de gsoap</p>
<p><span id="more-311"></span></p>
<pre>echo "" &gt; env.h
soapcpp2 -L -C -d../proxy -penv env.h
# Las dos lineas anteriores son para generar un esqueleto de código de acceso a los webservices, que permitan
# una compilación sin problemas.
wsdl2h -nJasperServer -NJasperServer -y -g -I${INCLUDEGSOAP}/ -o JasperServer.h JasperServer.wsdl
soapcpp2 -C -d../proxy -I${INCLUDEGSOAP}/ -n -pjasperserver -qJasperServer JasperServer.h</pre>
<p>Con esto se generan una serie de ficheros, .c, .cpp y .h que incluiremos en AbanQ. Concretamente, a flbase.pro, le añadiremos los siguientes ficheros:</p>
<pre>-De la distribución de gsoap directamente
stdsoap2.cpp
-Archivos generados con el código anterior
JasperServerClientLib.cpp
envC.cpp
envClient.cpp
stdsoap2.h
JasperServerrepositorySoapBindingProxy.h
JasperServer.nsmap</pre>
<p>Ya podemos meter código propio en AbanQ. En el zip que puse días anteriores, viene todo esto integrado. Destaco aquí el código de acceso a los webservices.</p>
<p>Voy a crear un nuevo método en FLUtil que permita, desde QSA, acceder a los webservices. Ese método será tal que así</p>
<pre>// dpinelo
bool FLUtil::downloadJasperServerFile(const QString &amp; xmlRequest, const QString &amp; rutaDestino, QString &amp; mensajeSalida)
{
 bool value = false;
 int r;
 std::string result;
 std::string xml = xmlRequest;

 QSettings settings;
 settings.setPath( "InfoSiAL", "FacturaLUX", QSettings::User );
 QString keybase( "/facturalux/lite/jasperserver/" );
 // Lo hago así para garantizar que los objetos existen hasta el final
 QString *userJasperServer = new QString;
 QString *passwordJasperServer = new QString;
 QString *endPointJasperServer = new QString;
 *userJasperServer = settings.readEntry( keybase + "User", "" );
 *passwordJasperServer = settings.readEntry( keybase + "Password", "" );
 *endPointJasperServer = settings.readEntry( keybase + "EndPoint", "" );

 JasperServer::repositorySoapBinding rep;
 // si lo hago sin punteros, esto es peligroso
 rep.soap-&gt;userid = (*userJasperServer).ascii();
 rep.soap-&gt;passwd = (*passwordJasperServer).ascii();
 rep.endpoint = (*endPointJasperServer).ascii();

 qDebug("Antes de llamar a jasperserver");
 r = rep.JasperServer__runReport(xml, result);
 if ( r == SOAP_OK ) {
 qDebug("Lo ha llamado y es correcto");
 soap_multipart::iterator attachment = rep.soap-&gt;mime.begin();
 ++attachment;
 if ( attachment != rep.soap-&gt;mime.end() ) {
 saveDocument (*attachment, rutaDestino);
 value = true;
 } else {
 value = false;
 mensajeSalida = result;
 }
 } else {
 qDebug("Hubo un fallo");
 soap_print_fault(rep.soap, stderr);
 value = false;
 }
 delete userJasperServer;
 delete passwordJasperServer;
 return value;    

}

// dpinelo
void FLUtil::saveDocument(const struct soap_multipart &amp; attachment, const QString &amp; path)
{
 QFile file (path);
 file.open ( IO_WriteOnly | IO_Truncate );
 file.writeBlock(attachment.ptr, attachment.size);
 file.close();
}</pre>
<p>Listo. ¿Cómo invocar a un informe en JasperServer? Desde QSA es tan sencillo como: Por ejemplo, para imprimir un recibo de proveedor</p>
<pre>function oficial_imprimir(codRecibo:String)
{
 var util:FLUtil = new FLUtil;
 var xml:String = "&lt;request operationName=\"runReport\" locale=\"es\"&gt;";
 xml = xml + "&lt;argument name=\"RUN_OUTPUT_FORMAT\"&gt;PDF&lt;/argument&gt;";
 xml = xml + "&lt;resourceDescriptor name=\"\" wsType=\"\" ";
 xml = xml + "uriString=\"/Documentos/Pagare_a_Proveedores/PagareProveedores\" ";
 xml = xml + "isNew=\"false\"&gt;";
 xml = xml + "&lt;label&gt;null&lt;/label&gt;";
 xml = xml + "&lt;parameter name=\"P_ID_PAGARE\"&gt;&lt;![CDATA[100]]&gt;&lt;/parameter&gt;";
 xml = xml + "&lt;parameter name=\"ANIO\"&gt;&lt;![CDATA[09]]&gt;&lt;/parameter&gt;";
 xml = xml + "&lt;/resourceDescriptor&gt;";
 xml = xml + "&lt;/request&gt;";
 var rutaDestino:String = "/home/david/prueba.pdf";
 var mensajeSalida:String;
 util.downloadJasperServerFile ( xml, rutaDestino, mensajeSalida);

}</pre>
<p>Como veis, construyo un XML con el formato adecuado, según describe JasperServer, indicando los valores de los parámetros del propio report. El archivo se obtiene directamente en PDF (aunque se puede obtener en otros muchos formatos cambiando el RUN_OUTPUT_FORMAT), y por supuesto, podéis ponerle el nombre que querais. (construid adecuadamente rutaDestino).</p>
<p>La integración seía aún mejor en la medida en la que se oculte ese código XML que se construye en QSA, asociando parámetros de JasperReports/JasperServer con columnas de FLSqlCursor. Pero, a mí, este nivel me sirve.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.pinelo.com/blog/2009/12/10/abanq-integrando-con-jasperserver-jasperreports-ii/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>AbanQ: Antes de seguir&#8230; cómo se compila</title>
		<link>http://www.pinelo.com/blog/2009/12/05/abanq-antes-de-seguir-como-se-compila/</link>
		<comments>http://www.pinelo.com/blog/2009/12/05/abanq-antes-de-seguir-como-se-compila/#comments</comments>
		<pubDate>Sat, 05 Dec 2009 08:48:42 +0000</pubDate>
		<dc:creator>David Pinelo</dc:creator>
				<category><![CDATA[ERP Open Source]]></category>
		<category><![CDATA[Librerías Qt]]></category>
		<category><![CDATA[Programación]]></category>

		<guid isPermaLink="false">http://www.pinelo.com/blog/?p=317</guid>
		<description><![CDATA[Bueno, antes de seguir con los artículos de cómo integrar JasperServer, u OpenKM, o introducir alguno de los cambios que propongo... creo que es mejor proporcionaros las herramientas para compilar estas versiones de AbanQ tanto en windows como en linux, para crear así los clientes con las opciones de debug, o quick. He creado un [...]]]></description>
			<content:encoded><![CDATA[<p>Bueno, antes de seguir con los artículos de cómo integrar JasperServer, u <a href="http://www.openkm.com/">OpenKM</a>, o introducir alguno de los cambios que propongo... creo que es mejor proporcionaros las herramientas para compilar estas versiones de AbanQ tanto en windows como en linux, para crear así los clientes con las opciones de debug, o quick.</p>
<p>He creado un <a href="http://pinelo.com/blog/postimgs/2009/12/abanq-dpinelo-20091204.zip">.zip</a> con <em>mi</em> versión actual de AbanQ, y digo <em>"mi"</em> versión, porque incluye los múltiples cambios que he ido publicando en este blog, y que enumero</p>
<ul>
<li>Formateado automático de los FLFieldDB que contienen números, de tal forma, que las cantidades tales como 100000 se representan como 100.000,00 Esto hace mucho más legible las cantidades. <a href="http://www.pinelo.com/blog/2009/03/20/abanq-dando-formato-a-los-flfielddb-numericos/">Aquí</a></li>
<li>Desde QSA (el lenguaje de scripting) pueden accederse al manejo de los QComboBox (así, por ejemplo, he podido meter en algunos formularios objetos QComboBox y utilizarlos directamente sin necesidad de tener que estar asociados a columnas de bases de datos. Esto es especialmente útil para realizar filtrados de campos estáticos o no guardados en bases de datos).</li>
<li>Los FLTableDB pueden presentar el resultado de Querys complejas, al agregarse una propiedad: qryVisualizacion. De esta forma, la riqueza visual de lo que pueden representar es mayor. La diferencia con la versión oficial de AbanQ, es que ese FLTableDB no es readonly, sino que se utiliza en la edición, creación y eliminación de registros. <a href="http://www.pinelo.com/blog/2009/03/05/mostrando-el-resultado-de-consultas-en-los-flformdb-de-abanq/">Aquí</a></li>
<li>Cambios estéticos que afectan principalmente a la versión para Windows, ya que la original, no me gustaba (dejo el tema de windows por defecto, sin modificarlo). <a href="http://www.pinelo.com/blog/2009/02/26/modificaciones-en-abanq">Aquí</a></li>
<li>Los tamaños y posiciones de los formularios utilizados se almacenan, permitiendo una mejor experiencia del usuario. <a href="http://www.pinelo.com/blog/2009/03/02/guardando-automaticamente-los-tamanos-de-los-formularios-en-abanq/">Aquí</a>.</li>
<li>Posibilidad de utilizar vistas cuando se utilice un motor PostgreSQL. Esto, entre otras cosas, permite una mejor integración de los datos de otros programas con AbanQ. <a href="http://www.pinelo.com/blog/2009/05/27/buenas-practicas-en-abanq-usando-vistas-con-postgresql/">Aquí</a></li>
<li>Corrección de un bug por el que no se filtraban adecuadamente campos con valores int, uint o double. <a href="http://www.pinelo.com/blog/2009/06/08/bug-en-abanq-filtrado-de-campos-double-int-o-uint-desde-fltabledb">Aquí</a>.</li>
<li>Incorporación del estilo Windows XP (esto no lo incluye la versión original de AbanQ).</li>
<li>El código de la integración con JasperServer que se verá en siguientes artículos.</li>
</ul>
<p>Sólo tenéis que descomprimir el archivo .zip que os adjunto. Una vez hecho esto, si utilizáis Windows, debéis ajustar las variables señaladas en los archivos setenv.bat y build-config.bat. Si utilizáis Linux, debéis hacer lo mismo en el archivo setenv.h</p>
<p>Para compilar en Linux, necesitáis gcc, g++ y demás herramientas que seguro tendréis instaladas. Para compilar en Windows, necesitáis descargaros, <a href="http://www.mingw.org/">MinGW</a>. Además, es conveniente instalar el MinGW API for MS-Windows  de <a href="http://sourceforge.net/projects/mingw/files">aquí</a>.</p>
<p>Compilación bajo Windows. Ubicados en el directorio donde habéis descomprimido las fuentes, ejecutar en este orden (por si no me explico bien haced un Inicio-&gt;Ejecutar-&gt; cmd y cd al directorio en el que tengáis las fuentes ;-):</p>
<pre>setenv.bat
build-config.bat
build-pthreads.bat
build-qt.bat
build-qsa.bat
build-abanq.bat</pre>
<p>Para los que estéis en Linux, es tan sencillo, como desde una consola, y en el directorio de las fuentes, ejecutar</p>
<pre>setenv.sh
compilar-qt.sh
compilar-qsa.sh
compilar-abanq.sh</pre>
<p>Por defecto, la versión Windows viene preparada para compilar una versión final con un cliente quick, y la versión de Linux, para compilar una versión debug y con cliente "pesado" o completo.</p>
<p>Tened en cuenta que hay dependencias cruzadas, que no están muy bien resueltas: Por ejemplo, flbase depende de kugar, y kugar depende de flbase... así que al construir por primera vez, puede que os de error (la librería de flbase quiere enlazar a la de kugar, pero la de kugar no está construida porque faltan archivos objetos de flbase que no pueden construirse sin hacer previamente un moc de flbase...). Solución si os perdéis un poco en estos temas: entrad directamente en los directorios (src/flbase o src/kugar) y haced ahí el make.</p>
<p>Espero que con estas herramientas muchos os animéis a compilar y a introducir modificaciones, y corregir los muchos fallos que seguro he cometido. Cuanto más crezca la comunidad, mejor para todos.</p>
<p><a href="http://pinelo.com/blog/postimgs/2009/12/abanq-dpinelo-20091204.zip">Aquí teneís el archivo</a></p>
<p>Nota: Muchas de las modificaciones necesarias para compilar bajo Windows, las proporcionó juancrobles, a través del foro de AbanQ en Google, a  través de diversos hilos y del fichero Modificaciones_abanq2.3-v1.1.zip disponible <a href="http://groups.google.com/group/abanq/files">aqui</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.pinelo.com/blog/2009/12/05/abanq-antes-de-seguir-como-se-compila/feed/</wfw:commentRss>
		<slash:comments>41</slash:comments>
		</item>
		<item>
		<title>AbanQ: Integrando con JasperServer / JasperReports (I)</title>
		<link>http://www.pinelo.com/blog/2009/12/02/abanq-integrando-con-jasperserverjasperreports/</link>
		<comments>http://www.pinelo.com/blog/2009/12/02/abanq-integrando-con-jasperserverjasperreports/#comments</comments>
		<pubDate>Wed, 02 Dec 2009 09:35:20 +0000</pubDate>
		<dc:creator>David Pinelo</dc:creator>
				<category><![CDATA[ERP Open Source]]></category>
		<category><![CDATA[Librerías Qt]]></category>
		<category><![CDATA[Programación]]></category>

		<guid isPermaLink="false">http://www.pinelo.com/blog/?p=297</guid>
		<description><![CDATA[No me gusta el editor de informes de AbanQ... ni me gusta Kugar, ni me gusta que Qt Designer como gestor de informes. El primero, porque creo que está muy por detrás de otros motores de generación de informes que existen en el mundo del software libre (aparte de que está abandonado como proyecto, para [...]]]></description>
			<content:encoded><![CDATA[<p>No me gusta el editor de informes de AbanQ... ni me gusta <a href="http://www.thekompany.com/projects/kugar/">Kugar</a>, ni me gusta que Qt Designer como gestor de informes. El primero, porque creo que está muy por detrás de otros motores de generación de informes que existen en el mundo del software libre (aparte de que está abandonado como proyecto, para integrarlo en <a href="http://www.koffice.org">KOffice</a> dentro de <a href="http://www.koffice.org/kexi">Kexi</a>, y del segundo, porque no es un generador de informes.</p>
<p>Me gusta <a href="http://www.jasperforge.org/jasperreports">JasperReports</a>: Es una herramienta muy completa, en software libre y que rivaliza con cualquier herramienta de generación de informes (he sido usuario/programador durante mucho tiempo de Crystal Reports y Oracle Reports y realmente, para mis necesidades y según mi experiencia, JasperReports está por encima).</p>
<p>Además, JasperReports, tiene una interfaz de usuario para la edición de informes, <a href="http://jasperforge.org/projects/ireport">iReports</a>. Si la probáis, veréis que es muy muy superior a Kugar o QtDesigner (utilizando esta como generación de informes) y por encima de Crystal Repors, por ejemplo.</p>
<p>Por si esto no fuera poco, dispone de <a href="http://jasperforge.org/plugins/project/project_home.php?projectname=jasperserver">JasperServer</a> que es una aplicación, que actúa como servidor y/o repositorio de los diferentes documentos que generéis con las anteriores aplicaciones. En realidad, es mucho más, tratando de ser una herramienta de Bussiness Inteligence... permitiendo, junto con JasperAnalysis, ser una herramienta de análisis....</p>
<p>La idea de funcionamiento con estas herramientas es la siguiente:</p>
<p>Se instala JasperServer en vuestro servidor. (Yo lo tengo instalado en un Jboss con PostgreSQL). Os proporciona una aplicación web en la que podéis gestionar, administrar y utilizar los informes que vayáis definiendo. No sólo eso, permite programar informes, enviando los resultados a correo electrónico, generación automática de los informes de formato HTML, PDF, Excel, OOffice...</p>
<p>Utilizáis iReport en vuestro ordenador para crear los informes (bien sea con los asistentes o desde cero)... los informes permiten agregarles código Java, con lo cual podéis hacer cualquier cosa, repito <em>cualquier cosa</em> con los informes. Genero documentos, que nunca diríais que están generados por una herramienta de gestión de informes... pensaríais que son cartas escritas por una persona. Desde iReport, y mediante webservices podéis interactuar y manipular el repositorio de JasperServer de una manera muy cómoda, con lo que la  creación, modificación o actualización de informes es una tarea sencilla y fácilmente asumible.</p>
<p>¿Cómo integrar esto con AbanQ? Queremos unir mundo Java y C++... además, queremos integrar un servidor de informes en nuestro AbanQ. Solución: WebServices. JasperServer permite acceder a todo su repositorio desde WebServices... Y eso es lo que haremos en AbanQ. Para ello, integraremos dentro de AbanQ, la librería de C++ gsoap.</p>
<p>Vamos a hacer una integración sencilla, pero funcional (cuando termine los artículos indicaré por dónde creo que deberían ir las líneas de mejora de esto).</p>
]]></content:encoded>
			<wfw:commentRss>http://www.pinelo.com/blog/2009/12/02/abanq-integrando-con-jasperserverjasperreports/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>AbanQ: Añadiendo una nueva columna de ordenación en el FLDataTable</title>
		<link>http://www.pinelo.com/blog/2009/11/30/abanq-anadiendo-una-nueva-columna-de-ordenacion-en-el-fldatatable/</link>
		<comments>http://www.pinelo.com/blog/2009/11/30/abanq-anadiendo-una-nueva-columna-de-ordenacion-en-el-fldatatable/#comments</comments>
		<pubDate>Mon, 30 Nov 2009 07:00:43 +0000</pubDate>
		<dc:creator>David Pinelo</dc:creator>
				<category><![CDATA[ERP Open Source]]></category>
		<category><![CDATA[Librerías Qt]]></category>
		<category><![CDATA[Programación]]></category>

		<guid isPermaLink="false">http://www.pinelo.com/blog/?p=285</guid>
		<description><![CDATA[El trabajo con sistemas ERP, y especialmente con AbanQ siempre se basan en la navegación por listados de datos (o grids de datos). No estoy muy seguro de que realmente ese sea el sistema más usable o adecuado, pero al menos es muy popular. En mi afán de tratar de hacer la herramienta aún más [...]]]></description>
			<content:encoded><![CDATA[<p>El trabajo con sistemas ERP, y especialmente con AbanQ siempre se basan en la navegación por listados de datos (o grids de datos). No estoy muy seguro de que realmente ese sea el sistema más usable o adecuado, pero al menos es muy popular.</p>
<p>En mi afán de tratar de hacer la herramienta aún más usable, echaba en falta una segunda columna de ordenación. Si mi empresa factura todo a través de AbanQ, el número de datos en esa rejilla es grande. Puede filtrarse utilizando el campo de filtro directo que AbanQ provee, pero ese campo de filtro también limita la ordenación de los datos filtrados que se presentan. Tener una segunda columna, como aparece en la imágen, puede ayudar aún más a localizar datos rápidamente.<br />
<img src="http://www.pinelo.com/blog/postimgs/2009/11/ordenaciondoble.jpg" alt="Doble columna de ordenacion" width="480" /></p>
<p>Bien, veamos cómo hacerlo.</p>
<p>Como siempre, tocaremos un poquito de código de AbanQ... y de Qt 3.3.</p>
<p><span id="more-285"></span>Al lío:</p>
<p>Primero añadir, en el FLWidgetTableDB.ui mediante el Qt Designer 3.3 el segundo combobox de ordenación. Lo vamos a llamar comboBoxFieldToSearch2 (muy original... :P). También debemos añadir en el Designer un Slot,</p>
<p>void putSecondCol (const QString &amp;)</p>
<p>y añadimos una conexión:</p>
<pre>Sender: comboBoxFieldToSearch2
Signal: activated (const QString &amp;)
Receiver: FLWidgetTableDB
Slot: putSecondCol(const QString &amp;)</pre>
<p>Vale. Hemos terminado en Designer. Toca código en C++ de AbanQ.</p>
<p>Definimos ese slot que acabamos de crear en Designer en el código de AbanQ (en esto sí que han ganado las Qt 4 eliminando la definición de slots en el designer...)</p>
<pre>Archivo: FLObjectFactory.h
....
class FL_EXPORT FLTableDBInterface : public QObject {
  ...</pre>
<pre>public:
...
/**
dpinelo: Establece el segundo campo de búsqueda
*/
void putSecondCol( const QString &amp; c ) {
obj_-&gt;putSecondCol( c );
}</pre>
<p>De esta forma, cuando el usuario cliquee en el combo, se producirá la ordenación por el segundo campo.</p>
<pre>Archivo: FLTableDB.h
...
class FL_EXPORT FLTableDB: public FLWidgetTableDB {
  ...
  private:
  /**
  dpinelo Indica el número de columna por la que ordenar los registros
   */
  int sortColumn2_;
  /**
  dpinelo Indica el sentido ascendente o descendente del la ordenacion actual de los registros
   */
  bool orderAsc2_;

  protected slots:
  ...
  // dpinelo
  void putSecondCol( int c );
  void putSecondCol( const QString &amp; c );</pre>
<p>Y ahora vamos al código de verdad. Sólo he querido añadir la funcionalidad de ordenación secundaria... de modo que sigo dejando la mayor parte de la responsabilidad de la ordenación al código de AbanQ. Busco que el usuario pueda visualizar por una columna diferente, y cambiar el orden de visualización en esa columna.</p>
<p>Para este fichero, mejor pongo un diff .... Ya digo, tengo que organizar todas mis diferencias de código y publicarlas aquí adecuadamente. Espero poder hacerlo la semana que viene, antes de publicar las modificaciones de integración con OpenKM y JasperServer</p>
<pre>Index: abanq/src/flbase/FLTableDB.cpp
===================================================================
--- abanq/src/flbase/FLTableDB.cpp	(revisión: 117)
+++ abanq/src/flbase/FLTableDB.cpp	(copia de trabajo)
@@ -89,7 +89,9 @@
     tabDataLayout-&gt;addWidget( tableRecords_ );
     setTabOrder( tableRecords_, lineEditSearch );
     setTabOrder( lineEditSearch, comboBoxFieldToSearch );
-    lineEditSearch-&gt;installEventFilter( this );
+	// dpinelo
+	setTabOrder( comboBoxFieldToSearch, comboBoxFieldToSearch2 );
+	lineEditSearch-&gt;installEventFilter( this );
     tableRecords_-&gt;installEventFilter( this );
     connect( tableRecords_-&gt;horizontalHeader(), SIGNAL( clicked( int ) ), this, SLOT( switchSortOrder( int ) ) );
   }
@@ -109,7 +111,9 @@
     tabDataLayout-&gt;addWidget( tableRecords_ );
     setTabOrder( tableRecords_, lineEditSearch );
     setTabOrder( lineEditSearch, comboBoxFieldToSearch );
-    lineEditSearch-&gt;installEventFilter( this );
+	// dpinelo
+	setTabOrder( comboBoxFieldToSearch, comboBoxFieldToSearch2 );
+	lineEditSearch-&gt;installEventFilter( this );
     tableRecords_-&gt;installEventFilter( this );
   }
   FLSqlCursor * tCursor = tableRecords_-&gt;cursor();
@@ -127,7 +131,8 @@
 }
 bool FLTableDB::eventFilter( QObject * obj, QEvent * ev ) {
-  if ( !tableRecords_ || !lineEditSearch || !comboBoxFieldToSearch || !cursorVisualizacion_ )
+	// dpinelo
+  if ( !tableRecords_ || !lineEditSearch || !comboBoxFieldToSearch || !cursorVisualizacion_ || !comboBoxFieldToSearch2 )
     return FLWidgetTableDB::eventFilter( obj, ev );
   if ( ev-&gt;type() == QEvent::KeyPress &amp;&amp; obj == tableRecords_ ) {
     QKeyEvent * k = static_cast&lt;QKeyEvent *&gt;( ev );
@@ -165,21 +170,55 @@
 }
 void FLTableDB::putFirstCol( const QString &amp; c ) {
-        // dpinelo: No entiendo cómo el código funciona sin esto...
-        FLTableMetaData *tMD = cursorVisualizacion_-&gt;metadata();
-        cursorVisualizacion_-&gt;setSort( cursorVisualizacion_-&gt;index( tMD-&gt;fieldAliasToName( c ) ) );
-  moveCol( c, QString::null );
+	// dpinelo: Introducimos la ordenación por los dos campos
+	QSqlIndex sort;
+	FLTableMetaData *tMD = cursorVisualizacion_-&gt;metadata();
+	sort.append ( tMD-&gt;fieldAliasToName( c ) );
+	sort.append ( tMD-&gt;fieldAliasToName( comboBoxFieldToSearch2-&gt;text( comboBoxFieldToSearch2-&gt;currentItem() ) ) );
+	cursorVisualizacion_-&gt;setSort( sort );
+	moveCol( c, QString::null );
 }
 void FLTableDB::putFirstCol( int c ) {
-        // dpinelo: No entiendo cómo el código funciona sin esto...
-        QHeader *horizHeader = tableRecords()-&gt;horizontalHeader();
-        FLTableMetaData *tMD = cursorVisualizacion_-&gt;metadata();
-        cursorVisualizacion_-&gt;setSort( cursorVisualizacion_-&gt;index( tMD-&gt;fieldAliasToName( horizHeader-&gt;label(c) ) ) );
-  moveCol( c, 0 );
+	// dpinelo: Introducimos la ordenación por los dos campos
+	QSqlIndex sort;
+	QHeader *horizHeader = tableRecords()-&gt;horizontalHeader();
+	FLTableMetaData *tMD = cursorVisualizacion_-&gt;metadata();
+	sort.append ( tMD-&gt;fieldAliasToName( horizHeader-&gt;label(c) ) );
+	sort.append ( tMD-&gt;fieldAliasToName( comboBoxFieldToSearch2-&gt;text( comboBoxFieldToSearch2-&gt;currentItem() ) ) );
+	cursorVisualizacion_-&gt;setSort( sort );
+	moveCol( c, 0 );
 }
-void FLTableDB::moveCol( const QString &amp; from, const QString &amp; to ) {
+// dpinelo
+void FLTableDB::putSecondCol( const QString &amp; c ) {
+    // dpinelo: Introducimos la ordenación por los dos campos
+	QSqlIndex sort;
+	FLTableMetaData *tMD = cursorVisualizacion_-&gt;metadata();
+	sort.append ( tMD-&gt;fieldAliasToName( comboBoxFieldToSearch-&gt;text( comboBoxFieldToSearch-&gt;currentItem() ) ) );
+	sort.append ( tMD-&gt;fieldAliasToName( c ) );
+	cursorVisualizacion_-&gt;setSort( sort );
+	// Aprovechamos que siempre, el combo box de filtro presenta sus items ordenados igual que el grid
+	if ( c != comboBoxFieldToSearch-&gt;text( comboBoxFieldToSearch-&gt;currentItem() + 1 ) ) {
+		moveCol( c, comboBoxFieldToSearch-&gt;text( comboBoxFieldToSearch-&gt;currentItem() + 1 ), false );
+	}
+}
+
+void FLTableDB::putSecondCol( int c ) {
+    // dpinelo: No entiendo cómo el código funciona sin esto...
+	QSqlIndex sort;
+	QHeader *horizHeader = tableRecords()-&gt;horizontalHeader();
+	FLTableMetaData *tMD = cursorVisualizacion_-&gt;metadata();
+	sort.append ( tMD-&gt;fieldAliasToName( comboBoxFieldToSearch-&gt;text( comboBoxFieldToSearch-&gt;currentItem() ) ) );
+	sort.append (  tMD-&gt;fieldAliasToName( horizHeader-&gt;label(c) ) );
+	cursorVisualizacion_-&gt;setSort( sort );
+	// Aprovechamos que siempre, el combo box de filtro presenta sus items ordenados igual que el grid
+	if ( c != ( comboBoxFieldToSearch-&gt;currentItem() + 1 ) ) {
+		moveCol( c, ( comboBoxFieldToSearch-&gt;currentItem() + 1), false );
+	}
+}
+
+void FLTableDB::moveCol( const QString &amp; from, const QString &amp; to, bool firstSearch ) {
   if ( !topWidget || !cursorVisualizacion_ || !showed )
     return ;
@@ -210,11 +249,12 @@
 #endif
     return;
   }
-  moveCol( iFrom - sortColumn_, iTo - sortColumn_ );
+  moveCol( iFrom - sortColumn_, iTo - sortColumn_, firstSearch );
 }
-void FLTableDB::moveCol( int from, int to ) {
-  if ( from == to || !lineEditSearch || !comboBoxFieldToSearch || !cursorVisualizacion_ )
+void FLTableDB::moveCol( int from, int to, bool firstSearch ) {
+	// dpinelo
+  if ( from == to || !lineEditSearch || !comboBoxFieldToSearch || !cursorVisualizacion_ || !comboBoxFieldToSearch2 )
     return ;
   if ( comboBoxFieldToSearch-&gt;text( from ) == "*" || comboBoxFieldToSearch-&gt;text( to ) == "*" )
@@ -252,10 +292,13 @@
   refresh( true );
   if ( !textSearch.isEmpty() ) {
     refresh( false, true );
-    disconnect( lineEditSearch, SIGNAL( textChanged( const QString&amp; ) ), this, SLOT( filterRecords( const QString&amp; ) ) );
-    lineEditSearch-&gt;setText( textSearch );
-    connect( lineEditSearch, SIGNAL( textChanged( const QString&amp; ) ), this, SLOT( filterRecords( const QString&amp; ) ) );
-    lineEditSearch-&gt;selectAll();
+	if ( firstSearch ) {
+		disconnect( lineEditSearch, SIGNAL( textChanged( const QString&amp; ) ), this, SLOT( filterRecords( const QString&amp; ) ) );
+		// dpinelo
+    	lineEditSearch-&gt;setText( textSearch );
+    	connect( lineEditSearch, SIGNAL( textChanged( const QString&amp; ) ), this, SLOT( filterRecords( const QString&amp; ) ) );
+	    lineEditSearch-&gt;selectAll();
+	}
     seekCursor();
     QTimer::singleShot( 0, tableRecords_, SLOT( ensureRowSelectedVisible() ) );
   } else
@@ -329,7 +372,9 @@
 }
 void FLTableDB::refresh( const bool refreshHead, const bool refreshData ) {
-  if ( !lineEditSearch || !comboBoxFieldToSearch || !cursorVisualizacion_ || ( topWidget &amp;&amp; !topWidget-&gt;isShown() ) )
+	// dpinelo
+  if ( !lineEditSearch || !comboBoxFieldToSearch || !cursorVisualizacion_ || ( topWidget &amp;&amp; !topWidget-&gt;isShown() ) ||
+	 !comboBoxFieldToSearch2 )
     return ;
   FLTableMetaData * tMD = cursorVisualizacion_-&gt;metadata();
@@ -349,7 +394,7 @@
                                           true, 0, 0, false, false, false, QVariant(), false, QString::null, true, false, false );
         tMD-&gt;addFieldMD( fieldCheck );
       }
-          if ( !cursorVisualizacion_-&gt;contains( fieldNameCheckColumn_ ) ) {
+      if ( !cursorVisualizacion_-&gt;contains( fieldNameCheckColumn_ ) ) {
                   QSqlRecord recordCursor( *( cursorVisualizacion_-&gt;editBuffer() ) );
         QSqlFieldInfo fakeFieldInfo( fieldNameCheckColumn_, QVariant::Bool, -1, -1, -1, QVariant(), 0, false, false, true );
                 cursorVisualizacion_-&gt;clear();
@@ -432,9 +477,11 @@
 	comboBoxFieldToSearch-&gt;insertItem( "*" );
     horizHeader-&gt;setClickEnabled( false );
     horizHeader-&gt;setClickEnabled( true, sortColumn_ );
-    horizHeader-&gt;setSortIndicator( -1, Qt::Ascending );
+	// dpinelo
+	horizHeader-&gt;setClickEnabled( true, sortColumn2_ );
+	horizHeader-&gt;setSortIndicator( -1, Qt::Ascending );
     horizHeader-&gt;setSortIndicator( sortColumn_, ( orderAsc_ ? Qt::Ascending : Qt::Descending ) );
-    horizHeader-&gt;show();
+	horizHeader-&gt;show();
   }
   if ( refreshData || sender() ) {
@@ -1013,10 +1060,15 @@
   aliasCheckColumn_ = t;
 }
-void FLTableDB::switchSortOrder( int ) {
-  orderAsc_ = !orderAsc_;
-  tableRecords()-&gt;hide();
-  refresh( true, true );
+void FLTableDB::switchSortOrder( int col ) {
+  // dpinelo
+	if ( col == 0 ) {
+		orderAsc_ = !orderAsc_;
+	} else if ( col == 1 ) {
+		orderAsc2_ = !orderAsc2_;
+	}
+	tableRecords()-&gt;hide();
+	refresh( true, true );
 }
 void FLTableDB::activeTabData( bool on ) {</pre>
<p>Eah... por ahí va eso.</p>
<pre><!--EndFragment--></pre>
]]></content:encoded>
			<wfw:commentRss>http://www.pinelo.com/blog/2009/11/30/abanq-anadiendo-una-nueva-columna-de-ordenacion-en-el-fldatatable/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>AbanQ: Ampliando la funcionalidad de los optionslist</title>
		<link>http://www.pinelo.com/blog/2009/11/27/abanq-ampliando-la-funcionalidad-de-los-optionslist/</link>
		<comments>http://www.pinelo.com/blog/2009/11/27/abanq-ampliando-la-funcionalidad-de-los-optionslist/#comments</comments>
		<pubDate>Fri, 27 Nov 2009 10:55:00 +0000</pubDate>
		<dc:creator>David Pinelo</dc:creator>
				<category><![CDATA[ERP Open Source]]></category>
		<category><![CDATA[Librerías Qt]]></category>
		<category><![CDATA[Programación]]></category>

		<guid isPermaLink="false">http://www.pinelo.com/blog/?p=280</guid>
		<description><![CDATA[Una de las ayudas que tiene AbanQ a la hora de construir formularios que presentan registros asociados, es sin duda la posibilidad de definir OptionsList. Para una columna de base de datos, podemos definir un conjunto de posibles valores que se almacenarán en la misma, utilizando el tag optionslist dentro de la definición del campo, [...]]]></description>
			<content:encoded><![CDATA[<p>Una de las ayudas que tiene AbanQ a la hora de construir formularios que presentan registros asociados, es sin duda la posibilidad de definir OptionsList. Para una columna de base de datos, podemos definir un conjunto de posibles valores que se almacenarán en la misma, utilizando el tag optionslist dentro de la definición del campo, en el .mtd de la tabla correspondiente. AbanQ, automáticamente, creará un combobox en la vista (el formulario) para que el usuario sólo pueda escoger uno de esos valores.</p>
<p>Pero esta solución, aunque muy buena, no es óptima. Si la columna es un campo de tipo varchar (o string, como se le llama en AbanQ), el optionslist, guarda siempre en esa columna la cadena entera. Supongamos, por ejemplo, una columna "tipo_articulo"  y un options list</p>
<pre>&lt;optionslist&gt;Articulo prefabricado, Materia prima, Producto intermedio, Producto terminado&lt;/optionslist&gt;</pre>
<p>El detalle, es que esas cadenas se almacenan constantemente en la columna de la base de datos. Eso no es mayor problema si queremos indexar sobre esa columna, por ejemplo... Quizás es más interesante, el poder guardar en lugar de esos valores, otros, según la siguiente relación</p>
<pre>&lt;optionslist&gt;Articulo prefabricado, Materia prima, Producto intermedio, Producto terminado&lt;/optionslist&gt;
&lt;optionsvalues&gt;1,2,3,4&lt;/optionsvalues&gt;</pre>
<p>De esta forma, cuando el usuario escoga en el combo box "Artículo prefabricado" en la columna de la base de datos, se guardará un 1.</p>
<p><span id="more-280"></span>¿Cómo se puede implementar esto en AbanQ? Es relativamente sencillo. Os pongo a continuación las diferencias entre archivos.</p>
<pre>Index: abanq/src/flbase/FLDataTable.cpp
===================================================================
--- abanq/src/flbase/FLDataTable.cpp	(revisión: 114)
+++ abanq/src/flbase/FLDataTable.cpp	(copia de trabajo)
@@ -233,18 +233,37 @@
       break;
       case QVariant::String: {
-        text = field-&gt;value().toString();
+        QString textView;
+		text = field-&gt;value().toString();
         if ( fieldTMD-&gt;hasOptionsList() ) {
-          QStringList ol( fieldTMD-&gt;optionsList() );
-          if ( !ol.contains( text ) ) {
-            QVariant defVal( fieldTMD-&gt;defaultValue() );
-            if ( defVal.isValid() )
-              text = defVal.toString();
-            else
-              text = ol.first();
-          }
-          text = FLUtil::translate( "MetaData", text );
-        }
+			// dpinelo: El valor del campo lo buscamos en la lista de valores si está definida
+			if ( fieldTMD-&gt;optionsValues().isEmpty() ) {
+				QStringList ol( fieldTMD-&gt;optionsList() );
+				if ( !ol.contains( text ) ) {
+					QVariant defVal( fieldTMD-&gt;defaultValue() );
+					if ( defVal.isValid() )
+						text = defVal.toString();
+					else
+						text = ol.first();
+				}
+				text = FLUtil::translate( "MetaData", text );
+			} else {
+				QStringList ol( fieldTMD-&gt;optionsValues() );
+				QStringList olList ( fieldTMD-&gt;optionsList() );
+			// ¿Contiene el valor?
+				if ( !ol.contains( text ) ) {
+					QVariant defVal( fieldTMD-&gt;defaultValue() );
+					if ( defVal.isValid() )
+						textView = defVal.toString();
+					else {
+						textView = olList.first();
+					}
+				} else {
+					textView = olList[ol.findIndex(text)];
+				}
+			}
+			text = FLUtil::translate( "MetaData", textView );
+		}
         p-&gt;drawText( 2, 2, cr.width() - 4, cr.height() - 4, fieldAlignment( field ), text );
       }
       break;

Index: abanq/src/flbase/FLFieldDB.cpp
===================================================================
--- abanq/src/flbase/FLFieldDB.cpp	(revisión: 114)
+++ abanq/src/flbase/FLFieldDB.cpp	(copia de trabajo)
@@ -294,7 +294,12 @@
   QString tAux( t );

   if ( ol &amp;&amp; editor_ )
-    tAux = field-&gt;optionsList()[ ::qt_cast( editor_ )-&gt;currentItem()];
+	  // dpinelo
+	  if ( field-&gt;optionsValues().isEmpty() ) {
+	  	tAux = field-&gt;optionsList()[ ::qt_cast( editor_ )-&gt;currentItem()];
+	  } else {
+		tAux = field-&gt;optionsValues()[ ::qt_cast( editor_ )-&gt;currentItem()];
+	  }

   if ( !cursor_-&gt;bufferIsNull( fieldName_ ) ) {
     if ( tAux == cursor_-&gt;valueBuffer( fieldName_ ).toString() ) {
@@ -403,12 +408,25 @@

   if ( field-&gt;hasOptionsList() ) {
     int idxItem = -1;
-    if ( v.type() == QVariant::String )
-      idxItem = field-&gt;optionsList().findIndex( v.toString() );
-    if ( idxItem == -1 )
+    if ( v.type() == QVariant::String ) {
+		// dpinelo
+		if ( field-&gt;optionsValues().isEmpty() ) {
+			idxItem = field-&gt;optionsList().findIndex( v.toString() );
+		} else {
+			idxItem = field-&gt;optionsValues().findIndex( v.toString() );
+		}
+	}
+	if ( idxItem == -1 )
       idxItem = v.toInt();
     ::qt_cast( editor_ )-&gt;setCurrentItem( idxItem );
-    updateValue( ::qt_cast( editor_ )-&gt;currentText() );
+	if ( field-&gt;optionsValues().isEmpty() ) {
+		updateValue( ::qt_cast( editor_ )-&gt;currentText() );
+	} else {
+		int idxValue = field-&gt;optionsList().findIndex( ::qt_cast( editor_ )-&gt;currentText() );
+		if ( idxValue != -1 ) {
+			updateValue( field-&gt;optionsValues()[idxValue] );
+		}
+	}
 #ifdef FL_TEST
     ::qt_cast( qApp )-&gt;continueTesting( FLTester::FIELD_READY, this-&gt;name() );
 #endif
@@ -1317,19 +1335,39 @@
       break;

     case QVariant::String: {
-      bool doHome = false;
-      if ( ol ) {
-        if ( v.toString() == ::qt_cast( editor_ )-&gt;currentText() )
-          return ;
-      } else {
-        if ( v.toString() == ::qt_cast( editor_ )-&gt;text() )
-          return ;
-        doHome = ( ::qt_cast( editor_ )-&gt;text().isEmpty() );
-      }
+		bool doHome = false;
+		if ( ol ) {
+			// dpinelo
+			if ( field-&gt;optionsValues().isEmpty() ) {
+				if ( v.toString() == ::qt_cast( editor_ )-&gt;currentText() ) {
+					return ;
+				} else {
+					if ( v.toString() == ::qt_cast( editor_ )-&gt;text() )
+						return ;
+					doHome = ( ::qt_cast( editor_ )-&gt;text().isEmpty() );
+				}
+			} else {
+				int index = field-&gt;optionsValues().findIndex( v.toString() );
+				if ( index != -1 ) {
+					QString comp = field-&gt;optionsList()[index];
+					if ( comp == ::qt_cast( editor_ )-&gt;currentText() ) {
+						return ;
+					} else {
+						if ( comp == ::qt_cast( editor_ )-&gt;text() )
+							return ;
+						doHome = ( ::qt_cast( editor_ )-&gt;text().isEmpty() );
+					}
+				}
+			}
+		}
 	  disconnect( editor_, SIGNAL( textChanged( const QString &amp; ) ), this, SLOT( updateValue( const QString &amp; ) ) );
       if ( v.isValid() &amp;&amp; !v.isNull() ) {
-        if ( ol )
-          ::qt_cast( editor_ )-&gt;setCurrentItem( field-&gt;optionsList().findIndex( v.toString() ) );
+        if ( ol ) {
+			// dpinelo
+			int index = field-&gt;optionsValues().findIndex( v.toString() );
+			::qt_cast( editor_ )-&gt;setCurrentItem( index );
+		//		::qt_cast( editor_ )-&gt;setCurrentItem( field-&gt;optionsList().findIndex( v.toString() ) );
+		}
         else
           ::qt_cast( editor_ )-&gt;setText( v.toString() );
       } else {
@@ -1534,8 +1572,11 @@
         doHome = ( ::qt_cast( editor_ )-&gt;text().isEmpty() );
 	  disconnect( editor_, SIGNAL( textChanged( const QString &amp; ) ), this, SLOT( updateValue( const QString &amp; ) ) );
       if ( !null ) {
-        if ( ol )
-          ::qt_cast( editor_ )-&gt;setCurrentItem( field-&gt;optionsList().findIndex( v.toString() ) );
+        if ( ol ) {
+			// dpinelo
+			int index = field-&gt;optionsValues().findIndex( v.toString() );
+			::qt_cast( editor_ )-&gt;setCurrentItem( index );
+		}
         else
           ::qt_cast( editor_ )-&gt;setText( v.toString() );
       } else {

Index: abanq/src/flbase/FLFieldMetaData.cpp
===================================================================
--- abanq/src/flbase/FLFieldMetaData.cpp	(revisión: 114)
+++ abanq/src/flbase/FLFieldMetaData.cpp	(copia de trabajo)
@@ -59,6 +59,8 @@
     delete relationM1_;

   optionsList_.clear();
+  // dpinelo
+  optionsValues_.clear();
 }

 FLFieldMetaData::FLFieldMetaData( const QString &amp; n, const QString &amp; a, bool aN, bool iPK,
@@ -196,3 +198,24 @@
   }
   return type;
 }
+
+// dpinelo
+void FLFieldMetaData::setOptionsValues(const QString &amp; ol)
+{
+	d-&gt;optionsValues_.clear();
+	QString olTranslated = ol;
+	if ( ol.contains( "QT_TRANSLATE_NOOP" ) ) {
+		QStringList components = QStringList::split( ';', olTranslated );
+		QString component;
+
+		olTranslated = "";
+		for ( int i = 0; i &lt; components.count(); i++ ) {
+			component = components[ i ];
+			component = component.mid( 30, component.length() - 32 );
+			if ( i &gt; 0 )
+				olTranslated += ",";
+			olTranslated += component;
+		}
+	}
+	d-&gt;optionsValues_ = QStringList::split( ',', olTranslated );
+}

Index: abanq/src/flbase/FLFieldMetaData.h
===================================================================
--- abanq/src/flbase/FLFieldMetaData.h	(revisión: 114)
+++ abanq/src/flbase/FLFieldMetaData.h	(copia de trabajo)
@@ -384,6 +384,14 @@
   QStringList optionsList();

   /**
+  dpinelo: La lista de opciones para el campo se compone del valor que se guarda
+  y del valor que se muestra
+
+  @return Lista de valores del campo
+  */
+  QStringList optionsValues();
+
+  /**
   Establece la lista de opciones para el campo

   @param ol Cadena de texto con la opciones para el campo
@@ -392,6 +400,14 @@
   void setOptionsList( const QString &amp; ol );

   /**
+  dpinelo: Establece la lista de valores para el campo
+
+  @param ol Cadena de texto con la opciones para el campo
+  separada por comas, p.e. "opcion1,opcion2,opcion3"
+   */
+  void setOptionsValues( const QString &amp; ol );
+
+  /**
   Obtiene si el campo es de tipo Check
   */
   bool isCheck() const;
@@ -572,6 +588,11 @@
   Lista de opciones para el campo
   */
   QStringList optionsList_;
+
+  /**
+  dpinelo: Lista de valores para el campo
+  */
+  QStringList optionsValues_;

   /**
   Indica si las modificaciones del campo se hacen fuera de cualquier transaccion.
@@ -746,6 +767,11 @@
   return d-&gt;optionsList_;
 }

+// dpinelo
+inline QStringList FLFieldMetaData::optionsValues() {
+	return d-&gt;optionsValues_;
+}
+
 inline bool FLFieldMetaData::isCompoundKey() const {
   return d-&gt;isCompoundKey_;
 }

Index: abanq/src/flbase/FLManager.cpp
===================================================================
--- abanq/src/flbase/FLManager.cpp	(revisión: 114)
+++ abanq/src/flbase/FLManager.cpp	(copia de trabajo)
@@ -302,6 +302,8 @@
   bool aN = true, iPK = true, c = false, iNX = false, uNI = false, coun = false, oT = false, vG = true;
   int t = QVariant::Int, l = 0, pI = 4, pD = 0;
   QVariant dV = QVariant();
+  // dpinelo
+  QString olValues;

   QDomNode no = field-&gt;firstChild();

@@ -446,7 +448,13 @@
         no = no.nextSibling();
         continue;
       }
-    }
+	  // dpinelo
+	  if ( e.tagName() == "optionsvalues" ) {
+		  olValues = e.text();
+		  no = no.nextSibling();
+		  continue;
+	  }
+	}
     no = no.nextSibling();
   }

@@ -455,6 +463,10 @@

   if ( !ol.isEmpty() )
     f-&gt;setOptionsList( ol );
+
+  // dpinelo
+  if ( !olValues.isEmpty() )
+	  f-&gt;setOptionsValues ( olValues );

   no = field-&gt;firstChild();</pre>
<p>Detalles: Los números de líneas quizás no sean los correctos. Mi código está algo modificado, pero al menos sí aparecen el código que he modificado. En cualquier caso, voy a extraer todos las diferencias entre mi versión de AbanQ y la publicada por InfoSial indicando qué he añadido, para que así podáis sincronizar vuestro código con el mío sin problemas... pero necesito algo de tiempo para hacerlo.</p>
<pre><!--EndFragment--></pre>
]]></content:encoded>
			<wfw:commentRss>http://www.pinelo.com/blog/2009/11/27/abanq-ampliando-la-funcionalidad-de-los-optionslist/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Buenas prácticas en AbanQ: Usando vistas con PostgreSQL</title>
		<link>http://www.pinelo.com/blog/2009/05/27/buenas-practicas-en-abanq-usando-vistas-con-postgresql/</link>
		<comments>http://www.pinelo.com/blog/2009/05/27/buenas-practicas-en-abanq-usando-vistas-con-postgresql/#comments</comments>
		<pubDate>Wed, 27 May 2009 09:50:06 +0000</pubDate>
		<dc:creator>David Pinelo</dc:creator>
				<category><![CDATA[ERP Open Source]]></category>
		<category><![CDATA[Programación]]></category>

		<guid isPermaLink="false">http://www.pinelo.com/blog/?p=256</guid>
		<description><![CDATA[Una de las características fundamentales de los sistemas ERP es el ofrecer información útil para el usuario. Generalmente, esa información útil surge de cruzar datos de diferentes orígenes (en nuestro caso, tablas de base de datos). Vamos a poner un ejemplo: En AbanQ tenemos el formulario de artículos dentro del módulo de almacén. Quizás sería [...]]]></description>
			<content:encoded><![CDATA[<p>Una de las características fundamentales de los sistemas ERP es el ofrecer información útil para el usuario. Generalmente, esa información útil surge de cruzar datos de diferentes orígenes (en nuestro caso, tablas de base de datos).</p>
<p>Vamos a poner un ejemplo: En AbanQ tenemos el formulario de artículos dentro del módulo de almacén. Quizás sería interesante añadir una opción para comprobar los movimientos de stocks, entendiendo por los mismos las entradas a almacén (albaranes dados de alta) y salidas (albaranes a clientes). Sería interesante visualizar en un TableDB el resultado de una query que agrupara ambas entradas. Claro, ocurre que para hacer esa query, es necesario utilizar una cláusula SQL, <em>union</em>.  Así por ejemplo, podríamos desear tener una consulta tal como esta</p>
<pre>select 'Entrada' as tipo, lp.referencia, lp.cantidad, ap.idalbaran
from albaranesprov as ap inner join lineasalbaranesprov as lp
on ap.idalbaran = lp.idalbaran
union
select 'Salida' as tipo, lc.referencia, lc.cantidad, ac.idalbaran
from albaranescli as ac inner join lineasalbaranescli as lc
on ac.idalbaran = lc.idalbaran

<span id="more-256"></span></pre>
<p>Si usáis PosgreSQL, lo ideal, es crear esta consulta como una vista (así garantizáis que la consulta se encuentra precompilada y optimizada antes de ejecutarse, lo que proporciona una mayor velocidad de ejecución, en general). Pongamos por ejemplo, que esta consulta le damos el nombre tvw_movimientosstock.</p>
<p>Ahora bien, ¿cómo maneja AbanQ las vistas en PostgreSQL? No las maneja, ni falta que hace (bueno, relativamente). Como utiliza las Qt, éstas hacen transparente el acceso a tablas y/o vistas en PostgreSQL por el driver que traen integrado. Esto significa que podemos crear una vista, y acceder a ella desde AbanQ generando el archivo de metadatos de tabla .mtd y el archivo de query .qry correspondiente. Sólo hay que tener en cuenta un par de detalles.</p>
<p>Veamos el archivo Query movimientosstocksqry.qry.</p>
<pre>&lt;!DOCTYPE QRY&gt;
&lt;QRY&gt;
	&lt;name&gt;movimientosstocksqry&lt;/name&gt;

	&lt;tables&gt;lineasalbaranesprov,albaranesprov&lt;/tables&gt;

	&lt;select&gt;
		tipo, referencia, cantidad, idalbaran
	&lt;/select&gt;

	&lt;from&gt;
		tvw_movimientosstocks
	&lt;/from&gt;

	&lt;where&gt;
	&lt;/where&gt;

	&lt;order&gt;
	&lt;/order&gt;
&lt;/QRY&gt;</pre>
<p>El contenido de &lt;table&gt; "importa poco", ya que quien realmente tiene la información de cómo acceder es el elemento vista tvw_movimientosstock. El .mtd es sencillo: movimientosstocksqry.mtd</p>
<pre>&lt;!DOCTYPE TMD&gt;
&lt;TMD&gt;
    &lt;!--dpinelo: Tabla creada para mantener el almacen de papeles--&gt;
    &lt;name&gt;movimientosstocksqry&lt;/name&gt;
    &lt;query&gt;movimientosstocksqry&lt;/query&gt;
    &lt;alias&gt;QT_TRANSLATE_NOOP(&amp;quot;MetaData&amp;quot;,&amp;quot;Movimientos del stocks&amp;quot;)&lt;/alias&gt;
    &lt;field&gt;
        &lt;name&gt;referencia&lt;/name&gt;
        &lt;alias&gt;QT_TRANSLATE_NOOP(&amp;quot;MetaData&amp;quot;,&amp;quot;Referencia&amp;quot;)&lt;/alias&gt;
        &lt;null&gt;false&lt;/null&gt;
        &lt;type&gt;string&lt;/type&gt;
        &lt;length&gt;18&lt;/length&gt;
    &lt;/field&gt;
    &lt;field&gt;
        &lt;name&gt;tipo&lt;/name&gt;
        &lt;alias&gt;QT_TRANSLATE_NOOP(&amp;quot;MetaData&amp;quot;,&amp;quot;Movimiento&amp;quot;)&lt;/alias&gt;
        &lt;null&gt;true&lt;/null&gt;
        &lt;pk&gt;false&lt;/pk&gt;
        &lt;type&gt;string&lt;/type&gt;
        &lt;length&gt;20&lt;/length&gt;
    &lt;/field&gt;
    &lt;field&gt;
        &lt;name&gt;cantidad&lt;/name&gt;
        &lt;alias&gt;QT_TRANSLATE_NOOP(&amp;quot;MetaData&amp;quot;,&amp;quot;Cantidad&amp;quot;)&lt;/alias&gt;
        &lt;null&gt;true&lt;/null&gt;
        &lt;pk&gt;false&lt;/pk&gt;
        &lt;type&gt;uint&lt;/type&gt;
    &lt;/field&gt;
    &lt;field&gt;
        &lt;name&gt;idalbaran&lt;/name&gt;
        &lt;alias&gt;QT_TRANSLATE_NOOP(&amp;quot;MetaData&amp;quot;,&amp;quot;ID Albarán&amp;quot;)&lt;/alias&gt;
        &lt;null&gt;true&lt;/null&gt;
        &lt;pk&gt;false&lt;/pk&gt;
        &lt;type&gt;uint&lt;/type&gt;
    &lt;/field&gt;
&lt;/TMD&gt;</pre>
<p>En este .mtd lo que hay que tener claro y en cuenta, es que el nombre de la tabla (&lt;name&gt;) y el de la query (&lt;query&gt;) deberían coincidir. ¿Porqué? Para que no se cree una tabla con el nombre de la vista. De esta forma, podemos asignar en cualquier FLTableDB el nombre de la tabla que queremos mostrar a esta query (incluso con foreign key), garantizando la ejecución desde una vista, y cierta mejora en el rendimiento.</p>
<p>Por contra, AbanQ no está preparado para trabajar con vistas explícitamente: No las espera. Por tanto no esperéis que las administre. Eso es labor vuestra, directamente en PostgreSQL (atentos por ejemplo, al tema permisos).</p>
]]></content:encoded>
			<wfw:commentRss>http://www.pinelo.com/blog/2009/05/27/buenas-practicas-en-abanq-usando-vistas-con-postgresql/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Tiempos de reducción de costes&#8230; ¡¡Software libre!!</title>
		<link>http://www.pinelo.com/blog/2009/05/21/tiempos-de-reduccion-de-costes-%c2%a1%c2%a1software-libre/</link>
		<comments>http://www.pinelo.com/blog/2009/05/21/tiempos-de-reduccion-de-costes-%c2%a1%c2%a1software-libre/#comments</comments>
		<pubDate>Thu, 21 May 2009 17:19:56 +0000</pubDate>
		<dc:creator>David Pinelo</dc:creator>
				<category><![CDATA[General]]></category>
		<category><![CDATA[Programación]]></category>

		<guid isPermaLink="false">http://www.pinelo.com/blog/?p=253</guid>
		<description><![CDATA[Realmente, utilizar software libre, implica reducir costes (ojo, software libre no es igual a gratis, pero sí reduce costes). Ejemplo, teníamos necesidad de una nueva centralita de teléfonos. La actual, estaba desfasada y daba problemas. Pidiendo presupuestos a diferentes empresas, cualquier solución suponía un coste de mínimo 2.500 €. Toda una cifra en esta época. [...]]]></description>
			<content:encoded><![CDATA[<p>Realmente, utilizar software libre, implica reducir costes (ojo, software libre no es igual a gratis, pero sí reduce costes). Ejemplo, teníamos necesidad de una nueva centralita de teléfonos. La actual, estaba desfasada y daba problemas. Pidiendo presupuestos a diferentes empresas, cualquier solución suponía un coste de mínimo 2.500 €. Toda una cifra en esta época.</p>
<p>Telefónica oferta propiamente el alquiler de una centralita y sus terminales, pagando un alquiler por teléfono y centralita mensual que es realmente interesante... Pero aun así, pensé en hacer una prueba antes. Utilizar <a href="http://www.asterisk.org">Asterisk</a>, una herramientaen software libre que permite implementar una centralita, utilizando además, protocolos libres (detalle este muy importante para futuras interoperabilidades) como SIP.</p>
<p>Así que manos a la obra, lo único que necesitaba era adquirir una tarjeta que recibiera las líneas de teléfono analógicas que tenemos contratadas. Las reinas indiscutibles en este sector son las <a href="http://www.digium.com">Digium</a>, básicamente porque son las tarjetas que desarrolla la empresa del creador y principal motor de Asterisk. Su problema, el precio. 180 euros para una prueba (en cualquier caso, mucho menos que 2.500 euros!!). Así, que adquirí las <a href="http://www.openvox.com.cn">OpenVox</a>, concretamente la A400P, que es un clon de la TDM410P (la de Digium)... Sí, OpenVox es una empresa china. Además, adquirí un módulo FXS para recibir llamadas.</p>
<p>Y manos a la obra, instalé <a href="http://www.trixbox.org">TrixBox</a>, una distribución Linux que viene ya preparada con todo el software para configurar una centralita... Me reconoció todo el hardware... y configuré.</p>
<p>En unas 6 horas, había configurado la centralita, con operadora virtual, grupos de capturas, múltiples extensiones, franjas horarias, buzones de voz... todo en un ordenador un tanto antiguo (un Pentium III con 256 megas de RAM).</p>
<p>O sea, que probando, he conseguido ahorrar 2.500 €... Y eso es lo que voy a hacer. Ahora dejaré las pruebas, y haré la implementación real de Asterisk en un ordenador algo más potente.  Por supuesto, tuve FUD de algún que otro comercial ("ni te imaginas cuántas de esas asterisk he tenido que quitar..."). Asterisk es una solución tan buena como cualquier otra en este mundo. Hay empresas que se dedican a implementar este tipo de herramientas... y seguro que sale más barato. Por preguntar, y que os hagan una prueba, no perdéis nada.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.pinelo.com/blog/2009/05/21/tiempos-de-reduccion-de-costes-%c2%a1%c2%a1software-libre/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>

