#include "hbclass.ch"
#include "Xailer.ch"

#define LL_PREVIEW_TOOL_HEIGHT  52
#define LL_PREVIEW_THUMB_WIDTH  210
#define LL_PREVIEW_PAGE_MARGIN  14
#define LL_PREVIEW_THUMB_HEIGHT 214
#define LL_PREVIEW_THUMB_MARGIN 8
#define LL_PREVIEW_THUMB_PAGE_H 180
#define LL_PREVIEW_THUMB_LABEL_H 18
#define LL_PREVIEW_THUMB_POOL_MIN 16
#define LL_PREVIEW_THUMB_POOL_EXTRA 4
#define LL_PREVIEW_PRELOAD_BUDGET_MS 45
#define LL_PREVIEW_PRELOAD_BATCH_MAX 8
#define LLDOMINUS_XAILER_PREVIEW_BUILD_ID "20260829-preview-fit-v1"

#define LL_ZOOM_MIN             10
#define LL_ZOOM_MAX             3200

#define LL_ICON_FIRST           1
#define LL_ICON_PREVIOUS        2
#define LL_ICON_NEXT            3
#define LL_ICON_LAST            4
#define LL_ICON_PRINT           5
#define LL_ICON_CLOSE           6
#define LL_ICON_ZOOM_OUT        7
#define LL_ICON_ZOOM_IN         8
#define LL_ICON_THUMBNAILS      9
#define LL_ICON_PRINT_RANGE     10
#define LL_ICON_PDF             11
#define LL_ICON_IMAGE           12
#define LL_ICON_CSV             13
#define LL_ICON_XLSX            14
#define LL_ICON_EXPORT          15

FUNCTION LLPreviewBuildId()

RETURN LLDOMINUS_XAILER_PREVIEW_BUILD_ID

CREATE CLASS TLLPreview FROM TComponent

   DATA oPrinter INIT NIL
   DATA oPreviewControl INIT NIL
   DATA oToolbarHost INIT NIL
   DATA oThumbHost INIT NIL
   DATA oPageHost INIT NIL
   DATA oCache INIT NIL
   DATA oRenderer INIT NIL
   DATA oDocument INIT NIL
   DATA cJobTitle INIT "LL-Dominus"
   DATA lPreviewReleased INIT .F.
   DATA lContinuous INIT .F.

   METHOD New( oPrinter, oParent, oToolbarHost, oThumbHost, oPageHost, lContinuous, oCache, oRenderer, oDocument, cJobTitle ) CONSTRUCTOR
   METHOD Create()
   METHOD End()

   METHOD ReleasePreview()

ENDCLASS

METHOD New( oPrinter, oParent, oToolbarHost, oThumbHost, oPageHost, lContinuous, oCache, oRenderer, oDocument, cJobTitle ) CLASS TLLPreview

   ::oPrinter         := oPrinter
   ::oParent          := oParent
   ::oPreviewControl  := NIL
   ::oToolbarHost     := oToolbarHost
   ::oThumbHost       := oThumbHost
   ::oPageHost        := oPageHost
   ::oCache           := oCache
   ::oRenderer        := oRenderer
   ::oDocument        := oDocument
   ::cJobTitle        := iif( Empty( cJobTitle ), "LL-Dominus", cJobTitle )
   ::lPreviewReleased := .F.
   ::lContinuous      := lContinuous == .T.

RETURN Self

METHOD Create() CLASS TLLPreview

   LOCAL oPreviewDC := NIL

   IF HB_ISOBJECT( ::oCache )
      IF ::oCache:TotalPages() < 1 .OR. ! ::oCache:HasPage( 1 )
         RETURN Self
      ENDIF
   ELSE
      IF ! HB_ISOBJECT( ::oPrinter ) .OR. ! HB_ISOBJECT( ::oPrinter:oPreviewDC ) .OR. Empty( ::oPrinter:oPreviewDC:aEmf )
         RETURN Self
      ENDIF
   ENDIF

   IF ! HB_ISOBJECT( ::oToolbarHost ) .OR. ! HB_ISOBJECT( ::oThumbHost ) .OR. ! HB_ISOBJECT( ::oPageHost )
      RETURN Self
   ENDIF
   IF HB_ISOBJECT( ::oPrinter )
      oPreviewDC := ::oPrinter:oPreviewDC
   ENDIF

   WITH OBJECT ::oPreviewControl := TLLPreviewControl():New( ::oParent, ;
      oPreviewDC, ;
      ::oToolbarHost, ::oThumbHost, ::oPageHost, ::lContinuous, ;
      ::oCache, ::oRenderer, ::oDocument, ::cJobTitle )
      :Create()
   END

RETURN Self

METHOD End() CLASS TLLPreview

   IF HB_ISOBJECT( ::oCache )
      ::oCache:Stop()
   ENDIF
   IF HB_ISOBJECT( ::oPreviewControl )
      ::oPreviewControl:End()
   ENDIF

   ::ReleasePreview()
   IF HB_ISOBJECT( ::oCache )
      ::oCache:End()
   ENDIF
   ::oPreviewControl := NIL
   ::oDocument       := NIL
   ::oRenderer       := NIL
   ::oCache          := NIL
   ::oPageHost       := NIL
   ::oThumbHost      := NIL
   ::oToolbarHost    := NIL
   ::oPrinter        := NIL

RETURN NIL

METHOD ReleasePreview() CLASS TLLPreview

   IF ! HB_ISOBJECT( ::oCache ) .AND. ! ::lPreviewReleased .AND. HB_ISOBJECT( ::oPrinter ) .AND. HB_ISOBJECT( ::oPrinter:oPreviewDC )
      ::oPrinter:oPreviewDC:Destroy( .F. )
      ::lPreviewReleased := .T.
   ENDIF

RETURN NIL

CREATE CLASS TLLPreviewControl FROM TComponent

   DATA oPreviewDC INIT NIL
   DATA oToolBand INIT NIL
   DATA oThumbHost INIT NIL
   DATA oPageHost INIT NIL
   DATA oCache INIT NIL
   DATA oRenderer INIT NIL
   DATA oDocument INIT NIL
   DATA cJobTitle INIT "LL-Dominus"
   DATA oPreloadTimer INIT NIL
   DATA oThumbButton INIT NIL
   DATA oFirstButton INIT NIL
   DATA oPrevButton INIT NIL
   DATA oNextButton INIT NIL
   DATA oLastButton INIT NIL
   DATA oPrintButton INIT NIL
   DATA oPrintAllButton INIT NIL
   DATA oPdfButton INIT NIL
   DATA oImageButton INIT NIL
   DATA oCSVButton INIT NIL
   DATA oXLSXButton INIT NIL
   DATA oExportButton INIT NIL
   DATA oCloseButton INIT NIL
   DATA oPageEdit INIT NIL
   DATA oPageLabel INIT NIL
   DATA oZoomOutButton INIT NIL
   DATA oZoomTrackBar INIT NIL
   DATA oZoomLabel INIT NIL
   DATA oZoomInButton INIT NIL
   DATA oThumbPane INIT NIL
   DATA oPreviewPage INIT NIL
   DATA aThumbPages INIT {}
   DATA aThumbLabels INIT {}
   DATA aThumbPinned INIT {}
   DATA nThumbTopSeen INIT NIL
   DATA aZoomLevels INIT {}
   DATA nPage INIT 1
   DATA nMainPinnedPage INIT 0
   DATA nDirection INIT 1
   DATA nMinimumZoom INIT LL_ZOOM_MIN
   DATA lSyncZoom INIT .F.
   DATA lContinuous INIT .F.
   DATA lEnding INIT .F.

   METHOD New( oParent, oPreviewDC, oToolBand, oThumbHost, oPageHost, lContinuous, oCache, oRenderer, oDocument, cJobTitle ) CONSTRUCTOR
   METHOD Create()
   METHOD End()

   METHOD CreateToolbar()
   METHOD CreateIconButton( oParent, nLeft, nIcon, cToolTip, bClick )
   METHOD CreateThumbnails()
   METHOD TotalPages()
   METHOD PageEmf( nPage, lGenerate )
   METHOD ShowCurrentPage()
   METHOD PreloadTick()
   METHOD RefreshVisibleThumbnails()
   METHOD IsThumbnailNearVisible( nPage )
   METHOD ReleaseBorrowedEmfs()
   METHOD ToggleThumbnails()
   METHOD CommitPageEdit()
   METHOD SetPage( nPage )
   METHOD SetThumbnailPage( nPage )
   METHOD UpdateState()
   METHOD NextZoom( nDirection )
   METHOD ZoomStep( nDirection )
   METHOD FitPage()
   METHOD SetZoomTrack( nPosition )
   METHOD SyncZoomTrack( nZoom )
   METHOD ZoomChanged( nZoom )
   METHOD PrintCurrentPage()
   METHOD SelectPrintRange()
   METHOD PrintRange( nFromPage, nToPage )
   METHOD HasValidDocument()
   METHOD ExportPDF()
   METHOD ExportImage()
   METHOD ExportCSV()
   METHOD ExportXLSX()
   METHOD ShowExportMenu()

ENDCLASS

METHOD New( oParent, oPreviewDC, oToolBand, oThumbHost, oPageHost, lContinuous, oCache, oRenderer, oDocument, cJobTitle ) CLASS TLLPreviewControl

   ::oParent        := oParent
   ::oPreviewDC     := oPreviewDC
   ::oToolBand      := oToolBand
   ::oThumbHost     := oThumbHost
   ::oPageHost      := oPageHost
   ::oCache         := oCache
   ::oRenderer      := oRenderer
   ::oDocument      := oDocument
   ::cJobTitle      := iif( Empty( cJobTitle ), "LL-Dominus", cJobTitle )
   ::oPreloadTimer  := NIL
   ::aThumbPages    := {}
   ::aThumbLabels   := {}
   ::aThumbPinned   := {}
   ::nThumbTopSeen  := NIL
   ::aZoomLevels    := {}
   ::nPage          := 1
   ::nMainPinnedPage := 0
   ::nDirection     := 1
   ::nMinimumZoom   := LL_ZOOM_MIN
   ::lSyncZoom      := .F.
   ::lContinuous    := lContinuous == .T.
   ::lEnding        := .F.

RETURN Self

METHOD Create() CLASS TLLPreviewControl

   LOCAL oPreviewPrinter

   IF HB_ISOBJECT( ::oCache )
      IF ::oCache:TotalPages() < 1 .OR. ! ::oCache:HasPage( 1 ) .OR. ! HB_ISOBJECT( ::oRenderer ) .OR. ! HB_ISOBJECT( ::oRenderer:oPrinter )
         RETURN Self
      ENDIF
      oPreviewPrinter := ::oRenderer:oPrinter
   ELSE
      IF ! HB_ISOBJECT( ::oPreviewDC ) .OR. Empty( ::oPreviewDC:aEmf )
         RETURN Self
      ENDIF
      oPreviewPrinter := ::oPreviewDC:oPrinter
   ENDIF

   ::CreateToolbar()

   WITH OBJECT ::oThumbPane := TLLPreviewThumbPane():New( ::oThumbHost )
      :oPreviewOwner    := Self
      :nAlign           := alCLIENT
      :lBorder          := .F.
      :nClrPane         := ::oThumbHost:nClrPane
      :nScrollIncrement := 36
      :OnScrollModV     := {|| ::RefreshVisibleThumbnails() }
      :Create()
   END

   WITH OBJECT ::oPreviewPage := TLLPreviewPage():New( ::oPageHost, oPreviewPrinter )
      :oPreviewOwner  := Self
      :lContinuous    := ::lContinuous
      :nAlign         := alCLIENT
      :lBorder        := .F.
      :nClrPane       := ::oPageHost:nClrPane
      :OnFirstPage    := {|| ::SetPage( 1 ) }
      :OnPrevPage     := {|| ::SetPage( ::nPage - 1 ) }
      :OnNextPage     := {|| ::SetPage( ::nPage + 1 ) }
      :OnLastPage     := {|| ::SetPage( ::TotalPages() ) }
      :OnZoomIn       := {|| ::NextZoom( 1 ) }
      :OnZoomOut      := {|| ::NextZoom( -1 ) }
      :OnZoomChanged  := {|oSender, nZoom| ::ZoomChanged( nZoom ) }
      :Create()
   END
   ::CreateThumbnails()
   // ShowCurrentPage() fija la pagina en cache y realiza la unica asignacion
   // inicial del EMF. Asignarlo tambien antes duplicaba el costoso replay GDI.
   ::ShowCurrentPage()
   IF HB_ISOBJECT( ::oCache )
      ::oCache:PrepareQueue( 1, 1 )
      WITH OBJECT ::oPreloadTimer := TTimer():Create( ::oParent, 15, {|| ::PreloadTick() } )
         :lEnabled := .T.
      END
   ENDIF
   ::UpdateState()
   ::oPreviewPage:SetFocus()

RETURN Self

METHOD End() CLASS TLLPreviewControl

   ::lEnding := .T.
   IF HB_ISOBJECT( ::oPreloadTimer )
      ::oPreloadTimer:lEnabled := .F.
      ::oPreloadTimer:Destroy()
   ENDIF
   ::oPreloadTimer := NIL
   ::ReleaseBorrowedEmfs()
   ::aThumbPages       := {}
   ::aThumbLabels      := {}
   ::aThumbPinned      := {}
   ::oPreviewPage      := NIL
   ::oThumbPane        := NIL
   ::oZoomInButton     := NIL
   ::oZoomLabel        := NIL
   ::oZoomTrackBar     := NIL
   ::oZoomOutButton    := NIL
   ::oCloseButton      := NIL
   ::oImageButton      := NIL
   ::oCSVButton        := NIL
   ::oXLSXButton       := NIL
   ::oExportButton     := NIL
   ::oPdfButton        := NIL
   ::oPrintAllButton   := NIL
   ::oPrintButton      := NIL
   ::oLastButton       := NIL
   ::oNextButton       := NIL
   ::oPrevButton       := NIL
   ::oFirstButton      := NIL
   ::oThumbButton      := NIL
   ::oPageEdit         := NIL
   ::oPageLabel        := NIL
   ::oPageHost         := NIL
   ::oThumbHost        := NIL
   ::oToolBand         := NIL
   ::oDocument         := NIL
   ::oRenderer         := NIL
   ::oCache            := NIL
   ::oPreviewDC        := NIL
   ::oParent           := NIL

RETURN NIL

