CursoClip7JSL

De Clip en Castellano

Contenido

Curso Clip: lenguaje xBase para GNU/Linux

Séptimas Jornadas de Software Libre - Córdoba, Argentina

Autor: Gustavo A. Courault

Temario del curso:

  • Instalación de Clip en Ubuntu/Debian
  • Características y bondades de Clip
  • Mi primer programa en Clip
  • Observaciones y cuidados con las localización
  • Migrar un programa escrito en CA-Clipper a Clip
  • Usando Clip con MySQL como ejemplo de base de datos relacional
  • Usar Clip para el desarrollo de aplicaciones gráficas con Glade y libglade.


Instalación de Clip

Primero debe descargar desde ITK la última versión de Clip que viene en un tarball del estilo:

clip-prg-1.2.0-1.tgz

Y lo descompactamos:

$ tar xzvf clip-prg-1.2.0-1.tgz

Para instalar Clip en Ubuntu Dapper, Edgy o Feisty Fawn se necesitan tener instaldos los siguiente paquetes, los cuales se pueden instalar mediate apt-get o synaptic:

  • gcc
  • make
  • bison
  • flex
  • libc6-dev
  • libncurses5-dev

Si uno desea además soporte para MySQL, PostrgreSQL, ODBC o Firebird necesita instalar los siguiente paquetes respectivamente:

  • libmysqlclient15-dev
  • libpq-dev
  • libodbc2-dev
  • firebird2-dev

Para que Clip tenga soporte gráfico, se deben instalar los siguientes paquetes:

  • libgtk1.2-dev
  • libglade2-dev

Para la instalación de estos paquete y otros podemos utilizar apt-get o synaptic. Estas instrucciones también funcionan para Debian Sarge y Etch.

Nos cambiamos al directorio que vamos a compilar:

$ cd clip-prg-1.2.0-1

Cambiamos la variable LANG a una que entienda Clip:

$ export LANG=es_ES

Compilamos para todo el sistema:

$ sudo make system

Como Clip no soporta UTF-8, para que se vean correctamente el set de caracteres en el ambiente X se puede usar el emulador de terminal rxvt:

$ sudo apt-get install rxvt

Una vez instalado Clip, podemos ver si funciona correctamente con el comando:

$ clip -V

Nos da el número de versión, la licencia GPL y termina.

Características y bonades de Clip

Breve Historia

Clip es un superset de CA-Clipper 5.3, el legendario compilador de xBase que hizo furor en los 80. La compañía que creó Clipper: Nantucket Corporation, vendió este compilador a Computer Associates (CA), empresa que iba a desarrollar el así llamado "Proyecto Aspen", una aproximación del mundo xBase a la programación bajo MS-Windows. Es así que nace el malogrado CA-Visual Objects, un fracaso desde todo punto de vista; dejando sin más desarrollo a Clipper, el cual detiene su desarrollo en la versión 5.3.

A esto se suma la compra por parte de Microsoft de FoxPro, en ese momento por la versión 2.5. Microsoft libera FoxPro 2.6 como última versión orientada a caracteres y con una adaptación a MS-Windows. Sin embargo deja de lado todos esos desarrollos en pos de Visual-Fox, el cual gracias a las quejas de los usuarios y su gran plataforma de desarrolladores no es dejado de lado por la versión 7.

Es así que Clipper y otros lenguajes como FoxPro quedan sin soporte ni continuidad, dejando huérfanos a una enorme cantidad de desarrolladores, bibliotecas y sistemas.

En los 90 y con el auge del software libre se gestan varios proyectos para suplir este vacío, Clip es uno de ellos.

Características

Clip tiene licencia GPL, es decir que a menos que compremos el compilador a la empresa ITK: http://www.itk.ru/english/index.shtml nuestro desarrollo deberá tener esa licencia.

Es multiplataforma debido a que usa GCC para generar los binarios. En estos momentos funciona muy bien con GNU/Linux pero no tan bien con MS-Windows debido a que se debe usar Cygwin. El desarrollo de Clip para Cygwin está detenido.

   * Es orientado a objetos, a diferencia de Clipper uno puede crear sus propias clases.
   * Se conecta a bases de datos relacionales: PostgreSQL, MySQL, Firebird, Oracle y ODBC
   * Puede usar DBFCDX, DBFSIX, DBFNTX, DBFMDX
   * Tiene la tecnología de FoxPro: "rushmore" para las tablas DBF
   * Puede utilizar "sockets" TCP
   * Es muy rápido
   * Permite la internacionalización
   * Genera ejecutables muy pequeños.
   * Es fácil para migrar de Clipper a Clip
   * Soporta alguna extensiones FoxPro
   * Soporta sintaxis Foxplus
   * Mediante bibliotecas permite crear interfaces gráficas.


Actividad Nro 1: Descargar e instalar Clip en cada estación de trabajo

Mi primer programa Clip

Vayamos con el consabido "Hola Mundo"

¿Con qué editar? Clip no posee una IDE, de modo que hay que editar con un editor tal como vi, vim, emacs, kedit, nano, pico, gedit, etc.

 /* Mi primer progama Clip
  hola.prg
 */
 function main()
 clear // pone a Clip en modo "pantalla"
 ? "hola mundo"
 return NIL



Compilamos:

 $ export LANG=es_ES     #sino nos dice que no tiene el lenguaje utf-8
 $ clip -e hola.prg

ejecutamos dentro de una terminal rxvt (de nuevo por las internacionalizaciones)

 $ ./hola

