tutorial de kde

58
El ejemplo que mostramos con p1 es una de las aplicaciones Qt mas simples, todo el código está en el fichero main.cpp: #include <qapplication.h> #include <qpushbutton.h> int main( int argc, char **argv ) { QApplication a( argc, argv ); QPushButton *hello=new QPushButton( "Hello world!", 0 ); hello->resize( 100, 30 ); QObject::connect( hello, SIGNAL(clicked()), &a, SLOT(quit()) ); a.setMainWidget( hello ); hello->show(); return a.exec(); } main.cpp Veamos que hace cada una de las lineas. QApplication a( argc, argv ); Este código crea un objeto QApplication que controlará el flujo de ejecución de la aplicación, así como la mayoría de las opciones del sistema y la aplicación (para conseguir un entorno mas uniforme), etc. QPushButton *hello=new QPushButton( "Hello world!", 0 ); hello->resize( 100, 30 ); Con este código creamos un objeto QPushButton. Este es un botón pulsable, en contraposición con botones que tienen estados (como QRadioButton, donde hay varios botones de los que sólo uno puede estar pulsado en un

Upload: kotzmamanis

Post on 04-Jul-2015

107 views

Category:

Documents


3 download

TRANSCRIPT

Page 1: Tutorial de KDE

El ejemplo que mostramos con p1 es una de las aplicaciones Qt mas simples, todo el código está en el fichero main.cpp:

#include <qapplication.h>#include <qpushbutton.h>

int main( int argc, char **argv ){ QApplication a( argc, argv );

QPushButton *hello=new QPushButton( "Hello world!", 0 ); hello->resize( 100, 30 ); QObject::connect( hello, SIGNAL(clicked()), &a, SLOT(quit()) ); a.setMainWidget( hello ); hello->show(); return a.exec();}

main.cpp

Veamos que hace cada una de las lineas.

QApplication a( argc, argv );

Este código crea un objeto QApplication que controlará el flujo de ejecución de la aplicación, así como la mayoría de las opciones del sistema y la aplicación (para conseguir un entorno mas uniforme), etc.

QPushButton *hello=new QPushButton( "Hello world!", 0 ); hello->resize( 100, 30 );

Con este código creamos un objeto QPushButton. Este es un botón pulsable, en contraposición con botones que tienen estados (como QRadioButton, donde hay varios botones de los que sólo uno puede estar pulsado en un momento dado, o QCheckBox, que es un botón que puede estar marcado(pulsado) o no).

El botón contienen el texto "Hello world!, y no tiene ventana padre (0 se pasa como un puntero NULL ). Usando un widget sin padre hacemos que el gestor de ventanas maneje este widget como otra ventana del escritorio. Usualmente las aplicaciones tienen una sola ventana sin padre, que es el "main widget" o widget principal, veremos más sobre esto posteriormente.

Además redimensionamos el widget para que tenga 100 pixeles de largo y 30 de altura.

Page 2: Tutorial de KDE

QObject::connect( hello, SIGNAL(clicked()), &a, SLOT(quit()) );

Para explicar este código deberiamos de tener un poco de conocimiento sobre el mecanismo de señales y slots (signals/slots). Intentaré explicarlo un poco aquí, aunque si queda alguna duda se puede leer el magnífico capítulo que sobre este tema trae la documentacion de Qt, llamado "Introduction to Signals and Slots".

Todas las clases que heredan de QObject ( directa o indirectamente heredando de una subclase de QObject ) pueden contener señales y/o slots. Un objeto emite una señal siempre que cambia su estado de alguna forma. Por ejemplo, cuando se pulsa sobre un QPushButton se emite una señal clicked(), o cuando se hace scroll con una QScrollBar se emite una señal valueChanged(). Por otro lado, un slot es un método normal en un objeto que tiene la capacidad de que puede recibir señales.

Un objeto no tiene por qué saber si un slot está conectado a múltiples señales, e incluso se puede conectar una señal a múltiples slots o múltiples señales a un solo slot. Cualquier combinación es válida. Esto permite crear componentes verdaderamente reusables en el desarrollo de una aplicación.

El código que se necesita para llamar a los slots es generado por moc (meta object compiler) junto a otro código que inicializa la información de los metaobjetos. Veremos posteriormente como usar moc, pero como por ahora nos centramos en el ejemplo 1, y no hace falta, no usaremos moc todavia.

Finalmente, el código de arriba se usa para conectar la señal de "clicked()" del objeto hello al slot "quit()" de la aplicación (a) que cuando se ejecuta, cierra la aplicación.

a.setMainWidget( hello ); hello->show();

Este código hace que hello sea el widget principal (main) de la aplicación (el que cerrará la aplicación cuando sea destruido), y lo muestra en la pantalla.

return a.exec();

Finalmente, el bucle de eventos es ejecutado y dejamos que el usuario tome el control de la aplicación. Siempre que el usuario pulse en el botón de "hello", el bucle de eventos llamará al slot quit() de la aplicación y esta función devolverá el código de retorno con el que salimos de la aplicación.

¿ No es fácil ? :-)

Page 3: Tutorial de KDE

Ahora haremos p2, que es el ejemplo análogo a p1, pero usando las librerías de KDE, todo el código esta de nuevo en main.cpp:

#include <kapp.h>#include <klocale.h>#include <qpushbutton.h> int main( int argc, char **argv ){ KApplication a( argc, argv , "p2"); QPushButton *hello=new QPushButton( i18n("Hello World !"), 0 ); hello->setAutoResize( TRUE ); QObject::connect( hello, SIGNAL(clicked()), &a, SLOT(quit()) ); a.setMainWidget( hello ); hello->show(); return a.exec();}

main.cpp

Analicémoslo.

KApplication a( argc, argv , "p2");

Este código crea un objeto KApplication. KApplication es una clase que hereda de QApplication para añadirle características específicas de las librerías de KDE, como internacionalización, configuración de las aplicaciones, etc.

QPushButton *hello=new QPushButton( i18n("Hello World !"), 0 ); hello->setAutoResize( TRUE );

La diferencia entre este código y el usado en p1 es que ahora no usamos la cadena "Hello World !" directamente, sino el valor que devuelve i18n("Hello World !"), Lo hacemos porque i18n es una función que devuelve la cadena traducida al lenguaje que el usuario seleccionó en el módulo de lenguaje de KControl. Con este simple cambio se tiene capacidad para soportar los mas de 70 lenguajes a los que se traduce KDE actualmente.

Debido a esto, no podemos usar un tamño fijo para los botones, ya que no podemos conocer a priori la longitud de la cadena tras hacer la traducción, y no estaría bien si no mostramos la cadena completa porque el botón sea muy pequeño. Usando el flag de autoResize, los botones cambiarán su tamaño

Page 4: Tutorial de KDE

automáticamente siempre que su contenido cambie, con lo que estaremos seguros de que todo el texto se muestre siempre correctamente.

El resto de la aplicación no cambia respecto a p1

Ya hemos creado una aplicación simple de KDE en p2, pero en la mayoría de los casos habrá que usar un interfaz mas complicado que un simple botón :-), asi que veamos como añadir un widget principal standard con una barra de menú.

#include <kapp.h>#include "p3.h" int main( int argc, char **argv ){ KApplication a( argc, argv, "p3"); MainWindow *window=new MainWindow( "Tutorial - p3" ); window->resize( 400, 300 ); a.setMainWidget( window ); window->show(); return a.exec();}

main.cpp

#include <kmainwindow.h> class MainWindow : public KMainWindow{ Q_OBJECT public: MainWindow ( const char * name ); public slots: void fileOpen(); void fileSave();};

p3.h

#include "p3.h"#include <kfiledialog.h>#include <kapp.h>#include <kmenubar.h>#include <klocale.h>#include <kmessagebox.h>#include <kpopupmenu.h>#include <qtextview.h>

Page 5: Tutorial de KDE

MainWindow::MainWindow ( const char * name ) : KMainWindow ( 0L, name ){ setCaption("KDE Tutorial - p3");

QPopupMenu *filemenu = new QPopupMenu; filemenu->insertItem( i18n( "&Open" ), this, SLOT(fileOpen()) ); filemenu->insertItem( i18n( "&Save" ), this, SLOT(fileSave()) ); filemenu->insertItem( i18n( "&Quit" ), kapp, SLOT(quit()) ); QString about = i18n("p3 1.0\n\n" "(C) 1999-2002 Antonio Larrosa Jimenez\n" "[email protected]\t\[email protected]\n" "Malaga (Spain)\n\n" "Simple KDE Tutorial\n" "This tutorial comes with ABSOLUTELY NO WARRANTY\n" "This is free software, and you are welcome to redistribute it\n" "under certain conditions\n"); QPopupMenu *helpmenu = helpMenu( about ); KMenuBar *menu = menuBar(); menu->insertItem( i18n( "&File" ), filemenu ); menu->insertSeparator(); menu->insertItem( i18n( "&Help" ), helpmenu ); QTextView *hello=new QTextView( i18n("<H2>Hello World !</H2><BR>This is a simple"

" window with <I><font size=5><B>R<font color=red"" size=5>ich </font><font color=blue size=5>Text""</font></B></I> capabilities<BR>Try to resize"" this window, all this is automatic !"), "", this );

setCentralWidget( hello ); } void MainWindow::fileOpen(){ KURL filename = KFileDialog::getOpenURL( QString::null, "*", this ); QString msg = QString( i18n("Now this app should open the url %1 .") ).arg(filename.url()); KMessageBox::information( 0, msg, i18n( "Information" ),

"fileOpenInformationDialog" );} void MainWindow::fileSave(){ KURL filename=KFileDialog::getSaveURL( QString::null, "*", this );}