METHOD CreateToolbar() CLASS TLLPreviewControl

   IF ! HB_ISOBJECT( ::oToolBand )
      RETURN NIL
   ENDIF

   ::oThumbButton := ::CreateIconButton( ::oToolBand, 8, LL_ICON_THUMBNAILS, "Mostrar u ocultar miniaturas", {|| ::ToggleThumbnails() } )
   ::oFirstButton := ::CreateIconButton( ::oToolBand, 52, LL_ICON_FIRST, hb_UTF8ToStr( "Primera página", "ESWIN" ), {|| ::SetPage( 1 ) } )
   ::oPrevButton  := ::CreateIconButton( ::oToolBand, 90, LL_ICON_PREVIOUS, hb_UTF8ToStr( "Página anterior", "ESWIN" ), {|| ::SetPage( ::nPage - 1 ) } )

   WITH OBJECT ::oPageEdit := TEditMod():New( ::oToolBand )
      :SetBounds( 132, 12, 56, 28 )
      :nMaxLength  := 5
      :nAlignment  := taCENTER
      :nClrPane    := ::oToolBand:nClrPane
      :oFont       := TFont():Create( "Segoe UI", 10, 0, 400 )
      :cText       := "1"
      :OnExit      := {|| ::CommitPageEdit() }
      :Create()
   END

   WITH OBJECT ::oPageLabel := TLabel():New( ::oToolBand )
      :SetBounds( 194, 17, 48, 18 )
      :lAutoSize  := .F.
      :nAlignment := taCENTER
      :oFont      := TFont():Create( "Segoe UI", 10, 0, 400 )
      :cText      := "/ " + LTrim( Str( ::TotalPages() ) )
      :Create()
   END

   ::oNextButton := ::CreateIconButton( ::oToolBand, 248, LL_ICON_NEXT, hb_UTF8ToStr( "Página siguiente", "ESWIN" ), {|| ::SetPage( ::nPage + 1 ) } )
   ::oLastButton := ::CreateIconButton( ::oToolBand, 286, LL_ICON_LAST, hb_UTF8ToStr( "Última página", "ESWIN" ), {|| ::SetPage( ::TotalPages() ) } )

   ::oZoomOutButton := ::CreateIconButton( ::oToolBand, 360, LL_ICON_ZOOM_OUT, "Reducir zoom", {|| ::ZoomStep( -1 ) } )

   WITH OBJECT ::oZoomTrackBar := TTrackBarMod():New( ::oToolBand )
      :SetBounds( 402, 10, 150, 32 )
      :nMin          := 1
      :nMax          := 1
      :nValue        := 1
      :nClrPane      := ::oToolBand:nClrPane
      :nClrBarFore   := RGB( 11, 94, 215 )
      :nClrBarBack   := RGB( 208, 214, 223 )
      :lTransparent  := .F.
      :lShowBalloon  := .F.
      :OnChange      := {|oSender, nPosition| ::SetZoomTrack( nPosition ) }
      :Create()
   END

   WITH OBJECT ::oZoomLabel := TLabel():New( ::oToolBand )
      :SetBounds( 556, 14, 64, 24 )
      :lAutoSize  := .F.
      :nAlignment := taCENTER
      :oFont      := TFont():Create( "Segoe UI", 12, 0, 500 )
      :cText      := "1:1"
      :OnClick    := {|| ::FitPage() }
      :Create()
   END

   ::oZoomInButton := ::CreateIconButton( ::oToolBand, 624, LL_ICON_ZOOM_IN, "Aumentar zoom", {|| ::ZoomStep( 1 ) } )

   ::oPrintButton    := ::CreateIconButton( ::oToolBand, 744, LL_ICON_PRINT, hb_UTF8ToStr( "Imprimir página actual", "ESWIN" ), {|| ::PrintCurrentPage() } )
   ::oPrintAllButton := ::CreateIconButton( ::oToolBand, 786, LL_ICON_PRINT_RANGE, hb_UTF8ToStr( "Imprimir páginas...", "ESWIN" ), {|| ::SelectPrintRange() } )
   ::oExportButton   := ::CreateIconButton( ::oToolBand, 828, LL_ICON_EXPORT, "Exportar...", {|| ::ShowExportMenu() } )
   ::oCloseButton    := ::CreateIconButton( ::oToolBand, 886, LL_ICON_CLOSE, "Salir de la vista previa", {|| ::oParent:Close() } )

RETURN NIL

METHOD CreateIconButton( oParent, nLeft, nIcon, cToolTip, bClick ) CLASS TLLPreviewControl

   LOCAL oButton
   LOCAL lWin7 := XA_IsWin7()

   WITH OBJECT oButton := TBtnBmp():New( oParent )
      :SetBounds( nLeft, 7, 36, 38 )
      :nBmpWidth   := 32
      :nBmpHeight  := 32
      :oBitmaps    := { LLPreviewIcon( nIcon, oParent:nClrPane, RGB( 52, 64, 84 ), lWin7 ), ;
                        LLPreviewIcon( nIcon, oParent:nClrPane, RGB( 11, 94, 215 ), lWin7 ), ;
                        LLPreviewIcon( nIcon, oParent:nClrPane, RGB( 11, 94, 215 ), lWin7 ), ;
                        LLPreviewIcon( nIcon, oParent:nClrPane, RGB( 152, 162, 179 ), lWin7 ) }
      :lFlat       := .T.
      :nFlatStyle  := fsRECTANGLE
      :lXPLook     := .F.
      :nClrPane    := oParent:nClrPane
      :cTooltip    := cToolTip
      :OnClick     := bClick
      :Create()
   END

RETURN oButton

METHOD CreateThumbnails() CLASS TLLPreviewControl

   LOCAL nSlot
   LOCAL nPages       := ::TotalPages()
   LOCAL nPoolCount
   LOCAL nTop
   LOCAL nThumbWidth
   LOCAL nThumbLeft
   LOCAL oThumb
   LOCAL oLabel
   LOCAL oReferenceEmf := ::PageEmf( 1, .F. )
   LOCAL oPreviewPrinter
   LOCAL nStarted := LLPreviewMilliseconds()

   IF HB_ISOBJECT( ::oRenderer )
      oPreviewPrinter := ::oRenderer:oPrinter
   ELSEIF HB_ISOBJECT( ::oPreviewDC )
      oPreviewPrinter := ::oPreviewDC:oPrinter
   ENDIF

   // Fuerza primero la barra vertical para calcular con el ancho cliente real.
   ::oThumbPane:nVirtualHeight := ( LL_PREVIEW_THUMB_MARGIN * 2 ) + ( nPages * LL_PREVIEW_THUMB_HEIGHT )
   ::oThumbPane:nVirtualWidth  := ::oThumbPane:nClientWidth
   ::oThumbPane:nClientLeft    := 0

   nThumbWidth := Max( 1, Min( 182, ::oThumbPane:nClientWidth - ( LL_PREVIEW_THUMB_MARGIN * 2 ) ) )
   nThumbLeft  := Max( 0, Round( ( ::oThumbPane:nClientWidth - nThumbWidth ) / 2, 0 ) )
   nPoolCount  := Min( nPages, Max( LL_PREVIEW_THUMB_POOL_MIN, ;
      Int( ( ::oThumbPane:nClientHeight + LL_PREVIEW_THUMB_HEIGHT - 1 ) / LL_PREVIEW_THUMB_HEIGHT ) + ;
      LL_PREVIEW_THUMB_POOL_EXTRA ) )
   ::aThumbPinned := Array( nPages )
   AFill( ::aThumbPinned, .F. )

   FOR nSlot := 1 TO nPoolCount
      nTop := LL_PREVIEW_THUMB_MARGIN + ( ( nSlot - 1 ) * LL_PREVIEW_THUMB_HEIGHT )
      WITH OBJECT oThumb := TLLPreviewThumbPage():New( ::oThumbPane, oPreviewPrinter )
         :nPageIndex    := 0
         :oPreviewOwner := Self
         :SetBounds( nThumbLeft, nTop, nThumbWidth, LL_PREVIEW_THUMB_PAGE_H )
         :lBorder       := .F.
         :nClrPane      := ::oThumbPane:nClrPane
         :Create()
      END
      oThumb:nFitInPage := pfALL
      oThumb:SetPlaceholder( oReferenceEmf )
      AAdd( ::aThumbPages, oThumb )

      WITH OBJECT oLabel := TLabel():New( ::oThumbPane )
         :SetBounds( nThumbLeft, nTop + LL_PREVIEW_THUMB_PAGE_H + 2, nThumbWidth, LL_PREVIEW_THUMB_LABEL_H )
         :lAutoSize  := .F.
         :nAlignment := taCENTER
         :cText      := ""
         :Create()
      END
      AAdd( ::aThumbLabels, oLabel )
   NEXT

   ::oThumbPane:nVirtualWidth  := ::oThumbPane:nClientWidth
   ::oThumbPane:nVirtualHeight := ( LL_PREVIEW_THUMB_MARGIN * 2 ) + ( nPages * LL_PREVIEW_THUMB_HEIGHT )
   ::RefreshVisibleThumbnails()
   LLPreviewLog( "thumbnails pages=" + hb_NToS( nPages ) + ;
      " controls=" + hb_NToS( nPoolCount ) + ;
      " create_ms=" + hb_NToS( LLPreviewMilliseconds() - nStarted ) )

RETURN NIL

METHOD TotalPages() CLASS TLLPreviewControl

   IF HB_ISOBJECT( ::oCache )
      RETURN ::oCache:TotalPages()
   ENDIF
   IF HB_ISOBJECT( ::oPreviewDC )
      RETURN Len( ::oPreviewDC:aEmf )
   ENDIF

RETURN 0

METHOD PageEmf( nPage, lGenerate ) CLASS TLLPreviewControl

   DEFAULT lGenerate TO .F.
   IF nPage < 1 .OR. nPage > ::TotalPages()
      RETURN NIL
   ENDIF
   IF HB_ISOBJECT( ::oCache )
      RETURN ::oCache:GetPage( nPage, lGenerate )
   ENDIF

RETURN ::oPreviewDC:aEmf[ nPage ]

METHOD ShowCurrentPage() CLASS TLLPreviewControl

   LOCAL oEmf

   IF ! HB_ISOBJECT( ::oPreviewPage )
      RETURN NIL
   ENDIF

   IF HB_ISOBJECT( ::oCache )
      IF ::nMainPinnedPage != ::nPage
         ::oPreviewPage:ClearEmf()
         IF ::nMainPinnedPage > 0
            ::oCache:UnpinPage( ::nMainPinnedPage )
         ENDIF
         ::nMainPinnedPage := ::nPage
         ::oCache:PinPage( ::nMainPinnedPage )
      ENDIF
      oEmf := ::PageEmf( ::nPage, .F. )
      IF HB_ISOBJECT( oEmf )
         ::oPreviewPage:AssignEmf( oEmf )
      ELSE
         ::oPreviewPage:ClearEmf()
         ::oCache:QueuePage( ::nPage, .T. )
      ENDIF
   ELSE
      oEmf := ::PageEmf( ::nPage, .F. )
      IF HB_ISOBJECT( oEmf )
         ::oPreviewPage:AssignEmf( oEmf )
      ENDIF
   ENDIF

RETURN NIL

METHOD PreloadTick() CLASS TLLPreviewControl

   LOCAL nGeneratedPage := 0
   LOCAL nBatchStarted := hb_MilliSeconds()
   LOCAL nBatchPages := 0
   LOCAL lShowCurrent := .F.
   LOCAL lRefreshThumbnails := .F.

   IF ::lEnding .OR. ! HB_ISOBJECT( ::oCache )
      RETURN NIL
   ENDIF

   IF HB_ISOBJECT( ::oThumbPane ) .AND. ( ::nThumbTopSeen == NIL .OR. ::nThumbTopSeen != ::oThumbPane:nClientTop )
      ::RefreshVisibleThumbnails()
   ENDIF

   DO WHILE nBatchPages < LL_PREVIEW_PRELOAD_BATCH_MAX .AND. ;
      ( nBatchPages == 0 .OR. hb_MilliSeconds() - nBatchStarted < LL_PREVIEW_PRELOAD_BUDGET_MS )
      nGeneratedPage := ::oCache:PreloadNext()
      IF nGeneratedPage <= 0
         EXIT
      ENDIF
      nBatchPages++
      lShowCurrent := lShowCurrent .OR. nGeneratedPage == ::nPage
      lRefreshThumbnails := lRefreshThumbnails .OR. ::IsThumbnailNearVisible( nGeneratedPage )
   ENDDO

   IF nBatchPages > 0
      IF lShowCurrent
         ::ShowCurrentPage()
      ENDIF
      IF lRefreshThumbnails
         ::RefreshVisibleThumbnails()
      ENDIF
      ::oCache:Trim( ::nPage )
   ENDIF

   IF ::oCache:IsFinished()
      IF HB_ISOBJECT( ::oPreloadTimer )
         ::oPreloadTimer:lEnabled := .F.
      ENDIF
   ENDIF

RETURN NIL

METHOD RefreshVisibleThumbnails() CLASS TLLPreviewControl

   LOCAL nPages := ::TotalPages()
   LOCAL nVisibleTop
   LOCAL nVisibleBottom
   LOCAL nFirstPage
   LOCAL nLastPage
   LOCAL nSlot
   LOCAL nPage
   LOCAL nOldPage
   LOCAL nLogicalTop
   LOCAL nThumbWidth
   LOCAL nThumbLeft
   LOCAL aMissingPages := {}
   LOCAL oEmf

   IF ::lEnding .OR. ! HB_ISOBJECT( ::oThumbPane ) .OR. nPages < 1
      RETURN NIL
   ENDIF

   ::nThumbTopSeen := ::oThumbPane:nClientTop
   nVisibleTop    := Max( 0, -::oThumbPane:nClientTop )
   nVisibleBottom := nVisibleTop + ::oThumbPane:nClientHeight
   nFirstPage     := Max( 1, Int( Max( 0, nVisibleTop - LL_PREVIEW_THUMB_MARGIN ) / LL_PREVIEW_THUMB_HEIGHT ) )
   nLastPage      := Min( nPages, ;
      Int( Max( 0, nVisibleBottom - LL_PREVIEW_THUMB_MARGIN ) / LL_PREVIEW_THUMB_HEIGHT ) + 2 )
   nThumbWidth    := Max( 1, Min( 182, ::oThumbPane:nClientWidth - ( LL_PREVIEW_THUMB_MARGIN * 2 ) ) )
   nThumbLeft     := Max( 0, Round( ( ::oThumbPane:nClientWidth - nThumbWidth ) / 2, 0 ) )

   FOR nSlot := 1 TO Len( ::aThumbPages )
      nOldPage := ::aThumbPages[ nSlot ]:nPageIndex
      IF nOldPage >= 1 .AND. nOldPage <= Len( ::aThumbPinned ) .AND. ;
         ( nOldPage < nFirstPage .OR. nOldPage > nLastPage ) .AND. ::aThumbPinned[ nOldPage ]
         ::aThumbPages[ nSlot ]:ClearEmf()
         ::oCache:UnpinPage( nOldPage )
         ::aThumbPinned[ nOldPage ] := .F.
      ENDIF
   NEXT

   FOR nSlot := 1 TO Len( ::aThumbPages )
      nPage := nFirstPage + nSlot - 1
      IF nPage > nLastPage
         ::aThumbPages[ nSlot ]:ClearEmf()
         ::aThumbPages[ nSlot ]:nPageIndex := 0
         ::aThumbPages[ nSlot ]:lVisible := .F.
         ::aThumbLabels[ nSlot ]:lVisible := .F.
         LOOP
      ENDIF

      nLogicalTop := LL_PREVIEW_THUMB_MARGIN + ( ( nPage - 1 ) * LL_PREVIEW_THUMB_HEIGHT )
      ::aThumbPages[ nSlot ]:nPageIndex := nPage
      ::aThumbPages[ nSlot ]:lSelected := nPage == ::nPage
      ::aThumbPages[ nSlot ]:SetBounds( nThumbLeft, ;
         nLogicalTop, nThumbWidth, LL_PREVIEW_THUMB_PAGE_H )
      ::aThumbPages[ nSlot ]:lVisible := .T.
      ::aThumbLabels[ nSlot ]:SetBounds( nThumbLeft, ;
         nLogicalTop + LL_PREVIEW_THUMB_PAGE_H + 2, ;
         nThumbWidth, LL_PREVIEW_THUMB_LABEL_H )
      ::aThumbLabels[ nSlot ]:cText := hb_UTF8ToStr( "Página", "ESWIN" ) + " " + LTrim( Str( nPage ) )
      ::aThumbLabels[ nSlot ]:lVisible := .T.

      IF HB_ISOBJECT( ::oCache ) .AND. ! ::aThumbPinned[ nPage ]
         ::oCache:PinPage( nPage )
         ::aThumbPinned[ nPage ] := .T.
      ENDIF
      oEmf := ::PageEmf( nPage, .F. )
      IF HB_ISOBJECT( oEmf )
         ::aThumbPages[ nSlot ]:AssignEmf( oEmf )
      ELSE
         ::aThumbPages[ nSlot ]:ClearEmf()
         IF HB_ISOBJECT( ::oCache )
            AAdd( aMissingPages, nPage )
         ENDIF
      ENDIF
      ::aThumbPages[ nSlot ]:Refresh()
   NEXT

   IF HB_ISOBJECT( ::oCache )
      FOR nSlot := Len( aMissingPages ) TO 1 STEP -1
         ::oCache:QueuePage( aMissingPages[ nSlot ], .T. )
      NEXT
      ::oCache:QueuePage( ::nPage, .T. )
   ENDIF

