cómo hacer una galería de imágenes con FWH

Desde hace mucho tiempo he querido incorporar una galería de imágenes a algunos de mis programas. Esto data de mis tiempos de fanboy de Apple, cuando quedé prendado de un programa llamado Delicious Library al que me referí en esta entrada.

El caso es que hace poco estaba dándole vueltas a la cabeza en la manera de hacerlo y pregunté en el foro de FWH. La primera intención fue crear una clase a medida dentro de una ventana, pero esa solución no me valía porqué el flujo del programa es muy complicado de controlar pues las ventanas quedan independientes. Hubo otra propuesta de clase basada en diálogo utilizando la clase TPanel de FWH, y buscando ejemplos de esta clase me topé con la clase TScrollPanel que no conocía y de la que apenas hay documentación. Utilizando esta clase y el código que Mr. Rao había realizado para la clase TAlbum fue muy sencillo implementar la galería de imágenes.

Esta galería de imágenes la puedo utilizar, por ejemplo, para crear una galería de portadas de libros.

Ejemplo de galería de portadas de libros

Lo bueno de esta galería es que muestra las portadas en el orden de la rejilla de libros y si hay algún filtro activo la galería muestra unicamente los libros que aparecen en el filtro.

El código que he utilizado para crear la galería es el siguiente:

Function LiGaleria()
   LOCAL cAlias  := "LI"
   LOCAL nRecno  := ( cAlias )->( RecNo() )
   LOCAL nOrder  := ( cAlias )->( ordNumber() )
   local aImages := {}
   local aLabels := {}
   local aRecno  := {}
   local oDlgAlbum, oAlbum

   LI->(DbGoTop())
   WHILE ! LI->(EOF())
      IF ! Empty(Rtrim(LI->LiImagen))
         AAdd(aImages, Rtrim(LI->LiImagen))
         AAdd(aLabels, Rtrim(LI->LiTitulo))
         AAdd(aRecno, LI->(Recno()))
      ENDIF
      LI->(DbSkip())
   ENDDO
   LI->(DbGoTo(nRecno))

   DEFINE DIALOG oDlgAlbum SIZE oApp():oGrid:nWidth, oApp():oGrid:nHeight PIXEL TRUEPIXEL ;
      TITLE "Galería de portadas de libros - dobleclik para editar un libro"
   oDlgAlbum:SetFont(oApp():oFont)
   //
   oAlbum := TScrollPanel():New( 20, 20, oApp():oGrid:nHeight-50, oApp():oGrid:nWidth-20, oDlgAlbum, .f. )
   oAlbum:SetColor(CLR_WHITE, CLR_WHITE)
   oAlbum:SetFont( oDlgAlbum:oFont )

   @ oApp():oGrid:nHeight-40, oApp():oGrid:nWidth-96 BUTTON "Aceptar" ;
      SIZE 76, 24 PIXEL OF oDlgAlbum ACTION oDlgAlbum:End()

   ACTIVATE DIALOG oDlgAlbum ;
      ON INIT ( LiAlbum( oAlbum, aImages, aRecno, oDlgAlbum ), oDlgAlbum:Center( oApp():oWndMain ) )

   RETURN NIL  

function LiAlbum( oPanel, aPhotos, aRecno, oDlgAlbum )
   local nImgPerRow  := 8
   local nImgWidth   // := 180
   local nImgHeight  // := Int( nImgWidth * 4 / 3 )
   local nHGutter    := 10
   local nVGutter    := 20
   local nCols       := nImgPerRow 
   local nRows, nRow, nCol, x, y, nImage, xMax, nImages := Len( aPhotos )
   local oImage, oSay

   // el ancho del scrollbar es 16
   nImgWidth := INT((oPanel:nWidth-16-(nImgPerRow+1)*nHGutter)/nImgPerRow)
   nImgHeight:= Int( nImgWidth * 4 / 3 )
   nRows    := Ceiling( nImages / nCols )
   xMax     := nCols * ( nImgWidth * nHGutter )
   y        := nVGutter
   nImage   := 1
   do while nImage <= nImages
      x     := nHGutter
      nCol  := 1
      do while nCol <= nCols .and. nImage <= nImages
         // llamo a una funcion para conseguir detached locals
         LiAlbumImage(y, x, nImgWidth, nImgHeight, oPanel, aPhotos, aRecno, nImage, nVGutter, oDlgAlbum)
         nImage++
         nCol++
         x  += ( nImgWidth + nHGutter )
      enddo
      y  += ( nImgHeight + nVGutter )
   enddo
   //::nImgCols  := nCols
   // ::nHeight   := y
   oPanel:SetRange() // call this after defining all controls

return nil

Function LiAlbumImage(y, x, nImgWidth, nImgHeight, oPanel, aPhotos, aRecno, nImage, nVGutter, oDlgAlbum)
   local oImage, oSay, nLiRecno
   nLiRecno := aRecno[nImage]
   @ y, x XIMAGE oImage SIZE nImgWidth, nImgHeight OF oPanel NOBORDER
   oImage:SetSource( If( HB_ISARRAY( aPhotos[ nImage ] ), aPhotos[ nImage, 1 ], aPhotos[ nImage ] ) )
   oImage:nUserControl := 0
   oImage:lBmpTransparent := .f.
   oImage:bLDblClick := { || ( LiForm( oApp():oGrid, "edt", , nLiRecno, oDlgAlbum )) }

   //@ y+nImgHeight+(nVGutter/2), x SAY oSay PROMPT nLiRecno FONT oApp():oFont ;
   //   COLOR CLR_BLACK, CLR_WHITE ;
   //   SIZE nImgWidth, 2*nVGutter CENTER PIXEL OF oPanel