p3.cpp

Este código parece mucho mas complejo, pero no lo es, echemos un vistazo.

Page 6: Tutorial de KDE

El código en main.cpp no ha cambiado mucho desde p1 o p2. La única diferencia es que ahora creamos un objeto MainWindow en vez de un QPushButton, y que ahora no especificamos un puntero NULL en el widget padre, ya que se toma por defecto.

Además, no conectamos ninguna señal aquí, ya que MainWindow no tiene ninguna señal como clicked() (ni tendrí mucho sentido que la tuviera).

class MainWindow : public KMainWindow{ Q_OBJECT

En p3.h definimos la clase MainWindow, que hereda de KMainWindow, que es incluida en las kdelibs e incluye todos los métodos necesarios para crear una aplicación normal de KDE con un menú, barra de herramientas (toolbar), barra de estado (status bar), etc. Observe que en versiones anteriores del tutorial, heredábamos de KTMainWindow, pero esta clase se considera obsoleta.

Fijémonos que hemos escrito Q_OBJECT en la declaración de nuestra clase. Q_OBJECT es una macro que declara las variables especiales y los miembros virtuales que necesita el meta-objeto para implementar el mecanismo de señal/slot y otras cosas, y además le dice a moc que este es un QObject y que puede tener señales y/o slots.

public slots: void fileOpen(); void fileSave();

Como aprendimos en p1, los slots son métodos normales, así que pueden ser públicos, protegidos o privados (public:, protected: o private:) como cualquier otro. La única diferencia sera que debemos especificar public slots:,protected slots: o private slots:, para que moc sepa que son slots.

El preprocesador tendrá cuidado de quitar o substituir todas esas marcas especiales para que no molesten al compilador.

Veamos ahora el fichero p3.cpp:

QPopupMenu *filemenu = new QPopupMenu; filemenu->insertItem( i18n( "&Open" ), this, SLOT(fileOpen()) ); filemenu->insertItem( i18n( "&Save" ), this, SLOT(fileSave()) ); filemenu->insertItem( i18n( "&Quit" ), kapp, SLOT(quit()) );

Page 7: Tutorial de KDE

filemenu es un QPopupmenu, esto es, un menú que contiene "items" (elementos) que el usuario pulsará para realizar una acción dada.

Insertamos esos items llamando a insertItem. El primer parámetro es el texto que deberá de estar en el menú, con un caracter & justo delante de la letra que queramos usar como acceso rápido. Usando i18n nos aseguramos que el texto aparecerá en el lenguaje en el que el usuario tenga configurado su escritorio.

Cuando el usuario selecciona un elemento, el menú emite una señal que puede (¡ y debe !) ser conectada a una función para hacer algo. Por conveniencia, en vez de usar connect, podemos conectar esta señal directamente cuando definimos la entrada del menú. Ésto es lo que hacemos en el segundo y tercer parámetros. Conectamos el elemento de menú Open al slot fileOpen() en este (this) objeto, la entrada Save al slot fileSave() en este (this) objeto, y la entrada Quit al slot quit() de la aplicación (kapp), tal como hicimos en p1 y p2.

Fijémonos en que kapp es una función de conveniencia que puede ser usada para acceder a la instancia actual de KApplication.

QString about = i18n("p3 1.0\n\n" "(C) 1999-2002 Antonio Larrosa Jimenez\n" "[email protected]\t\[email protected]\n" "Malaga (Spain)\n\n" "Simple KDE Tutorial\n" "This tutorial comes with ABSOLUTELY NO WARRANTY\n" "This is free software, and you are welcome to redistribute it\n" "under certain conditions\n"); QPopupMenu *helpmenu = helpMenu( about );

Primero ponemos esa cadena dentro del objeto QString. QString es una clase de cadena de texto de propósito general que se usa para manipular texto de muy diversas formas. Uno de los principales beneficios de QString es el poder usar Unicode en la aplicación de forma automática, sin tener que preocuparse de ello. Ademas provée un mecanismo implícito de compartición de las cadenas que incrementa su eficiencia, ya que cuando se crea una copia de un QString no se copia realmente su contenido hasta que uno de ellos cambia.

Después creamos otro menú popup, pero usando el miembro helpMenu de KMainWindow. Haciendo eso conseguimos un menú estandard de ayuda, con las entradas para documentación, un diálogo sobre el programa (about), etc. Además, el diálogo sobre el programa incluye todo el texto que hemos escrito en la cadenaabout.

Page 8: Tutorial de KDE

KMenuBar *menu = menuBar(); menu->insertItem( i18n( "&File" ), filemenu ); menu->insertSeparator(); menu->insertItem( i18n( "&Help" ), helpmenu );

Ya estamos preparados para terminar la creación de nuestro menú. Primero, obtenemos la barra de menú usada por nuestra ventana principal usando menuBar(). Como no hay barra de menú todavía, este método la creará para nosotros y nos devolverá un objeto KMenuBar vacío que podemos rellenar. KMenuBar provée un menú estandard de KDE, con una apariencia común en todas las aplicaciones

Entonces insertamos los dos QPopupMenus que ya tenemos, con un separador enmedio, y ya hemos terminado de crear el menú.

QTextView *hello=new QTextView( i18n("<H2>Hello World !</H2><BR>This is a simple"

" window with <I><font size=5><B>R<font color=red"" size=5>ich </font><font color=blue size=5>Text""</font></B></I> capabilities<BR>Try to resize"" this window, all this is automatic !"), "", this );

setCentralWidget( hello );

Quería que esta aplicación hiciera algo bonito y simple, asi que le vamos a añadir un widget QTextView. Este widget es un visor bastante útil de texto enriquecido (Rich Text), donde podemos especificar el tipo de fuente y color entre otras cosas. Despues de crearlo con el texto anterior lo ponemos como el widget central (centralWidget ) de esta ventana.

Pero, ¿qué es un widget central?. Un widget central es el widget que el usuario puede ver bajo el menú (o la barra de herramientas si existe). Es decir, el widget que muestra el documento que el usuario tiene abierto, o cualquier cosa que no es parte estandar de KMainWindow (esto es, el menú, la barra de herramientas y la de estado).

KURL filename = KFileDialog::getOpenURL( QString::null, "*", this );

Ahora miramos a la implementación del slot de fileOpen. Este código abre un diálogo de abrir fichero que permite al usuario abrir un fichero existente.

Los tres parámetros indican el directorio en el que se comienza a buscar (no nos importa en cual comienza, asi que dejamos el actual por defecto), los filtros (para ver solo los archivos que cumplen una determinada expresión regular) y el objeto padre.

Page 9: Tutorial de KDE

Ahora tenemos el url que el usuario quiere abrir almacenado en filename.

Observa que se recomienda encarecidamente el permitir al usuario abrir cualquier URL, no sólo archivos locales. Para esto, hemos utilizado el método getOpenURL, que permite al usuario seleccionar cualquier URL. Mire abajo un ejemplo sobre como usar la librería KIO.

QString msg=QString( i18n("Now this app should open the url %1 .") ).arg(filename.url());

Podemos manipular cadenas con la clase QString, en realidad estamos usando esto para componer el mensaje que vamos a mostrar a continuación (ya que abrir un fichero no se encuentra dentro de los objetivos de este tutorial). El formato es similar al que se usa con sprintf, pero sin necesidad de preocuparse sobre los tipos de los datos, ya que sólo habrá que usar %1, %2, etc. y despues .arg(var1).arg(var2).arg(var3), etc. Otra cosa buena sobre QString es que no habrá problemas cuando la cadena destino no tenga espacio para la cadena resultante, ya que se reservará mas espacio automaticamente.

Por cierto, filename.url() es un QString que contiene la URL guardada en el objeto KURL, o sea, protocolo+nombre del host+camino+query+todolodemás :-).

KMessageBox::information( 0, msg, i18n( "Information" ), "fileOpenInformationDialog" );

Con la clase KMessageBox y sus miembros estáticos, podemos crear una variedad de cajas de mensajes. En este ejemplo, usamos una caja de información para mostrar qué fichero se debería de haber abierto. El tercer parámetro es el mensaje que aparecerá como título de la caja, y el cuarto es un identificador, que se usa para almacenar en un fichero de configuración (usualmente ~/.kde/share/config/p3rc) si el usuario quiere que no se le muestre mas este diálogo, cosa que se decide usando un checkbox que aparece en el diálogo.

¡ Fijémonos en que no hemos escrito ninguna forma de volver a mostrar ese diálogo de nuevo !. Esto se consigue llamando aKMessageBox::enableAllMessages();, pero me parece que sería un buen ejercicio para el lector de este tutorial el añadir un menú que haga esta llamada en p3 :-) .

KURL filename=KFileDialog::getSaveURL( QString::null, "*", this );