RETURN NIL

METHOD IsThumbnailNearVisible( nPage ) CLASS TLLPreviewControl

   LOCAL nItemTop
   LOCAL nItemBottom
   LOCAL nVisibleTop
   LOCAL nVisibleBottom

   IF ! HB_ISOBJECT( ::oThumbPane )
      RETURN .F.
   ENDIF

   nItemTop      := LL_PREVIEW_THUMB_MARGIN + ( ( nPage - 1 ) * LL_PREVIEW_THUMB_HEIGHT )
   nItemBottom   := nItemTop + LL_PREVIEW_THUMB_HEIGHT
   nVisibleTop   := -::oThumbPane:nClientTop - LL_PREVIEW_THUMB_HEIGHT
   nVisibleBottom := -::oThumbPane:nClientTop + ::oThumbPane:nClientHeight + LL_PREVIEW_THUMB_HEIGHT

RETURN nItemBottom >= nVisibleTop .AND. nItemTop <= nVisibleBottom

METHOD ReleaseBorrowedEmfs() CLASS TLLPreviewControl

   LOCAL nSlot
   LOCAL nPage

   IF HB_ISOBJECT( ::oPreviewPage )
      ::oPreviewPage:ClearEmf()
   ENDIF
   IF HB_ISOBJECT( ::oCache ) .AND. ::nMainPinnedPage > 0
      ::oCache:UnpinPage( ::nMainPinnedPage )
   ENDIF
   ::nMainPinnedPage := 0

   FOR nSlot := 1 TO Len( ::aThumbPages )
      ::aThumbPages[ nSlot ]:ClearEmf()
   NEXT
   FOR nPage := 1 TO Len( ::aThumbPinned )
      IF HB_ISOBJECT( ::oCache ) .AND. ::aThumbPinned[ nPage ]
         ::oCache:UnpinPage( nPage )
         ::aThumbPinned[ nPage ] := .F.
      ENDIF
   NEXT

RETURN NIL

METHOD SetPage( nPage ) CLASS TLLPreviewControl

   LOCAL nOldPage := ::nPage

   IF nPage < 1 .OR. nPage > ::TotalPages()
      RETURN NIL
   ENDIF

   IF nPage != nOldPage
      ::nDirection := iif( nPage < nOldPage, -1, 1 )
      ::nPage := nPage
      IF HB_ISOBJECT( ::oCache )
         ::oCache:PrepareQueue( nPage, ::nDirection )
      ENDIF
   ENDIF
   ::ShowCurrentPage()
   ::UpdateState()
   ::oPreviewPage:SetFocus()

RETURN NIL

METHOD SetThumbnailPage( nPage ) CLASS TLLPreviewControl

   LOCAL nSlot

   IF nPage < 1 .OR. nPage > ::TotalPages()
      RETURN NIL
   ENDIF

   IF nPage != ::nPage
      ::SetPage( nPage )
   ENDIF
   IF HB_ISOBJECT( ::oThumbPane )
      ::oThumbPane:EnsurePageVisible( nPage )
   ENDIF
   FOR nSlot := 1 TO Len( ::aThumbPages )
      IF ::aThumbPages[ nSlot ]:nPageIndex == nPage
         ::aThumbPages[ nSlot ]:SetFocus()
         EXIT
      ENDIF
   NEXT

RETURN NIL

METHOD ToggleThumbnails() CLASS TLLPreviewControl

   ::oThumbHost:lVisible := ! ::oThumbHost:lVisible
   ::oParent:AlignControls()
   IF ::oThumbHost:lVisible
      ::RefreshVisibleThumbnails()
   ENDIF

RETURN NIL

METHOD CommitPageEdit() CLASS TLLPreviewControl

   LOCAL nPage := Val( AllTrim( ::oPageEdit:cText ) )

   IF nPage >= 1 .AND. nPage <= ::TotalPages()
      ::SetPage( nPage )
   ENDIF
   ::oPageEdit:cText := LTrim( Str( ::nPage ) )

RETURN NIL

METHOD UpdateState() CLASS TLLPreviewControl

   LOCAL nIndex

   ::oPageEdit:cText  := LTrim( Str( ::nPage ) )
   ::oPageLabel:cText := "/ " + LTrim( Str( ::TotalPages() ) )
   ::ZoomChanged( ::oPreviewPage:nZoomFactor )
   ::oFirstButton:lEnabled := ::nPage > 1
   ::oPrevButton:lEnabled  := ::nPage > 1
   ::oNextButton:lEnabled  := ::nPage < ::TotalPages()
   ::oLastButton:lEnabled  := ::nPage < ::TotalPages()
   IF HB_ISOBJECT( ::oExportButton )
      ::oExportButton:lEnabled := ::HasValidDocument()
   ENDIF

   FOR nIndex := 1 TO Len( ::aThumbPages )
      ::aThumbPages[ nIndex ]:lSelected := ::aThumbPages[ nIndex ]:nPageIndex == ::nPage
      ::aThumbPages[ nIndex ]:Refresh()
   NEXT
   ::oThumbPane:EnsurePageVisible( ::nPage )
   ::RefreshVisibleThumbnails()

RETURN NIL

METHOD NextZoom( nDirection ) CLASS TLLPreviewControl

   LOCAL aZoomLevels := { 10, 25, 50, 75, 100, 125, 150, 200, 300, 400, 600, 800, 1000, 1200, 1600, 2400, 3200 }
   LOCAL nCurrent     := ::oPreviewPage:nZoomFactor
   LOCAL nMinimum     := ::nMinimumZoom
   LOCAL nIndex

   IF nDirection > 0
      FOR nIndex := 1 TO Len( aZoomLevels )
         IF aZoomLevels[ nIndex ] > nCurrent
            RETURN aZoomLevels[ nIndex ]
         ENDIF
      NEXT
      RETURN LL_ZOOM_MAX
   ENDIF

   FOR nIndex := Len( aZoomLevels ) TO 1 STEP -1
      IF aZoomLevels[ nIndex ] < nCurrent .AND. aZoomLevels[ nIndex ] > nMinimum
         RETURN aZoomLevels[ nIndex ]
      ENDIF
   NEXT

RETURN nMinimum

METHOD ZoomStep( nDirection ) CLASS TLLPreviewControl

   LOCAL nZoom := ::NextZoom( nDirection )

   IF nDirection < 0 .AND. nZoom <= ::nMinimumZoom
      ::FitPage()
   ELSE
      ::oPreviewPage:ApplyZoom( nZoom )
   ENDIF

RETURN NIL

METHOD FitPage() CLASS TLLPreviewControl

   ::oPreviewPage:nFitInPage := pfALL
   ::ZoomChanged( ::oPreviewPage:nZoomFactor )
   ::oPreviewPage:SetFocus()

RETURN NIL

METHOD SetZoomTrack( nPosition ) CLASS TLLPreviewControl

   LOCAL nZoom

   IF ::lSyncZoom .OR. nPosition < 1 .OR. nPosition > Len( ::aZoomLevels )
      RETURN NIL
   ENDIF

   nZoom := ::aZoomLevels[ nPosition ]
   IF nZoom == 0
      ::FitPage()
   ELSE
      ::oPreviewPage:ApplyZoom( nZoom )
   ENDIF

RETURN NIL

METHOD SyncZoomTrack( nZoom ) CLASS TLLPreviewControl

   LOCAL nPosition := 1

   IF ::oPreviewPage:nFitInPage != pfALL
      nPosition := AScan( ::aZoomLevels, Round( nZoom, 0 ) )
      IF nPosition == 0
         nPosition := 1
      ENDIF
   ENDIF

   IF HB_ISOBJECT( ::oZoomTrackBar )
      ::lSyncZoom            := .T.
      ::oZoomTrackBar:nMax   := Max( 1, Len( ::aZoomLevels ) )
      ::oZoomTrackBar:nValue := nPosition
      ::lSyncZoom            := .F.
   ENDIF

RETURN NIL

METHOD ZoomChanged( nZoom ) CLASS TLLPreviewControl

   LOCAL aBaseLevels := { 10, 25, 50, 75, 100, 125, 150, 200, 300, 400, 500, 600, 800, 1000, 1200, 1600, 2400, 3200 }
   LOCAL nLevel

   IF ::oPreviewPage:nFitInPage == pfALL
      ::nMinimumZoom := Round( nZoom, 0 )
   ENDIF

   ::aZoomLevels := { 0 }
   FOR EACH nLevel IN aBaseLevels
      IF nLevel > ::nMinimumZoom
         AAdd( ::aZoomLevels, nLevel )
      ENDIF
   NEXT

   ::SyncZoomTrack( nZoom )

   IF HB_ISOBJECT( ::oZoomLabel )
      ::oZoomLabel:cText := iif( ::oPreviewPage:nFitInPage == pfALL, "1:1", LTrim( Str( Round( nZoom, 0 ) ) ) + "%" )
   ENDIF
   IF HB_ISOBJECT( ::oZoomOutButton )
      ::oZoomOutButton:lEnabled := ::oPreviewPage:nFitInPage != pfALL .AND. nZoom > ::nMinimumZoom
   ENDIF

RETURN NIL

METHOD PrintCurrentPage() CLASS TLLPreviewControl

RETURN ::PrintRange( ::nPage, ::nPage )

METHOD SelectPrintRange() CLASS TLLPreviewControl

   LOCAL oRangeForm
   LOCAL hRange

   WITH OBJECT oRangeForm := TLLPreviewPrintRangeForm():New( ::oParent, ::TotalPages() )
      hRange := :Execute()
      :End()
   END
   oRangeForm := NIL

   IF HB_ISHASH( hRange ) .AND. hRange[ "lAccepted" ]
      RETURN ::PrintRange( hRange[ "nFromPage" ], hRange[ "nToPage" ] )
   ENDIF

RETURN .F.

METHOD PrintRange( nFromPage, nToPage ) CLASS TLLPreviewControl

   LOCAL lOk := .F.
   LOCAL oPrintError := NIL
   LOCAL nTotalPages := ::TotalPages()

   nFromPage := Max( 1, Min( nTotalPages, Int( nFromPage ) ) )
   nToPage   := Max( 1, Min( nTotalPages, Int( nToPage ) ) )
   IF nFromPage > nToPage
      RETURN .F.
   ENDIF

   IF HB_ISOBJECT( ::oCache ) .AND. HB_ISOBJECT( ::oRenderer ) .AND. HB_ISOBJECT( ::oDocument )
      ::oCache:Suspend()
      BEGIN SEQUENCE WITH {| oSequenceError | Break( oSequenceError ) }
         lOk := ::oRenderer:Print( ::oDocument, ::cJobTitle, nFromPage, nToPage )
      RECOVER USING oPrintError
      END SEQUENCE
      ::oCache:Resume()
      ::oCache:PrepareQueue( ::nPage, ::nDirection )
      IF oPrintError != NIL
         LLPreviewLog( "preview-print error" )
      ENDIF
      RETURN lOk
   ENDIF

RETURN ::oPreviewDC:oPrinter:EmfToPrinter( NIL, nFromPage, nToPage )

METHOD HasValidDocument() CLASS TLLPreviewControl

   IF HB_ISOBJECT( ::oDocument ) .AND. ;
      __objHasMsg( ::oDocument, "TOTALPAGINAS" ) .AND. ;
      __objHasMsg( ::oDocument, "NANCHO" ) .AND. ;
      __objHasMsg( ::oDocument, "NALTO" )
      RETURN ::oDocument:TotalPaginas() > 0 .AND. ::oDocument:nAncho > 0 .AND. ::oDocument:nAlto > 0
   ENDIF

RETURN .F.

METHOD ExportPDF() CLASS TLLPreviewControl

   LOCAL cFile
   LOCAL cError
   LOCAL cPdfMessage
   LOCAL oRenderPDF
   LOCAL oProgress
   LOCAL nDocumentPages
   LOCAL lCanceled := .F.
   LOCAL lOk := .F.

   IF ! ::HasValidDocument()
      MsgStop( hb_UTF8ToStr( "El preview no contiene un IR válido para exportar.", "ESWIN" ), "LL-Dominus" )
      RETURN .F.
   ENDIF
   cFile := FileSaveDlg( ::oParent, "Exportar a PDF", "", "Documento PDF (*.pdf)|*.pdf", 1 )
   IF ! HB_ISSTRING( cFile ) .OR. Empty( AllTrim( cFile ) )
      RETURN .F.
   ENDIF
   IF Lower( hb_FNameExt( cFile ) ) != ".pdf"
      cFile += ".pdf"
   ENDIF

   oRenderPDF := TRenderPDF():New()
   nDocumentPages := ::oDocument:TotalPaginas()
   oProgress := TFormLLDominusProgress():New( ::oParent )
   oProgress:StartProgress( ::oParent )
   oProgress:StartPDFProgress( nDocumentPages )
   oRenderPDF:SetProgressCallback( ;
      {| nActual, nTotal, nPagina, nPaginas | ;
         oProgress:UpdateRenderProgress( ;
            nActual, nTotal, nPagina, nPaginas ) } )
   lOk := oRenderPDF:Render( ::oDocument, cFile )
   oRenderPDF:SetProgressCallback( NIL )
   lCanceled := oRenderPDF:WasCanceled()
   IF lOk
      oProgress:FinishProgress()
   ENDIF
   oProgress:CloseProgress()
   oProgress:End()
   oProgress := NIL

   IF lOk
      cPdfMessage := hb_UTF8ToStr( "PDF creado correctamente:", "ESWIN" ) + hb_eol() + cFile
      IF Len( oRenderPDF:GetAvisos() ) > 0
         cPdfMessage += hb_eol() + hb_eol() + hb_UTF8ToStr( "Avisos: ", "ESWIN" ) + LTrim( Str( Len( oRenderPDF:GetAvisos() ) ) )
      ENDIF
      MsgInfo( cPdfMessage, "LL-Dominus" )
   ELSEIF ! lCanceled
      cError := oRenderPDF:GetLastError()
      MsgStop( hb_UTF8ToStr( "No se ha podido crear el PDF.", "ESWIN" ) + ;
         iif( Empty( cError ), "", hb_eol() + hb_UTF8ToStr( cError, "ESWIN" ) ), "LL-Dominus" )
   ENDIF
   oRenderPDF:End()
   oRenderPDF := NIL

RETURN lOk