Veamos un ejemplo con un menu usando la clase omenu:

 /*
 Demostración de un menu
 menu.prg
 */
 #include "button.ch" // Menu definitions
 #include "inkey.ch" // Key definitions
 local oInfo // Es el nombre del menu a crear
 SET( _SET_EVENTMASK, INKEY_ALL ) //Para que funcionen en distintas terminales las tecla de funcion
 msetcursor(.T.) // muestra el cursor
 cls //Borra la pantalla
 oInfo=MENU_Create() // Creamos el menu
 DO WHILE MenuModal(oInfo,1,24,1,79,"r/w") <> 999 //Estamos en un lazo hasta que no salimos con 999
 ENDDO
 return(NIL)
 // This function will create the menu and return the newly created
 // MenuObject as its return value. This menu consists of three "Main Menu choices" ..
 // File, Edit, and Options. Additionally, the Edit TopBarMenu item has a secondary menu for
 // one of its menu items.
 function MENU_Create()
 local oTopBar, oPopUp, oPopUp1, oItem
 // TopBar() creates the menu at the very top of the screen
 oTopBar := TopBar( 0, 0, 79)
 oTopBar:ColorSpec :="b/w,gr+/rb,r/w,g/rb,n+/w,w+/b"
 // Create a new popup menu named FILE and add it to the TopBar object
 oPopUp := PopUp()
 oPopUp :ColorSpec:= "b/w,gr+/rb,r/w,g/rb,n+/w,w+/b"
 oTopBar:AddItem( MenuItem ( "&File",oPopUp) )
 // Add some menu items to the newly created File popup
 oItem :=MenuItem( "&New" ,{|| MyCreateFile() }, K_CTRL_N,"Create a new file", 101)
 oPopUp:AddItem( oItem)
 oItem :=MenuItem( "&Open..." ,{|| MyOpenFile() }, K_CTRL_O,"Open a file")
 oPopUp:AddItem( oItem)
 oItem :=MenuItem( "&Save" ,{|| MySaveFile() }, K_CTRL_S,"Save a file")
 // Disable this menu item
 oItem:Enabled := .f.
 oPopUp:AddItem( oItem)
 // Add a separator
 oPopUp:AddItem( MenuItem( MENU_SEPARATOR ) )
 oItem :=MenuItem( "&Print..." ,{|| MyPrintFile() }, K_CTRL_P,"Print a file",HASH_Print)
 // Disable this menu item
 oItem:Enabled := .f.
 oPopUp:AddItem( oItem)
 // Another separator
 oPopUp:AddItem( MenuItem( MENU_SEPARATOR ) )
 oItem :=MenuItem( "E&xit" ,{|| .t. }, K_ALT_F4,"End of application", 999)
 oPopUp:AddItem( oItem)
 // Create a second popup menu named EDIT and attach it to oTopBar 
 oPopUp := PopUp()
 oPopUp :ColorSpec:= "b/w,gr+/rb,r/w,g/rb,n+/w,w+/b"
 oTopBar:AddItem( MenuItem ( "&Edit",oPopUp) )
 // Add some menu items to this EDIT popup menu
 oItem :=MenuItem( "&Undo" ,{|| MyUndo() }, K_CTRL_Z,"Reverse changes made to this file")
 oPopUp:AddItem( oItem)
 oItem :=MenuItem( "Cu&t" ,{|| MyCut() }, K_CTRL_X,"Cut to clipboard")
 oPopUp:AddItem( oItem)
 oItem :=MenuItem( "&Copy" ,{|| MyCopy() }, K_CTRL_C,"Copy to clipboard")
 oPopUp:AddItem( oItem)
 oItem :=MenuItem( "&Paste" ,{|| MyPaste() }, K_CTRL_V,"Paste from clipboard")
 oPopUp:AddItem( oItem)
 // Another separator
 oPopUp:AddItem( MenuItem( MENU_SEPARATOR ) )
 // Create a new popup menu on the EDIT popup menu named Go. (This is also
 // known as a "Cascading" menu.)
 oPopUp1 := PopUp()
 oPopUp1 :ColorSpec:= "b/w,gr+/rb,r/w,g/rb,n+/w,w+/b"
 oItem :=MenuItem( "&Go",oPopUp1 )
 oPopup:AddItem( oItem ) 
 // Add some items to the Go cascading menu
 oItem :=MenuItem( "&Go To..." ,{|| MyGoToLine() }, K_F5,"Go to a specific line number")
 oPopUp1:AddItem( oItem)
 oItem :=MenuItem( "G&o To Top" ,{|| MyGoTop() }, K_CTRL_HOME,"Go to top of file")
 oPopUp1:AddItem( oItem)
 oItem :=MenuItem( "Go To &Bottom" ,{|| MyGoBottom() }, K_CTRL_END,"Go to bottom of file")
 oPopUp1:AddItem( oItem)
 // Add yet another separator
 oPopUp:AddItem( MenuItem( MENU_SEPARATOR ) )
 // Add one final menu item to the EDIT popup menu
 oItem :=MenuItem( "&Find..." ,{|| MySearch() }, K_ALT_F3,"Search for text")
 oPopUp:AddItem( oItem)
 //Create a 3rd PopUpMenu called OPTIONS and attatch it to oTopBar
 oPopUp := PopUp()
 oPopUp :ColorSpec:= "b/w,gr+/rb,r/w,g/rb,n+/w,w+/b"
 oTopBar:AddItem( MenuItem ( "&Options",oPopUp) )
 oItem :=MenuItem( "AutoSave" ,{|| MyAutoSave() },,"Toggle auto save preference")
 oItem:Checked := .t.
 oPopUp:AddItem( oItem)
 oItem :=MenuItem( "Tab Stops..." ,{|| MyTabStops() },,"Set number of spaces for tab stops")
 oPopUp:AddItem( oItem)
 // Return our TopBar object back to MENU_Test()
 return ( oTopBar)
 // The following are dummy functions which do not do anything
 // These functions are here to avoid receiving Unresolved External
 // Errors
 FUNCTION MyCreateFile()
 alert("create file")
 RETURN NIL
 FUNCTION MyOpenFile()
 RETURN NIL
 FUNCTION MySaveFile()
 RETURN NIL
 FUNCTION MyPrintFile()
 RETURN NIL
 FUNCTION MyUndo()
 RETURN NIL
 FUNCTION MyCut()
 RETURN NIL
 FUNCTION MyCopy()
 RETURN NIL
 FUNCTION MyPaste()
 RETURN NIL
 FUNCTION MyGoToLine()
 RETURN NIL
 FUNCTION MyGoTop()
 RETURN NIL
 FUNCTION MyGoBottom()
 RETURN NIL
 FUNCTION MySearch()
 RETURN NIL
 FUNCTION MyAutoSave()
 RETURN NIL
 FUNCTION MyTabStops()
 RETURN NIL


Compilamos:

$ export LANG=es_ES ; clip -e menu.prg

ejecutamos:

$ rxvt -e ./menu

Observaciones y cuidados con la localización

Cuando se compila Clip, se crea una serie de archivos para administrar correctamente los distintos tipos de idiomas, terminales y conjunto de caracteres. No olvidemos que el Clip es de origen ruso, de modo que ellos sufren en carne propia los errores y omisiones en tal sentido. El manual de Clip tiene un extenso capítulo para explicar como crear las distintas mapeos de terminal y de teclado. Sin embargo tenemos la fortuna de que para el castellano dicho mapeo está disponible y funciona correctamente.

Para ello es indispensable darle a Clip el entorno adecuado al momento de compilar, eso se hace simplemente con la variable de entorno LANG:

 $ export LANG=es_ES

Esto se debe hacer antes de compilar el Clip en sí mismo y cualquier programa modo caracter.

Si consulto la variable de entorno de un Ubuntu Dapper, por ejemplo:

$ echo LANG
$ es_AR.UTF-8

El cual no concuerda con ninguno de los conjuntos de caracteres que tiene Clip preconfigurados.

Cuando configuramos correctamente el Clip, nos muestra los mensajes de error en castellano y muestra correctamente el conjunto de caracteres. Sin embargo no funciona correctamente en la gnome-terminal, ya que esta le dice al Clip que es una "xterm", la cual no tiene las características que necesita Clip para adminstrar la pantalla. Es por eso que es necesiario usar alguna otra terminal simple, la que mejor funciona por su facilidad de configuración es "rxvt".

Para invocar un programa modo caracter desde una terminal tal como la gnome-terminal, hacemos:

$ rxvt -e ./programa

Actividad 2: Ejecutar los programas anteriormente compilados en una gnome-terminal y en rxvt

Actividad 3: Hacer un acceso directo al escritorio

Migrar un programa CA-Clipper a Clip

Para migrrar un sistema Clipper a Clip es necesario seguir los siguientes pasos:

  1. Agregar al programa principal la sentencia "function main()"
  2. Renombrar el primer programa para que sea el primero en un "ls"
  3. Pasar todos los nombres de los archivos a minúsculas
  4. Fijarse en las incompatibilidades que nombra el manual de Clip: http://www.itk.ru/clip-doc.en/prb.html
  5. dar la siguiente orden para que tome bien el set de caracteres de MS-DOS:
 $ export LANG=en_EN.CP437

Compilar con:

 $ clip -e *.prg

Los fuentes, bases de datos y una mejor explicación lo pueden en AplicacionApadir

Actividad 4: Pasar a Clip una biblioteca (las superfunctions) y la aplicación "apadir"