Finalmente, en fileSave llamamos a una función análoga a getOpenURL, getSaveURL.

Page 10: Tutorial de KDE

Fijémonos en que si usamos getOpenURL deberíamos de usar la librería KIO para obtener un fichero remoto si eso es lo que quiere el usuario, esto se puede hacer de forma trivial con el siguiente código:

QString tmpFile; if( KIO::NetAccess::download( "ftp://ftp.kde.org/myfile.tgz", tmpFile ) ) { /* Aquí hacemos lo que queramos con el fichero _local_ guardado en tmpFile */

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

Pero esto está fuera del ámbito de este tutorial, aunque puede que lo incremente en el futuro con un ejemplo de la libreria KIO, libkio.

Bueno, espero que no fuera demasiado difícil.

Ahora que sabemos crear un menú haremos una aplicación casi real en p4. Gracias al trabajo de Lars Knoll, Dirk Mueller, Antti Koivisto, Waldo Bastian y muchos otros, podemos usar un objeto KHTML, un widget que es capaz de mostrar una página HTML, y que usando la libreria KIO es capaz de traer esa página de la red. Veamos como funciona.

#include <kapp.h>#include "p4.h" int main( int argc, char **argv ){ KApplication a( argc, argv, "p4" ); MainWindow *window=new MainWindow( "Tutorial - p4" ); window->resize( 300, 200 ); a.setMainWidget( window ); window->show(); return a.exec();}

main.cpp

#include <kmainwindow.h>#include <kurl.h>#include <kparts/browserextension.h>

class QLineEdit;

Page 11: Tutorial de KDE

class KHTMLPart; class MainWindow : public KMainWindow{ Q_OBJECT public: MainWindow ( const char * name ); public slots: void changeLocation(); void openURLRequest(const KURL &url, const KParts::URLArgs & ); private: QLineEdit *location; KHTMLPart *browser; };

p4.h

#include "p4.h"#include <qvbox.h>#include <qlineedit.h>#include <kapp.h>#include <kmenubar.h>#include <klocale.h>#include <kpopupmenu.h>#include <khtml_part.h>

MainWindow::MainWindow ( const char * name ) : KMainWindow ( 0L, name ){ setCaption("KDE Tutorial - p4"); QPopupMenu * filemenu = new QPopupMenu; filemenu->insertItem( i18n( "&Quit" ), kapp, SLOT( quit() ) ); QString about = i18n("p4 1.0\n\n" "(C) 1999-2002 Antonio Larrosa Jimenez\n" "[email protected]\t\[email protected]\n" "Malaga (Spain)\n\n" "Simple KDE Tutorial\n" "This tutorial comes with ABSOLUTELY NO WARRANTY \n" "This is free software, and you are welcome to redistribute it\n" "under certain conditions\n"); QPopupMenu *helpmenu = helpMenu(about); KMenuBar * menu = menuBar(); menu->insertItem( i18n( "&File" ), filemenu); menu->insertSeparator(); menu->insertItem(i18n("&Help"), helpmenu); QVBox * vbox = new QVBox ( this ); location = new QLineEdit ( vbox );

Page 12: Tutorial de KDE

location->setText( "http://localhost" ); browser=new KHTMLPart( vbox ); browser->openURL( location->text() ); connect( location , SIGNAL( returnPressed() ), this, SLOT( changeLocation() ) ); connect( browser->browserExtension(), SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ), this, SLOT( openURLRequest(const KURL &, const KParts::URLArgs & ) ) );

setCentralWidget( vbox );}

void MainWindow::changeLocation(){ browser->openURL( location->text() );}

void MainWindow::openURLRequest(const KURL &url, const KParts::URLArgs & ){ location->setText(url.url()); changeLocation();}

p4.cpp

La primera diferencia que vemos con p3 esta en p4.h . Solo vamos a usar los menús para salir de la aplicación, así que hemos quitado los dos slots que usabamos para los diálogos de Open y Save.

public slots: void changeLocation(); void openURLRequest(const KURL &url, const KParts::URLArgs & );

private: QLineEdit *location; KHTMLPart *browser;

Estamos definiendo un slot changeLocation() para que sea llamado cuando el usuario quiera cargar una página pulsando Enter en la barra de dirección . El método openURLRequest es un slot que se llama cuando el usuario pulsa en un enlace en la página html, y la página html "nos pide" que abramos esa dirección (que se pasa como parámetro). Ahora vamos a usar dos widgets, uno es un widget QLineEdit y el otro un KHTMLPart. QLineEdit implementa una linea en la que el usuario puede editar texto, en este caso la dirección de la página html que va a mostrar KHTMLPart.

Page 13: Tutorial de KDE

QVBox * vbox = new QVBox ( this );

Estamos usando un QVBox para manejar la disposición (layout) de los widgets. Como queremos mostrar el widget QLineEdit y el KHTMLPart uno bajo el otro, esta es una misión perfecta para QVBox. Este widget automáticamente pone todos sus hijos en una disposición vertical (asi como un QHBox los pone horizontalmente), y actualiza su disposición siempre que el QVBox es redimensionado. Hay que fijarse que podemos usar disposiciones mas complejas usando otras clases, como QGridLayout, para manejar su geometría, o incluso manejarlo manualmente como hacen otros toolkits. Pero todo esto simplifica el código, a la vez que lo hacen más flexible. Hay que observar que cada vez que el usuario redimensiona la ventana, el widget KHTMLPart ocupa la altura máxima que puede mientras que el QLineEdit siempre ocupa una altura fija, y entre los dos siempre ocupan el tamaño total de la ventana principal de la aplicación.

location = new QLineEdit ( vbox ); location->setText( "http://localhost" );

Creamos un objeto QLineEdit, location, como un hijo de vbox y le ponemos algo de texto que sera el que muestre por defecto.

Ahora se puede navegar con internet con esta aplicación, pero como muchas personas solamente lo podran testear con su servidor local web, por estar realizando las pruebas sin conexión a internet, es el que hemos puesto por defecto. Asegurate de estar ejecutando apache en tu máquina para que esta aplicación (¡ y las siguientes !) funcionen.

browser=new KHTMLPart( vbox ); browser->openURL( location->text() );

Con este código creamos el objeto navegador como un hijo de vbox, con lo que será añadido debajo de location (la barra que contiene la dirección).

Llamando a openURL le pedimos a KHTMLPart que descargue la url que está escrita en la linea de edición de location, y que lo muestre.

connect( location , SIGNAL( returnPressed() ), this, SLOT( changeLocation() ) );

Cuando el usuario presiona enter en la barra de la dirección, ésta emite una señal returnPressed(), con este código conectamos esa señal al slotchangeLocation() en este (this) objeto. Podemos llamar a connect directamente (sin usar QObject::) ya que estamos en una clase que

Page 14: Tutorial de KDE

hereda de QObject ( indirectamente, ya que heredamos de KMainWindow, que a su vez hereda de QWidget, que a su vez hereda de QObject).

connect( browser->browserExtension(), SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ), this, SLOT( openURLRequest(const KURL &, const KParts::URLArgs & ) ) );

Estamos ahora conectando otra señal a un slot. Lo bueno ahora es que el browser emite una señal openURLRequest( const KURL &, const KParts::URLArgs & ) cuando quiere abrir una URL (por ejemplo, porque el usuario haya pulsado en un enlace). De hecho, la señal no la emite el propio objeto KHTMLPart, sino su objeto browserExtension, y es por eso que lo ponemos como primer parámetro (el objeto que emite la señal).

Por cierto, el segundo parámetro no lo usamos, así que vamos a ignorarlo pues sólo se usa en ocasiones muy especiales.

setCentralWidget( vbox );

Ahora ponemos el widget vbox como el widget central del objeto KMainWindow.

browser->openURL( location->text() );

El slot changeLocation simplemente pide al browser que abra el url que esta escrito en la barra de dirección.

void MainWindow::openURLRequest(const KURL &url, const KParts::URLArgs & ){ location->setText(url.url()); changeLocation();}

Éste es el código que se llama cuando el navegador quiere abrir una nueva dirección. Como recordará, el método url() de la clase KURL nos devuelve la url completa, así que la escribimos en la barra de dirección. Entonces llamamos a changeLocation(), para abrir la url que hay en la barra de dirección dentro de la "parte" html.

Piense en todo lo que hemos hecho con sólo 96 lineas. ¡Imagine lo que se puede conseguir con 1000! :-)

Page 15: Tutorial de KDE

Aqui se va a mostrar nuestra primera aplicación que se comunicará con otras ( en este caso con p6 ). Aqui esta el código fuente:

#include <kapp.h>#include "p5.h" int main( int argc, char **argv ){ KApplication a( argc, argv, "p5" ); MainWindow *mywindow=new MainWindow( "Tutorial - p5" ); mywindow->resize( 300, 200 ); a.setMainWidget( mywindow ); mywindow->show(); return a.exec();}

main.cpp

#include <kmainwindow.h>#include <kurl.h>#include <kparts/browserextension.h>

class QLineEdit;class KHTMLPart;class QSplitter;class QPushButton; class MainWindow : public KMainWindow{ Q_OBJECTpublic: MainWindow ( const char * titulo ); public slots: void changeLocation(); void openURLRequest(const KURL &url, const KParts::URLArgs & ); void bookLocation();

private: QLineEdit *location; KHTMLPart *browser; QPushButton *bookmark; QSplitter *split;};