METHOD ExportCSV() CLASS TLLPreviewControl

   LOCAL oCSV
   LOCAL aTables
   LOCAL hTable
   LOCAL oChoiceForm
   LOCAL hChoice
   LOCAL cMode := "table"
   LOCAL cTableId := ""
   LOCAL cFile
   LOCAL cDirectory
   LOCAL cBaseName
   LOCAL cError
   LOCAL cMessage
   LOCAL oProgress
   LOCAL nTotalRecords := 0
   LOCAL nTablesToExport := 1
   LOCAL lCanceled := .F.
   LOCAL lOk

   IF ! ::HasValidDocument()
      MsgStop( hb_UTF8ToStr( "El preview no contiene un IR vÃ¡lido para exportar.", "ESWIN" ), "LL-Dominus" )
      RETURN .F.
   ENDIF
   oCSV := TExportCSV():New():Create()
   oCSV:lUtf8Bom := .F.
   aTables := oCSV:GetTables( ::oDocument )
   IF Len( aTables ) == 0
      MsgStop( "El informe no contiene tablas exportables.", "LL-Dominus" )
      oCSV:End()
      oCSV := NIL
      RETURN .F.
   ENDIF
   hTable := aTables[ 1 ]
   cTableId := hTable[ "cId" ]
   IF Len( aTables ) > 1
      oChoiceForm := TLLPreviewCSVExportForm():New( ::oParent, aTables )
      hChoice := oChoiceForm:Execute()
      oChoiceForm:End()
      oChoiceForm := NIL
      IF ! hChoice[ "lAccepted" ]
         oCSV:End()
         oCSV := NIL
         RETURN .F.
      ENDIF
      cMode := hChoice[ "cMode" ]
      cTableId := hChoice[ "cTableId" ]
      FOR EACH hTable IN aTables
         IF hTable[ "cId" ] == cTableId
            EXIT
         ENDIF
      NEXT
   ENDIF
   cFile := FileSaveDlg( ::oParent, "Exportar CSV", "", "Archivo CSV (*.csv)|*.csv", 1 )
   IF ! HB_ISSTRING( cFile ) .OR. Empty( AllTrim( cFile ) )
      oCSV:End()
      oCSV := NIL
      RETURN .F.
   ENDIF
   IF cMode == "all"
      nTablesToExport := Len( aTables )
      FOR EACH hTable IN aTables
         nTotalRecords += hTable[ "nRecordCount" ]
      NEXT
   ELSE
      nTotalRecords := iif( HB_ISHASH( hTable ), hTable[ "nRecordCount" ], 0 )
   ENDIF
   oCSV:lOverwrite := cMode != "all"
   oProgress := TFormLLDominusProgress():New( ::oParent )
   oProgress:StartProgress( ::oParent )
   oProgress:StartCSVProgress( nTotalRecords, nTablesToExport )
   oCSV:SetProgressCallback( {| nRecord, nRecords, nTable, nTables | oProgress:UpdateCSVProgress( nRecord, nRecords, nTable, nTables ) } )
   IF cMode == "all"
      cDirectory := hb_FNameDir( cFile )
      cBaseName := hb_FNameName( cFile )
      IF Empty( cDirectory )
         cDirectory := "."
      ENDIF
      lOk := oCSV:ExportAllTables( ::oDocument, cDirectory, cBaseName )
   ELSE
      lOk := oCSV:ExportTable( ::oDocument, cTableId, cFile )
   ENDIF
   oCSV:SetProgressCallback( NIL )
   lCanceled := oCSV:WasCanceled()
   IF lOk
      oProgress:FinishProgress()
   ENDIF
   oProgress:CloseProgress()
   oProgress:End()
   oProgress := NIL
   IF lOk
      cMessage := iif( cMode == "all", ;
         "Archivos CSV creados correctamente: " + LTrim( Str( Len( oCSV:GetOutputFiles() ) ) ) + hb_eol() + cDirectory, ;
         "CSV creado correctamente:" + hb_eol() + cFile )
      MsgInfo( cMessage, "LL-Dominus" )
   ELSEIF ! lCanceled
      cError := oCSV:GetLastError()
      MsgStop( "No se ha podido crear el CSV." + iif( Empty( cError ), "", hb_eol() + cError ), "LL-Dominus" )
   ENDIF
   oCSV:End()
   oCSV := NIL

RETURN lOk

METHOD ExportXLSX() CLASS TLLPreviewControl

   LOCAL oXLSX
   LOCAL aTables
   LOCAL hTable
   LOCAL oChoiceForm
   LOCAL hChoice
   LOCAL cMode := "table"
   LOCAL cTableId := ""
   LOCAL cFile
   LOCAL cError
   LOCAL cMessage
   LOCAL oProgress
   LOCAL nTotalRecords := 0
   LOCAL nTablesToExport := 1
   LOCAL lCanceled := .F.
   LOCAL lOk

   IF ! ::HasValidDocument()
      MsgStop( hb_UTF8ToStr( "El preview no contiene un IR válido para exportar.", "ESWIN" ), "LL-Dominus" )
      RETURN .F.
   ENDIF
   oXLSX := TExportXLSX():New():Create()
   aTables := oXLSX:GetTables( ::oDocument )
   IF Len( aTables ) == 0
      MsgStop( "El informe no contiene tablas exportables.", "LL-Dominus" )
      oXLSX:End()
      oXLSX := NIL
      RETURN .F.
   ENDIF
   hTable := aTables[ 1 ]
   cTableId := hTable[ "cId" ]
   IF Len( aTables ) > 1
      oChoiceForm := TLLPreviewCSVExportForm():New( ::oParent, aTables, "XLSX" )
      hChoice := oChoiceForm:Execute()
      oChoiceForm:End()
      oChoiceForm := NIL
      IF ! hChoice[ "lAccepted" ]
         oXLSX:End()
         oXLSX := NIL
         RETURN .F.
      ENDIF
      cMode := hChoice[ "cMode" ]
      cTableId := hChoice[ "cTableId" ]
      FOR EACH hTable IN aTables
         IF hTable[ "cId" ] == cTableId
            EXIT
         ENDIF
      NEXT
   ENDIF
   cFile := FileSaveDlg( ::oParent, "Exportar a Excel", "", "Libro de Excel (*.xlsx)|*.xlsx", 1 )
   IF ! HB_ISSTRING( cFile ) .OR. Empty( AllTrim( cFile ) )
      oXLSX:End()
      oXLSX := NIL
      RETURN .F.
   ENDIF
   IF Lower( hb_FNameExt( cFile ) ) != ".xlsx"
      cFile += ".xlsx"
   ENDIF
   IF cMode == "all"
      nTablesToExport := Len( aTables )
      FOR EACH hTable IN aTables
         nTotalRecords += hTable[ "nRecordCount" ]
      NEXT
   ELSE
      nTotalRecords := iif( HB_ISHASH( hTable ), hTable[ "nRecordCount" ], 0 )
   ENDIF
   oXLSX:lOverwrite := .T.
   oProgress := TFormLLDominusProgress():New( ::oParent )
   oProgress:StartProgress( ::oParent )
   oProgress:StartXLSXProgress( nTotalRecords, nTablesToExport )
   oXLSX:SetProgressCallback( {| nRecord, nRecords, nTable, nTables | oProgress:UpdateXLSXProgress( nRecord, nRecords, nTable, nTables ) } )
   IF cMode == "all"
      lOk := oXLSX:ExportAllTables( ::oDocument, cFile )
   ELSE
      lOk := oXLSX:ExportTable( ::oDocument, cTableId, cFile )
   ENDIF
   oXLSX:SetProgressCallback( NIL )
   lCanceled := oXLSX:WasCanceled()
   IF lOk
      oProgress:FinishProgress()
   ENDIF
   oProgress:CloseProgress()
   oProgress:End()
   oProgress := NIL
   IF lOk
      cMessage := "Libro Excel creado correctamente:" + hb_eol() + cFile
      IF Len( oXLSX:GetAvisos() ) > 0
         cMessage += hb_eol() + hb_eol() + "Avisos: " + LTrim( Str( Len( oXLSX:GetAvisos() ) ) )
      ENDIF
      MsgInfo( cMessage, "LL-Dominus" )
   ELSEIF ! lCanceled
      cError := oXLSX:GetLastError()
      MsgStop( "No se ha podido crear el libro Excel." + iif( Empty( cError ), "", hb_eol() + cError ), "LL-Dominus" )
   ENDIF
   oXLSX:End()
   oXLSX := NIL

RETURN lOk

METHOD ExportImage() CLASS TLLPreviewControl

   LOCAL cFile
   LOCAL cFilter
   LOCAL cFormat
   LOCAL cError
   LOCAL cMessage
   LOCAL cDirectory
   LOCAL cBaseName
   LOCAL oRenderImage
   LOCAL oOptionsForm
   LOCAL oRangeForm
   LOCAL oProgress
   LOCAL hOptions
   LOCAL hRange
   LOCAL nDocumentPages
   LOCAL nFromPage
   LOCAL nToPage
   LOCAL nExportPages
   LOCAL lCanceled := .F.
   LOCAL lOk

   IF ! ::HasValidDocument()
      MsgStop( hb_UTF8ToStr( "El preview no contiene un IR válido para exportar.", "ESWIN" ), "LL-Dominus" )
      RETURN .F.
   ENDIF

   oOptionsForm := TLLPreviewImageExportForm():New( ::oParent )
   hOptions := oOptionsForm:Execute()
   oOptionsForm:End()
   oOptionsForm := NIL
   IF ! hOptions[ "lAccepted" ]
      RETURN .F.
   ENDIF
   nDocumentPages := ::TotalPages()
   nFromPage := ::nPage
   nToPage := ::nPage
   IF ! hOptions[ "lCurrentPage" ]
      oRangeForm := TLLPreviewPrintRangeForm():New( ::oParent, nDocumentPages, ;
         hb_UTF8ToStr( "Exportar páginas", "ESWIN" ), "exportar", "Exportar" )
      hRange := oRangeForm:Execute()
      oRangeForm:End()
      oRangeForm := NIL
      IF ! hRange[ "lAccepted" ]
         RETURN .F.
      ENDIF
      nFromPage := hRange[ "nFromPage" ]
      nToPage := hRange[ "nToPage" ]
   ENDIF
   cFormat := hOptions[ "cFormat" ]
   DO CASE
   CASE cFormat == "JPG"; cFilter := "Imagen JPEG (*.jpg)|*.jpg"
   CASE cFormat == "BMP"; cFilter := "Imagen BMP (*.bmp)|*.bmp"
   OTHERWISE; cFilter := "Imagen PNG (*.png)|*.png"
   ENDCASE
   cFile := FileSaveDlg( ::oParent, "Exportar imagen", "", cFilter, 1 )
   IF ! HB_ISSTRING( cFile ) .OR. Empty( AllTrim( cFile ) )
      RETURN .F.
   ENDIF

   oRenderImage := TRenderImage():New()
   oRenderImage:cFormat := cFormat
   oRenderImage:nDPI := 150
   oRenderImage:lTransparent := cFormat == "PNG"
   oRenderImage:lCropToContent := hOptions[ "lCropToContent" ]
   oRenderImage:nCropPadding := 0
   oRenderImage:nJpegQuality := 90
   oRenderImage:cBackgroundColor := "#ffffff"
   nExportPages := nToPage - nFromPage + 1
   oProgress := TFormLLDominusProgress():New( ::oParent )
   oProgress:StartProgress( ::oParent )
   oProgress:StartImageProgress( nExportPages )
   oRenderImage:SetProgressCallback( ;
      {| nActual, nTotal, nPagina, nPaginas | ;
         oProgress:UpdateRenderProgress( nActual, nTotal, nPagina, nPaginas ) } )
   IF nFromPage != nToPage
      cDirectory := hb_FNameDir( cFile )
      cBaseName := hb_FNameName( cFile )
      IF Empty( cDirectory )
         cDirectory := "."
      ENDIF
      lOk := oRenderImage:RenderAllPages( ::oDocument, cDirectory, cBaseName, nFromPage, nToPage )
   ELSE
      lOk := oRenderImage:RenderPage( ::oDocument, nFromPage, cFile )
      cFile := oRenderImage:GetLastOutputFile()
      IF lOk
         oProgress:UpdateRenderProgress( 1, 1, 1, 1 )
      ENDIF
   ENDIF
   oRenderImage:SetProgressCallback( NIL )
   lCanceled := oRenderImage:WasCanceled()
   IF lOk
      oProgress:FinishProgress()
   ENDIF
   oProgress:CloseProgress()
   oProgress:End()
   oProgress := NIL
   IF lOk
      IF nFromPage != nToPage
         cMessage := hb_UTF8ToStr( "Imágenes ", "ESWIN" ) + cFormat + hb_UTF8ToStr( " creadas correctamente: ", "ESWIN" ) + ;
            LTrim( Str( nToPage - nFromPage + 1 ) ) + hb_eol() + cDirectory + hb_eol() + ;
            cBaseName + "_" + StrZero( nFromPage, Max( 3, Len( LTrim( Str( nDocumentPages ) ) ) ) ) + ;
            iif( cFormat == "JPG", ".jpg", iif( cFormat == "BMP", ".bmp", ".png" ) ) + " ..."
      ELSE
         cMessage := "Imagen " + cFormat + " creada correctamente:" + hb_eol() + cFile
      ENDIF
      IF Len( oRenderImage:GetAvisos() ) > 0
         cMessage += hb_eol() + hb_eol() + hb_UTF8ToStr( "Avisos: ", "ESWIN" ) + LTrim( Str( Len( oRenderImage:GetAvisos() ) ) )
      ENDIF
      MsgInfo( cMessage, "LL-Dominus" )
   ELSEIF ! lCanceled
      cError := oRenderImage:GetLastError()
      MsgStop( "No se ha podido crear la imagen." + ;
         iif( Empty( cError ), "", hb_eol() + hb_UTF8ToStr( cError, "ESWIN" ) ), "LL-Dominus" )
   ENDIF
   oRenderImage:End()
   oRenderImage := NIL

RETURN lOk

METHOD ShowExportMenu() CLASS TLLPreviewControl

   LOCAL nCommand

   IF ! ::HasValidDocument() .OR. ! HB_ISOBJECT( ::oExportButton )
      RETURN .F.
   ENDIF

   nCommand := LLPreviewExportMenu( ::oParent:Handle )
   DO CASE
   CASE nCommand == 1
      RETURN ::ExportPDF()
   CASE nCommand == 2
      RETURN ::ExportImage()
   CASE nCommand == 3
      RETURN ::ExportCSV()
   CASE nCommand == 4
      RETURN ::ExportXLSX()
   ENDCASE

RETURN .F.

CREATE CLASS TLLPreviewCSVExportForm FROM TForm

   DATA aTables INIT {}
   DATA cExportType INIT "CSV"
   DATA nSelectedTable INIT 1
   DATA oTableLabel INIT NIL
   DATA lAccepted INIT .F.
   DATA cMode INIT "table"

   METHOD New( oParent, aTables, cExportType ) CONSTRUCTOR
   METHOD CreateForm( lCreate )
   METHOD Execute()
   METHOD End()
   METHOD MoveTable( nDirection )
   METHOD UpdateTableLabel()
   METHOD AcceptTable()
   METHOD AcceptAll()
   METHOD CancelOptions()

ENDCLASS

METHOD New( oParent, aTables, cExportType ) CLASS TLLPreviewCSVExportForm
   ::aTables := iif( HB_ISARRAY( aTables ), aTables, {} )
   ::cExportType := iif( Upper( AllTrim( iif( HB_ISSTRING( cExportType ), cExportType, "CSV" ) ) ) == "XLSX", "XLSX", "CSV" )