return NIL

En este código estoy utilizando tres funciones:

  • LaGaleria que recorre el fichero de libros para crear 3 arrays donde guardo las portadas, los títulos y los número de registro a que se refiere cada portada. Aquí creo el diálogo y el ScrollPanel.
  • LiAlbum donde recorro el array de imágenes para crear las imágenes dentro del ScrollPanel. Como luego quiero acceder a cada imagen para editar el libro haciendo doble click tengo que crear las imágenes en otra función utilizando la técnica de ‘detached locals’.
  • LiAlbumImage que es donde creo cada una de las imágenes, este código lo realizó Mr. Rao en el foro de FWH.

En una próxima actualización de Cuaderno de Bitácora incluiré galerías con las portadas de libros, discos y videos.

generando ficheros .rtf con harbour/fwh

Una de las opciones que trae el Puchero es la posibilidad de generar un fichero .rtf con la información almacenada de una receta. Hasta ahora el fichero .rtf que se generaba era algo como lo siguiente:

Fichero .rtf a partir de una receta… hasta ahora 😉

Para generar este fichero utilizo la clase TRtfFile de FWH que basicamente genera un fichero rtf a partir de una variable de tipo texto, pero sin permitir formateos ni muchas alegrías. Algo muy básico. La pregunta que te harás es ¿ por qué no generar un fichero docx directamente con la clase TWord o similar ? Pues porque la clase Tword para FWH requiere que el usuario tenga Word instalado, y eso es algo que evito a toda costa. La generación de ficheros XLS en mis programas la realizo utilizando la clase FileXls que Ramón Avendaño publicó en el foro de FWH hace muchos años, y que he subido a un repositorio de GitHub para evitar que desaparezca. Al generar el fichero desde cero, creando un fichero con el formato adecuado, se evita que el programa pierda funcionalidad si el usuario no tiene tal o cual programa instalado. En mi caso utilizo desde hace muchos años LibreOffice sin echar de menos ninguna funcionalidad del paquete Office de Microsoft, y los ficheros XLS los abro perfectamente con Calc.

Me puse a investigar sobre la generación de ficheros .rtf desde Harbour y FWH y encontré cosas interesantes hasta que llegué a un post en el grupo de Harbour Users en que había una clase para generar ficheros .rtf que me llamó la atención. Me puse a probarla y era lo que estaba buscando, la posibilidad de generar ficheros .rtf desde cero, creando el fichero con el formato adecuado. La clase está escrita por Thomas R. Marchione y la he publicado en Github sin tocar una coma. Espero que si el autor lee este post no haya ningún problema.

Utilizando esta clase, la exportación de receta a formato .rtf en el Puchero ahora mejora sustancialmente, puedo dar formato a los párrafos y crear tablas dentro del .rtf. Justo que lo quería.

A la hora de utilizar la clase con FWH hay que tener en cuenta lo siguiente:

  • En el fichero richtext.prg hay una instrucción #Command SET DEFAULT <x> TO <y> => <x> := IIF( HB_ISNIL( <x> ), <x> := <y>, <x> ) que genera un error debido a que FWH tiene una definición similar. Lo que he hecho ha sido cambiar esta instrucción por #Command SET RTFDEFAULT <x> TO <y> => <x> := IIF( HB_ISNIL( <x> ), <x> := <y>, <x> ) y modificar el fichero en consecuencia.
  • También hay una función FUNCTION cValToChar( xVal ) que hace reescribe otra de FWH y da problemas, en mi caso el los bitmaps de los browses aparecía NIL encima del bitmap en cuestión. En mi caso la comenté y listo.

A ver si alguien se anima a incluir posibilidad de añadir imágenes en el fichero .rtf

😉

clase TTagEver para FivewinHarbour

Una funcionalidad que quería implementar en el Puchero es la gestión de dietas y tolerancias de las recetas. Es decir que para cada receta se pueda poner si es adecuada para gente con colesterol, celíacos, o para dietas de puntos, Dunkan o lo que sea. Esto supone que cada receta podía llevar asociadas múltiples dietas y la verdad es que no tenía claro cómo hacerlo. Muchas veces lo principal no es la funcionalidad sino como se implementa esta funcionalidad lo que hace que esta sea aceptada por los usuarios o no.

El caso es que dándole vueltas a la cabeza recordé el sistema de etiquetado de documentos que usa Evernote. Algo así:

En FWH hay una clase llamada TTagCloud que podía servirme como base. Esta clase la hizo Francisco García Fernández, que creo que es un gran creador de controles para FWH y además un buen amigo mio. Estuve varias semanas dándole vueltas al control sin conseguir avances significativos, hasta que me puse en contacto con Paco para pedirle ayuda. Paco en un par de días hizo el control, y ahora en el Puchero tengo hecha la implementación de las dietas de esta manera.

Aspecto de la clase TTagEver

La clase desarrollada por Paco está disponible en el siguiente repositorio de GitHub: https://github.com/JoseluisSanchez/TTagEver

Mil gracias Paco.