p5.h

#include "p5.h"#include <qvbox.h>

Page 16: Tutorial de KDE

#include <qlineedit.h>#include <qpushbutton.h>#include <qsplitter.h>#include <dcopclient.h>#include <kapp.h>#include <kmenubar.h>#include <klocale.h>#include <kpopupmenu.h>#include <khtml_part.h>#include <kdebug.h> MainWindow::MainWindow ( const char * name ) : KMainWindow ( 0L, name ){ setCaption("KDE Tutorial - p5"); QPopupMenu * filemenu = new QPopupMenu; filemenu->insertItem( i18n( "&Quit" ), kapp, SLOT( quit() ) ); QString about = i18n("p5 1.0\n\n" "(C) 1999-2002 Antonio Larrosa Jimenez\n" "[email protected]\t\[email protected]\n" "Malaga (Spain)\n\n" "Simple KDE Tutorial\n" "This tutorial comes with ABSOLUTELY NO WARRANTY \n" "This is free software, and you are welcome to redistribute it\n" "under certain conditions\n"); QPopupMenu *helpmenu = helpMenu(about); KMenuBar * menu = menuBar(); menu->insertItem( i18n( "&File" ), filemenu); menu->insertSeparator(); menu->insertItem(i18n("&Help"), helpmenu); QVBox * vbox = new QVBox ( this ); location = new QLineEdit ( vbox ); location->setText( "http://localhost" ); connect( location , SIGNAL( returnPressed() ), this, SLOT( changeLocation() ) ); split = new QSplitter ( vbox ); split->setOpaqueResize(); bookmark = new QPushButton ( i18n("Add to Bookmarks"), split ); connect( bookmark, SIGNAL( clicked() ), this, SLOT( bookLocation() ) ); browser=new KHTMLPart( split ); browser->openURL( location->text() ); connect( browser->browserExtension(), SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ), this, SLOT( openURLRequest(const KURL &, const KParts::URLArgs & ) ) );

Page 17: Tutorial de KDE

setCentralWidget( vbox );

DCOPClient *client = kapp->dcopClient(); client->attach();} void MainWindow::changeLocation(){ browser->openURL( location->text() );}

void MainWindow::openURLRequest(const KURL &url, const KParts::URLArgs & ){ location->setText( url.url() ); changeLocation();} void MainWindow::bookLocation(){ DCOPClient *client=kapp->dcopClient(); QByteArray params; QDataStream stream(params, IO_WriteOnly); stream << location->text(); if (!client->send("p6-*", "bookmarkList", "add(QString)", params)) kdDebug() << "Error with DCOP\n";}

p5.cpp

Esta aplicación y p6 son un simple ejemplo de la nueva tecnología para comunicación de escritorio, DCOP. DCOP es un mecanismo de IPC/RPC basado en ICE que usa sockets de dominio unix o sockets TCP/IP de forma transparente.

p6 sera la aplicación que almacene los marcadores (bookmarks) del usuario, y p5 le dira que los almacene. Veamos cuan facilmente podemos hacerlo:

public slots: void changeLocation(); void openURLRequest(const KURL &url, const KParts::URLArgs & ); void bookLocation();

private: QLineEdit *location; KHTMLPart *browser; QPushButton *bookmark; QSplitter *split;

Page 18: Tutorial de KDE

Vamos a usar un nuevo slot que sera llamado cuando el usuario quiera almacenar una dirección en su aplicación de marcadores. Ademas hay un par de nuevos widgets que necesitaremos.

split = new QSplitter ( vbox ); split->setOpaqueResize();

Queremos mostrar un gran botón al lado del widget navegador de html. En vez de hacerlo de un tamaño fijo, dejaremos que el usuario cambie el tamaño del botón, esto se consigue usando un widget QSplitter, este es un widget que distribuye su tamaño entre sus hijos.

Haremos que split haga un redimensionamiento opaco para redibujar a los hijos mientras el usuario cambia el tamaño, en vez de dejar el redibujado para cuando termine de cambiarlo.

bookmark = new QPushButton ( i18n("Add to Bookmarks"), split ); connect( bookmark, SIGNAL( clicked() ), this, SLOT( bookLocation() ) );

Ahora hemos creado un nuevo QPushButton con el texto traducible.

La señal clicked() la conectamos a nuestro slot bookLocation().

browser=new KHTMLWidget( split );

Junto a nuevo botón bookmark, el browser es ahora un hijo de split. Esto se ha hecho así para dejar que split maneje el tamaño de ambos widgets, tal como se explico anteriormente.

DCOPClient *client = kapp->dcopClient(); client->attach();

Ya conocemos la macro kapp, que se usa para referenciar a la instancia actual de KApplication, de esta forma conseguimos el objeto DCOPClient, y le decimos que añada (attach) esta aplicación al servidor DCOP que debe de estar ejecutandose en el sistema (dcopserver es una parte fundamental del escritorio, con lo que siempre debera estar ejecutandose).

DCOPClient *client=kapp->dcopClient(); QByteArray params; QDataStream stream(params, IO_WriteOnly);

Page 19: Tutorial de KDE

Cuando el cliente pulsa el botón para añadir el url actual a la lista de bookmarks este slot es llamado. Primero cogemos el objeto DCOPClient de esta aplicación y despues inicializamos un objeto QDataStream.

QDataStream es una clase que se usa para serializar datos de cualquier tipo. Entre sus características se encuentran las de ser completamente independiente del equipo anfitrion (orden de byte, CPU, etc.). De esta forma se puede crear un stream (caudal, conjunto de datos que se envían uno tras otro) en un sistema big-endian y leerlo en una little-endian sin ningún problema.

stream << location->text();

Como el stream puede contener cualquier tipo de datos, lo usaremos para almacenar los parámetros de la función que llamaremos en p6. Almacenamos el texto de la dirección en el stream.

if (!client->send("p6-*", "bookmarkList", "add(QString)", params)) kdDebug() << "Error with DCOP\n";

Finalmente, enviamos (send) una llamada DCOP a p6.

El primer parámetro es el nombre de la aplicación a la que queremos conectarnos. El segundo es el nombre del objeto que queremos usar en la aplicación destino, y el tercero es el nombre del miembro de ese objeto al que queremos llamar. params es un stream que contiene los parámetros que queremos pasarle a ese método.

En otras palabras, p6 es una aplicación que tiene un objeto llamado bookmarkList, y este objeto tiene un método llamado add que tiene un parámetro QString, con lo que esa simple linea llama el metodo deseado en p6 con los parámetros dados. Como no queremos obtener un valor de retorno de p6, volvera enseguida y se continuara ejecutando p5 mientras p6 añade el url a la lista de bookmarks. ¿ No es bonito ? ;-)

Observa que hemos añadido -* al nombre de la aplicación. Lo hemos hecho porque la aplicación p6 obtendrá un nombre en el servidor dcop de la forma "p6-<PID>" (p.ej. "p6-12532") . Usando un asterisco como último caracter, enviamos la señal a todas las aplicaciones cuyo nombre coincide con los primeros caracteres. Si la aplicación que estás desarrollando necesita enviar una señal a una aplicación específica, entonces seguramente conoces el PID de la aplicación con la que te quieres comunicar, y por lo tanto, puedes mandar facilmente una sola señal usando "nombreDeLaAplicación-PID".

Page 20: Tutorial de KDE

Estamos ahora chequeando el valor de retorno, por si acaso el dcopserver no ha podido completar la llamada. En este caso, usamos las rutinas de depuración de KDE para dejar que la aplicación nos muestre que hay algo que va mal. Como este mensaje esta dirigido solamente a los desarrolladores no se usa i18n.

kdDebug() devuelve un stream que puedes usar como salida para tus cadenas de depuración. Hay varios niveles de logs : kdDebug, kdWarning, kdError y kdFatal. Con estos indicas la seriedad del mensaje. También se puede especificar un número de area para especificar la zona del programa que emitió el mensaje y activar/desactivar areas para depurar partes específicas de tu aplicación.

Y esto es todo en p5. Deberias ejecutar a la vez p5 y p6 para que trabajen en equipo y ver como se comunican.

Aqui explicaremos p6, que es una aplicación muy simple que simplemente muestra una lista con bookmarks.

#include <kapp.h>#include "p6.h" int main( int argc, char **argv ){ KApplication a( argc, argv, "p6" ); MainList *mylist=new MainList; mylist->resize( 300, 200 ); a.setMainWidget( mylist ); mylist->show(); return a.exec();}

main.cpp

#ifndef __P6IFACE_H__#define __P6IFACE_H__ #include <dcopobject.h>#include <qstring.h> class p6Iface : virtual public DCOPObject{ K_DCOPpublic: k_dcop: virtual void add( const QString s ) = 0;

Page 21: Tutorial de KDE

}; #endif

p6Iface.h

#include "p6Iface.h"#include <qlistview.h> class MainList : public QListView, virtual public p6Iface{ Q_OBJECT public: MainList(); void add ( const QString s ); };

p6.h

#include "p6.h"#include <klocale.h>#include <kapp.h>#include <dcopclient.h> MainList::MainList() : QListView ( 0L, "Bookmarks" ),