RETURN ::Super:New( oParent )

METHOD CreateForm( lCreate ) CLASS TLLPreviewCSVExportForm
   LOCAL oTitle
   LOCAL oSubtitle
   LOCAL oPrevious
   LOCAL oNext
   LOCAL oExportTable
   LOCAL oExportAll
   LOCAL oCancel

   DEFAULT lCreate TO .T.
   IF HB_ISOBJECT( ::oTableLabel )
      RETURN Self
   ENDIF
   IF lCreate
      ::SetBounds( 310, 220, 540, 330 )
      ::SetClientSize( 532, 296 )
      ::cText := iif( ::cExportType == "XLSX", "Exportar a Excel", "Exportar CSV" )
      ::oFont := TFont():Create( "Segoe UI", 10, 0, 400 )
      ::nClrPane := clWhite
      ::lCentered := .T.
      ::nBorderStyle := bsDIALOG
      ::lSysMenu := .F.
      ::lCloseBox := .F.
      ::lMinimizeBox := .F.
      ::lMaximizeBox := .F.
      ::Create()
   ENDIF
   ::Super:CreateForm( .F. )
   WITH OBJECT oTitle := TLabel():New( Self )
      :SetBounds( 28, 22, 476, 34 ); :cText := iif( ::cExportType == "XLSX", "Exportar a Excel", "Exportar CSV" ); :oFont := TFont():Create( "Segoe UI", 15, 0, 600 ); :lAutoSize := .F.; :Create()
   END
   WITH OBJECT oSubtitle := TLabel():New( Self )
      :SetBounds( 28, 58, 476, 24 ); :cText := iif( ::cExportType == "XLSX", "Seleccione una tabla o cree una hoja por cada tabla.", "Seleccione una tabla o exporte todas por separado." ); :nClrText := RGB( 112, 122, 137 ); :lAutoSize := .F.; :Create()
   END
   WITH OBJECT oPrevious := TButtonMod():New( Self )
      :SetBounds( 28, 100, 42, 38 ); :cText := "<"; :OnClick := {|| ::MoveTable( -1 ), .F. }; :Create()
   END
   WITH OBJECT ::oTableLabel := TLabel():New( Self )
      :SetBounds( 78, 105, 376, 28 ); :nAlignment := taCENTER; :oFont := TFont():Create( "Segoe UI", 11, 0, 600 ); :lAutoSize := .F.; :Create()
   END
   WITH OBJECT oNext := TButtonMod():New( Self )
      :SetBounds( 462, 100, 42, 38 ); :cText := ">"; :OnClick := {|| ::MoveTable( 1 ), .F. }; :Create()
   END
   WITH OBJECT oExportTable := TButtonMod():New( Self )
      :SetBounds( 28, 160, 232, 38 ); :cText := "Exportar tabla seleccionada"; :OnClick := {|| ::AcceptTable(), .F. }; :Create()
   END
   WITH OBJECT oExportAll := TButtonMod():New( Self )
      :SetBounds( 272, 160, 232, 38 ); :cText := iif( ::cExportType == "XLSX", "Todas en un libro", "Exportar todas las tablas" ); :OnClick := {|| ::AcceptAll(), .F. }; :Create()
   END
   WITH OBJECT oCancel := TButtonMod():New( Self )
      :SetBounds( 378, 226, 126, 36 ); :cText := "Cancelar"; :lCancel := .T.; :OnClick := {|| ::CancelOptions(), .F. }; :Create()
   END
   ::UpdateTableLabel()
RETURN Self

METHOD Execute() CLASS TLLPreviewCSVExportForm
   ::CreateForm()
   ::nSelectedTable := 1
   ::lAccepted := .F.
   ::cMode := "table"
   ::UpdateTableLabel()
   ::ShowModal()
RETURN { ;
   "lAccepted" => ::lAccepted, ;
   "cMode" => ::cMode, ;
   "cTableId" => iif( Len( ::aTables ) > 0, ::aTables[ ::nSelectedTable ][ "cId" ], "" ) ;
}

METHOD End() CLASS TLLPreviewCSVExportForm
   ::oTableLabel := NIL
   ::aTables := {}
RETURN ::Super:End()

METHOD MoveTable( nDirection ) CLASS TLLPreviewCSVExportForm
   IF Len( ::aTables ) > 0
      ::nSelectedTable += nDirection
      IF ::nSelectedTable < 1; ::nSelectedTable := Len( ::aTables ); ENDIF
      IF ::nSelectedTable > Len( ::aTables ); ::nSelectedTable := 1; ENDIF
      ::UpdateTableLabel()
   ENDIF
RETURN NIL

METHOD UpdateTableLabel() CLASS TLLPreviewCSVExportForm
   IF HB_ISOBJECT( ::oTableLabel ) .AND. Len( ::aTables ) > 0
      ::oTableLabel:cText := LTrim( Str( ::nSelectedTable ) ) + " / " + LTrim( Str( Len( ::aTables ) ) ) + "  " + ::aTables[ ::nSelectedTable ][ "cName" ]
      ::oTableLabel:Refresh()
   ENDIF
RETURN NIL

METHOD AcceptTable() CLASS TLLPreviewCSVExportForm
   ::lAccepted := .T.; ::cMode := "table"; ::Close( .T. )
RETURN NIL

METHOD AcceptAll() CLASS TLLPreviewCSVExportForm
   ::lAccepted := .T.; ::cMode := "all"; ::Close( .T. )
RETURN NIL

METHOD CancelOptions() CLASS TLLPreviewCSVExportForm
   ::lAccepted := .F.; ::Close( .T. )
RETURN NIL

CREATE CLASS TLLPreviewImageExportForm FROM TForm

   DATA oPngButton INIT NIL
   DATA oJpgButton INIT NIL
   DATA oBmpButton INIT NIL
   DATA oFullPageButton INIT NIL
   DATA oCropButton INIT NIL
   DATA oCurrentPageButton INIT NIL
   DATA oPageRangeButton INIT NIL
   DATA cFormat INIT "PNG"
   DATA lCropToContent INIT .F.
   DATA lCurrentPage INIT .T.
   DATA lAccepted INIT .F.

   METHOD New( oParent ) CONSTRUCTOR
   METHOD CreateForm( lCreate )
   METHOD Execute()
   METHOD End()
   METHOD SetFormat( cFormat )
   METHOD SetCropMode( lCropToContent )
   METHOD SetPageMode( lCurrentPage )
   METHOD UpdateSelection()
   METHOD StyleChoice( oButton, lSelected, cText )
   METHOD AcceptOptions()
   METHOD CancelOptions()

ENDCLASS

METHOD New( oParent ) CLASS TLLPreviewImageExportForm
RETURN ::Super:New( oParent )

METHOD CreateForm( lCreate ) CLASS TLLPreviewImageExportForm

   LOCAL oTitle
   LOCAL oSubtitle
   LOCAL oFormatLabel
   LOCAL oModeLabel
   LOCAL oInfoLabel
   LOCAL oPagesLabel
   LOCAL oAcceptButton
   LOCAL oCancelButton

   DEFAULT lCreate TO .T.
   IF HB_ISOBJECT( ::oPngButton )
      RETURN Self
   ENDIF
   IF lCreate
      ::SetBounds( 310, 160, 520, 500 )
      ::SetClientSize( 512, 466 )
      ::cText        := "Exportar imagen"
      ::oFont        := TFont():Create( "Segoe UI", 10, 0, 400 )
      ::nClrPane     := clWhite
      ::lCentered    := .T.
      ::nBorderStyle := bsDIALOG
      ::lSysMenu     := .F.
      ::lCloseBox    := .F.
      ::lMinimizeBox := .F.
      ::lMaximizeBox := .F.
      ::Create()
   ENDIF
   ::Super:CreateForm( .F. )

   WITH OBJECT oTitle := TLabel():New( Self )
      :SetBounds( 28, 22, 456, 34 )
      :cText := "Exportar imagen"
      :oFont := TFont():Create( "Segoe UI", 15, 0, 600 )
      :nClrText := RGB( 34, 43, 56 )
      :lAutoSize := .F.
      :Create()
   END
   WITH OBJECT oSubtitle := TLabel():New( Self )
      :SetBounds( 28, 56, 456, 22 )
      :cText := hb_UTF8ToStr( "Seleccione el formato y el área de la página.", "ESWIN" )
      :oFont := TFont():Create( "Segoe UI", 9, 0, 400 )
      :nClrText := RGB( 112, 122, 137 )
      :lAutoSize := .F.
      :Create()
   END
   WITH OBJECT oFormatLabel := TLabel():New( Self )
      :SetBounds( 28, 94, 456, 22 )
      :cText := "Formato"
      :oFont := TFont():Create( "Segoe UI", 10, 0, 600 )
      :lAutoSize := .F.
      :Create()
   END
   WITH OBJECT ::oPngButton := TButtonMod():New( Self )
      :SetBounds( 28, 120, 140, 36 ); :cText := "PNG"; :OnClick := {|| ::SetFormat( "PNG" ), .F. }; :Create()
   END
   WITH OBJECT ::oJpgButton := TButtonMod():New( Self )
      :SetBounds( 178, 120, 140, 36 ); :cText := "JPG"; :OnClick := {|| ::SetFormat( "JPG" ), .F. }; :Create()
   END
   WITH OBJECT ::oBmpButton := TButtonMod():New( Self )
      :SetBounds( 328, 120, 140, 36 ); :cText := "BMP"; :OnClick := {|| ::SetFormat( "BMP" ), .F. }; :Create()
   END
   WITH OBJECT oModeLabel := TLabel():New( Self )
      :SetBounds( 28, 180, 456, 22 )
      :cText := hb_UTF8ToStr( "Área de exportación", "ESWIN" )
      :oFont := TFont():Create( "Segoe UI", 10, 0, 600 )
      :lAutoSize := .F.
      :Create()
   END
   WITH OBJECT ::oFullPageButton := TButtonMod():New( Self )
      :SetBounds( 28, 206, 215, 38 ); :cText := hb_UTF8ToStr( "Página completa", "ESWIN" ); :OnClick := {|| ::SetCropMode( .F. ), .F. }; :Create()
   END
   WITH OBJECT ::oCropButton := TButtonMod():New( Self )
      :SetBounds( 253, 206, 215, 38 ); :cText := "Recortar al contenido"; :OnClick := {|| ::SetCropMode( .T. ), .F. }; :Create()
   END
   WITH OBJECT oPagesLabel := TLabel():New( Self )
      :SetBounds( 28, 264, 456, 22 )
      :cText := hb_UTF8ToStr( "Páginas", "ESWIN" )
      :oFont := TFont():Create( "Segoe UI", 10, 0, 600 )
      :lAutoSize := .F.
      :Create()
   END
   WITH OBJECT ::oCurrentPageButton := TButtonMod():New( Self )
      :SetBounds( 28, 290, 215, 38 ); :cText := hb_UTF8ToStr( "Página actual", "ESWIN" ); :OnClick := {|| ::SetPageMode( .T. ), .F. }; :Create()
   END
   WITH OBJECT ::oPageRangeButton := TButtonMod():New( Self )
      :SetBounds( 253, 290, 215, 38 ); :cText := hb_UTF8ToStr( "Desde / hasta páginas", "ESWIN" ); :OnClick := {|| ::SetPageMode( .F. ), .F. }; :Create()
   END
   WITH OBJECT oInfoLabel := TLabel():New( Self )
      :SetBounds( 28, 342, 440, 32 )
      :cText := "150 DPI. JPG y BMP utilizan fondo blanco."
      :oFont := TFont():Create( "Segoe UI", 9, 0, 400 )
      :nClrText := RGB( 112, 122, 137 )
      :lAutoSize := .F.
      :Create()
   END
   WITH OBJECT oCancelButton := TButtonMod():New( Self )
      :SetBounds( 208, 396, 126, 36 ); :cText := "Cancelar"; :lCancel := .T.; :OnClick := {|| ::CancelOptions(), .F. }; :Create()
   END
   WITH OBJECT oAcceptButton := TButtonMod():New( Self )
      :SetBounds( 342, 396, 126, 36 ); :cText := "Exportar"; :lDefault := .T.; :OnClick := {|| ::AcceptOptions(), .F. }; :Create()
   END
   ::UpdateSelection()

RETURN Self

METHOD Execute() CLASS TLLPreviewImageExportForm
   ::CreateForm()
   ::cFormat := "PNG"
   ::lCropToContent := .F.
   ::lCurrentPage := .T.
   ::lAccepted := .F.
   ::UpdateSelection()
   ::ShowModal()
RETURN { "lAccepted" => ::lAccepted, "cFormat" => ::cFormat, "lCropToContent" => ::lCropToContent, "lCurrentPage" => ::lCurrentPage }

METHOD End() CLASS TLLPreviewImageExportForm
   ::oPageRangeButton := NIL; ::oCurrentPageButton := NIL; ::oCropButton := NIL; ::oFullPageButton := NIL
   ::oBmpButton := NIL; ::oJpgButton := NIL; ::oPngButton := NIL
RETURN ::Super:End()

METHOD SetFormat( cFormat ) CLASS TLLPreviewImageExportForm
   ::cFormat := cFormat
   ::UpdateSelection()
RETURN NIL

METHOD SetCropMode( lCropToContent ) CLASS TLLPreviewImageExportForm
   ::lCropToContent := lCropToContent == .T.
   ::UpdateSelection()
RETURN NIL

METHOD SetPageMode( lCurrentPage ) CLASS TLLPreviewImageExportForm
   ::lCurrentPage := lCurrentPage == .T.
   ::UpdateSelection()
RETURN NIL

METHOD UpdateSelection() CLASS TLLPreviewImageExportForm
   ::StyleChoice( ::oPngButton, ::cFormat == "PNG", "PNG" )
   ::StyleChoice( ::oJpgButton, ::cFormat == "JPG", "JPG" )
   ::StyleChoice( ::oBmpButton, ::cFormat == "BMP", "BMP" )
   ::StyleChoice( ::oFullPageButton, ! ::lCropToContent, hb_UTF8ToStr( "Página completa", "ESWIN" ) )
   ::StyleChoice( ::oCropButton, ::lCropToContent, "Recortar al contenido" )
   ::StyleChoice( ::oCurrentPageButton, ::lCurrentPage, hb_UTF8ToStr( "Página actual", "ESWIN" ) )
   ::StyleChoice( ::oPageRangeButton, ! ::lCurrentPage, hb_UTF8ToStr( "Desde / hasta páginas", "ESWIN" ) )
RETURN NIL

METHOD StyleChoice( oButton, lSelected, cText ) CLASS TLLPreviewImageExportForm
   IF ! HB_ISOBJECT( oButton ); RETURN NIL; ENDIF
   oButton:cText := iif( lSelected, "[ " + cText + " ]", cText )
   oButton:nClrPane := iif( lSelected, RGB( 52, 64, 84 ), RGB( 241, 244, 247 ) )
   oButton:nClrText := iif( lSelected, clWhite, RGB( 52, 64, 84 ) )
   oButton:nClrBorder := iif( lSelected, RGB( 52, 64, 84 ), RGB( 194, 202, 212 ) )
   oButton:nBorderRadius := 4
   oButton:Refresh()
RETURN NIL

METHOD AcceptOptions() CLASS TLLPreviewImageExportForm
   ::lAccepted := .T.
   ::Close( .T. )
RETURN NIL