Usando Clip con MySQL como ejemplo de base de datos relacional

Primera Parte Clip/MySQL - Nos conectamos a MySQL


Para ello escribimos las siguientes funciones que nos ocultan la complejidad y los detalles, esta serie de funciones las ponemos en un archivo llamado gcfunc.prg:

* ---------------------------------------
* Archivo: gcfunc.prg
* Función que se conecta a la base de datos MySQL
* Parámetros: usuario y clave
* Devuelve: el objeto de conexión
* ---------------------------------------
function conectargc( cUsuario, cPass )
  local oConn
  local host := ip_mysql()            // IP o nombre del host
  local port := NIL                   // uso el puerto por omisión
  local db := db_mysql()              // me conecto a la base de datos 
  local socket := NIL                 // uso el socket por omisión
  local flag := NIL                   // no se usa en MySQL

  // Conexión a la base de datos 
  oConn := ConnectNew( "MS" , host , port , cUsuario , cPass , db , socket , flag  )

return oConn
* -------------------
function ip_mysql()
return "127.0.0.1" 
* --------------------------------
function db_mysql()
return "gc" 
  • Fin de gcfunc.prg

El objeto de las funciones ip_mysql() y db_mysql() es mantener cierta parametrización la posibilidad de generalización de "conectargc" y cambiar con facilidad el nombre del host y el nombre de la base de datos.

Algunos datos como para probar.

Suponemos que el usuario de la base de datos MySQL es "gc" con clave "clip". Invocamos el monitor mysql:

mysql -h 127.0.0.1 -u root -p

Creamos la base de datos:

create gc;
use gc;

Utilizaremos la siguiente secuencia de comandos para crear y poblar nuestra base de datos:

CREATE TABLE `provincia` (
id_provincia` int(3) unsigned NOT NULL default '0',
pcia_nombre` varchar(20) NOT NULL default ,
pcia_abreviatura` char(3) NOT NULL default ,
pcia_codigoib` int(3) unsigned NOT NULL default '0',
pcia_letra` char(1) NOT NULL default ,
KEY `prov_nombre` (`pcia_nombre`,`pcia_codigoib`)
) TYPE=InnoDB COMMENT='Tabla de provincias unidas del río de la plata';
INSERT INTO `provincia` VALUES (901,'CAPITAL FEDERAL','CAP',901,'C');
INSERT INTO `provincia` VALUES (902,'BUENOS AIRES','BAI',902,'B');
INSERT INTO `provincia` VALUES (903,'CATAMARCA','CAT',903,'K');
INSERT INTO `provincia` VALUES (904,'CORDOBA','CBA',904,'X');
INSERT INTO `provincia` VALUES (905,'CORRIENTES','COR',905,'W');
INSERT INTO `provincia` VALUES (906,'CHACO','CHA',906,'H');
INSERT INTO `provincia` VALUES (907,'CHUBUT','CHU',907,'U');
INSERT INTO `provincia` VALUES (908,'ENTRE RIOS','ERI',908,'E');
INSERT INTO `provincia` VALUES (909,'FORMOSA','FOR',909,'P');
INSERT INTO `provincia` VALUES (910,'JUJUY','JUJ',910,'Y');
INSERT INTO `provincia` VALUES (911,'LA PAMPA','LAP',911,'L');
INSERT INTO `provincia` VALUES (912,'LA RIOJA','LAR',912,'F');
INSERT INTO `provincia` VALUES (913,'MENDOZA','MZA',913,'M');
INSERT INTO `provincia` VALUES (914,'MISIONES','MIS',914,'N');
INSERT INTO `provincia` VALUES (915,'NEUQUEN','NEU',915,'Q');
INSERT INTO `provincia` VALUES (916,'RIO NEGRO','RNE',916,'R');
INSERT INTO `provincia` VALUES (917,'SALTA','SAL',917,'A');
INSERT INTO `provincia` VALUES (918,'SAN JUAN','SJU',918,'J');
INSERT INTO `provincia` VALUES (919,'SAN LUIS','SLU',919,'D');
INSERT INTO `provincia` VALUES (920,'SANTA CRUZ','SCR',920,'Z');
INSERT INTO `provincia` VALUES (921,'SANTA FE','SFE',921,'S');
INSERT INTO `provincia` VALUES (922,'SANTIAGO DEL ESTERO','STG',922,'G');
INSERT INTO `provincia` VALUES (923,'TIERRA DEL FUEGO','TIE',923,'V');
INSERT INTO `provincia` VALUES (924,'TUCUMAN','TUC',924,'T');

Ahora que tenemos todo preparado vamos hacer nuestro primer programa Clip/MySQL:

* -----------------------------------------------------
* Programa para conectarse y mostrar la tabla provincia
* Nombre del programa: browselocalidad.prg
* -----------------------------------------------------
function main()
  oConn := conectargc( "gc" , "clip" )
  oRs := oConn:CreateRowset( "select * from localidad" )
  oRs:browse()
return NIL 

Compilamos ahora nuestra pequeña aplicación:

clip -e browselocalidad.prg gcfunc.prg -lclip-mysql

Probamos la aplicación:

./browselocalidad

¡Listo! Vemos la tabla en la pantalla y nos podemos mover con las flechas de movimiento del cursor. Mediante este browse sólo podemos ver los datos y no modificarlos.

Con la tecla Esc podemos salir del browse y retornar al shell.

Segunda Parte Clip/MySQL - Búsqueda usando MySQL y mostrar los resultados en un rowset

Ahora que tenemos nuestro andamiaje básico vamos a complicarlo un poco agregando una búsqueda.

El armado de rowset permite el pasaje de parámetros mediante un vector o arreglo de dos dimensiones.

Los elementos de este vector son entonces otros vectores

Supongamos que deseamos filtrar por las provincias que comiencen con cierto texto, por ejemplo SANTA FE de allí en adelante hasta digamos 20 registros, para ello usamos el siguiente comando SQL:

select * from provincia where pcia_nombre >= 'SANTA FE' limit 20

Pero queremos pasarle, en lugar de 'SANTA FE' una cadena ingresada por el usuario, supongamos que se llame cBusqueda esa cadena variable.

La forma de pasarle este parámetro es entonces:

* --
aParam := {} //Creamos un vector vacío

/* Ahora agregamos un elemento a aParam que es a su vez
   un vector con dos elementos: una cadena cPar que luego
   veremos para que se usa y lo que queremos buscar
*/
aadd( aParam , { "cPar" , cBusqueda } )

/* Generamos nuestro SQL
   Observemos que el parámetro se pasa mediante
   cPar y con dos puntos adelante.
*/
cSQL := "select * from provincia where pcia_nombre >= :cPar limit 20"

Pongamos ahora todo junto a ver que sucede:

* -----------------------------------------------------
* Programa para conectarse y mostrar la tabla provincia
* Nombre del programa: browsepcia.prg
* -----------------------------------------------------
#include "inkey.ch"
function main()
  local oConn
  local cBusqueda
  
  oConn := conectargc( "gc" , "clip" )
  
  while .t.
   clear
   cBusqueda := space(15)
   @ 1,1 say "Ingrese provincia a Buscar <Esc-Sale>" get cBusqueda
   read
   if lastkey() = K_ESC
     clear
     exit
   endif
   
   cBusqueda := alltrim( cBusqueda )
   aParam := {} //Creamos un vector vacío
   
   /* Ahora agregamos un elemento a aParam que es a su vez
      un vector con dos elementos: una cadena cPar que luego
      veremos para que se usa y lo que queremos buscar
   */
   aadd( aParam , { "cPar" , cBusqueda } )
   
   /* Generamos nuestro SQL
      Observemos que el parámetro se pasa mediante
      cPar y con dos puntos adelante.
   */
   cSQL := "select * from provincia where pcia_nombre >= :cPar limit 20"
   
   oRs := oConn:CreateRowset( cSQL , aParam )
   oRs:browse( 3 , 1 , 24 , 78 )      // Usamos el browse para ver los datos.
   oRs:destroy()                      // Destruimos el rowset para liberar los recursos
  enddo
return NIL
  • Fin BrowsePcia.prg

Ahora vamos a compilar nuestra aplicación:

$ clip -e browsepcia.prg gcfunc.prg -lclip-prg
$ ./browsepcia

¡Listo! ya tenemos una simple consulta funcionando.

Por supuesto podemos cambiar el comando SQL (ordenarlo de manera distinto por ejemplo)

Ahora que podemos mostrar los datos mediante una búsqueda, vamos a agregar registros, modificar registros y borrar registros de una tabla.

Clip nos ofrece dos métodos para las operaciones descriptas.

Metodo 1:

Mediante el mismo objeto *Rowset*, se le pasan como parámetros los comandos SQL, los cuales usando variables "implícitas" permite la modificación de la tabla.

Metodo 2:

Invocando el método *command* a la conexión. Se puede invocar este método siempre que no el comando SQL no sea un "select", es decir que tenga que devolver un resultado de la consulta.

El Método 2 es más rápido siempre. El Método 1 se vuelve lento a medida que crece el rowset en cantidad de registros (no importa la cantidad de registros que tenga la tabla, sino cuántos trae el rowset)

Usaremos un método simple para ingresar y modificar registros: cargar los datos en variables de memoria y luego aplicarles el comando SQL adecuado.

Siguiendo con nuestro ejemplo usaremos un "browse" para mostrar los datos y luego con las teclas <Insert>, <Intro> y <Supr>, agregar, modificar o borrar los registros.

Usaremos el método 2 por claridad en primer lugar.

Necesitamos entonces usar un TBrowse para controlar el ingreso de las teclas del usuario.

* --------------------------------------------------------
* Programa para conectarse y modificar la tabla provincia
* Metodo 2
* Nombre del programa: ambpcia.prg
* --------------------------------------------------------
#include "inkey.ch"
#include "pgch.ch"
function main() 

local oConn
local cBusqueda
local k

oConn := conectargc( "gc" , "clip" )

while .t.
  clear
  cBusqueda := space(15)
  @ 1,1 say "Ingrese provincia a Buscar <Esc-Sale>" get cBusqueda
  read
  if lastkey() = K_ESC
    clear
    exit
  endif

  cBusqueda := alltrim( cBusqueda )

  aParam := {} //Creamos un vector vacío

  /* Ahora agregamos un elemento a aParam que es a su vez
     un vector con dos elementos: una cadena cPar que luego
     veremos para que se usa y lo que queremos buscar
  */

  aadd( aParam , { "cPar" , cBusqueda } )

  /* Generamos nuestro SQL
     Observemos que el parámetro se pasa mediante
     cPar y con dos puntos adelante.
  */

  cSQL := "select * from provincia where pcia_nombre >= :cPar limit 20"

  oRs := oConn:CreateRowset( cSQL , aParam )
  
  /* Un lindo marco :-) y texto para el usuario*/

  @ 3,1 to 20,78
  @ 21,1 say " INTRO-Modifica, INS-Agrega, SUPR-Borra"

  /* Creamos el TBrowse */
  oTb := tbrowsenew( 4 , 2 , 19 , 77 )
  oTb:colSep := chr( PGCH_VLINE ) //Modificaciones
  oTb:headSep := chr( PGCH_TTEE ) + chr( PGCH_HLINE ) //para que luzca mejor

  /* Armamos las columnas del tbrowse */
  for k = 1 to oRs:NFields() // Cantidad de campos

  /* Usamos los métodos del rowset para escribir menos :-) */
  oTb:addcolumn( tbcolumnnew( oRs:Fieldname( k) , oRs:Fieldblock(k) ) )

  next k
  
  /* Declaramos los métodos para el movimiento en el rowset */
  oTB:goTopBlock := { || oRs:GoTop() }
  oTB:goBottomBlock := { || oRs:GoBottom() }
  oTB:skipBlock := { |n| SkipRecs( oRs , n ) } // La funcion SkipRecs se define en gcfunc

  /* El conocido lazo del TBrowse */
  while .t.
    while nextkey() == 0
      if oTB:stabilize()
        exit
      endif
    enddo
    oTB:refreshCurrent()
    oTB:ForceStable()

    nKey := inkey(0)
    do case
      case nKey == K_UP; oTB:up()
      case nKey == K_DOWN; oTB:down()
      case nKey == K_CTRL_PGUP; oTB:gotop()
      case nKey == K_CTRL_PGDN; oTB:gobottom()
      case nKey == K_LEFT; oTB:left()
      case nKey == K_RIGHT; oTB:right()
      case nKey == K_PGUP; oTB:pageup()
      case nKey == K_PGDN; oTB:pagedown()
      case nKey == K_INS // Agregamos un registro
        modpvcia( .t., oConn, oRs)
        // Refrescamos el rowset y el tbrowse
        oRs:refreshall()
        oTb:refreshCurrent()
      case nKey == K_ENTER // Modificamos un registro
        modpvcia( .f. ,oConn, oRs)
        // Refrescamos el rowset y el tbrowse
        oRs:refreshall()
        oTb:refreshCurrent()
      case nKey == K_DEL // Borramos un registro
        borrapvcia(oConn, oRs)
        // Refrescamos el rowset y el tbrowse
        oRs:refreshall()
        oTb:refreshCurrent()
      case nKey == K_ESC; exit
    endcase
  enddo
  oRs:destroy() // Destruimos el rowset para liberar los recursos
enddo
return NIL

/* Las funciones que necesitamos para modificar la tabla */

  • -----------------------------------------------

static function modpvcia( lAlta , oConn , oRs )

  • -----------------------------------------------
  • Parametro: lAlta es TRUE (.T.) si es un alta
  • FALSE (.F.) si es una edicion
  • oConn: Objeto conexión donde voy a ejecutar el
  • comando SQL
  • oRs: Rowset de donde buscar la informacion
  • Observación importante:
  • Los datos tipo caracter que traigo desde MySQL vienen del largo
  • de lo que está guardado y NO de la longitud del campo definida
  • en la estructura de la tabla
  • Si es un alta llenamos los campos con los valores en blanco,
  • sino guardamos los valores que están en la tabla
  • obteniendo el valor con el metodo GetValue

local cSQL local aParam

if lAlta nId_provincia := 0 cPcia_nombre := space(20) cPcia_abreviatura := space(3) nPcia_codigoib := 0 cPcia_letra := space(1)

  • El comando SQL para agregar el registro

cSQL := "insert into provincia values " + ; "( :id_provincia, " + ; ":pcia_nombre, " +; ":pcia_abreviatura, "+; ":pcia_codigoib, "+; ":pcia_letra )" else

  • Agrego espacios a la derecha cuando lo necesito

nId_provincia := oRs:GetValue("id_provincia") cPcia_nombre := padr( oRs:GetValue( "pcia_nombre") , 20 ) cPcia_abreviatura := padr( oRs:GetValue( "pcia_abreviatura" ) , 3 ) nPcia_codigoib := oRs:GetValue( "pcia_codigoib" ) cPcia_letra := padr( oRs:GetValue("pcia_letra") , 1 )

  • El comando SQL para actualizar el registro

cSQL := "update provincia set " + ; "pcia_nombre = :pcia_nombre, " +; "pcia_abreviatura = :pcia_abreviatura, "+; "pcia_codigoib = :pcia_codigoib, "+; "pcia_letra = :pcia_letra " +; "where id_provincia = :id_provincia "

endif

  • Ingresamos los datos

cPantalla := savescreen( 6, 4 , 12 , 75 ) @ 6,4 clear to 12, 75 @ 6,4 to 12, 75

@ 7,6 say "Id " get nId_provincia when lAlta @ 8,6 say "Nombre " get cPcia_nombre @ 9,6 say "Abreviatura" get cPcia_abreviatura @ 10,6 say "Codigo IB " get nPcia_codigoib @ 11,6 say "Letra " get cPcia_letra read

restscreen(6,4,12,75, cPantalla )

  • Armamos el vector de parámetros que necesitamos para el SQL
  • Los parámetros a pasar tienen el mismo nombre que los campos
  • de la tabla por cuestiones de orden, pueden tener cualquier
  • nombre


aParam := {} aadd( aParam , { "id_provincia" , nId_Provincia } ) aadd( aParam , { "pcia_nombre" , cPcia_nombre } ) aadd( aParam , { "pcia_abreviatura" , cPcia_abreviatura } ) aadd( aParam , { "pcia_codigoib" , nPcia_codigoib } ) aadd( aParam , { "pcia_letra" , cPcia_letra } )


  • Ejecutamos el comando

oConn:command( cSQL , aParam )

return NIL

static function borrapvcia( oConn, oRs ) local cSioNo := space(2) local aParam local nPar

  • Preguntamos si está seguro de hacer la operación

@ 24,2 say "Escriba SI si está seguro de borrar el registro" get cSioNO pict "!!" read if cSioNo == "SI" nIdProvincia := oRs:GetValue( "id_provincia" ) //Obtenemos el valor del ID

aParam := {} aadd( aParam , {"nPar" , nIdProvincia } ) //Usamos otro nombre por didactica oConn:command("delete from provincia where id_provincia = :nPar" , aParam ) endif @ 24,2 // Borramos la línea return NIL

  • Fin ModPcia.prg

Usando el método 1

Es necesario crear el rowset con los SQL adecuados para cada una de las operaciones. Es decir que al rowset se le pasan como parámetros los rowset y luego mediante métodos de ese rowset se los invoca.

Para ello debemos cambiar la creación del rowset:

aadd( aParam , { "cPar" , cBusqueda } )

cSQL := "select * from provincia where pcia_nombre >= :cPar limit 20"

cInserta := "insert into provincia values " + ; "( :id_provincia, " + ; ":pcia_nombre, " +; ":pcia_abreviatura, "+; ":pcia_codigoib, "+; ":pcia_letra )"

cActualiza := "update provincia set " + ; "pcia_nombre = :pcia_nombre, " +; "pcia_abreviatura = :pcia_abreviatura, "+; "pcia_codigoib = :pcia_codigoib, "+; "pcia_letra = :pcia_letra " +; "where id_provincia = :id_provincia "

cBorra := "delete from provincia where id_provincia = :id_provincia"

/* Pasamos esos comandos SQL al crear el rowset */

oRs := oConn:CreateRowset( cSQL , aParam , cInserta , cBorra , cActualiza )

Las funciones que modifican el rowset quedan entonces:

  • -----------------------------------------------

static function modpvcia( lAlta , oRs )

  • -----------------------------------------------
  • Parametro: lAlta es TRUE (.T.) si es un alta
  • FALSE (.F.) si es una edicion
  • oRs: Rowset de donde buscar la informacion y donde ejecutar
  • los SQL
  • Observación importante:
  • Los datos tipo caracter que traigo desde MySQL vienen del largo
  • de lo que está guardado y NO de la longitud del campo definida
  • en la estructura de la tabla
  • Si es un alta llenamos los campos con los valores en blanco,
  • sino guardamos los valores que están en la tabla
  • obteniendo el valor con el metodo GetValue

local oMap

if lAlta nId_provincia := 0 cPcia_nombre := space(20) cPcia_abreviatura := space(3) nPcia_codigoib := 0 cPcia_letra := space(1)

else

  • Agrego espacios a la derecha cuando lo necesito

nId_provincia := oRs:GetValue("id_provincia") cPcia_nombre := padr( oRs:GetValue( "pcia_nombre") , 20 ) cPcia_abreviatura := padr( oRs:GetValue( "pcia_abreviatura" ) , 3 ) nPcia_codigoib := oRs:GetValue( "pcia_codigoib" ) cPcia_letra := padr( oRs:GetValue("pcia_letra") , 1 )

endif

  • Ingresamos los datos

cPantalla := savescreen( 6, 4 , 12 , 75 ) @ 6,4 clear to 12, 75 @ 6,4 to 12, 75

@ 7,6 say "Id " get nId_provincia when lAlta @ 8,6 say "Nombre " get cPcia_nombre @ 9,6 say "Abreviatura" get cPcia_abreviatura @ 10,6 say "Codigo IB " get nPcia_codigoib @ 11,6 say "Letra " get cPcia_letra read

restscreen(6,4,12,75, cPantalla )

  • Cargamos un vector asociativo con los valores a pasarle al comando SQL

oMap := map() //Creamos el vector asociativo vacío oMap:id_provincia := nId_Provincia oMap:pcia_nombre := cPcia_nombre oMap:pcia_abreviatura := cPcia_abreviatura oMap:pcia_codigoib := nPcia_codigoib oMap:pcia_letra := cPcia_letra

if lAlta

  • Agregamos un registro

oRs:append( oMap ) else oRs:write( oMap ) endif

return NIL

static function borrapvcia( oRs ) local cSioNo := space(2) local aParam local nPar

  • Preguntamos si está seguro de hacer la operación

@ 24,2 say "Escriba SI si está seguro de borrar el registro" get cSioNO pict "!!" read if cSioNo == "SI" nIdProvincia := oRs:GetValue( "id_provincia" ) //Obtenemos el valor del ID

oMap := map() oMap:id_provincia := nIdProvincia oRs:delete(oMap) endif @ 24,2 // Borramos la línea return NIL

Nos queda entonces nuestro ABM con el segundo método de la siguiente manera:


  • --------------------------------------------------------
  • Programa para conectarse y modificar la tabla provincia
  • Metodo 1
  • Nombre del programa: ambpcia.prg
  • --------------------------------------------------------
  1. include "inkey.ch"
  2. include "pgch.ch"

function main()

local oConn local cBusqueda local k

oConn := conectargc( "gc" , "clip" )

while .t.

  clear
  cBusqueda := space(15)
  @ 1,1 say "Ingrese provincia a Buscar <Esc-Sale>" get cBusqueda
  read
  if lastkey() = K_ESC
    clear
    exit
  endif
  cBusqueda := alltrim( cBusqueda )
  aParam := {} //Creamos un vector vacío
  /* Ahora agregamos un elemento a aParam que es a su vez
  un vector con dos elementos: una cadena cPar que luego
  veremos para que se usa y lo que queremos buscar
  */
  aadd( aParam , { "cPar" , cBusqueda } )
  /* Generamos nuestro SQL
  Observemos que el parámetro se pasa mediante
  cPar y con dos puntos adelante.
  */
  cSQL := "select * from provincia where pcia_nombre >= :cPar limit 20"
  cInserta := "insert into provincia values " + ;
              "( :id_provincia, " + ;
              ":pcia_nombre, " +;
              ":pcia_abreviatura, "+;
              ":pcia_codigoib, "+;
              ":pcia_letra )"
  cActualiza := "update provincia set " + ;
                "pcia_nombre = :pcia_nombre, " +;
                "pcia_abreviatura = :pcia_abreviatura, "+;
                "pcia_codigoib = :pcia_codigoib, "+;
                "pcia_letra = :pcia_letra " +;
                "where id_provincia = :id_provincia "
  cBorra := "delete from provincia where id_provincia = :id_provincia"
  /* Pasamos esos comandos SQL al crear el rowset */
  oRs := oConn:CreateRowset( cSQL , aParam , cInserta , cBorra , cActualiza )
 /* Un lindo marco */
  @ 3,1 to 20,78
  @ 21,1 say "<Intro>Modifica Agrega <Supr>Borra"
  /* Creamos el TBrowse */
  oTb := tbrowsenew( 4 , 2 , 19 , 77 )
  oTb:colSep := chr( PGCH_VLINE ) //Modificaciones
  oTb:headSep := chr( PGCH_TTEE ) + chr( PGCH_HLINE ) //para que luzca mejor
  /* Armamos las columnas del tbrowse */
  for k = 1 to oRs:NFields() // Cantidad de campos
  /* Usamos los métodos del rowset para escribir menos :-) */
  oTb:addcolumn( tbcolumnnew( oRs:Fieldname( k) , oRs:Fieldblock(k) ) )
  next k
  /* Declaramos los métodos para el movimiento en el rowset */
  oTB:goTopBlock := { || oRs:GoTop() }
  oTB:goBottomBlock := { || oRs:GoBottom() }
  oTB:skipBlock := { |n| SkipRecs( oRs , n ) } // La funcion SkipRecs se
  // define en gcfunc
  /* El conocido lazo del TBrowse */
  while .t.
     while nextkey() == 0
       if oTB:stabilize()
         exit
       endif
     enddo
   oTB:refreshCurrent()
   oTB:ForceStable()
   nKey := inkey(0)
   do case
   case nKey == K_UP; oTB:up()
   case nKey == K_DOWN; oTB:down()
   case nKey == K_CTRL_PGUP; oTB:gotop()
   case nKey == K_CTRL_PGDN; oTB:gobottom()
   case nKey == K_LEFT; oTB:left()
   case nKey == K_RIGHT; oTB:right()
   case nKey == K_PGUP; oTB:pageup()
   case nKey == K_PGDN; oTB:pagedown()
   case nKey == K_INS // Agregamos un registro
   modpvcia( .t., oRs)
   * Refrescamos el rowset y el tbrowse
   oRs:refreshall()
   oTb:refreshCurrent()
   case nKey == K_ENTER // Modificamos un registro
   modpvcia( .f. , oRs)
   * Refrescamos el rowset y el tbrowse
   oRs:refreshall()
   oTb:refreshCurrent()
   case nKey == K_DEL // Borramos un registro
   borrapvcia(oRs)
   * Refrescamos el rowset y el tbrowse
   oRs:refreshall()
   oTb:refreshCurrent()
   case nKey == K_ESC
     exit
   endcase
 enddo

oRs:destroy() // Destruimos el rowset para liberar los recursos enddo return NIL

/* Las funciones que necesitamos para modificar la tabla */

  • -----------------------------------------------

static function modpvcia( lAlta , oRs )

  • -----------------------------------------------
  • Parametro: lAlta es TRUE (.T.) si es un alta
  • FALSE (.F.) si es una edicion
  • oRs: Rowset de donde buscar la informacion y donde ejecutar
  • los SQL
  • Observación importante:
  • Los datos tipo caracter que traigo desde MySQL vienen del largo
  • de lo que está guardado y NO de la longitud del campo definida
  • en la estructura de la tabla
  • Si es un alta llenamos los campos con los valores en blanco,
  • sino guardamos los valores que están en la tabla
  • obteniendo el valor con el metodo GetValue

local oMap

if lAlta

  nId_provincia := 0
  cPcia_nombre := space(20)
  cPcia_abreviatura := space(3)
  nPcia_codigoib := 0
  cPcia_letra := space(1)

else

  • Agrego espacios a la derecha cuando lo necesito
 nId_provincia := oRs:GetValue("id_provincia")
 cPcia_nombre := padr( oRs:GetValue( "pcia_nombre") , 20 )
 cPcia_abreviatura := padr( oRs:GetValue( "pcia_abreviatura" ) , 3 )
 nPcia_codigoib := oRs:GetValue( "pcia_codigoib" )
 cPcia_letra := padr( oRs:GetValue("pcia_letra") , 1 )

endif

  • Ingresamos los datos

cPantalla := savescreen( 6, 4 , 12 , 75 ) @ 6,4 clear to 12, 75 @ 6,4 to 12, 75

@ 7,6 say "Id " get nId_provincia when lAlta @ 8,6 say "Nombre " get cPcia_nombre @ 9,6 say "Abreviatura" get cPcia_abreviatura @ 10,6 say "Codigo IB " get nPcia_codigoib @ 11,6 say "Letra " get cPcia_letra read

restscreen(6,4,12,75, cPantalla )

  • Cargamos un vector asociativo con los valores a pasarle al comando SQL

oMap := map() //Creamos el vector asociativo vacío oMap:id_provincia := nId_Provincia oMap:pcia_nombre := cPcia_nombre oMap:pcia_abreviatura := cPcia_abreviatura oMap:pcia_codigoib := nPcia_codigoib oMap:pcia_letra := cPcia_letra

if lAlta

  • Agregamos un registro
 oRs:append( oMap )

else

 oRs:write( oMap )

endif

return NIL

static function borrapvcia( oRs ) local cSioNo := space(2) local aParam local nPar

  • Preguntamos si está seguro de hacer la operación

@ 24,2 say "Escriba SI si está seguro de borrar el registro" get cSioNO pict "!!" read if cSioNo == "SI"

  nIdProvincia := oRs:GetValue( "id_provincia" ) //Obtenemos el valor del ID
  oMap := map()
  oMap:id_provincia := nIdProvincia
  oRs:delete(oMap)

endif @ 24,2 // Borramos la línea return NIL


A gcfunc.prg necesitamos agregarle la siguiente función:

  • -------------------------------

function SkipRecs( oRs , nRecs )

  • -------------------------------
  • Función que controla el salto
  • de los registros dentro del
  • rowset
  • Parametros:
  • nRecs: cantidad de registros a saltar

local nSkipped := 0 do case case nRecs == 0

 oRs:Skip( 0 )

case nRecs < 0 while nSkipped > nRecs

  oRs:Skip( -1 )
  if oRs:bof()
     exit
  endif
  nSkipped--

enddo case nRecs > 0

  while nSkipped < nRecs
    oRs:Skip( 1 )
   if oRs:eof()
     exit
   endif
   nSkipped++
  enddo

endcase return nSkipped

  • EOF
 Actividad 5: Probar los ejemplos anteriores


Usar Clip para el desarrollo de aplicaciones gráficas con Glade y libglade.

Problemas que se suscitan ante el desafío de la programación gráfica Clip es un lenguaje extremadamente poderoso y flexible, que tiene las siguientes formas para hacer interfaces gráficas:

   * GTK
   * GTK2
   * Fivewin implementada con GTK
   * Clip-UI
   * Clip-glade2


Nos concentraremos en la forma de hacer una agenda muy simple mediante clip-glade2 por las siguientes razones:

1. Es sencillo crear las interfaces gráficas mediante Glade2 2. Es lo único que entiendo por ahora de la interfaz gráfica en GNU/Linux :-) 3. No conozco Fivewin 4. Clip-UI carece de generador de interfaces gráficas 5. Programar directamente usando las funciones de GTK y GTK2 se ve como tedioso

   *
     ¿Porqué GTK2?
     GTK se considera obsoleta y se detuvo su desarrollo.
     GTK2 es multiplataforma
     Hay abundante documentación
     Se puede usar Glade2
   *
     ¿Porqué Glade?
     Glade permite diseñar con facilidad las interfases
     Es ubicua a cualquier distribución y funciona aun en MS-Windows
     Con libglade es lo que usaremos para crear la interfaz gráfica.
   *
     ¿Es multiplataforma?
        En teoría es multiplataforma: Clip funciona con cygwin y Glade2 y GTK2 están portadas a MS-Windows.
   *
     Distintas alternativas a GTK2 para Clip

Como mencionamos antes, está la biblioteca que emula la famosa FiveWin para Clipper. Está basada en GTK. Clip-UI permite generar interfaces gráficas también con documentos XML, pero carece de un generador de tales documentos, tal como sería Glade2. Si se quiere ver un desarrollo hecho con Clip-UI, ver la siguiente página:

     http://eas.lrn.ru/index.php?id=screenshots
   *
     Primeros pasos con Glade2


Abrimos Glade2 y aparecen dos paneles:











Para el manejo de Glade y sus conceptos básicos ver la siguiente página:

http://eddy.writelinux.com/spanish/

Programando una simple agenda paso por paso.

Creo una ventana nueva con el icono correspondiente a la que llamamos 'window1' Le cambiamos el título a la ventana: “Agenda 0.2” Si no se ven las propiedades de los widgets, es porque no está activada en el menú “Ver” de Glade.

Ahora voy a crear el contenedor para la barra de tareas, los campos y las etiquetas de los campos.

El contenedor deberá tener entonces dos renglones y al renglón de abajo lo deberé dividir a su vez en otro contenedor de tres renglones con dos columnas (los campos a mostrar serán: nombre, irección y teléfono)

Como resultado tendremos lo siguiente:







Ahora agrego los botones que serán las acciones sobre la agenda: Agregar, Editar, Borrar, Buscar, Siguiente y Anterior.

Vemos como Glade acomoda automaticamente las dos secciones.

Para ello agregamos un widget “barra de herramientas” de 5 botones. Luego vamos agregando los botones y editando sus propiedades Ahora agregamos los botones a la barra de herramientas. Recordar que para que muestre el icono con el texto incluido hay que seleccionar "Botón de inventario".

A cada botón le agregamos la señal cuando “clicked”:













Lo que obtenemos al final, rellenando los distintos contenedores con etiquetas y campos de edición es lo siguiente:







Grabamos el proyecto como 'agenda.glade'

Ahora intentaremos hacer que con Clip nos muestre esta interfaz, sólo eso.

Siguiendo el ejemplo de Sergio Zayas en:

clip-prg-1.2-0-0/cliplibs/clip-glade2/examples
cargaremos la interfaz con el siguiente código:


/* agenda.prg Primera prueba con clip-libglade2

  • /

static _xmlfic

function xmlfic() return ( _xmlfic )

function main( glade_file)

local xml, widget _xmlfic := iif( empty( glade_file) , "agenda.glade" , glade_file )

if (!file( _xmlfic ) ) ? "No se encontró el archivo de interfaz: " + _xmlfic ? quit endif gtk_init() xml_w1 := glade_xml_new( _xmlfic , "window1" ) if (xml_w1 == nil ) ? "** Error de parser, archivo: " + _xmlfic return( 1 ) endif

widget := glade_xml_get_widget( xml_w1 , "window1" ) glade_xml_signal_autoconnect( xml_w1 ) //glade autoconecta las señales

gtk_widgetShow( widget ) //lo mostramos si no está visible gtk_main() // esto arranca todos los eventos

return( 0 )

Ahora compilamos:

$ clip -e agenda.prg -lclip-gtk2 -lclip-glade2

Ejecutamos nuestra agenda:

./agenda

Vemos lo siguiente:






Hasta aquí llegamos con la primera parte de nuestra pequeña investigación.

Vemos una "agenda" mas funcional:


/*

 agenda.prg
 Primera prueba con clip-libglade2
  • /

static _xmlfic

function xmlfic() return ( _xmlfic )

function main( glade_file)

local xml, widget _xmlfic := iif( empty( glade_file) , "agenda.glade" , glade_file )

if (!file( _xmlfic ) )

  ? "No se encontró el archivo de interfaz: " + _xmlfic
  ?
  quit

endif set delete on

  • ---------------------------------------------------

if !file( "agenda.dbf" )

 * Creamos la agenda con datos básicos de prueba
 aDbf := {}
 aadd( aDbf , { "NOMBRE" , "C" , 30 , 0 } )
 aadd( aDbf , { "DIRECCION" , "C" , 45 , 0 } )
 aadd( aDbf , { "TELEFONO" , "C" , 20 , 0 } )
 DBCREATE( "agenda" , aDbf )
 /* Cargamos un dato para mostrar luego */
 use agenda
 append blank
 replace nombre with "Sylvia Saint"
 replace direccion with "Alguna casa en California"
 replace telefono with "Si lo supiera!"
 use

endif

  • ---------------------------------------------------

gtk_init() xml_w1 := glade_xml_new( _xmlfic , "window1" ) if (xml_w1 == nil )

  ? "** Error de parser, archivo: " + _xmlfic
  return( 1 )

endif

widget := glade_xml_get_widget( xml_w1 , "window1" ) glade_xml_signal_autoconnect( xml_w1 ) //glade autoconecta las señales

// Esta no la autoconecta

gtk_signalConnect( widget , "delete-event",;

    { |w,e| on_window1_delete_event(w,e) } )

// Esta tampoco se autoconecta. Debe ser que no hago bien las cosas // Es para agregar un registro

glade_xml_signal_connect( xml_w1 , "on_toolbutton1_clicked", ;

    { |w| on_new_activate( w ) } )

// Esto es para editar el registro glade_xml_signal_connect( xml_w1 , "on_toolbutton2_clicked", ;

    { |w| on_edit_activate( w ) } )

// Esto es para borrar un registro glade_xml_signal_connect( xml_w1 , "on_toolbutton3_clicked" , ;

// Esto es para buscar un registro glade_xml_signal_connect( xml_w1 , "on_toolbutton4_clicked" , ; { |w| on_search_activate( w ) } ) // Siguiente glade_xml_signal_connect( xml_w1 , "on_toolbutton5_clicked" , ; { |w| on_skip_activate( w , 1 ) } ) // Anterior glade_xml_signal_connect( xml_w1 , "on_toolbutton6_clicked" , ; { |w| on_skip_activate( w , -1 ) } ) // Abrimos la base de datos use agenda rellena_datos( xml_w1 ) //cargo los datos en el widget window1 gtk_widgetShow( widget ) //lo mostramos si no está visible gtk_main() // esto arranca todos los eventos use // cerramos la base de datos return( 0 ) /* Funciones gestoras de señales */ function on_window1_delete_event(wid, event ) DialogoSale() return( .t. )
  • ----------------------------------------------------------
function DialogoSale() local xml, dialog xml := glade_xml_new( xmlfic() , "dialogo_salir" ) if (xml == 0 ) return endif dialog := glade_xml_get_widget( xml , "dialogo_salir" ) glade_xml_signal_connect( xml , "on_salir_clicked" ,;
glade_xml_signal_connect( xml , "on_nosale_clicked" ,;
gtk_signalConnect( dialog , "delete-event" , ; { |w,e| dialogo_cerrar_delete_event( w , e , dialog ) } ) return .t.
  • ------------------------------------------------------
function dialogo_cerrar_delete_event( wid, event, dlg ) gtk_widgetDestroy( dlg ) return( .f. )
  • -----------------------------------------------------
function on_salir_clicked( btn , dlg ) gtk_widgetDestroy( dlg ) gtk_quit() return .t.
  • -----------------------------------------------------
function on_nosale_clicked( btn , dlg ) gtk_widgetDestroy( dlg ) return .t.
  • -----------------------------------------------------
// Funciones relativas a los datos
  • --------------------------------
procedure on_new_activate( wid )
  • --------------------------------
nuevoregistro() return
  • ----------------------------------
procedure on_edit_activate( wid )
  • ----------------------------------
editaregistro() return
  • -------------------------------------
procedure on_delete_activate( wid )
  • -------------------------------------
borraregistro() return
  • ------------------------------------
procedure on_search_activate( wid )
  • ------------------------------------
buscaregistro() return
  • --------------------------------------------
procedure on_skip_activate( wid , nSalto )
  • --------------------------------------------
skip nSalto if eof() go top endif if bof() go bottom endif refresca_datos() return NIL
  • ----------------------------------------------------
function rellena_datos( xml )
  • Debo pasarle como parámetro el xml
  • ----------------------------------------------------
local wid
  • Abrimos la base de datos
  • Tomamos las referencia al gtk_entry definido en glade
  • Los campos son:
  • nombre
  • direccion
  • telefono
wid := glade_xml_get_widget( xml , "entry1" ) gtk_entrySetText( wid , field->nombre ) wid := glade_xml_get_widget( xml , "entry2" ) gtk_entrySetText( wid , field->direccion ) wid := glade_xml_get_widget( xml , "entry3" ) gtk_entrySetText( wid , field->telefono ) return NIL /* datos.prg Funciones relativas a los datos */
  • -----------------------
function nuevoregistro()
  • -----------------------
local xml, dialog xml := glade_xml_new( xmlfic() , "agrega_registro" ) if (xml == 0 ) return endif dialog := glade_xml_get_widget( xml , "agrega_registro" ) glade_xml_signal_connect( xml , "on_agregaregistro_clicked" , ;
glade_xml_signal_connect( xml , "on_cancelaagrega_clicked" , ; { | w , e | on_cancela_registro( w , dialog, xml ) } ) return NIL
  • -------------------------
function editaregistro()
  • -------------------------
local xml , dialog xml := glade_xml_new( xmlfic() , "agrega_registro" ) if (xml == 0 ) return endif wid := glade_xml_get_widget( xml , "entry4" ) gtk_entrySetText( wid , field->nombre ) wid := glade_xml_get_widget( xml , "entry5" ) gtk_entrySetText( wid , field->direccion ) wid := glade_xml_get_widget( xml , "entry6" ) gtk_entrySetText( wid , field->telefono ) wid := glade_xml_get_widget( xml , "entry4" ) field->nombre := gtk_entryGetText( wid ) dialog := glade_xml_get_widget( xml , "agrega_registro" ) glade_xml_signal_connect( xml , "on_agregaregistro_clicked" , ;
glade_xml_signal_connect( xml , "on_cancelaagrega_clicked" , ; { | w , e | on_cancela_registro( w , dialog, xml ) } ) return NIL
  • ------------------------
function borraregistro()
  • ------------------------
local xml, dialog xml := glade_xml_new( xmlfic() , "borra_registro" ) if (xml == 0 ) return NIL endif dialog := glade_xml_get_widget( xml , "borra_registro" ) glade_xml_signal_connect( xml , "on_okbutton1_clicked" , ; { | w,e| on_okborra( w , dialog , xml ) } ) glade_xml_signal_connect( xml , "on_cancelbutton1_clicked" , ; { | w , e | on_cancelaborra( w , dialog , xml ) } ) return NIL
  • --------------------------
function buscaregistro()
  • --------------------------
local xml, dialog xml := glade_xml_new( xmlfic() , "busca_registro") if ( xml == 0 ) return NIL endif dialog := glade_xml_get_widget( xml , "busca_registro" ) glade_xml_signal_connect( xml , "on_okbutton2_clicked" , ; { |w , e | on_okbusca( w , dialog , xml ) } ) return NIL
  • -------------------------------------------
function on_agrega_registro( w, dlg , xml )
  • -------------------------------------------
local wid append blank wid := glade_xml_get_widget( xml , "entry4" ) field->nombre := gtk_entryGetText( wid ) wid := glade_xml_get_widget( xml , "entry5" ) field->direccion := gtk_entryGetText( wid ) wid := glade_xml_get_widget( xml , "entry6" ) field->telefono := gtk_entryGetText( wid ) refresca_datos() gtk_widgetDestroy( dlg ) return NIL
  • -------------------------------------------
function on_edita_registro( w, dlg , xml )
  • -------------------------------------------
local wid wid := glade_xml_get_widget( xml , "entry4" ) field->nombre := gtk_entryGetText( wid ) wid := glade_xml_get_widget( xml , "entry5" ) field->direccion := gtk_entryGetText( wid ) wid := glade_xml_get_widget( xml , "entry6" ) field->telefono := gtk_entryGetText( wid ) refresca_datos() gtk_widgetDestroy( dlg ) return NIL
  • ----------------------------------------------
function on_cancela_registro( w , dlg , xml )
  • ----------------------------------------------
gtk_widgetDestroy( dlg ) return NIL
  • -------------------------------------
function on_okbusca( w , dlg , xml )
  • -------------------------------------
wid := glade_xml_get_widget( xml , "entry7" ) cTextoBusqueda := alltrim ( gtk_entryGetText( wid ) ) locate for field->nombre $ cTextoBusqueda if found() refresca_datos() else go bottom refresca_datos() endif gtk_widgetDestroy( dlg ) return NIL
  • -----------------------------------
function on_okborra( w , dlg, xml )
  • -----------------------------------
delete go top refresca_datos() gtk_widgetDestroy( dlg ) return NIL
  • ------------------------------------------
function on_cancelaborra( w , dlg, xml )
  • ------------------------------------------
gtk_widgetDestroy( dlg )
  • --------------------------
function refresca_datos()
  • --------------------------
/* Necesito los widgets que están en window1 */ wid := glade_xml_get_widget( xml_w1 , "entry1" ) gtk_entrySetText( wid , field->nombre ) wid := glade_xml_get_widget( xml_w1 , "entry2" ) gtk_entrySetText( wid , field->direccion ) wid := glade_xml_get_widget( xml_w1 , "entry3" ) gtk_entrySetText( wid , field->telefono ) return NIL
Herramientas personales