DCOPObject ( "bookmarkList" ){ addColumn( i18n("My Bookmarks") ); DCOPClient *client=kapp->dcopClient(); client->attach(); client->registerAs("p6");}; void MainList::add( const QString s ){ insertItem ( new QListViewItem ( this , s ) );};

p6.cpp

En vez de usar un KTMainWindow y para conseguir claridad en el código fuente, usaremos directamente un widget QListView a costa del diseño del interfaz gráfico.

Con QListView se puede crear un widget con una lista de elementos (además con diferentes columnas para mostrar las diferentes propiedades de cada elemento). Es además posible, y rápido, el crear un árbol de elementos para tenerlo todo

Page 22: Tutorial de KDE

mejor organizado. En este pequeño ejemplo, lo usaremos como una vista de una lista compuesta por una sola columna y con capacidades de ordenación. ( por esto es por lo que no estamos usando un QListBox, que satisfacería nuestros propósitos con un API mas simple).

Hay una gran diferencia con los ejemplos previos, tenemos un fichero llamado p6Iface.h, veamos ahora que es y para que sirve.

class p6Iface : virtual public DCOPObject{ K_DCOPpublic: k_dcop: virtual void add( const QString s ) = 0; };

Con este dichero definimos el interface que p6 exportará a otras aplicaciones para que lo usen. Usando la marca especial k_dcop ( que sera eliminada por el preprocesador antes de llegar al compilador ), definimos los miembros del objeto que otros podran llamar remotamente usando DCOP, en este caso add.

Hay que fijarse en que definimos esta función como pure virtual, con lo que no esta implementada en la clase p6Iface. El truco aquí es usar dcopidl, una herramienta que traduce nuestra definición parecida a un fichero .h a un lenguaje de definición de interfaces común (IDL, Interface Definition Language). Esto le permite a otras aplicaciones, como a dcopidl2cpp, el escribir por nosotros una plantilla de la implementación, que se usa internamente por DCOPObject para saber que métodos estan disponibles en este objeto.

class MainList : public QListView, virtual public p6Iface

Mirando en p6.h, podemos ver que nuestro widget principal hereda de QListView (ya que será natural el mostrar la lista en el X-Windows), pero ademas hereda de la clase p6Iface que acabamos de definir. De esta forma podemos implementar los métodos accesibles remotamente en las clases que usemos.

MainList::MainList() : QListView ( 0L, "Bookmarks" ), DCOPObject ( "bookmarkList" )

Como no hemos implementado todavía el constructor de p6Iface, llamaremos aquí a DCOPObject. El parámetro del constructor es el nombre con el que DCOP

Page 23: Tutorial de KDE

conocerá a este objeto, con lo que las llamadas remotas a uno de sus métodos deberan de ser hechas usando bookmarkList como el nombre del objeto.

addColumn( i18n("My Bookmarks") );

Añadimos una columna ( al menos debe haber una para que QListView sea útil).

DCOPClient *client=kapp->dcopClient(); client->attach(); client->registerAs("p6");

Ahora esta aplicación está unida (attached) al servidor DCOP, tal como hicimos en p5, pero de una nueva forma. Como en p5 no ibamos a recivir ninguna llamada usamos una conexión anónima. En este caso queremos que otras aplicaciones se conecten a esta, con lo que debemos registrarnos con un nombre, que es lo que hace registerAs.

Obviamente, ese es el nombre que otros usaran cuando hagan una llamada a esta aplicación usando DCOP.

insertItem ( new QListViewItem ( this , s ) );

Finalmente el método add toma el parámetro QString, construye un QListViewItem con el y añade el elemento (item) a QListView.

Quizá te preguntes porque no hay un método mas simple para insertar un QString en un QListView. La razón es simple: Un objeto QListView puede contener cosas mucho mas complicadas que cadenas, por ejemplo, se pueden tener múltiples columnas, donde algunas de ellas son objetos QPixmap (imágenes), etc.

Scripting (guiones)

Uno de los múltiples beneficios de usar DCOP es su habilidad para facilmente permitir la creación de scripts para cualquier aplicación que use este interface. Como ejemplo de esto, mostraremos un script en Python que añade un url a la lista de marcadores.

Para poder usarlo sera necesaria la librería xmlrpc para Python. Esta disponible en http://www.pythonware.com/downloads/xmlrpc-0.9.8--990621.zip, solo hace falta descomprimirla en el directorio de librerías de Python (usualmente /usr/lib/python1.5). Además hará falta ejecutar kxmlrpcd, que es un demonio de xmlrpc que funciona como puente entre el protocolo xmlrpc y el protocolo DCOP.

Page 24: Tutorial de KDE

El script usado es el siguiente:

#!/usr/bin/python from xmlrpclib import *import os rc = open(os.environ['HOME'] + '/.kxmlrpcd', 'r')config = string.split(rc.read(), ',')port = config[0]auth = config[1] server = Server("http://localhost:" + port +"/p6") server.bookmarkList.add(auth, "http://www.kde.org")

addurl.py

Quisiera aquí darle las gracias a Kurt Granroth por darme el código fuente de este script, ya que mi conocimiento de Python es nulo.

De cualquier forma, nos podemos fijar en que estamos usando la dirección localhost cuando nos conectamos al servidor. Simplemente cambiando esa linea (y teniendo los permisos correctos), podemos conectarnos a servidores en hosts remotos (incluso con diferentes tipos de CPU y sistemas operativos), y controlar aplicaciones remotas desde scripts.

¡ Incluso se puede hacer un script para una aplicación de KDE usando un script en bash !

Hemos terminado de examinar p6. Veamos qué podemos hacer a continuación.

En este paso, estamos preparados para aumentar la usabilidad de p5 para aprender como usar otras cuantas clases standars de KDE que deberán de ser usadas casi en todas las aplicaciones.

#include <kapp.h>#include "p7.h" int main( int argc, char **argv ){ KApplication a( argc, argv, "p7" ); MainWindow *mywindow=new MainWindow( "Tutorial - p7" ); mywindow->resize( 300, 200 ); a.setMainWidget( mywindow );

Page 25: Tutorial de KDE

mywindow->show(); return a.exec();}

main.cpp

#include <dcopobject.h> class p7Iface : virtual public DCOPObject{ K_DCOP k_dcop: virtual void setURL( QString s )=0; };

p7Iface.h

#include "p7Iface.h"#include <kmainwindow.h>#include <kurl.h>#include <kparts/browserextension.h>#include <qvaluestack.h> class QLineEdit;class KHTMLPart; class MainWindow : public KMainWindow, virtual public p7Iface{ Q_OBJECT public: MainWindow ( const char * titulo ); virtual void setURL ( const QString url ); public slots: void fileSetDefaultPage(); void changeLocation(); void bookLocation(); void gotoPreviousPage(); void openURLRequest(const KURL &url, const KParts::URLArgs & ); private: QLineEdit *location; KHTMLPart *browser; QValueStack <QString> history;};

p7.h

#include "p7.h"

Page 26: Tutorial de KDE

#include <qvbox.h>#include <qlineedit.h>#include <dcopclient.h>#include <kfiledialog.h>#include <kapp.h>#include <kmenubar.h>#include <ktoolbar.h>#include <klocale.h>#include <kpopupmenu.h>#include <khtml_part.h>#include <kdebug.h>#include <kconfig.h>#include <kiconloader.h> #define TOOLBAR_ID_ADDBOOKMARK 1#define TOOLBAR_ID_BACK 2#define TOOLBAR_ID_QUIT 3 MainWindow::MainWindow ( const char * name ) : KMainWindow ( 0L, name ),

DCOPObject ( "browser" ){ setCaption("KDE Tutorial - p7"); QPopupMenu * filemenu = new QPopupMenu; filemenu->insertItem( i18n( "&Set default page" ),

this, SLOT( fileSetDefaultPage() ) ); filemenu->insertItem( i18n( "&Quit" ), kapp, SLOT( quit() ) ); QString about = i18n("p7 1.0\n\n" "(C) 1999-2002 Antonio Larrosa Jimenez\n" "[email protected]\t\[email protected]\n" "Malaga (Spain)\n\n" "Simple KDE Tutorial\n" "This tutorial comes with ABSOLUTELY NO WARRANTY \n" "This is free software, and you are welcome to redistribute it\n" "under certain conditions\n"); QPopupMenu *helpmenu = helpMenu(about); KMenuBar * menu = menuBar(); menu->insertItem( i18n( "&File" ), filemenu); menu->insertSeparator(); menu->insertItem(i18n( "&Help" ), helpmenu); KToolBar *toolbar=new KToolBar(this);

toolbar->insertButton(BarIcon("reload"), TOOLBAR_ID_ADDBOOKMARK, SIGNAL(clicked(int)),this,SLOT(bookLocation()),TRUE, i18n("Add to Bookmarks")); toolbar->insertButton(BarIcon("back"), TOOLBAR_ID_BACK, SIGNAL(clicked(int)),this,SLOT(gotoPreviousPage()), FALSE, i18n("Back to previous page")); toolbar->insertButton(BarIcon("exit"), TOOLBAR_ID_QUIT, SIGNAL(clicked(int)),kapp,SLOT(quit()),TRUE,

Page 27: Tutorial de KDE

i18n("Quit the application")); addToolBar(toolbar); QVBox * vbox = new QVBox ( this ); location = new QLineEdit ( vbox ); KConfig *config=kapp->config(); config->setGroup("Settings"); location->setText( config->readEntry( "defaultPage",

"http://localhost") ); connect( location , SIGNAL( returnPressed() ), this, SLOT( changeLocation() ) ); browser=new KHTMLPart( vbox ); browser->openURL( location->text() ); connect( browser->browserExtension(), SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ), this, SLOT( openURLRequest(const KURL &, const KParts::URLArgs & ) ) );