METHOD CancelOptions() CLASS TLLPreviewImageExportForm
   ::lAccepted := .F.
   ::Close( .T. )
RETURN NIL

CREATE CLASS TLLPreviewPrintRangeForm FROM TForm

   DATA oFromEdit INIT NIL
   DATA oToEdit INIT NIL
   DATA oErrorLabel INIT NIL
   DATA nTotalPages INIT 1
   DATA lAccepted INIT .F.
   DATA nFromPage INIT 1
   DATA nToPage INIT 1
   DATA cWindowTitle INIT ""
   DATA cActionInfinitive INIT ""
   DATA cActionButton INIT ""

   METHOD New( oParent, nTotalPages, cWindowTitle, cActionInfinitive, cActionButton ) CONSTRUCTOR
   METHOD CreateForm( lCreate )
   METHOD Execute()
   METHOD End()
   METHOD AcceptRange()
   METHOD CancelRange()

ENDCLASS

METHOD New( oParent, nTotalPages, cWindowTitle, cActionInfinitive, cActionButton ) CLASS TLLPreviewPrintRangeForm

   ::nTotalPages := Max( 1, Int( nTotalPages ) )
   ::cWindowTitle := iif( Empty( cWindowTitle ), hb_UTF8ToStr( "Imprimir páginas", "ESWIN" ), cWindowTitle )
   ::cActionInfinitive := iif( Empty( cActionInfinitive ), "imprimir", cActionInfinitive )
   ::cActionButton := iif( Empty( cActionButton ), "Imprimir", cActionButton )

RETURN ::Super:New( oParent )

METHOD CreateForm( lCreate ) CLASS TLLPreviewPrintRangeForm

   LOCAL oTitle
   LOCAL oSubtitle
   LOCAL oFromLabel
   LOCAL oToLabel
   LOCAL oAcceptButton
   LOCAL oCancelButton
   LOCAL nPageDigits  := Len( LTrim( Str( ::nTotalPages ) ) )
   LOCAL cPagePicture := Replicate( "9", nPageDigits )

   DEFAULT lCreate TO .T.

   IF HB_ISOBJECT( ::oFromEdit )
      RETURN Self
   ENDIF

   IF lCreate
      ::SetBounds( 310, 249, 474, 340 )
      ::SetClientSize( 466, 306 )
      ::cText        := ::cWindowTitle
      ::oFont        := TFont():Create( "Segoe UI", 10, 0, 400 )
      ::nClrPane     := clWhite
      ::lCentered    := .T.
      ::nBorderStyle := bsDIALOG
      ::lSysMenu     := .F.
      ::lCloseBox    := .F.
      ::lMinimizeBox := .F.
      ::lMaximizeBox := .F.
      ::Create()
   ENDIF

   ::Super:CreateForm( .F. )

   WITH OBJECT oTitle := TLabel():New( Self )
      :SetBounds( 28, 24, 410, 34 )
      :cText      := hb_UTF8ToStr( "Seleccionar páginas", "ESWIN" )
      :oFont      := TFont():Create( "Segoe UI", 15, 0, 600 )
      :nClrText   := RGB( 34, 43, 56 )
      :lAutoSize  := .F.
      :Create()
   END

   WITH OBJECT oSubtitle := TLabel():New( Self )
      :SetBounds( 28, 58, 410, 22 )
      :cText      := "Indique el intervalo que desea " + ::cActionInfinitive + " (1 a " + LTrim( Str( ::nTotalPages ) ) + ")"
      :oFont      := TFont():Create( "Segoe UI", 9, 0, 400 )
      :nClrText   := RGB( 112, 122, 137 )
      :lAutoSize  := .F.
      :Create()
   END

   WITH OBJECT oFromLabel := TLabel():New( Self )
      :SetBounds( 32, 116, 170, 22 )
      :cText      := hb_UTF8ToStr( "Desde página", "ESWIN" )
      :oFont      := TFont():Create( "Segoe UI", 10, 0, 500 )
      :lAutoSize  := .F.
      :Create()
   END

   WITH OBJECT ::oFromEdit := TMaskEdit():New( Self )
      :SetBounds( 32, 142, 170, 32 )
      :cPicture   := cPagePicture
      :nMaxLength := nPageDigits
      :nAlignment := taCENTER
      :oFont      := TFont():Create( "Segoe UI", 11, 0, 400 )
      :Value      := 1
      :Create()
   END

   WITH OBJECT oToLabel := TLabel():New( Self )
      :SetBounds( 264, 116, 170, 22 )
      :cText      := hb_UTF8ToStr( "Hasta página", "ESWIN" )
      :oFont      := TFont():Create( "Segoe UI", 10, 0, 500 )
      :lAutoSize  := .F.
      :Create()
   END

   WITH OBJECT ::oToEdit := TMaskEdit():New( Self )
      :SetBounds( 264, 142, 170, 32 )
      :cPicture   := cPagePicture
      :nMaxLength := nPageDigits
      :nAlignment := taCENTER
      :oFont      := TFont():Create( "Segoe UI", 11, 0, 400 )
      :Value      := ::nTotalPages
      :Create()
   END

   WITH OBJECT ::oErrorLabel := TLabel():New( Self )
      :SetBounds( 32, 182, 402, 22 )
      :cText      := ""
      :oFont      := TFont():Create( "Segoe UI", 9, 0, 400 )
      :nClrText   := RGB( 190, 42, 42 )
      :lAutoSize  := .F.
      :Create()
   END

   WITH OBJECT oCancelButton := TButtonMod():New( Self )
      :SetBounds( 174, 238, 126, 36 )
      :cText         := "Cancelar"
      :oFont         := TFont():Create( "Segoe UI", 10, 0, 500 )
      :nClrPane      := RGB( 241, 244, 247 )
      :nClrText      := RGB( 52, 64, 84 )
      :nClrBorder    := RGB( 194, 202, 212 )
      :nBorderRadius := 4
      :lCancel       := .T.
      :OnClick       := {|| ::CancelRange(), .F. }
      :Create()
   END

   WITH OBJECT oAcceptButton := TButtonMod():New( Self )
      :SetBounds( 308, 238, 126, 36 )
      :cText         := ::cActionButton
      :oFont         := TFont():Create( "Segoe UI", 10, 0, 500 )
      :nClrPane      := RGB( 52, 64, 84 )
      :nClrText      := clWhite
      :nClrBorder    := RGB( 52, 64, 84 )
      :nBorderRadius := 4
      :lDefault      := .T.
      :OnClick       := {|| ::AcceptRange(), .F. }
      :Create()
   END

RETURN Self

METHOD Execute() CLASS TLLPreviewPrintRangeForm

   ::CreateForm()
   ::lAccepted := .F.
   ::oFromEdit:Value := 1
   ::oToEdit:Value := ::nTotalPages
   ::oErrorLabel:cText := ""
   ::oFromEdit:SetFocus()
   ::ShowModal()

RETURN { "lAccepted" => ::lAccepted, "nFromPage" => ::nFromPage, "nToPage" => ::nToPage }

METHOD End() CLASS TLLPreviewPrintRangeForm

   ::oErrorLabel := NIL
   ::oToEdit     := NIL
   ::oFromEdit   := NIL

RETURN ::Super:End()

METHOD AcceptRange() CLASS TLLPreviewPrintRangeForm

   LOCAL cFrom := AllTrim( ::oFromEdit:cText )
   LOCAL cTo   := AllTrim( ::oToEdit:cText )
   LOCAL nFrom := Val( cFrom )
   LOCAL nTo   := Val( cTo )

   IF ! LLPreviewPageTextIsNumeric( cFrom ) .OR. ! LLPreviewPageTextIsNumeric( cTo ) .OR. ;
      nFrom != Int( nFrom ) .OR. nTo != Int( nTo ) .OR. ;
      nFrom < 1 .OR. nTo < 1 .OR. ;
      nFrom > ::nTotalPages .OR. nTo > ::nTotalPages .OR. nFrom > nTo
      ::oErrorLabel:cText := "Use un intervalo valido entre 1 y " + LTrim( Str( ::nTotalPages ) ) + "."
      RETURN NIL
   ENDIF

   ::nFromPage := Int( nFrom )
   ::nToPage   := Int( nTo )
   ::lAccepted := .T.
   ::Close( .T. )

RETURN NIL

METHOD CancelRange() CLASS TLLPreviewPrintRangeForm

   ::lAccepted := .F.
   ::Close( .T. )

RETURN NIL

STATIC FUNCTION LLPreviewPageTextIsNumeric( cText )

   LOCAL cDigit

   IF Empty( cText )
      RETURN .F.
   ENDIF
   FOR EACH cDigit IN cText
      IF cDigit < "0" .OR. cDigit > "9"
         RETURN .F.
      ENDIF
   NEXT

RETURN .T.

CREATE CLASS TLLPreviewThumbPane FROM TScrollingWinControl

   DATA oPreviewOwner INIT NIL

   METHOD EnsurePageVisible( nPage )
   METHOD ScrollTo( nTop )
   METHOD WMSetCursor( nWParam, nLParam )

ENDCLASS

METHOD EnsurePageVisible( nPage ) CLASS TLLPreviewThumbPane

   LOCAL nItemTop       := LL_PREVIEW_THUMB_MARGIN + ( ( nPage - 1 ) * LL_PREVIEW_THUMB_HEIGHT )
   LOCAL nItemBottom    := nItemTop + LL_PREVIEW_THUMB_PAGE_H + 2 + LL_PREVIEW_THUMB_LABEL_H
   LOCAL nVisibleTop    := -::nClientTop
   LOCAL nVisibleBottom := nVisibleTop + ::nClientHeight
   LOCAL nTargetTop     := ::nClientTop

   IF nPage == 1
      nTargetTop := 0
   ELSEIF ( nItemTop - LL_PREVIEW_THUMB_MARGIN ) < nVisibleTop
      nTargetTop := -( nItemTop - LL_PREVIEW_THUMB_MARGIN )
   ELSEIF ( nItemBottom + LL_PREVIEW_THUMB_MARGIN ) > nVisibleBottom
      nTargetTop := -( nItemBottom + LL_PREVIEW_THUMB_MARGIN - ::nClientHeight )
   ENDIF

   ::ScrollTo( nTargetTop )

RETURN NIL

METHOD ScrollTo( nTop ) CLASS TLLPreviewThumbPane

   LOCAL nMinimumTop := Min( 0, ::nClientHeight - ::nVirtualHeight )
   LOCAL nTargetTop  := Max( nMinimumTop, Min( 0, nTop ) )
   LOCAL nIncrement

   IF nTargetTop == ::nClientTop
      RETURN NIL
   ENDIF

   nIncrement         := ::nScrollIncrement
   ::nScrollIncrement := Max( 1, Abs( nTargetTop - ::nClientTop ) )
   ::SendMsg( WM_VSCROLL, iif( nTargetTop < ::nClientTop, SB_LINEDOWN, SB_LINEUP ), 0 )
   ::nScrollIncrement := nIncrement
   IF HB_ISOBJECT( ::oPreviewOwner )
      ::oPreviewOwner:RefreshVisibleThumbnails()
   ENDIF

RETURN NIL

METHOD WMSetCursor( nWParam, nLParam ) CLASS TLLPreviewThumbPane

   HB_SYMBOL_UNUSED( nWParam )
   HB_SYMBOL_UNUSED( nLParam )
   LLPreviewSetArrowCursor()

RETURN 1

CREATE CLASS TLLPreviewPage FROM TPreviewPage

   DATA oPreviewOwner INIT NIL
   DATA lContinuous INIT .F.
   DATA lHasAssignedEmf INIT .F.

   METHOD AssignEmf( oEmf )
   METHOD ClearEmf()
   METHOD SetPlaceholder( oEmf )
   METHOD SetEmfCoors( nPosX, nPosY )
   METHOD ApplyZoom( nZoom )
   METHOD WMPaint()
   METHOD WMSetCursor( nWParam, nLParam )
   METHOD WMMouseWheel( nWParam, nLParam )
   METHOD WMRButtonDown( nWParam, nLParam )
   METHOD WMSize( nWParam, nLParam )

ENDCLASS

METHOD AssignEmf( oEmf ) CLASS TLLPreviewPage

   IF HB_ISOBJECT( oEmf )
      // Escribir el backing field no dispara SetEmfCoors() antes de que el
      // EMF tenga dimensiones validas. El setter publico si lo hace.
      IF ! ::lHasAssignedEmf
         ::FnFitInPage := pfALL
      ENDIF
      ::SetEmf( oEmf )
      ::lHasAssignedEmf := .T.
   ENDIF

RETURN NIL

METHOD ClearEmf() CLASS TLLPreviewPage

   ::FoEmf := NIL
   ::Refresh()

RETURN NIL

METHOD SetPlaceholder( oEmf ) CLASS TLLPreviewPage

   IF HB_ISOBJECT( oEmf )
      ::SetEmf( oEmf )
      ::FoEmf := NIL
      ::Refresh()
   ENDIF

RETURN NIL

METHOD SetEmfCoors( nPosX, nPosY ) CLASS TLLPreviewPage

   LOCAL nAvailableWidth
   LOCAL nAvailableHeight

   IF ! HB_ISOBJECT( ::oEmf ) .OR. ::nAspectRatio <= 0
      RETURN NIL
   ENDIF

   // El calculo estandar de TPreviewPage dimensiona primero el rollo a su
   // altura completa. En papel continuo eso bloquea el hilo GUI antes de que
   // LL-Dominus pueda corregir las coordenadas. Para "encajar pagina" se
   // calcula directamente el rectangulo visible, igual que en la miniatura.
   IF ::lContinuous .AND. ::nFitInPage == pfALL
      nAvailableWidth  := Max( 1, ::nClientWidth - ( LL_PREVIEW_PAGE_MARGIN * 2 ) )
      nAvailableHeight := Max( 1, ::nClientHeight - ( LL_PREVIEW_PAGE_MARGIN * 2 ) )

      IF ::lLandScape
         ::nEmfWidth  := nAvailableWidth
         ::nEmfHeight := Round( ::nEmfWidth * ::nAspectRatio, 0 )
         IF ::nEmfHeight > nAvailableHeight
            ::nEmfHeight := nAvailableHeight
            ::nEmfWidth  := Round( ::nEmfHeight / ::nAspectRatio, 0 )
         ENDIF
      ELSE
         ::nEmfHeight := nAvailableHeight
         ::nEmfWidth  := Round( ::nEmfHeight * ::nAspectRatio, 0 )
         IF ::nEmfWidth > nAvailableWidth
            ::nEmfWidth  := nAvailableWidth
            ::nEmfHeight := Round( ::nEmfWidth / ::nAspectRatio, 0 )
         ENDIF
      ENDIF

      ::nEmfLeft       := LL_PREVIEW_PAGE_MARGIN
      ::nEmfTop        := LL_PREVIEW_PAGE_MARGIN
      ::FnZoomFactor   := iif( ::nEmfHorz100 > 0, Round( 100 * ::nEmfWidth / ::nEmfHorz100, 0 ), 100 )
      ::nClientLeft    := 0
      ::nClientTop     := 0
      ::nVirtualWidth  := ::nClientWidth
      ::nVirtualHeight := ::nClientHeight
      RETURN NIL
   ENDIF

   ::Super:SetEmfCoors( nPosX, nPosY )

   IF ::nFitInPage == pfALL
      IF ::lLandScape
         ::nEmfWidth  := ::nClientWidth - ( LL_PREVIEW_PAGE_MARGIN * 2 )
         ::nEmfHeight := Round( ::nEmfWidth * ::nAspectRatio, 0 )
         IF ::nEmfHeight > ( ::nClientHeight - ( LL_PREVIEW_PAGE_MARGIN * 2 ) )
            ::nEmfHeight := ::nClientHeight - ( LL_PREVIEW_PAGE_MARGIN * 2 )
            ::nEmfWidth  := Round( ::nEmfHeight / ::nAspectRatio, 0 )
         ENDIF
      ELSE
         ::nEmfHeight := ::nClientHeight - ( LL_PREVIEW_PAGE_MARGIN * 2 )
         ::nEmfWidth  := Round( ::nEmfHeight * ::nAspectRatio, 0 )
         IF ::nEmfWidth > ( ::nClientWidth - ( LL_PREVIEW_PAGE_MARGIN * 2 ) )
            ::nEmfWidth  := ::nClientWidth - ( LL_PREVIEW_PAGE_MARGIN * 2 )
            ::nEmfHeight := Round( ::nEmfWidth / ::nAspectRatio, 0 )
         ENDIF
      ENDIF
      ::FnZoomFactor := Round( 100 * ::nEmfWidth / ::nEmfHorz100, 0 )
   ENDIF

   ::nEmfLeft       := LL_PREVIEW_PAGE_MARGIN
   ::nEmfTop        := iif( ::lContinuous, LL_PREVIEW_PAGE_MARGIN, Max( LL_PREVIEW_PAGE_MARGIN, Round( ( ::nClientHeight - ::nEmfHeight ) / 2, 0 ) ) )
   ::nVirtualWidth  := Max( ::nVirtualWidth, ::nEmfWidth + ( LL_PREVIEW_PAGE_MARGIN * 2 ) )
   ::nVirtualHeight := Max( ::nVirtualHeight, ::nEmfHeight + ( LL_PREVIEW_PAGE_MARGIN * 2 ) )

RETURN NIL

METHOD ApplyZoom( nZoom ) CLASS TLLPreviewPage

   nZoom          := Max( LL_ZOOM_MIN, Min( LL_ZOOM_MAX, nZoom ) )
   ::FnZoomFactor := nZoom
   ::FnFitInPage  := pfNONE
   ::nClientLeft  := 0
   ::nClientTop   := 0
   ::SetEmfCoors()
   ::Refresh()
   ::OnZoomChanged( ::FnZoomFactor )
   ::SetFocus()

RETURN NIL

METHOD WMPaint() CLASS TLLPreviewPage

   LOCAL hDC          := ::BeginPaint()
   LOCAL hPaneBrush
   LOCAL hShadowBrush
   LOCAL hPaperBrush
   LOCAL hBorderBrush
   LOCAL nLeft        := ::nEmfLeft + ::nClientLeft
   LOCAL nTop         := ::nEmfTop + ::nClientTop
   LOCAL nRight       := nLeft + ::nEmfWidth
   LOCAL nBottom      := nTop + ::nEmfHeight

   IF ! Empty( hDC )
      hPaneBrush   := CreateSolidBrush( ::nClrPane )
      hShadowBrush := CreateSolidBrush( RGB( 205, 205, 205 ) )
      hPaperBrush  := CreateSolidBrush( clWhite )
      hBorderBrush := CreateSolidBrush( RGB( 180, 180, 180 ) )

      FillRect( hDC, { 0, 0, ::nClientWidth, ::nClientHeight }, hPaneBrush )
      FillRect( hDC, { nLeft + 3, nTop + 3, nRight + 4, nBottom + 4 }, hShadowBrush )
      FillRect( hDC, { nLeft, nTop, nRight, nBottom }, hPaperBrush )

      IF HB_ISOBJECT( ::oEmf )
         ::oEmf:Paint( hDC, nLeft, nTop, nRight, nBottom )
      ELSE
         SetBkMode( hDC, TRANSPARENT )
         SetTextColor( hDC, RGB( 104, 112, 124 ) )
         DrawText( hDC, hb_UTF8ToStr( "Generando página...", "ESWIN" ), ;
            { nLeft + 8, nTop + 8, nRight - 8, nBottom - 8 }, ;
            nOr( DT_CENTER, DT_VCENTER, DT_SINGLELINE ) )
      ENDIF
      FrameRect( hDC, { nLeft, nTop, nRight, nBottom }, hBorderBrush )

      DeleteObject( hBorderBrush )
      DeleteObject( hPaperBrush )
      DeleteObject( hShadowBrush )
      DeleteObject( hPaneBrush )
   ENDIF
   ::EndPaint()

RETURN 0

METHOD WMSetCursor( nWParam, nLParam ) CLASS TLLPreviewPage

   HB_SYMBOL_UNUSED( nWParam )
   HB_SYMBOL_UNUSED( nLParam )
   LLPreviewSetZoomCursor( ::Handle, 0, 0, ::nClientWidth, ::nClientHeight )

RETURN 1

METHOD WMMouseWheel( nWParam, nLParam ) CLASS TLLPreviewPage

   LOCAL nDelta      := HiInt( nWParam )
   LOCAL nNotches    := Max( 1, Int( Abs( nDelta ) / 120 ) )
   LOCAL nStep       := Max( 64, Round( ::nClientHeight * 0.18, 0 ) ) * nNotches
   LOCAL nMinimumTop := Min( 0, ::nClientHeight - ::nVirtualHeight )

   IF lAnd( LoWord( nWParam ), MK_SHIFT ) .OR. ::nVirtualHeight <= ::nClientHeight
      RETURN ::Super:WMMouseWheel( nWParam, nLParam )
   ENDIF

   IF nDelta < 0
      ::nClientTop := Max( nMinimumTop, ::nClientTop - nStep )
   ELSEIF nDelta > 0
      ::nClientTop := Min( 0, ::nClientTop + nStep )
   ENDIF

   ::Refresh()

RETURN 0

METHOD WMRButtonDown( nWParam, nLParam ) CLASS TLLPreviewPage

   IF HB_ISOBJECT( ::oPreviewOwner )
      ::oPreviewOwner:ZoomStep( -1 )
   ENDIF

RETURN 0

METHOD WMSize( nWParam, nLParam ) CLASS TLLPreviewPage

   ::Super:WMSize( nWParam, nLParam )
   IF HB_ISOBJECT( ::oPreviewOwner )
      ::oPreviewOwner:ZoomChanged( ::nZoomFactor )
   ENDIF

RETURN 0

CREATE CLASS TLLPreviewThumbPage FROM TPreviewPage

   DATA oPreviewOwner INIT NIL
   DATA nPageIndex INIT 1
   DATA lSelected INIT .F.

   METHOD AssignEmf( oEmf )
   METHOD ClearEmf()
   METHOD SetPlaceholder( oEmf )
   METHOD SetEmfCoors( nPosX, nPosY )
   METHOD WMPaint()
   METHOD WMSetCursor( nWParam, nLParam )
   METHOD WMMouseWheel( nWParam, nLParam )
   METHOD WMKeyDown( nKey, nFlags, hWnd )
   METHOD WMLButtonDown( nWParam, nLParam )
   METHOD WMRButtonDown( nWParam, nLParam )

ENDCLASS

METHOD AssignEmf( oEmf ) CLASS TLLPreviewThumbPage

   IF HB_ISOBJECT( oEmf )
      ::SetEmf( oEmf )
   ENDIF

RETURN NIL

METHOD ClearEmf() CLASS TLLPreviewThumbPage

   ::FoEmf := NIL
   ::Refresh()

RETURN NIL

METHOD SetPlaceholder( oEmf ) CLASS TLLPreviewThumbPage

   IF HB_ISOBJECT( oEmf )
      ::SetEmf( oEmf )
      ::FoEmf := NIL
      ::Refresh()
   ENDIF

RETURN NIL

METHOD SetEmfCoors( nPosX, nPosY ) CLASS TLLPreviewThumbPage

   LOCAL nAvailableWidth
   LOCAL nAvailableHeight

   IF ! HB_ISOBJECT( ::oEmf ) .OR. ::nAspectRatio <= 0
      RETURN NIL
   ENDIF

   nAvailableWidth  := Max( 1, ::nClientWidth - ( LL_PREVIEW_THUMB_MARGIN * 2 ) )
   nAvailableHeight := Max( 1, ::nClientHeight - ( LL_PREVIEW_THUMB_MARGIN * 2 ) )

   IF ::lLandScape
      ::nEmfWidth  := nAvailableWidth
      ::nEmfHeight := Round( ::nEmfWidth * ::nAspectRatio, 0 )

      IF ::nEmfHeight > nAvailableHeight
         ::nEmfHeight := nAvailableHeight
         ::nEmfWidth  := Round( ::nEmfHeight / ::nAspectRatio, 0 )
      ENDIF
   ELSE
      ::nEmfHeight := nAvailableHeight
      ::nEmfWidth  := Round( ::nEmfHeight * ::nAspectRatio, 0 )

      IF ::nEmfWidth > nAvailableWidth
         ::nEmfWidth  := nAvailableWidth
         ::nEmfHeight := Round( ::nEmfWidth / ::nAspectRatio, 0 )
      ENDIF
   ENDIF

   ::nEmfLeft       := Round( ( ::nClientWidth - ::nEmfWidth ) / 2, 0 )
   ::nEmfTop        := Round( ( ::nClientHeight - ::nEmfHeight ) / 2, 0 )
   ::FnZoomFactor   := Round( 100 * ::nEmfWidth / ::nEmfHorz100, 0 )
   ::nClientLeft    := 0
   ::nClientTop     := 0
   ::nVirtualWidth  := ::nClientWidth
   ::nVirtualHeight := ::nClientHeight

RETURN NIL

METHOD WMPaint() CLASS TLLPreviewThumbPage

   LOCAL hDC          := ::BeginPaint()
   LOCAL hPaneBrush
   LOCAL hShadowBrush
   LOCAL hPaperBrush
   LOCAL hBorderBrush
   LOCAL nLeft        := ::nEmfLeft + ::nClientLeft
   LOCAL nTop         := ::nEmfTop + ::nClientTop
   LOCAL nRight       := nLeft + ::nEmfWidth
   LOCAL nBottom      := nTop + ::nEmfHeight
   LOCAL nSelectLeft   := Max( 0, nLeft - 3 )
   LOCAL nSelectTop    := Max( 0, nTop - 3 )
   LOCAL nSelectRight  := Min( ::nClientWidth, nRight + 3 )
   LOCAL nSelectBottom := Min( ::nClientHeight, nBottom + 3 )

   IF ! Empty( hDC )
      hPaneBrush   := CreateSolidBrush( ::nClrPane )
      hShadowBrush := CreateSolidBrush( RGB( 214, 214, 214 ) )
      hPaperBrush  := CreateSolidBrush( clWhite )
      hBorderBrush := CreateSolidBrush( iif( ::lSelected, RGB( 0, 120, 212 ), RGB( 190, 190, 190 ) ) )

      FillRect( hDC, { 0, 0, ::nClientWidth, ::nClientHeight }, hPaneBrush )
      FillRect( hDC, { nLeft + 2, nTop + 2, nRight + 3, nBottom + 3 }, hShadowBrush )
      FillRect( hDC, { nLeft, nTop, nRight, nBottom }, hPaperBrush )

      IF HB_ISOBJECT( ::oEmf )
         ::oEmf:Paint( hDC, nLeft, nTop, nRight, nBottom )
      ELSE
         SetBkMode( hDC, TRANSPARENT )
         SetTextColor( hDC, RGB( 132, 140, 151 ) )
         DrawText( hDC, "...", ;
            { nLeft + 4, nTop + 4, nRight - 4, nBottom - 4 }, ;
            nOr( DT_CENTER, DT_VCENTER, DT_SINGLELINE ) )
      ENDIF
      FrameRect( hDC, { nLeft, nTop, nRight, nBottom }, hBorderBrush )

      IF ::lSelected
         FrameRect( hDC, { nSelectLeft, nSelectTop, nSelectRight, nSelectBottom }, hBorderBrush )
         FrameRect( hDC, { nSelectLeft + 1, nSelectTop + 1, nSelectRight - 1, nSelectBottom - 1 }, hBorderBrush )
      ENDIF

      DeleteObject( hBorderBrush )
      DeleteObject( hPaperBrush )
      DeleteObject( hShadowBrush )
      DeleteObject( hPaneBrush )
   ENDIF
   ::EndPaint()

RETURN 0

METHOD WMSetCursor( nWParam, nLParam ) CLASS TLLPreviewThumbPage

   HB_SYMBOL_UNUSED( nWParam )
   HB_SYMBOL_UNUSED( nLParam )
   LLPreviewSetArrowCursor()

RETURN 1

METHOD WMMouseWheel( nWParam, nLParam ) CLASS TLLPreviewThumbPage

   LOCAL nDelta
   LOCAL nNotches
   LOCAL nStep
   LOCAL nDirection

   IF ! HB_ISOBJECT( ::oParent )
      RETURN 0
   ENDIF

   nDelta      := HiInt( nWParam )

   IF nDelta == 0
      RETURN 0
   ENDIF

   nNotches    := Max( 1, Int( Abs( nDelta ) / 120 ) )
   nDirection  := iif( nDelta < 0, SB_LINEDOWN, SB_LINEUP )

   FOR nStep := 1 TO nNotches * 3
      ::oParent:SendMsg( WM_VSCROLL, nDirection, 0 )
   NEXT

RETURN 0

METHOD WMKeyDown( nKey, nFlags, hWnd ) CLASS TLLPreviewThumbPage

   IF HB_ISOBJECT( ::oPreviewOwner )
      IF nKey == VK_UP
         ::oPreviewOwner:SetThumbnailPage( Max( 1, ::nPageIndex - 1 ) )
         RETURN 0
      ELSEIF nKey == VK_DOWN
         ::oPreviewOwner:SetThumbnailPage( Min( ::oPreviewOwner:TotalPages(), ::nPageIndex + 1 ) )
         RETURN 0
      ENDIF
   ENDIF

RETURN ::Super:WMKeyDown( nKey, nFlags, hWnd )

METHOD WMLButtonDown( nWParam, nLParam ) CLASS TLLPreviewThumbPage

   HB_SYMBOL_UNUSED( nWParam )
   HB_SYMBOL_UNUSED( nLParam )

   IF HB_ISOBJECT( ::oPreviewOwner )
      ::oPreviewOwner:SetThumbnailPage( ::nPageIndex )
   ENDIF

RETURN NIL

METHOD WMRButtonDown( nWParam, nLParam ) CLASS TLLPreviewThumbPage

RETURN ::WMLButtonDown( nWParam, nLParam )

#pragma BEGINDUMP

#include <windows.h>
#include <hbapi.h>
#include <math.h>

HB_FUNC( LLPREVIEWEXPORTMENU )
{
   HWND hOwner = ( HWND ) ( HB_PTRUINT ) hb_parnint( 1 );
   HMENU hMenu;
   POINT menuPoint;
   UINT command = 0;

   if( ! hOwner )
   {
      hb_retni( 0 );
      return;
   }

   hMenu = CreatePopupMenu();
   if( ! hMenu )
   {
      hb_retni( 0 );
      return;
   }

   AppendMenuW( hMenu, MF_STRING, 1, L"Exportar a PDF" );
   AppendMenuW( hMenu, MF_STRING, 2, L"Exportar imagen" );
   AppendMenuW( hMenu, MF_STRING, 3, L"Exportar CSV" );
   AppendMenuW( hMenu, MF_STRING, 4, L"Exportar a Excel" );

   GetCursorPos( &menuPoint );

   SetForegroundWindow( hOwner );
   command = TrackPopupMenu( hMenu, TPM_LEFTALIGN | TPM_TOPALIGN |
      TPM_RIGHTBUTTON | TPM_RETURNCMD, menuPoint.x, menuPoint.y, 0, hOwner, NULL );
   DestroyMenu( hMenu );
   PostMessage( hOwner, WM_NULL, 0, 0 );

   hb_retni( ( int ) command );
}