setCentralWidget( vbox ); DCOPClient *client = kapp->dcopClient(); client->attach(); client->registerAs("p7");} void MainWindow::changeLocation(){ history.push( browser->url().url() ); toolBar()->setItemEnabled( TOOLBAR_ID_BACK, TRUE); browser->openURL( location->text() );} void MainWindow::setURL( QString url ){ location->setText( url ); changeLocation();} void MainWindow::openURLRequest( const KURL &url, const KParts::URLArgs & ){ setURL( url.url() );}

void MainWindow::gotoPreviousPage(){ location->setText( history.pop() ); if (history.isEmpty()) toolBar()->setItemEnabled( TOOLBAR_ID_BACK, FALSE); browser->openURL( location->text() );}

Page 28: Tutorial de KDE

void MainWindow::bookLocation(){ DCOPClient *client=kapp->dcopClient(); QByteArray params; QDataStream stream(params, IO_WriteOnly); stream << location->text(); if (!client->send("p8-*", "bookmarkList", "add(QString)", params)) kdDebug << "Error with DCOP\n"; } void MainWindow::fileSetDefaultPage(){ KConfig *config=kapp->config(); config->setGroup("Settings"); config->writeEntry( "defaultPage", browser->url().url() );}

p7.cpp

Lo primero que hacemos es añadir un interfaz DCOP en p7 para usarlo en p8, que será la siguiente generación de listas de marcadores :-). Es muy similar a lo que usamos en p6, asi que no lo comentare mas.

#define TOOLBAR_ID_ADDBOOKMARK 1#define TOOLBAR_ID_BACK 2#define TOOLBAR_ID_QUIT 3

Bien, ahora estamos mirando al principio de p7.cpp. Aqui definimos algunos identificadores para identificar a los botones de la barra de herramientas (toolbar). Esto no es siempre necesario, ya que se puede dejar a la toolbar que le asigne indentificadores por defecto. Pero como vamos a habilitar y deshabilitar algunos botones queda mas claro si se hace así.

MainWindow::MainWindow ( const char * name ) : KMainWindow ( 0L, name ), DCOPObject ( "browser" )

{ QPopupMenu * filemenu = new QPopupMenu; filemenu->insertItem( i18n( "&Set default page" ),

this, SLOT( fileSetDefaultPage() ) );

La clase MainWindow ahora hereda además de p7Iface, asi que tenemos que llamar al constructor DCOPObject. Vamos a llamar a este objeto browser. Ahora hay otro elemento en el menú de fichero que será usado para que la página actual sea la se cargue por defecto cuando se ejecute la aplicación. Así mostraremos como almacenar una variable de configuración.

Page 29: Tutorial de KDE

KToolBar *toolbar=new KToolBar(this);

La clase KToolBar es la que implementa una toolbar en KDE. Usandola se obtiene automaticamente una apariencia standard en las toolbar, entre otras cosas.

toolbar->insertButton(BarIcon("reload"), TOOLBAR_ID_ADDBOOKMARK, SIGNAL(clicked(int)),this,SLOT(bookLocation()),TRUE, i18n("Add to Bookmarks")); toolbar->insertButton(BarIcon("back"), TOOLBAR_ID_BACK, SIGNAL(clicked(int)),this,SLOT(gotoPreviousPage()), FALSE, i18n("Back to previous page")); toolbar->insertButton(BarIcon("exit"), TOOLBAR_ID_QUIT, SIGNAL(clicked(int)),kapp,SLOT(quit()),TRUE, i18n("Quit the application"));

Vamos a añadir tres botones, el primero se usará en vez del gran botón (y si se me permite, feo) que usamos en p5. El segundo botón se usará para volver a las páginais visitadas previamente, así que habrá que manejar un historial de las páginas visitadas. El tercer botón se usará para salir de la aplicación. (Nota: Las autoridades en diseño de interfaz de usuario recomiendan no poner un botón en la toolbar para salir de la aplicación, como esto no es una aplicación real, vamos a omitir ese serio consejo).

La función BarIcon("reload") automaticamente busca el icono de reload (recargar), esté donde esté, siempre que sea en un sitio estandard para los iconos de la toolbar, como $KDEDIR/share/toolbar o $KDEDIR/share/apps/<appname>/toolbar, y devuelve un objeto QPixmap válido que lo almacena.

El siguiente parámetro es el identificador que vamos a usar para después referirnos a este botón, después viene la señal que vamos a conectar y el objeto que la recivirá, seguido por el slot que será llamado. En la mayoría de los casos no habra que preocuparse por los botones de la toolbar, con lo que se puede usar 0 como identificador para cada botón, ya que no hay que asignar un único identificador a cada uno. Una excepción a esto es cuando se quiere habilitar/ deshabilitar un botón. tal como queremos hacer con el botón de vuelta (back.

addToolBar(toolbar);

Hemos terminado de construir la barra de herramientas, así que la añadimos al widget principal con addToolBar. addToolBar no sigue la misma nomenclatura que setMenu porque no se esta restringido a usar una sola barra de herramientas (como es el caso de los menús), así que no la estas poniendo, sino añadiéndo.

Page 30: Tutorial de KDE

KConfig *config=kapp->config(); config->setGroup("Settings"); location->setText( config->readEntry( "defaultPage",

"http://localhost") );

Ya sabemos lo que es kapp, pero no hemos usado el metodo de configuración ( config() ) antes. Este método devuelve el objeto KConfig activo para esta aplicación. KConfig es usado para almacenar las variables de configuración de la aplicación. Automaticamente almacena los campos de configuración en el fichero de configuración de la aplicación y la recupera cuando se ejecuta la aplicación de nuevo.

Una vez que tenemos el objeto de configuración, ponemos el grupo activo a Settings para trabajar en el. Los grupos se usan para estructurar los ficheros de configuración.

Finalmente, en vez de usar la cadena "http://localhost" como dirección por defecto, la leemos de la configuración de la aplicación. El nombre del campo que queremos leer es defaultPage, y "http://localhost" es el valor por defecto que se devuelve si el fichero de configuración no contiene ningún valor para ese campo.

Además se pueden leer valores enteros con KConfig::readNumEntry, valores booleanos con KConfig::readBoolEntry, etc.

DCOPClient *client = kapp->dcopClient(); client->attach(); client->registerAs("p7");

Finalmente añadimos esta aplicación al servidor DCOP y la registramos como p7, ya que queremos que p8 puede llamarla para poner una página.

void MainWindow::setURL( QString url ){ location->setText( url ); changeLocation();}

Este miembro es ahora usado para poner de forma genérica un url. Actualmente esta función puede ser llamada por el interface DCOP o cuando el usuario pulsa sobre un enlace. Primero hacemos que el texto de la dirección contenga el url, y entonces llamamos a la función changeLocation.

void MainWindow::changeLocation(){ history.push( browser->url() );

Page 31: Tutorial de KDE

toolBar()->setItemEnabled( TOOLBAR_ID_BACK, TRUE); browser->openURL( location->text() );}

changeLocation (que además está conectada a la señal enterPressed() en el objeto location) se usa como el lugar central para cargar una nueva página una vez que el url este en la barra de direcciones. Esto se realiza para implementar la capacidad de almacenar un historial de direcciones en p7.

Nos fijamos ahora en que definimos un objeto historia ( history ) en p7.h del tipo QValueStack <QString> . QValueStack es una clase de Qt que implementa una pila usando un template (plantilla) como tipo de datos para sus elementos. La diferencia entre QValueStack y QStack es que el primero almacena el valor de los elementos que se añaden, creando una copia de cada elemento (debe de haber un constructor de copia para eso), mientras el segundo simplemente almacena punteros a los elementos que se añaden, asi que habría que tener cuidado de no borrarlos mientras permanecen en la pila.

Como la clase QString comparte los datos entre todas las copias de la cadena hasta que se realiza un cambio a una de ellas, no hay apenas penalización por crear copias de los urls.

Ahora tenemos la barra de herramientas por defecto ( si hubiera mas, podriamos conseguir las otras usando un identificador como parámetro a toolBar), y activar el botón de vuelta (back) para que el usuario pueda usarlo.

Finalmente abrimos la nueva url en el widget navegador ( browser ) cogiendo la url de la barra de dirección.

void MainWindow::gotoPreviousPage(){ location->setText( history.pop() );

Cuando el usuario quiera volver hacia atra a una página anterior simplemente tomaremos el último elemento insertado en la pila del historial ( con un pop, ya que es una pila después de todo :-) ), y lo ponemos en la barra de direcciones. La cadena es eliminada de la pila, con lo que la próxima vez que se haga un pop tomará el siguiente y así sucesivamente hasta que se vacíe la pila.

if (history.isEmpty()) toolBar()->setItemEnabled( TOOLBAR_ID_BACK, FALSE); browser->openURL( location->text() );}

Page 32: Tutorial de KDE

Si la pila está vacía, mejor deshabilitamos el botón back, una vez que añadimos un elemento el botón es habilitado de nuevo en changeLocation(). Entonces abrimos el url que sacamos (popped) de la pila en el browser.

void MainWindow::fileSetDefaultPage(){ KConfig *config=kapp->config(); config->setGroup("Settings"); config->writeEntry( "defaultPage", browser->url() );}

Finalmente implementamos el método que es conectado al elemento del menú Set default page. Este método almacena el url actual en el fichero de configuración de esta aplicación.

Primero cogemos el objeto KConfig y lo activamos el grupo de Settings, tal como hicimos en el constructor. Entonces escribimos una nueva entrada conwriteEntry.

El primer parámetro es el nombre del campo, y el segundo el valor que le queremos asignar. Gracias a la sobrecarga de funciones podemos usar writeEntry con diferentes tipos de datos en el segundo parámetro ( int, bool, float, QString, QStringList, QFont , etc. ).

Ahora estamos llegando al final del tutorial, pero primero tenemos que hacerle un pequeño cambio a p6 para que se pueda comunicar con p7 para poner la página actual cuando se pulse sobre un url en la lista de marcadores, ya que en otro caso no tendría mucho sentido tener una lista de marcadores :-).

Este programa, p8, es una versión actualizada de p6 con la habilidad de enviarle a p7 el url que se seleccione de la lista. Esto permitirá a p7 el abrir nuestros urls favoritos.

#include <kapp.h>#include "p8.h" int main( int argc, char **argv ){ KApplication a( argc, argv, "p8" ); MainList *mylist=new MainList; mylist->resize( 300, 200 ); a.setMainWidget( mylist ); mylist->show();

Page 33: Tutorial de KDE

return a.exec();}

main.cpp

#include <dcopobject.h>#include <qstring.h> class p8Iface : virtual public DCOPObject{ K_DCOP

k_dcop: virtual void add( const QString s ) = 0; };

p8Iface.h

#include "p8Iface.h"#include <qlistview.h> class MainList : public QListView, virtual public p8Iface{ Q_OBJECT public: MainList(); void add ( const QString url );public slots: void setURLInBrowser ( QListViewItem *item ); };

p8.h

#include "p8.h"#include <klocale.h>#include <kapp.h>#include <dcopclient.h>#include <kdebug.h> MainList::MainList() : QListView ( 0L, "Bookmarks" ), DCOPObject ( "bookmarkList" ){ addColumn( i18n("My Bookmarks") ); connect( this, SIGNAL(clicked(QListViewItem *)),

this, SLOT(setURLInBrowser(QListViewItem *))); DCOPClient *client=kapp->dcopClient(); client->attach();

Page 34: Tutorial de KDE

client->registerAs("p8");} void MainList::add( const QString url ){ insertItem ( new QListViewItem ( this , url ) );} void MainList::setURLInBrowser( QListViewItem *item ){ if (item==0L) return; DCOPClient *client=kapp->dcopClient(); QByteArray params; QDataStream stream(params, IO_WriteOnly); stream << item->text(0); if (!client->send("p7-*", "browser", "setURL(QString)", params)) kdDebug() << "Error with DCOP\n";

}

p8.cpp

Tanto main.cpp, como p8Iface.h son muy similares a los ficheros respectivos en p6, asi que no los comentare.

connect( this, SIGNAL(clicked(QListViewItem *)), this, SLOT(setURLInBrowser(QListViewItem *)));

Hemos añadido un nuevo slot, setURLInBrowser(QListViewItem *), que pasa el url que esta en QListViewItem al navegador (browser) (p7).

Este es un curioso ejemplo de como se puede conectar una señal en un objeto a un slot del mismo objeto. Esta claro que no es la mejor manera de conseguir esa funcionalidad, pero al menos deja un código claro para ser usado en un tutorial.

Por si acaso no se recuerda de p6, QListViewItem es la clase que representa un elemento en un QListView. Cuando se pulsa un elemento, el objeto QListView emite una señal con un parámetro que es el QListViewItem sobre el que ha pulsado el usuario.

void MainList::setURLInBrowser( QListViewItem *item ){ if (item==0L) return;

En este slot primero nos aseguramos de que el usuario ha pulsado sobre un elemento. Si se ha pulsado sobre un area vacía el widget emite también una señal (al fin y al cabo el usuario ha pulsado en él), pero con un parámetro NULL. En ese caso no hacemos nada.

Page 35: Tutorial de KDE

DCOPClient *client=kapp->dcopClient(); QByteArray params; QDataStream stream(params, IO_WriteOnly); stream << item->text(0);

Si tenemos un url para enviar, cogemos el objeto DCOPCLient tal como hicimos en p5 o p7 para enviar un mensaje DCOP y entonces preparamos el flujo de datos (data stream).

Con item->text(0) cogemos el texto en la primera columna del elemento pulsado (la primera columna es la única que hemos creado), y lo almacenamos en el stream.

if (!client->send("p7-*", "browser", "setURL(QString)", params)) kdebugError( "Error with DCOP");}

Finalmente enviamos una llamada DCOP a la aplicación p7, objeto browser y método setURL(QString) con el parámetro params.

Ahora estamos alcanzando el final del tutorial, pero primero, vamos a hacer algunas modificaciones para simplificar el código fuente y usar las últimas tecnicas disponibles.

Ahora vamos a simplificar p7 a la vez que vamos a maximizar la configurabilidad del programa usando la arquitectura XMLGUI para construir el interfaz de usuario.

#include <kapp.h>#include <kcmdlineargs.h>#include <klocale.h>#include <kaboutdata.h>#include "p9.h"

int main( int argc, char **argv ){ KAboutData *aboutdata = new KAboutData("p9", I18N_NOOP("KDE Tutorial - p9"), "1.0", I18N_NOOP("Step 9 of a simple tutorial"), KAboutData::License_GPL, "(C) 2000, 2001 Antonio Larrosa Jimenez","", "http://devel-home.kde.org/~larrosa/tutorial.html"); aboutdata->addAuthor("Antonio Larrosa Jimenez", I18N_NOOP("Original Developer/Mantainer"),"[email protected]", "http://devel-home.kde.org/~larrosa/index.html");

KCmdLineArgs::init(argc, argv, aboutdata);

KApplication a;

Page 36: Tutorial de KDE

MainWindow *mywindow=new MainWindow( "Tutorial - p9" ); mywindow->resize( 300, 200 );

a.setMainWidget( mywindow ); mywindow->show();

return a.exec();

}

main.cpp

#include <dcopobject.h> class p9Iface : virtual public DCOPObject{ K_DCOP k_dcop: virtual void setURL( const QString s )=0; };

p9Iface.h

#include "p9Iface.h"#include <kmainwindow.h>#include <kurl.h>#include <kparts/browserextension.h>#include <qvaluestack.h> class QLineEdit;class KHTMLPart;

class MainWindow : public KMainWindow, virtual public p9Iface{ Q_OBJECTpublic: MainWindow ( const char * titulo );

virtual void setURL ( const QString url );

public slots: void fileSetDefaultPage(); void changeLocation(); void bookLocation(); void gotoPreviousPage(); void openURLRequest(const KURL &url, const KParts::URLArgs & );

private: QLineEdit *location; KHTMLPart *browser; QValueStack <QString> history;};

Page 37: Tutorial de KDE

p9.h

#include "p9.h"#include <qvbox.h>#include <qlineedit.h>#include <dcopclient.h>#include <kfiledialog.h>#include <kapp.h>#include <kaction.h>#include <klocale.h>#include <khtml_part.h>#include <kdebug.h>#include <kconfig.h>#include <kstdaction.h>

MainWindow::MainWindow ( const char * name ) : KMainWindow ( 0L, name ),DCOPObject ( "browser" ){

KStdAction::quit(this, SLOT(close()), actionCollection());

(void)new KAction(i18n("&Set default page"), "gohome", 0, this, SLOT(fileSetDefaultPage()), actionCollection(),

"set_default_page");

(void)new KAction(i18n("Add to Bookmarks"), "reload", 0, this, SLOT(bookLocation()), actionCollection(),

"add_to_bookmarks");

(void)new KAction(i18n("Back to previous page"), "back", 0, this, SLOT(gotoPreviousPage()), actionCollection(), "back");

actionCollection()->action("back")->setEnabled(false);

createGUI("p9ui.rc");

QVBox * vbox = new QVBox ( this );

location = new QLineEdit ( vbox );

KConfig *config=kapp->config(); config->setGroup("Settings"); location->setText( config->readEntry( "defaultPage", "http://localhost") );

connect( location , SIGNAL( returnPressed() ), this, SLOT( changeLocation() ) );

browser=new KHTMLPart( vbox ); browser->openURL( location->text() );

connect( browser->browserExtension(), SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ), this, SLOT( openURLRequest(const KURL &, const KParts::URLArgs & ) ) );

Page 38: Tutorial de KDE

setCentralWidget( vbox );

DCOPClient *client = kapp->dcopClient(); client->attach(); client->registerAs("p7");}