static void ll_preview_cursor_pixel( DWORD *pPixels, int x, int y,
                                     BYTE r, BYTE g, BYTE b, BYTE a )
{
   DWORD *pPixel;

   if( x < 0 || x >= 32 || y < 0 || y >= 32 || a == 0 )
      return;

   pPixel = &pPixels[ y * 32 + x ];
   if( (BYTE) ( *pPixel >> 24 ) > a )
      return;

   *pPixel = ( (DWORD) a << 24 ) |
             ( (DWORD) ( r * a / 255 ) << 16 ) |
             ( (DWORD) ( g * a / 255 ) << 8 ) |
             (DWORD) ( b * a / 255 );
}

static double ll_preview_segment_distance( double px, double py,
                                           double x1, double y1,
                                           double x2, double y2 )
{
   double dx = x2 - x1;
   double dy = y2 - y1;
   double length2 = dx * dx + dy * dy;
   double t;

   if( length2 <= 0.0 )
      return sqrt( ( px - x1 ) * ( px - x1 ) + ( py - y1 ) * ( py - y1 ) );

   t = ( ( px - x1 ) * dx + ( py - y1 ) * dy ) / length2;
   if( t < 0.0 ) t = 0.0;
   if( t > 1.0 ) t = 1.0;

   return sqrt( ( px - ( x1 + t * dx ) ) * ( px - ( x1 + t * dx ) ) +
                ( py - ( y1 + t * dy ) ) * ( py - ( y1 + t * dy ) ) );
}

static BYTE ll_preview_cursor_alpha( double value )
{
   if( value <= 0.0 )
      return 0;
   if( value >= 255.0 )
      return 255;
   return (BYTE) value;
}

static HCURSOR ll_preview_zoom_cursor( void )
{
   static HCURSOR hCursor = NULL;
   BITMAPV5HEADER bi;
   HDC hScreen;
   void *pBits = NULL;
   HBITMAP hColor;
   HBITMAP hMask;
   ICONINFO ii;
   DWORD *pPixels;
   int x;
   int y;

   if( hCursor )
      return hCursor;

   ZeroMemory( &bi, sizeof( bi ) );
   bi.bV5Size        = sizeof( bi );
   bi.bV5Width       = 32;
   bi.bV5Height      = -32;
   bi.bV5Planes      = 1;
   bi.bV5BitCount    = 32;
   bi.bV5Compression = BI_BITFIELDS;
   bi.bV5RedMask     = 0x00FF0000;
   bi.bV5GreenMask   = 0x0000FF00;
   bi.bV5BlueMask    = 0x000000FF;
   bi.bV5AlphaMask   = 0xFF000000;

   hScreen = GetDC( NULL );
   hColor = CreateDIBSection( hScreen, (BITMAPINFO *) &bi, DIB_RGB_COLORS,
                              &pBits, NULL, 0 );
   ReleaseDC( NULL, hScreen );
   if( !hColor || !pBits )
      return LoadCursor( NULL, IDC_CROSS );

   pPixels = (DWORD *) pBits;
   ZeroMemory( pPixels, 32 * 32 * sizeof( DWORD ) );

   for( y = 0; y < 32; ++y )
   {
      for( x = 0; x < 32; ++x )
      {
         double px = x + 0.5;
         double py = y + 0.5;
         double ring = fabs( sqrt( ( px - 11.0 ) * ( px - 11.0 ) +
                                   ( py - 11.0 ) * ( py - 11.0 ) ) - 7.2 );
         double handle = ll_preview_segment_distance( px, py, 16.0, 16.0, 25.0, 25.0 );
         double badge = sqrt( ( px - 24.0 ) * ( px - 24.0 ) +
                              ( py - 7.0 ) * ( py - 7.0 ) );

         if( ring < 1.8 )
            ll_preview_cursor_pixel( pPixels, x, y, 35, 48, 66,
                                     ll_preview_cursor_alpha( ( 1.8 - ring ) * 230.0 ) );
         if( handle < 1.7 )
            ll_preview_cursor_pixel( pPixels, x, y, 35, 48, 66,
                                     ll_preview_cursor_alpha( ( 1.7 - handle ) * 240.0 ) );
         if( badge < 6.0 )
            ll_preview_cursor_pixel( pPixels, x, y, 11, 94, 215,
                                     ll_preview_cursor_alpha( ( 6.0 - badge ) * 255.0 ) );
         if( badge < 5.0 && ( fabs( px - 24.0 ) < 1.0 || fabs( py - 7.0 ) < 1.0 ) )
            ll_preview_cursor_pixel( pPixels, x, y, 255, 255, 255, 255 );
      }
   }

   hMask = CreateBitmap( 32, 32, 1, 1, NULL );
   ZeroMemory( &ii, sizeof( ii ) );
   ii.fIcon    = FALSE;
   ii.xHotspot = 11;
   ii.yHotspot = 11;
   ii.hbmMask  = hMask;
   ii.hbmColor = hColor;
   hCursor = CreateIconIndirect( &ii );
   DeleteObject( hMask );
   DeleteObject( hColor );

   return hCursor ? hCursor : LoadCursor( NULL, IDC_CROSS );
}

HB_FUNC( LLPREVIEWSETZOOMCURSOR )
{
   HWND hWnd = (HWND) ( HB_PTRUINT ) hb_parnint( 1 );
   RECT paper;
   POINT point;
   BOOL inside = FALSE;

   paper.left   = hb_parni( 2 );
   paper.top    = hb_parni( 3 );
   paper.right  = hb_parni( 4 );
   paper.bottom = hb_parni( 5 );

   SetCursor( LoadCursor( NULL, IDC_ARROW ) );
   if( hWnd && GetCursorPos( &point ) && ScreenToClient( hWnd, &point ) )
   {
      inside = PtInRect( &paper, point );
      if( inside )
         SetCursor( ll_preview_zoom_cursor() );
   }

   hb_retl( inside );
}

HB_FUNC( LLPREVIEWSETARROWCURSOR )
{
   SetCursor( LoadCursor( NULL, IDC_ARROW ) );
}

static void ll_preview_line( HDC hDC, int x1, int y1, int x2, int y2 )
{
   MoveToEx( hDC, x1, y1, NULL );
   LineTo( hDC, x2, y2 );
}

static void ll_preview_arrow( HDC hDC, int nCenterX, int nDirection )
{
   ll_preview_line( hDC, nCenterX + ( 5 * nDirection ), 8, nCenterX - ( 3 * nDirection ), 16 );
   ll_preview_line( hDC, nCenterX - ( 3 * nDirection ), 16, nCenterX + ( 5 * nDirection ), 24 );
}

static void ll_preview_printer( HDC hDC )
{
   Rectangle( hDC, 8, 4, 24, 13 );
   Rectangle( hDC, 5, 11, 27, 23 );
   Rectangle( hDC, 8, 19, 24, 29 );
   ll_preview_line( hDC, 20, 15, 23, 15 );
   ll_preview_line( hDC, 11, 23, 21, 23 );
   ll_preview_line( hDC, 11, 26, 21, 26 );
}

static void ll_preview_zoom( HDC hDC, BOOL lZoomIn )
{
   Ellipse( hDC, 5, 4, 23, 22 );
   ll_preview_line( hDC, 19, 19, 28, 28 );
   ll_preview_line( hDC, 10, 13, 18, 13 );
   if( lZoomIn )
      ll_preview_line( hDC, 14, 9, 14, 17 );
}

HB_FUNC( LLPREVIEWICON )
{
   int nIcon          = hb_parni( 1 );
   COLORREF nClrPane  = (COLORREF) hb_parnl( 2 );
   COLORREF nClrLine  = (COLORREF) hb_parnl( 3 );
   BOOL lWin7Fallback = hb_parl( 4 );
   HDC hScreen        = GetDC( NULL );
   HDC hDC            = CreateCompatibleDC( hScreen );
   HBITMAP hBitmap    = CreateCompatibleBitmap( hScreen, 32, 32 );
   HGDIOBJ hOldBitmap = SelectObject( hDC, hBitmap );
   HPEN hPen          = CreatePen( PS_SOLID, 1, nClrLine );
   HGDIOBJ hOldPen    = SelectObject( hDC, hPen );
   HBRUSH hPaneBrush  = CreateSolidBrush( nClrPane );
   HFONT hFont        = NULL;
   HGDIOBJ hOldFont   = NULL;
   HGDIOBJ hOldBrush;
   RECT aRect         = { 0, 0, 32, 32 };
   WCHAR cGlyph[ 2 ]  = { 0, 0 };

   FillRect( hDC, &aRect, hPaneBrush );
   hOldBrush = SelectObject( hDC, GetStockObject( HOLLOW_BRUSH ) );
   SetBkMode( hDC, TRANSPARENT );
   SetTextColor( hDC, nClrLine );

   switch( nIcon )
   {
      case 1:
         ll_preview_line( hDC, 7, 8, 7, 24 );
         if( lWin7Fallback ) ll_preview_arrow( hDC, 17, 1 );
         else { cGlyph[ 0 ] = 0xE76B; aRect.left = 4; }
         break;

      case 2:
         if( lWin7Fallback ) ll_preview_arrow( hDC, 16, 1 );
         else cGlyph[ 0 ] = 0xE76B;
         break;

      case 3:
         if( lWin7Fallback ) ll_preview_arrow( hDC, 16, -1 );
         else cGlyph[ 0 ] = 0xE76C;
         break;

      case 4:
         if( lWin7Fallback ) ll_preview_arrow( hDC, 15, -1 );
         else { cGlyph[ 0 ] = 0xE76C; aRect.right = 28; }
         ll_preview_line( hDC, 25, 8, 25, 24 );
         break;

      case 5:
         if( lWin7Fallback ) ll_preview_printer( hDC );
         else cGlyph[ 0 ] = 0xE749;
         break;

      case 6:
         Rectangle( hDC, 5, 5, 18, 27 );
         ll_preview_line( hDC, 12, 16, 28, 16 );
         ll_preview_line( hDC, 23, 11, 28, 16 );
         ll_preview_line( hDC, 23, 21, 28, 16 );
         break;

      case 7:
         if( lWin7Fallback ) ll_preview_zoom( hDC, FALSE );
         else cGlyph[ 0 ] = 0xE71F;
         break;

      case 8:
         if( lWin7Fallback ) ll_preview_zoom( hDC, TRUE );
         else cGlyph[ 0 ] = 0xE8A3;
         break;

      case 9:
         Rectangle( hDC, 6, 4, 22, 26 );
         ll_preview_line( hDC, 9, 9, 18, 9 );
         ll_preview_line( hDC, 9, 13, 17, 13 );
         Ellipse( hDC, 16, 16, 27, 27 );
         ll_preview_line( hDC, 24, 24, 29, 29 );
         break;

      case 10:
         if( lWin7Fallback ) ll_preview_printer( hDC );
         else cGlyph[ 0 ] = 0xE749;
         Rectangle( hDC, 18, 17, 29, 29 );
         ll_preview_line( hDC, 21, 21, 26, 21 );
         ll_preview_line( hDC, 21, 24, 26, 24 );
         break;

      case 11:
      {
         HFONT hPdfFont = CreateFontW( -9, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
                                      DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
                                      CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY,
                                      DEFAULT_PITCH, L"Segoe UI" );
         HGDIOBJ hOldPdfFont;

         Rectangle( hDC, 6, 3, 26, 29 );
         ll_preview_line( hDC, 19, 3, 26, 10 );
         ll_preview_line( hDC, 19, 3, 19, 10 );
         ll_preview_line( hDC, 19, 10, 26, 10 );
         hOldPdfFont = SelectObject( hDC, hPdfFont );
         TextOutW( hDC, 8, 16, L"PDF", 3 );
         SelectObject( hDC, hOldPdfFont );
         DeleteObject( hPdfFont );
         break;
      }

      case 12:
      {
         Rectangle( hDC, 4, 6, 28, 27 );
         Ellipse( hDC, 19, 9, 24, 14 );
         ll_preview_line( hDC, 6, 24, 12, 17 );
         ll_preview_line( hDC, 12, 17, 17, 22 );
         ll_preview_line( hDC, 17, 22, 21, 18 );
         ll_preview_line( hDC, 21, 18, 27, 24 );
         break;
      }

      case 13:
      {
         HFONT hCsvFont = CreateFontW( -9, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
                                      DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
                                      CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY,
                                      DEFAULT_PITCH, L"Segoe UI" );
         HGDIOBJ hOldCsvFont;

         Rectangle( hDC, 6, 3, 26, 29 );
         ll_preview_line( hDC, 19, 3, 26, 10 );
         ll_preview_line( hDC, 19, 3, 19, 10 );
         ll_preview_line( hDC, 19, 10, 26, 10 );
         hOldCsvFont = SelectObject( hDC, hCsvFont );
         TextOutW( hDC, 7, 16, L"CSV", 3 );
         SelectObject( hDC, hOldCsvFont );
         DeleteObject( hCsvFont );
         break;
      }

      case 14:
      {
         HFONT hXlsxFont = CreateFontW( -9, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
                                       DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
                                       CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY,
                                       DEFAULT_PITCH, L"Segoe UI" );
         HGDIOBJ hOldXlsxFont;

         Rectangle( hDC, 6, 3, 26, 29 );
         ll_preview_line( hDC, 19, 3, 26, 10 );
         ll_preview_line( hDC, 19, 3, 19, 10 );
         ll_preview_line( hDC, 19, 10, 26, 10 );
         hOldXlsxFont = SelectObject( hDC, hXlsxFont );
         TextOutW( hDC, 8, 16, L"XLS", 3 );
         SelectObject( hDC, hOldXlsxFont );
         DeleteObject( hXlsxFont );
         break;
      }

      case 15:
         Rectangle( hDC, 5, 4, 22, 27 );
         ll_preview_line( hDC, 16, 4, 22, 10 );
         ll_preview_line( hDC, 16, 4, 16, 10 );
         ll_preview_line( hDC, 16, 10, 22, 10 );
         ll_preview_line( hDC, 17, 15, 29, 15 );
         ll_preview_line( hDC, 24, 10, 29, 15 );
         ll_preview_line( hDC, 24, 20, 29, 15 );
         break;
   }

   if( cGlyph[ 0 ] != 0 )
   {
      hFont = CreateFontW( -22, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
                           DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
                           CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY,
                           DEFAULT_PITCH, L"Segoe MDL2 Assets" );
      hOldFont = SelectObject( hDC, hFont );
      DrawTextW( hDC, cGlyph, 1, &aRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX );
      SelectObject( hDC, hOldFont );
      DeleteObject( hFont );
   }

   SelectObject( hDC, hOldBrush );
   SelectObject( hDC, hOldPen );
   SelectObject( hDC, hOldBitmap );
   DeleteObject( hPaneBrush );
   DeleteObject( hPen );
   DeleteDC( hDC );
   ReleaseDC( NULL, hScreen );

   hb_retnl( (LONG) hBitmap );
}

#pragma ENDDUMP