void MainWindow::changeLocation(){ history.push( browser->url().url() ); actionCollection()->action("back")->setEnabled(true); browser->openURL( location->text() );}

void MainWindow::setURL( const QString url ){ location->setText( url ); changeLocation();}

void MainWindow::openURLRequest( const KURL &url, const KParts::URLArgs & ){ setURL( url.url() );}

void MainWindow::gotoPreviousPage(){ location->setText( history.pop() ); if (history.isEmpty()) actionCollection()->action("back")->setEnabled(false); browser->openURL( location->text() );}

void MainWindow::bookLocation(){ DCOPClient *client=kapp->dcopClient(); QByteArray params; QDataStream stream(params, IO_WriteOnly); stream << location->text(); if (!client->send("p8-*", "bookmarkList", "add(QString)", params)) kdDebug << "Error with DCOP";}

void MainWindow::fileSetDefaultPage(){ KConfig *config=kapp->config();

config->setGroup("Settings"); config->writeEntry( "defaultPage", browser->url().url() );}

p9.cpp

Page 39: Tutorial de KDE

KAboutData *aboutdata = new KAboutData("p9", I18N_NOOP("KDE Tutorial - p9"), "1.0", I18N_NOOP("Step 9 of a simple tutorial"), KAboutData::License_GPL, "(C) 2000, Antonio Larrosa Jimenez","", "http://devel-home.kde.org/~larrosa/tutorial.html");

Cuando seguramente ya pensabas que no cambiariamos más el fichero main.cpp, aquí nos encontramos un cambio para generar el diálogo de "about" ("Sobre...") automaticamente.

La clase KAboutData se usa para guardar los datos para el diálogo de "about" (¿no era obvio? :-) ) . Primero pasamos el nombre interno de la aplicación, después el nombre "real" (el que aparece en la pantalla), la versión, una descripción corta, la licencia, la nota de copyright, un texto libre (nada en este caso) y la página web de la aplicación.

Observamos que en lugar de la típica función i18n, ahora estamos usando una macro llamada I18N_NOOP, esto lo hacemos así porque no podemos usar i18n antes de crear un objeto KApplication, con lo que usamos I18N_NOOP, que "marca" el texto para traducirlo más tarde.

aboutdata->addAuthor("Antonio Larrosa Jimenez", I18N_NOOP("Original Developer/Mantainer"),"[email protected]", "http://devel-home.kde.org/~larrosa/index.html");

Ahora, añadimos la información de un autor (podemos añadir información de tantos autores como queramos). Primero, el nombre (no, no lo marcamos para traducción ;-) ), después, el cargo dentro de la aplicación, la dirección de correo electrónico, y su página web personal. KCmdLineArgs::init(argc, argv, aboutdata);

KApplication a;

Aquí inicializamos los argumentos de la linea de comando, pasándoselos junto con el objeto aboutdata al método estático KCmdLineArgs::init.

Observa que ahora podemos simplemente usar los parámetros por defecto para el constructor de KApplication, ya que cogerá toda la información necesaria del objeto aboutdata.

Ok, ahora podemos echar un vistazo a p9.cpp :

#define TOOLBAR_ID_ADDBOOKMARK 1#define TOOLBAR_ID_BACK 2#define TOOLBAR_ID_QUIT 3

Page 40: Tutorial de KDE

Primero notamos que estas lineas ya no existen. Vamos a usar otro mecanismo que es mucho más intuitivo y por tanto nos permite simplificar el código.

Vamos a usar objetos KAction . Esta es una característica radicalmente nueva y potente que simplifica la creación y mantenimiento de interfaces de usuario.

Crearemos un objeto KAction para cada acción que el usuario pueda hacer. Hay dos tipos de acciones, las estándares (como abrir un archivo, guardar, deshacer, salir, etc.) y las propias (las específicas de cada aplicación).

KStdAction::quit(this, SLOT(close()), actionCollection());

Primero, creamos una acción estandar "quit" (salir), que está conectada al objeto this y al slot close(). El parámetro actionCollection() es sólo el objeto que almacena todas las acciones de la aplicación.

(void)new KAction(i18n("&Set default page"), "gohome", 0, this, SLOT(fileSetDefaultPage()), actionCollection(), "set_default_page")

Ahora creamos una nueva acción. El primer parámetro es el texto que queremos mostrar cuando esta acción está en una barra de menú, el segundo parámetro es el nombre del icono que será mostrado cuando esta acción esté en una barra de herramientas o de menú. Observa que sólo especificamos el nombre del icono en vez del propio icono. Esto permite a las librerías hacer algunas cosas interesantes, como aplicar efectos y elegir el tamaño adecuado del icono para las barras de menú o de herramientas. El tercer parámetro es la tecla "atajo" que el usuario puede pulsar para activar la acción (en este caso, no tenemos ningún atajo). Después, especificamos el objeto y slot que se llamarán cuando esta acción se active y el objeto actionCollection().

Finalmente, ponemos el nombre de la acción (el nombre debe identificar a la acción, con lo que debe ser único).

Observa que no hemos guardado la acción en ninguna variable, ya que será gestionada por el objeto devuelto por actionCollection()

(void)new KAction(i18n("Add to Bookmarks"), "reload", 0, this, SLOT(bookLocation()), actionCollection(),

"add_to_bookmarks");

(void)new KAction(i18n("Back to previous page"), "back", 0, this, SLOT(gotoPreviousPage()), actionCollection(), "back");

Page 41: Tutorial de KDE

Aquí creamos un par de acciones más, para añadir la página actual a la lista de marcadores y para ir atrás a la página anterior.

actionCollection()->action("back")->setEnabled(false);

Con esta llamada, deshabilitamos el botón de atrás ("back"). Primero cogemos la colección de acciones y le pedimos la acción con nombre "back", entonces llamamos a setEnabled(false) en esta acción para deshabilitarla.

El gran beneficio de esto es que no tenemos que llevar los elementos del menú y de la barra de herramientas por separado, simplemente dehabilitamos la acción y las correspondientes entradas de menú y botones de la barra de herramientas se deshabilitan automaticamente. No hay que usar nunca más millones de definiciones para cada ID de elementos de menú e IDs para botones de la barra de herramientas, ¿ no es fantástico ? :-)

createGUI("p9ui.rc");

Llamando a createGUI, hacemos que las acciones se conecten a las barras de menú y de herramientas.

Ahora miramos el contenido de p9ui.rc :

<!DOCTYPE kpartgui><kpartgui name="p9"><MenuBar> <Menu name="file"><text>&File</text> <Action name="set_default_page"/> </Menu></MenuBar><ToolBar fullWidth="true" name="mainToolBar"> <Action name="add_to_bookmarks"/> <Action name="back"/> <Separator/> <Action name="file_quit"/></ToolBar></kpartgui>

Este es un fichero XML que contiene la definición del entorno de usuario para p9:

<kpartgui name="p9">

Esto especifica que estamos creando la interfaz de la aplicación p9.

<MenuBar> <Menu name="file"><text>&File</text> <Action name="set_default_page"/>

Page 42: Tutorial de KDE

</Menu></MenuBar>

Primero, definimos una barra de menú en la sección MenuBar. Para cada menú, tenemos una sección "Menu" con su nombre (en este caso sólo tenemos un menú "file" ya que el menú de ayuda se añade automáticamente), y en cada sección "Menu", ponemos las acciones que queremos que aparezcan en ese menú.

<ToolBar fullWidth="true" name="mainToolBar"> <Action name="add_to_bookmarks"/> <Action name="back"/> <Separator/> <Action name="file_quit"/></ToolBar>

Ahora, definimos la barra de herramientas (que llamaremos "mainToolBar" ya que podemos tener más de una barra de herramientas). Sólo escribimos las acciones que queremos que aparezcan en la barra de herramientas, con algunos separadores (opcional).

Por cierto, la misma sintaxis para los separadores se puede usar para insertar separadores en los menús.

Volviendo a p9.cpp, observamos que estamos registrándolo como "p7" al servidor dcop. Hacemos esto sólo para poder usar p8 con p9 tal y como hicimos con p7.

Como una nota interesante, compararemos el número de lineas de p7.cpp con p9.cpp :

wc p7/p7.cpp p9/p9.cpp 124 324 3454 p7/p7.cpp 102 238 2687 p9/p9.cpp

En p9.cpp tenemos 22 lineas menos que en p7.cpp !!!

Así que a la vez que hemos eliminado el 17.7 por ciento del código fuente, hemos permitido la posibilidad de que el usuario configure el interfaz de la aplicación (sólo tiene que modificar el archivo XML y volver a ejecutar la aplicación, sin necesidad de recompilar ! )

Si quieres saber más sobre KXMLGUI, no olvides leer el excelente tutorial de Kurt Granroth en http://developer.kde.org/documentation/tutorials/xmlui/preface.html.

Page 43: Tutorial de KDE

Este es el último paso del tutorial. Espero que hayas aprendido muchas cosas, y decidas comenzar tu propia aplicación con KDE. Si es así, hecha un vistazo a las sugerencias en la Introducción para ver como resolver las dudas que vayan surgiendo y leer algunas ideas sobre como comenzar a escribir la aplicación.

Gracias por leer este tutorial,

Antonio Larrosa