//Source of this test file: https://github.com/patrickTingen/DataDigger/blob/master/DataDiggerLib.p
&ANALYZE-SUSPEND _VERSION-NUMBER AB_v10r12
&ANALYZE-RESUME
&ANALYZE-SUSPEND _UIB-CODE-BLOCK _CUSTOM _DEFINITIONS Procedure
/*------------------------------------------------------------------------

  Name: DataDiggerLib.p
  Desc: Library for DataDigger functions

------------------------------------------------------------------------*/
/*          This .W file was created with the Progress AppBuilder.       */
/*----------------------------------------------------------------------*/
DEFINE VARIABLE gcSaveDatabaseList  AS CHARACTER  NO-UNDO.
DEFINE VARIABLE giDataserverNr      AS INTEGER    NO-UNDO.  /* [JAG 01-11-2019] */
DEFINE VARIABLE glDirtyCache        AS LOGICAL    NO-UNDO.

/* Buildnr, temp-tables and forward defs */
{ DataDigger.i }

PROCEDURE GetUserNameA EXTERNAL "ADVAPI32.DLL":
  DEFINE INPUT        PARAMETER mUserId       AS MEMPTR NO-UNDO.
  DEFINE INPUT-OUTPUT PARAMETER intBufferSize AS LONG NO-UNDO.
  DEFINE RETURN       PARAMETER intResult     AS SHORT NO-UNDO.
END PROCEDURE.

/* Detect bitness of running Progress version
 * See Progress kb #54631
 */
&IF PROVERSION <= '8' &THEN  /* OE 10+ */
  &IF PROVERSION >= '11.3' &THEN   /* PROCESS-ARCHITECTURE function is available */
    &IF PROCESS-ARCHITECTURE = 32 &THEN /* 32-bit pointers */
      &GLOBAL-DEFINE POINTERTYPE LONG
      &GLOBAL-DEFINE POINTERBYTES 4
    &ELSEIF PROCESS-ARCHITECTURE = 64 &THEN /* 64-bit pointers */
      &GLOBAL-DEFINE POINTERTYPE INT64
      &GLOBAL-DEFINE POINTERBYTES 8
    &ENDIF  /* PROCESS-ARCHITECTURE */
  &ELSE   /* Can't check architecture pre-11.3 so default to 32-bit */
    &GLOBAL-DEFINE POINTERTYPE LONG
    &GLOBAL-DEFINE POINTERBYTES 4
  &ENDIF  /* PROVERSION > 11.3 */
&ELSE   /* pre-OE10 always 32-bit on Windows */
  &GLOBAL-DEFINE POINTERTYPE LONG
  &GLOBAL-DEFINE POINTERBYTES 4
&ENDIF  /* PROVERSION < 8 */

PROCEDURE GetKeyboardState EXTERNAL "user32.dll":
  DEFINE INPUT  PARAMETER KBState AS {&POINTERTYPE}. /* memptr */
  DEFINE RETURN PARAMETER RetVal  AS LONG. /* bool   */
END PROCEDURE.

/* Windows API entry point */
PROCEDURE ShowScrollBar EXTERNAL "user32.dll":
  DEFINE INPUT  PARAMETER hwnd        AS LONG.
  DEFINE INPUT  PARAMETER fnBar       AS LONG.
  DEFINE INPUT  PARAMETER fShow       AS LONG.
  DEFINE RETURN PARAMETER ReturnValue AS LONG.
END PROCEDURE.

PROCEDURE SendMessageA EXTERNAL "user32.dll":
  DEFINE INPUT  PARAMETER hwnd   AS long NO-UNDO.
  DEFINE INPUT  PARAMETER wmsg   AS long NO-UNDO.
  DEFINE INPUT  PARAMETER wparam AS long NO-UNDO.
  DEFINE INPUT  PARAMETER lparam AS long NO-UNDO.
  DEFINE RETURN PARAMETER rc     AS long NO-UNDO.
END PROCEDURE.

PROCEDURE RedrawWindow EXTERNAL "user32.dll":
  DEFINE INPUT PARAMETER v-hwnd  AS LONG NO-UNDO.
  DEFINE INPUT PARAMETER v-rect  AS LONG NO-UNDO.
  DEFINE INPUT PARAMETER v-rgn   AS LONG NO-UNDO.
  DEFINE INPUT PARAMETER v-flags AS LONG NO-UNDO.
  DEFINE RETURN PARAMETER v-ret  AS LONG NO-UNDO.
END PROCEDURE.

PROCEDURE SetWindowTextA EXTERNAL "user32.dll":
  DEFINE INPUT PARAMETER hwnd AS long.
  DEFINE INPUT PARAMETER txt AS CHARACTER.
END PROCEDURE.

PROCEDURE GetWindow EXTERNAL "user32.dll" :
  DEFINE INPUT PARAMETER hwnd AS LONG.
  DEFINE INPUT PARAMETER uCmd AS LONG.
  DEFINE RETURN PARAMETER hwndOther AS LONG.
END PROCEDURE.

PROCEDURE GetParent EXTERNAL "user32.dll" :
  DEFINE INPUT PARAMETER hwndChild AS LONG.
  DEFINE RETURN PARAMETER hwndParent AS LONG.
END PROCEDURE.

PROCEDURE GetCursorPos EXTERNAL "user32":
  DEFINE INPUT  PARAMETER  lpPoint     AS {&POINTERTYPE}. /* memptr */
  DEFINE RETURN PARAMETER  ReturnValue AS LONG.
END PROCEDURE.

PROCEDURE GetSysColor EXTERNAL "user32.dll":
  DEFINE INPUT PARAMETER nDspElement AS LONG.
  DEFINE RETURN PARAMETER COLORREF AS LONG.
END PROCEDURE.

PROCEDURE ScreenToClient EXTERNAL "user32.dll" :
  DEFINE INPUT  PARAMETER hWnd     AS LONG.
  DEFINE INPUT  PARAMETER lpPoint  AS MEMPTR.
END PROCEDURE.

/* Transparency */
PROCEDURE SetWindowLongA EXTERNAL "user32.dll":
  DEFINE INPUT PARAMETER HWND AS LONG.
  DEFINE INPUT PARAMETER nIndex AS LONG.
  DEFINE INPUT PARAMETER dwNewLong AS LONG.
  DEFINE RETURN PARAMETER stat AS LONG.
END PROCEDURE.

PROCEDURE SetLayeredWindowAttributes EXTERNAL "user32.dll":
  DEFINE INPUT PARAMETER HWND AS LONG.
  DEFINE INPUT PARAMETER crKey AS LONG.
  DEFINE INPUT PARAMETER bAlpha AS SHORT.
  DEFINE INPUT PARAMETER dwFlagsas AS LONG.
  DEFINE RETURN PARAMETER stat AS SHORT.
END PROCEDURE.


/* Find out if a file is locked */
&GLOBAL-DEFINE GENERIC_WRITE         1073741824 /* &H40000000 */
&GLOBAL-DEFINE OPEN_EXISTING         3
&GLOBAL-DEFINE FILE_SHARE_READ       1          /* = &H1 */
&GLOBAL-DEFINE FILE_ATTRIBUTE_NORMAL 128        /* = &H80 */

PROCEDURE CreateFileA EXTERNAL "kernel32":
  DEFINE INPUT PARAMETER lpFileName AS CHARACTER.
  DEFINE INPUT PARAMETER dwDesiredAccess AS LONG.
  DEFINE INPUT PARAMETER dwShareMode AS LONG.
  DEFINE INPUT PARAMETER lpSecurityAttributes AS LONG.
  DEFINE INPUT PARAMETER dwCreationDisposition AS LONG.
  DEFINE INPUT PARAMETER dwFlagsAndAttributes AS LONG.
  DEFINE INPUT PARAMETER hTemplateFile AS LONG.
  DEFINE RETURN PARAMETER ReturnValue AS LONG.
END PROCEDURE.

PROCEDURE CloseHandle EXTERNAL "kernel32" :
  DEFINE INPUT  PARAMETER hObject     AS LONG.
  DEFINE RETURN PARAMETER ReturnValue AS LONG.
END PROCEDURE.

/* Used in update check / about window */
PROCEDURE URLDownloadToFileA EXTERNAL "URLMON.DLL" :
  DEFINE INPUT PARAMETER pCaller    AS LONG.
  DEFINE INPUT PARAMETER szURL      AS CHARACTER.
  DEFINE INPUT PARAMETER szFilename AS CHARACTER.
  DEFINE INPUT PARAMETER dwReserved AS LONG.
  DEFINE INPUT PARAMETER lpfnCB     AS LONG.
  DEFINE RETURN PARAMETER ReturnValue AS LONG.
END PROCEDURE. /* URLDownloadToFileA */

PROCEDURE DeleteUrlCacheEntry EXTERNAL "WININET.DLL" :
  DEFINE INPUT PARAMETER lbszUrlName AS CHARACTER.
END PROCEDURE. /* DeleteUrlCacheEntry */

DEFINE TEMP-TABLE ttColor NO-UNDO
  FIELD cName  AS CHARACTER
  FIELD iColor AS INTEGER
  INDEX iPrim AS PRIMARY cName.

DEFINE TEMP-TABLE ttFont NO-UNDO
  FIELD cName  AS CHARACTER
  FIELD iFont  AS INTEGER
  INDEX iPrim AS PRIMARY cName.

/* If you have trouble with the cache, disable it in the settings screen */
DEFINE VARIABLE glCacheTableDefs AS LOGICAL NO-UNDO.
DEFINE VARIABLE glCacheFieldDefs AS LOGICAL NO-UNDO.

/* Vars for caching dirnames */
DEFINE VARIABLE gcProgramDir AS CHARACTER NO-UNDO.
DEFINE VARIABLE gcWorkFolder AS CHARACTER NO-UNDO.

/* Locking / unlocking windows */
&GLOBAL-DEFINE WM_SETREDRAW     11
&GLOBAL-DEFINE RDW_ALLCHILDREN 128
&GLOBAL-DEFINE RDW_ERASE         4
&GLOBAL-DEFINE RDW_INVALIDATE    1

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME


&ANALYZE-SUSPEND _UIB-PREPROCESSOR-BLOCK

/* ********************  Preprocessor Definitions  ******************** */

&Scoped-define PROCEDURE-TYPE Procedure
&Scoped-define DB-AWARE no



/* _UIB-PREPROCESSOR-BLOCK-END */
&ANALYZE-RESUME


/* ************************  Function Prototypes ********************** */

&IF DEFINED(EXCLUDE-addConnection) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD addConnection Procedure
FUNCTION addConnection RETURNS LOGICAL
  ( pcDatabase AS CHARACTER
  , pcSection  AS CHARACTER )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-formatQueryString) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD formatQueryString Procedure
FUNCTION formatQueryString RETURNS CHARACTER
  ( INPUT pcQueryString AS CHARACTER
  , INPUT plExpanded    AS LOGICAL )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getColor) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getColor Procedure
FUNCTION getColor RETURNS INTEGER
  ( pcName AS CHARACTER )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getColorByRGB) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getColorByRGB Procedure
FUNCTION getColorByRGB RETURNS INTEGER
  ( piRed   AS INTEGER
  , piGreen AS INTEGER
  , piBlue  AS INTEGER
  ) FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getColumnLabel) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getColumnLabel Procedure
FUNCTION getColumnLabel RETURNS CHARACTER
  ( INPUT phFieldBuffer AS HANDLE ) FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getColumnWidthList) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getColumnWidthList Procedure
FUNCTION getColumnWidthList RETURNS CHARACTER
  ( INPUT phBrowse AS HANDLE ) FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getDatabaseList) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getDatabaseList Procedure
FUNCTION getDatabaseList RETURNS CHARACTER FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getEscapedData) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getEscapedData Procedure
FUNCTION getEscapedData RETURNS CHARACTER
  ( pcTarget AS CHARACTER
  , pcString AS CHARACTER )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getFieldList) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getFieldList Procedure
FUNCTION getFieldList RETURNS CHARACTER
  ( pcDatabase AS CHARACTER
  , pcFile     AS CHARACTER
  ) FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getFileCategory) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getFileCategory Procedure
FUNCTION getFileCategory RETURNS CHARACTER
  ( piFileNumber AS INTEGER
  , pcFileName   AS CHARACTER
  )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getFont) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getFont Procedure
FUNCTION getFont RETURNS INTEGER
  ( pcName AS CHARACTER )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getImagePath) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getImagePath Procedure
FUNCTION getImagePath RETURNS CHARACTER
  ( pcImage AS CHARACTER )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getIndexFields) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getIndexFields Procedure
FUNCTION getIndexFields RETURNS CHARACTER
  ( INPUT pcDatabaseName AS CHARACTER
  , INPUT pcTableName    AS CHARACTER
  , INPUT pcFlags        AS CHARACTER
  )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getKeyList) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getKeyList Procedure
FUNCTION getKeyList RETURNS CHARACTER
  ( /* parameter-definitions */ )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getLinkInfo) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getLinkInfo Procedure
FUNCTION getLinkInfo RETURNS CHARACTER
  ( INPUT pcFieldName AS CHARACTER
  ) FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getMaxLength) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getMaxLength Procedure
FUNCTION getMaxLength RETURNS INTEGER
  ( cFieldList AS CHARACTER )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getOsErrorDesc) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getOsErrorDesc Procedure
FUNCTION getOsErrorDesc RETURNS CHARACTER
  (INPUT piOsError AS INTEGER) FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getProgramDir) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getProgramDir Procedure
FUNCTION getProgramDir RETURNS CHARACTER
  ( /* parameter-definitions */ )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getQuery) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getQuery Procedure
FUNCTION getQuery RETURNS CHARACTER
  ( INPUT pcDatabase AS CHARACTER
  , INPUT pcTable    AS CHARACTER
  , INPUT piQuery    AS INTEGER
  )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getReadableQuery) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getReadableQuery Procedure
FUNCTION getReadableQuery RETURNS CHARACTER
  ( INPUT pcQuery AS CHARACTER ) FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getRegistry) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getRegistry Procedure
FUNCTION getRegistry RETURNS CHARACTER
    ( pcSection AS CHARACTER
    , pcKey     AS CHARACTER
    )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getSchemaHolder) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getSchemaHolder Procedure
FUNCTION getSchemaHolder RETURNS CHARACTER
  ( INPUT pcDataSrNameOrDbName AS CHARACTER
  ) FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getStackSize) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getStackSize Procedure
FUNCTION getStackSize RETURNS INTEGER() FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getTableDesc) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getTableDesc Procedure
FUNCTION getTableDesc RETURNS CHARACTER
  ( INPUT pcDatabase AS CHARACTER
  , INPUT pcTable    AS CHARACTER
  )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getTableLabel) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getTableLabel Procedure
FUNCTION getTableLabel RETURNS CHARACTER
  ( INPUT  pcDatabase AS CHARACTER
  , INPUT  pcTable    AS CHARACTER
  )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getTableList) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getTableList Procedure
FUNCTION getTableList RETURNS CHARACTER
  ( INPUT  pcDatabaseFilter AS CHARACTER
  , INPUT  pcTableFilter    AS CHARACTER
  )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getUserName) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getUserName Procedure
FUNCTION getUserName RETURNS CHARACTER
  ( /* parameter-definitions */ )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getWidgetUnderMouse) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getWidgetUnderMouse Procedure
FUNCTION getWidgetUnderMouse RETURNS HANDLE
  ( phFrame AS HANDLE )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getWorkFolder) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getWorkFolder Procedure
FUNCTION getWorkFolder RETURNS CHARACTER
  ( /* parameter-definitions */ )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getXmlNodeName) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD getXmlNodeName Procedure
FUNCTION getXmlNodeName RETURNS CHARACTER
  ( pcFieldName AS CHARACTER )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-isDataServer) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD isDataServer Procedure
FUNCTION isDataServer RETURNS LOGICAL
  ( INPUT pcDataSrNameOrDbName AS CHARACTER
  ) FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-isDefaultFontsChanged) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD isDefaultFontsChanged Procedure
FUNCTION isDefaultFontsChanged RETURNS LOGICAL
  ( /* parameter-definitions */ )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-isFileLocked) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD isFileLocked Procedure
FUNCTION isFileLocked RETURNS LOGICAL
  ( pcFileName AS CHARACTER )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-isMouseOver) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD isMouseOver Procedure
FUNCTION isMouseOver RETURNS LOGICAL
  ( phWidget AS HANDLE )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-isTableFilterUsed) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD isTableFilterUsed Procedure
FUNCTION isTableFilterUsed RETURNS LOGICAL
  ( INPUT TABLE ttTableFilter )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-isValidCodePage) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD isValidCodePage Procedure
FUNCTION isValidCodePage RETURNS LOGICAL
  (pcCodepage AS CHARACTER) FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-readFile) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD readFile Procedure
FUNCTION readFile RETURNS LONGCHAR
  (pcFilename AS CHARACTER) FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-removeConnection) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD removeConnection Procedure
FUNCTION removeConnection RETURNS LOGICAL
  ( pcDatabase AS CHARACTER )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-resolveOsVars) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD resolveOsVars Procedure
FUNCTION resolveOsVars RETURNS CHARACTER
  ( pcString AS CHARACTER )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-resolveSequence) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD resolveSequence Procedure
FUNCTION resolveSequence RETURNS CHARACTER
  ( pcString AS CHARACTER )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-setColor) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD setColor Procedure
FUNCTION setColor RETURNS INTEGER
  ( pcName  AS CHARACTER
  , piColor AS INTEGER)  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-setColumnWidthList) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD setColumnWidthList Procedure
FUNCTION setColumnWidthList RETURNS LOGICAL
  ( INPUT phBrowse    AS HANDLE
  , INPUT pcWidthList AS CHARACTER) FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-setLinkInfo) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD setLinkInfo Procedure
FUNCTION setLinkInfo RETURNS LOGICAL
  ( INPUT pcFieldName AS CHARACTER
  , INPUT pcValue     AS CHARACTER
  ) FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-setRegistry) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION-FORWARD setRegistry Procedure
FUNCTION setRegistry RETURNS CHARACTER
  ( pcSection AS CHARACTER
  , pcKey     AS CHARACTER
  , pcValue   AS CHARACTER
  )  FORWARD.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF


/* *********************** Procedure Settings ************************ */

&ANALYZE-SUSPEND _PROCEDURE-SETTINGS
/* Settings for THIS-PROCEDURE
   Type: Procedure
   Allow:
   Frames: 0
   Add Fields to: Neither
   Other Settings: CODE-ONLY COMPILE
 */
&ANALYZE-RESUME _END-PROCEDURE-SETTINGS

/* *************************  Create Window  ************************** */

&ANALYZE-SUSPEND _CREATE-WINDOW
/* DESIGN Window definition (used by the UIB)
  CREATE WINDOW Procedure ASSIGN
         HEIGHT             = 41
         WIDTH              = 57.4.
/* END WINDOW DEFINITION */
                                                                        */
&ANALYZE-RESUME




&ANALYZE-SUSPEND _UIB-CODE-BLOCK _CUSTOM _MAIN-BLOCK Procedure


/* ***************************  Main Block  *************************** */

/* terminate it.                                                        */
ON CLOSE OF THIS-PROCEDURE
DO:
  DEFINE VARIABLE cEnvironment AS CHARACTER NO-UNDO.
  cEnvironment = SUBSTITUTE('DataDigger-&1', getUserName() ).

  UNLOAD 'DataDiggerHelp' NO-ERROR.
  UNLOAD 'DataDigger'     NO-ERROR.
  UNLOAD cEnvironment     NO-ERROR.
END. /* CLOSE OF THIS-PROCEDURE  */

/* Caching settings must be set from within UI.
 * Since the library might be started from DataDigger.p
 * we cannot rely on the registry being loaded yet
 */
glCacheTableDefs = TRUE.
glCacheFieldDefs = TRUE.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME


/* **********************  Internal Procedures  *********************** */

&IF DEFINED(EXCLUDE-applyChoose) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE applyChoose Procedure
PROCEDURE applyChoose :
/* Apply the choose event to a dynamically created widget
   */
  DEFINE INPUT  PARAMETER pihWidget AS HANDLE NO-UNDO.

  IF VALID-HANDLE(pihWidget) THEN
  DO:
    PUBLISH "debugInfo" (3, SUBSTITUTE("Apply CHOOSE to &1 &2", pihWidget:TYPE, pihWidget:NAME)).
    APPLY 'choose' TO pihWidget.
  END.

END PROCEDURE. /* applyChoose */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-applyEvent) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE applyEvent Procedure
PROCEDURE applyEvent :
/* Apply an event to a dynamically created widget
  */
  DEFINE INPUT  PARAMETER pihWidget AS HANDLE NO-UNDO.
  DEFINE INPUT  PARAMETER pcEvent   AS CHARACTER   NO-UNDO.

  IF VALID-HANDLE(pihWidget) THEN
  DO:
    PUBLISH "debugInfo" (3, SUBSTITUTE("Apply &1 to &2 &3", CAPS(pcEvent), pihWidget:TYPE, pihWidget:NAME)).
    APPLY pcEvent TO pihWidget.
  END.

END PROCEDURE. /* applyEvent */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-checkBackupFolder) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE checkBackupFolder Procedure
PROCEDURE checkBackupFolder :
/* If backup is on, create a folder for it
  */
  DEFINE OUTPUT PARAMETER plFolderOk AS LOGICAL NO-UNDO.
  DEFINE VARIABLE cFolder      AS CHARACTER   NO-UNDO.

  IF LOGICAL(getRegistry("DataDigger:Backup","BackupOnCreate"))
  OR LOGICAL(getRegistry("DataDigger:Backup","BackupOnDelete"))
  OR LOGICAL(getRegistry("DataDigger:Backup","BackupOnDelete")) THEN
  DO:
    RUN getDumpFileName
      ( INPUT 'dump' /* action */
      , INPUT ''     /* database */
      , INPUT ''     /* table */
      , INPUT ''     /* extension */
      , INPUT getRegistry("DataDigger:Backup", "BackupDir") /* template */
      , OUTPUT cFolder
      ).
    RUN createFolder(cFolder).

    /* Now check if folder is actually created */
    FILE-INFO:FILE-NAME = cFolder.
    plFolderOk = (FILE-INFO:FULL-PATHNAME <> ?).

    IF NOT plFolderOk THEN
    DO:
      RUN showHelp('CannotCreateBackupFolder', cFolder).
      setRegistry("DataDigger:Backup","BackupOnCreate", "NO").
      setRegistry("DataDigger:Backup","BackupOnUpdate", "NO").
      setRegistry("DataDigger:Backup","BackupOnDelete", "NO").
    END.
  END.
  ELSE
    plFolderOk = TRUE.

END PROCEDURE. /* checkBackupFolder */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-checkDir) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE checkDir Procedure
PROCEDURE checkDir :
/* Check if a folder exists, is accessible etc
  */
  DEFINE INPUT  PARAMETER pcFileName AS CHARACTER   NO-UNDO.
  DEFINE OUTPUT PARAMETER pcError    AS CHARACTER   NO-UNDO.

  DEFINE VARIABLE cDumpDir     AS CHARACTER NO-UNDO.
  DEFINE VARIABLE cDirToCreate AS CHARACTER NO-UNDO.
  DEFINE VARIABLE iDir         AS INTEGER   NO-UNDO.

  PUBLISH "debugInfo" (3, SUBSTITUTE("Check &1", pcFileName)).

  /* If no path is given, use startup folder */
  cDumpDir = SUBSTRING(pcFileName, 1, R-INDEX(pcFileName,"\")).
  IF cDumpDir = '' THEN cDumpDir = '.'.

  /* We cannot use the program dir itself */
  FILE-INFO:FILE-NAME = cDumpDir.
  IF TRIM(FILE-INFO:FULL-PATHNAME,'\/') = TRIM(getProgramDir(),"/\") THEN
  DO:
    pcError = getRegistry('DataDigger:Help', 'ExportToProgramdir:message').
    RETURN.
  END.

  PUBLISH "debugInfo" (3, SUBSTITUTE("Dir = &1", cDumpDir)).

  /* Ask to overwrite if it already exists */
  FILE-INFO:FILE-NAME = pcFileName.
  IF FILE-INFO:FULL-PATHNAME <> ? THEN
  DO:
    PUBLISH "debugInfo" (3, SUBSTITUTE("Already exists as &1 (&2)", FILE-INFO:FULL-PATHNAME, FILE-INFO:FILE-TYPE)).

    IF FILE-INFO:FILE-TYPE MATCHES '*F*' THEN
    DO:
      RUN showHelp('OverwriteDumpFile', pcFileName).
      IF getRegistry('DataDigger:Help', 'OverwriteDumpFile:answer') <> '1' THEN
      DO:
        /* Do not remember the answer "No" for this question, otherwise it will be
         * confusing the next time the user encounters this situation
         */
        setRegistry('DataDigger:Help', 'OverwriteDumpFile:answer',?).
        pcError = 'Aborted by user.'.
        RETURN.
      END.

      /* Write access to this file? */
      IF NOT FILE-INFO:FILE-TYPE MATCHES '*W*' THEN
      DO:
        pcError = SUBSTITUTE('Cannot overwrite output file "&1"', pcFileName).
        RETURN.
      END.
    END.

    /* If a dir already exists with the same name as the output file, we cannot create it */
    IF FILE-INFO:FILE-TYPE MATCHES '*D*' THEN
    DO:
      pcError = SUBSTITUTE('A directory named "&1" exists; cannot create a file with the same name.', pcFileName).
      RETURN.
    END.
  END.

  /* Check dir */
  FILE-INFO:FILE-NAME = cDumpDir.
  IF cDumpDir <> "" /* Don't complain about not using a dir */
    AND FILE-INFO:FULL-PATHNAME = ? THEN
  DO:
    RUN showHelp('CreateDumpDir', cDumpDir).
    IF getRegistry('DataDigger:Help', 'CreateDumpDir:answer') <> '1' THEN
    DO:
      pcError = 'Aborted by user.'.
      RETURN.
    END.
  END.

  /* Try to create path + file. Progress will not raise an error if it already exists */
  cDirToCreate = ENTRY(1,cDumpDir,'\').
  DO iDir = 2 TO NUM-ENTRIES(cDumpDir,'\').

    /* In which dir do we want to create a subdir? */
    IF iDir = 2 THEN
      FILE-INFO:FILE-NAME = cDirToCreate + '\'.
    ELSE
      FILE-INFO:FILE-NAME = cDirToCreate.

    /* Does it even exist? */
    IF FILE-INFO:FULL-PATHNAME = ? THEN
    DO:
      pcError = SUBSTITUTE('Directory "&1" does not exist.', cDirToCreate).
      PUBLISH "debugInfo" (3, SUBSTITUTE("Error: &1", pcError)).
      RETURN.
    END.

    /* Check if the dir is writable */
    IF FILE-INFO:FILE-TYPE MATCHES '*X*'  /* Happens on CD-ROM drives */
      OR (        FILE-INFO:FILE-TYPE MATCHES '*D*'
          AND NOT FILE-INFO:FILE-TYPE MATCHES '*W*' ) THEN
    DO:
      pcError = SUBSTITUTE('No write-access to directory: "&1"', cDirToCreate).
      PUBLISH "debugInfo" (3, SUBSTITUTE("Error: &1", pcError)).
      RETURN.
    END.

    /* Seems to exist and to be writable. */
    cDirToCreate = cDirToCreate + '\' + ENTRY(iDir,cDumpDir,'\').

    /* If a file already exists with the same name, we cannot create a dir */
    FILE-INFO:FILE-NAME = cDirToCreate.
    IF FILE-INFO:FILE-TYPE MATCHES '*F*' THEN
    DO:
      pcError = SUBSTITUTE('A file named "&1" exists; cannot create a dir with the same name.', cDirToCreate).
      PUBLISH "debugInfo" (3, SUBSTITUTE("Error: &1", pcError)).
      RETURN.
    END.

    /* Create the dir. Creating an existing dir gives no error */
    OS-CREATE-DIR value(cDirToCreate).
    IF OS-ERROR <> 0 THEN
    DO:
      pcError = getOsErrorDesc(OS-ERROR).
      PUBLISH "debugInfo" (3, SUBSTITUTE("Error: &1", pcError)).
      RETURN.
    END. /* error */

  END. /* iDir */

END PROCEDURE. /* checkDir */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-clearColorCache) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE clearColorCache Procedure
PROCEDURE clearColorCache :
/* Clear the registry cache
  */
  PUBLISH "debugInfo" (3, SUBSTITUTE("Clearing color cache")).
  EMPTY TEMP-TABLE ttColor.

END PROCEDURE. /* clearColorCache */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-clearDiskCache) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE clearDiskCache Procedure
PROCEDURE clearDiskCache :
/* Clear the cache files on disk
  */
  DEFINE VARIABLE cFile AS CHARACTER NO-UNDO EXTENT 3.

  PUBLISH "debugInfo" (3, SUBSTITUTE("Clearing disk cache")).

  FILE-INFORMATION:FILE-NAME = getWorkFolder() + "cache".
  IF FILE-INFORMATION:FULL-PATHNAME = ? THEN RETURN.

  INPUT FROM OS-DIR(FILE-INFORMATION:FULL-PATHNAME).
  REPEAT:
    IMPORT cFile.
    IF cFile[1] MATCHES "*.xml" THEN OS-DELETE VALUE( cFile[2]).
  END.
  INPUT CLOSE.

END PROCEDURE. /* clearDiskCache */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-clearFontCache) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE clearFontCache Procedure
PROCEDURE clearFontCache :
/* Clear the font cache
  */
  PUBLISH "debugInfo" (3, SUBSTITUTE("Clearing font cache")).
  EMPTY TEMP-TABLE ttFont.

END PROCEDURE. /* clearFontCache */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-clearMemoryCache) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE clearMemoryCache Procedure
PROCEDURE clearMemoryCache :
/* Clear the memory cache
  */
  PUBLISH "debugInfo" (3, SUBSTITUTE("Clearing memory cache")).
  EMPTY TEMP-TABLE ttFieldCache.

END PROCEDURE. /* clearMemoryCache */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-clearRegistryCache) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE clearRegistryCache Procedure
PROCEDURE clearRegistryCache :
/* Clear the registry cache
  */
  PUBLISH "debugInfo" (3, SUBSTITUTE("Clearing registry cache")).
  EMPTY TEMP-TABLE ttConfig.

END PROCEDURE. /* clearRegistryCache */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-collectQueryInfo) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE collectQueryInfo Procedure
PROCEDURE collectQueryInfo :
/* Fill the query temp-table
  */
  DEFINE INPUT  PARAMETER pcDatabase     AS CHARACTER   NO-UNDO.
  DEFINE INPUT  PARAMETER pcTable        AS CHARACTER   NO-UNDO.

  DEFINE VARIABLE iMaxQueryHistory AS INTEGER NO-UNDO.
  DEFINE VARIABLE iQueryNr         AS INTEGER NO-UNDO.
  DEFINE VARIABLE iLoop            AS INTEGER NO-UNDO.
  DEFINE VARIABLE cSetting         AS CHARACTER NO-UNDO.

  DEFINE BUFFER bQuery FOR ttQuery.
  {&timerStart}

  /* Delete all known queries in memory of this table */
  FOR EACH bQuery
    WHERE bQuery.cDatabase = pcDatabase
      AND bQuery.cTable    = pcTable:
    DELETE bQuery.
  END.

  iMaxQueryHistory = INTEGER(getRegistry("DataDigger", "MaxQueryHistory" )).
  IF iMaxQueryHistory = 0 THEN RETURN. /* no query history wanted */

  /* If it is not defined use default setting */
  IF iMaxQueryHistory = ? THEN iMaxQueryHistory = 10.

  collectQueries:
  DO iLoop = 1 TO iMaxQueryHistory:
    cSetting = getRegistry( SUBSTITUTE("DB:&1", pcDatabase)
                          , SUBSTITUTE('&1:query:&2', pcTable, iLoop )).

    IF cSetting = '<Empty>' THEN NEXT collectQueries.

    IF cSetting <> ? THEN
    DO:
      CREATE bQuery.
      ASSIGN
        iQueryNr         = iQueryNr + 1
        bQuery.cDatabase = pcDatabase
        bQuery.cTable    = pcTable
        bQuery.iQueryNr  = iQueryNr
        bQuery.cQueryTxt = cSetting.
    END.
    ELSE
      LEAVE collectQueries.

  END. /* 1 .. MaxQueryHistory */
  {&timerStop}
END PROCEDURE. /* collectQueryInfo */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-correctFilterList) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE correctFilterList Procedure
PROCEDURE correctFilterList :
/* Move negative entries from positive list to negative
  */
  DEFINE INPUT-OUTPUT PARAMETER pcPositive AS CHARACTER   NO-UNDO.
  DEFINE INPUT-OUTPUT PARAMETER pcNegative AS CHARACTER   NO-UNDO.

  DEFINE VARIABLE iWord AS INTEGER NO-UNDO.

  /* Strip entries that start with a ! */
  IF INDEX(pcPositive,"!") > 0 THEN
  DO:
    DO iWord = 1 TO NUM-ENTRIES(pcPositive):
      IF ENTRY(iWord,pcPositive) BEGINS "!" THEN
      DO:
        /* Add this word to the negative-list */
        pcNegative = TRIM(pcNegative + ',' + TRIM(ENTRY(iWord,pcPositive),'!'),',').

        /* And wipe it from the positive-list */
        ENTRY(iWord,pcPositive) = ''.
      END.
    END.

    /* Remove empty elements */
    pcPositive = TRIM(pcPositive,',').
    REPEAT WHILE INDEX(pcPositive,',,') > 0:
      pcPositive = REPLACE(pcPositive,',,',',').
    END.
  END.

END PROCEDURE. /* correctFilterList */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-createFolder) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE createFolder Procedure
PROCEDURE createFolder :
/* Create a folder structure
  */
  DEFINE INPUT PARAMETER pcFolder AS CHARACTER NO-UNDO.

  DEFINE VARIABLE iElement AS INTEGER     NO-UNDO.
  DEFINE VARIABLE cPath    AS CHARACTER   NO-UNDO.

  /* c:\temp\somefolder\subfolder\ */
  DO iElement = 1 TO NUM-ENTRIES(pcFolder,'\'):
    cPath = SUBSTITUTE('&1\&2', cPath, ENTRY(iElement,pcFolder,'\')).
    cPath = LEFT-TRIM(cPath,'\').

    IF iElement > 1 THEN OS-CREATE-DIR VALUE(cPath).
  END.

END PROCEDURE. /* createFolder */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-dumpRecord) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE dumpRecord Procedure
PROCEDURE dumpRecord :
/* Dump the record(s) to disk
  */
  DEFINE INPUT  PARAMETER pcAction   AS CHARACTER   NO-UNDO.
  DEFINE INPUT  PARAMETER phSource   AS HANDLE      NO-UNDO.
  DEFINE OUTPUT PARAMETER plContinue AS LOGICAL     NO-UNDO.

  DEFINE VARIABLE hExportTT       AS HANDLE    NO-UNDO.
  DEFINE VARIABLE hExportTtBuffer AS HANDLE    NO-UNDO.
  DEFINE VARIABLE hBuffer         AS HANDLE    NO-UNDO.
  DEFINE VARIABLE cFileName       AS CHARACTER NO-UNDO.
  DEFINE VARIABLE cError          AS CHARACTER NO-UNDO.
  DEFINE VARIABLE cMessage        AS CHARACTER NO-UNDO.
  DEFINE VARIABLE iRow            AS INTEGER   NO-UNDO.
  DEFINE VARIABLE lDefaultDump    AS LOGICAL   NO-UNDO.

  IF NOT VALID-HANDLE(phSource) THEN RETURN.

  /* Protect against wrong input */
  IF LOOKUP(pcAction,'Dump,Create,Update,Delete') = 0 THEN
  DO:
    MESSAGE 'Unknown action' pcAction VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.
    RETURN.
  END.

  /* Determine appropriate buffer and populate an intermediate tt
   * with the data to export
   */
  CASE phSource:TYPE:
    WHEN 'buffer' THEN
    DO:
      hBuffer = phSource.

      /* Create temptable-handle... */
      CREATE TEMP-TABLE hExportTt.
      hExportTt:CREATE-LIKE(SUBSTITUTE("&1.&2", hBuffer:DBNAME, hBuffer:TABLE)).

      /* Prepare the TempTable... */
      hExportTt:TEMP-TABLE-PREPARE(SUBSTITUTE("&1", hBuffer:TABLE)).
      hExportTtBuffer = hExportTt:DEFAULT-BUFFER-HANDLE.
      hExportTtBuffer:BUFFER-CREATE().
      hExportTtBuffer:BUFFER-COPY(hBuffer).
    END.

    WHEN 'browse' THEN
    DO:
      hBuffer = phSource:QUERY:GET-BUFFER-HANDLE(1).

      /* Create temptable-handle... */
      CREATE TEMP-TABLE hExportTt.
      hExportTt:CREATE-LIKE(SUBSTITUTE("&1.&2", hBuffer:DBNAME, hBuffer:TABLE)).

      /* Prepare the TempTable... */
      hExportTt:TEMP-TABLE-PREPARE(SUBSTITUTE("&1", hBuffer:TABLE)).
      hExportTtBuffer = hExportTt:DEFAULT-BUFFER-HANDLE.

      /* Copy the records */
      DO iRow = 1 TO phSource:NUM-SELECTED-ROWS:
        phSource:FETCH-SELECTED-ROW(iRow).
        hExportTtBuffer:BUFFER-CREATE().
        hExportTtBuffer:BUFFER-COPY(hBuffer).
      END.
    END.

    OTHERWISE RETURN.
  END CASE.

  /* Do we need to dump at all?
   * If the setting=NO or if no setting at all, then don't do any checks
   */
  IF pcAction <> 'Dump'
    AND (   getRegistry('DataDigger:Backup','BackupOn' + pcAction) = ?
        OR logical(getRegistry('DataDigger:Backup','BackupOn' + pcAction)) = NO
        ) THEN
  DO:
    ASSIGN plContinue = YES.
    RETURN.
  END.

  /* Determine the default name to save to */
  RUN getDumpFileName
    ( INPUT pcAction        /* Dump | Create | Update | Delete */
    , INPUT hBuffer:DBNAME
    , INPUT hBuffer:TABLE
    , INPUT "XML"
    , INPUT ""
    , OUTPUT cFileName
    ).

  RUN checkDir(INPUT cFileName, OUTPUT cError).
  IF cError <> "" THEN
  DO:
    MESSAGE cError VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.
    RETURN.
  END.

  /* Fix XML Node Names for fields in the tt */
  RUN setXmlNodeNames(INPUT hExportTt:DEFAULT-BUFFER-HANDLE).

  /* See if the user has specified his own dump program
   */
  plContinue = ?. /* To see if it ran or not */
  PUBLISH "customDump"
      ( INPUT pcAction
      , INPUT hBuffer:DBNAME
      , INPUT hBuffer:TABLE
      , INPUT hExportTt
      , INPUT cFileName
      , OUTPUT cMessage
      , OUTPUT lDefaultDump
      , OUTPUT plContinue
      ).

  IF plContinue <> ? THEN
  DO:
    IF cMessage <> "" THEN MESSAGE cMessage VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.
    IF NOT lDefaultDump OR NOT plContinue THEN RETURN.
  END.

  plContinue = hExportTT:WRITE-XML
    ( 'file'        /* TargetType     */
    , cFileName     /* File           */
    , YES           /* Formatted      */
    , ?             /* Encoding       */
    , ?             /* SchemaLocation */
    , NO            /* WriteSchema    */
    , NO            /* MinSchema      */
    ).

  DELETE OBJECT hExportTt.
END PROCEDURE. /* dumpRecord */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-dynamicDump) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE dynamicDump Procedure
PROCEDURE dynamicDump :
/* Dump the data to a file that is similar to those of Progress self.
  */
  DEFINE INPUT PARAMETER pihBrowse AS HANDLE      NO-UNDO.
  DEFINE INPUT PARAMETER picFile   AS CHARACTER   NO-UNDO.

  DEFINE VARIABLE cTimeStamp AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE hBuffer    AS HANDLE      NO-UNDO EXTENT 5.
  DEFINE VARIABLE hColumn    AS HANDLE      NO-UNDO.
  DEFINE VARIABLE hField     AS HANDLE      NO-UNDO.
  DEFINE VARIABLE hQuery     AS HANDLE      NO-UNDO.
  DEFINE VARIABLE iBack      AS INTEGER     NO-UNDO.
  DEFINE VARIABLE iBuffer    AS INTEGER     NO-UNDO.
  DEFINE VARIABLE iColumn    AS INTEGER     NO-UNDO.
  DEFINE VARIABLE iExtent    AS INTEGER     NO-UNDO.
  DEFINE VARIABLE iRecords   AS INTEGER     NO-UNDO.
  DEFINE VARIABLE iTrailer   AS INTEGER     NO-UNDO.
  DEFINE VARIABLE lFirst     AS LOGICAL     NO-UNDO.

  hQuery = pihBrowse:QUERY.

  /* Accept max 5 buffers for a query */
  DO iBuffer = 1 TO min(5, hQuery:NUM-BUFFERS):
    hBuffer[iBuffer] = hQuery:GET-BUFFER-HANDLE(iBuffer).
  END.

  ASSIGN
    iRecords   = 0
    cTimeStamp = STRING(YEAR( TODAY),"9999":u) + "/":u
              + string(MONTH(TODAY),"99":u  ) + "/":u
              + string(DAY(  TODAY),"99":u  ) + "-":u
              + string(TIME,"HH:MM:SS":u).

  hQuery:GET-FIRST.

  /* Open outputfile */
  OUTPUT to value(picFile) no-echo no-map.
  EXPORT ?.
  iBack = seek(output) - 1.
  SEEK OUTPUT TO 0.

  REPEAT WHILE NOT hQuery:QUERY-OFF-END
  ON STOP UNDO, LEAVE:

    ASSIGN
      iRecords = iRecords + 1
      lFirst   = TRUE
      .

    PROCESS EVENTS.

    browseColumn:
    DO iColumn = 1 TO pihBrowse:NUM-COLUMNS:

      /* Grab the handle */
      hColumn = pihBrowse:GET-BROWSE-COLUMN(iColumn).

      /* Skip invisible columns */
      IF NOT hColumn:VISIBLE THEN NEXT browseColumn.

      /* Find the buffer the column belongs to */
      SearchLoop:
      DO iBuffer = 1 TO 5:
        ASSIGN hField = hBuffer[iBuffer]:BUFFER-FIELD(hColumn:NAME) NO-ERROR.
        IF ERROR-STATUS:ERROR = FALSE
          AND hField <> ? THEN
          LEAVE SearchLoop.
      END.

      /* If no column found, something weird happened */
      IF hField = ? THEN NEXT browseColumn.

      IF hField:DATA-TYPE = "recid":u THEN NEXT browseColumn.

      IF lFirst THEN
        lFirst = FALSE.
      ELSE
      DO:
        SEEK OUTPUT TO seek(output) - iBack.
        PUT CONTROL ' ':u.
      END.

      IF hField:EXTENT > 1 THEN
      DO iExtent = 1 TO hField:EXTENT:
        IF iExtent > 1 THEN
        DO:
          SEEK OUTPUT TO SEEK(OUTPUT) - iBack.
          PUT CONTROL ' ':u.
        END.

        EXPORT hField:BUFFER-VALUE(iExtent).
      END.
      ELSE
        EXPORT hField:BUFFER-VALUE.
    END.

    hQuery:GET-NEXT().
  END.

  /* Add a checksum and nr of records at the end of the file.
  */
  PUT UNFORMATTED ".":u SKIP.
  iTrailer = SEEK(OUTPUT).

  PUT UNFORMATTED
        "PSC":u
    SKIP "filename=":u hBuffer[1]:TABLE
    SKIP "records=":u  STRING(iRecords,"9999999999999":u)
    SKIP "ldbname=":u  hBuffer[1]:DBNAME
    SKIP "timestamp=":u cTimeStamp
    SKIP "numformat=":u ASC(SESSION:NUMERIC-SEPARATOR) ",":u ASC(SESSION:NUMERIC-DECIMAL-POINT)
    SKIP "dateformat=":u SESSION:DATE-FORMAT "-":u SESSION:YEAR-OFFSET
    SKIP "map=NO-MAP":u
    SKIP "cpstream=":u SESSION:CPSTREAM
    SKIP ".":u
    SKIP STRING(iTrailer,"9999999999":u)
    SKIP.

  OUTPUT CLOSE.

END PROCEDURE. /* dynamicDump */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-flushRegistry) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE flushRegistry Procedure
PROCEDURE flushRegistry :
/* Flush all dirty registry settings to disk
*/
  {&timerStart}

  IF glDirtyCache THEN
    RUN saveConfigFileSorted.

  {&timerStop}
END PROCEDURE. /* flushRegistry */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getColumnSort) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE getColumnSort Procedure
PROCEDURE getColumnSort :
/* Return the column nr the browse is sorted on
  */
  DEFINE INPUT  PARAMETER phBrowse    AS HANDLE      NO-UNDO.
  DEFINE OUTPUT PARAMETER pcColumn    AS CHARACTER   NO-UNDO.
  DEFINE OUTPUT PARAMETER plAscending AS LOGICAL     NO-UNDO.

  DEFINE VARIABLE hColumn AS HANDLE      NO-UNDO.
  DEFINE VARIABLE iColumn AS INTEGER     NO-UNDO.

  {&timerStart}

  #BrowseColumns:
  DO iColumn = 1 TO phBrowse:NUM-COLUMNS:
    hColumn = phBrowse:GET-BROWSE-COLUMN(iColumn).
    IF hColumn:SORT-ASCENDING <> ? THEN
    DO:
      ASSIGN
        pcColumn    = hColumn:NAME
        plAscending = hColumn:SORT-ASCENDING
        .
      LEAVE #BrowseColumns.
    END.
  END.

  IF pcColumn = '' THEN
    ASSIGN
      pcColumn    = phBrowse:GET-BROWSE-COLUMN(1):name
      plAscending = TRUE.

  PUBLISH "debugInfo" (3, SUBSTITUTE("Sorting &1 on &2", STRING(plAscending,"up/down"), pcColumn)).

  {&timerStop}

END PROCEDURE. /* getColumnSort */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getDumpFileName) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE getDumpFileName Procedure
PROCEDURE getDumpFileName :
/* Return a file name based on a template
  */
  DEFINE INPUT  PARAMETER pcAction    AS CHARACTER   NO-UNDO.
  DEFINE INPUT  PARAMETER pcDatabase  AS CHARACTER   NO-UNDO.
  DEFINE INPUT  PARAMETER pcTable     AS CHARACTER   NO-UNDO.
  DEFINE INPUT  PARAMETER pcExtension AS CHARACTER   NO-UNDO.
  DEFINE INPUT  PARAMETER pcTemplate  AS CHARACTER   NO-UNDO.
  DEFINE OUTPUT PARAMETER pcFileName  AS CHARACTER   NO-UNDO.

  DEFINE VARIABLE cLastDir      AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cDayOfWeek    AS CHARACTER   NO-UNDO EXTENT 7 INITIAL ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].
  DEFINE VARIABLE cDumpName     AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cDumpDir      AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cBackupDir    AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE hBuffer       AS HANDLE      NO-UNDO.
  DEFINE VARIABLE cUserId       AS CHARACTER   NO-UNDO.

  /* Checks */
  IF LOOKUP(pcAction, "Dump,Create,Update,Delete") = 0 THEN
  DO:
    MESSAGE 'Unknown action' pcAction VIEW-AS ALERT-BOX.
    RETURN.
  END.

  /* If not provided, find the template from the settings,
   * depending on the action we want to perform.
   */
  IF pcTemplate = ? OR pcTemplate = "" THEN
  DO:
    IF pcAction = 'Dump' THEN
      pcFileName = "<DUMPDIR>" + getRegistry("DumpAndLoad", "DumpFileTemplate").
    ELSE
      pcFileName = "<BACKUPDIR>" + getRegistry("DataDigger:Backup", "BackupFileTemplate").
  END.
  ELSE
    pcFileName = pcTemplate.

  IF pcFileName = ? THEN pcFileName = "".

  PUBLISH "debugInfo" (3, SUBSTITUTE("Dump to: &1", pcFileName)).

  /* Dump dir / backup dir / last-used dir from settings */
  cDumpDir = RIGHT-TRIM(getRegistry("DumpAndLoad", "DumpDir"),'/\') + '\'.
  IF cDumpDir = ? OR cDumpDir = '' THEN cDumpDir = "<WORKDIR>dump\".

  cBackupDir  = RIGHT-TRIM(getRegistry("DataDigger:Backup", "BackupDir"),'/\') + '\'.
  IF cBackupDir = ? OR cBackupDir = '' THEN cBackupDir = "<WORKDIR>backup\".

  cLastDir = RIGHT-TRIM(getRegistry("DumpAndLoad", "DumpLastFileName"),'/\').
  cLastDir = SUBSTRING(cLastDir,1,R-INDEX(cLastDir,"\")).
  IF cLastDir = ? THEN cLastDir = "<WORKDIR>dump".
  cLastDir = RIGHT-TRIM(cLastDir,'\').

  /* Find _file for the dump-name */
  CREATE BUFFER hBuffer FOR TABLE SUBSTITUTE('&1._file', pcDatabase) NO-ERROR.
  IF VALID-HANDLE(hBuffer) THEN
  DO:
    hBuffer:FIND-UNIQUE(SUBSTITUTE('where _file-name = &1 and _File._File-Number < 32768', QUOTER(pcTable)),NO-LOCK).
    IF hBuffer:AVAILABLE THEN
      cDumpName = hBuffer::_dump-name.
    ELSE
      cDumpName = pcTable.
  END.
  ELSE
    cDumpName = pcTable.
  IF cDumpName = ? THEN cDumpName = pcTable.

  /* If you have no db connected, userid gives back unknown value
   * which misbehaves in a replace statement */
  cUserId = USERID(LDBNAME(1)).
  IF cUserId = ? THEN cUserId = ''.

  PUBLISH "debugInfo" (3, SUBSTITUTE("DumpDir  : &1", cDumpDir)).
  PUBLISH "debugInfo" (3, SUBSTITUTE("BackupDir: &1", cBackupDir)).
  PUBLISH "debugInfo" (3, SUBSTITUTE("LastDir  : &1", cLastDir)).
  PUBLISH "debugInfo" (3, SUBSTITUTE("DumpName : &1", cDumpName)).

  /* Now resolve all tags */
  pcFileName = REPLACE(pcFileName,"<DUMPDIR>"  , cDumpDir                    ).
  pcFileName = REPLACE(pcFileName,"<BACKUPDIR>", cBackupDir                  ).
  pcFileName = REPLACE(pcFileName,"<LASTDIR>"  , cLastDir                    ).
  pcFileName = REPLACE(pcFileName,"<PROGDIR>"  , getWorkFolder()             ).
  pcFileName = REPLACE(pcFileName,"<WORKDIR>"  , getWorkFolder()             ).

  pcFileName = REPLACE(pcFileName,"<ACTION>"   , pcAction                    ).
  pcFileName = REPLACE(pcFileName,"<USERID>"   , cUserId                     ).
  pcFileName = REPLACE(pcFileName,"<DB>"       , pcDatabase                  ).
  pcFileName = REPLACE(pcFileName,"<TABLE>"    , pcTable                     ).
  pcFileName = REPLACE(pcFileName,"<DUMPNAME>" , cDumpName                   ).
  pcFileName = REPLACE(pcFileName,"<EXT>"      , pcExtension                 ).

  pcFileName = REPLACE(pcFileName,"<TIMESTAMP>", "<YEAR><MONTH><DAY>.<HH><MM><SS>" ).
  pcFileName = REPLACE(pcFileName,"<DATE>"     , "<YEAR>-<MONTH>-<DAY>"      ).
  pcFileName = REPLACE(pcFileName,"<TIME>"     , "<HH>:<MM>:<SS>"            ).
  pcFileName = REPLACE(pcFileName,"<WEEKDAY>"  , STRING(WEEKDAY(TODAY))      ).
  pcFileName = REPLACE(pcFileName,"<DAYNAME>"  , cDayOfWeek[WEEKDAY(today)]  ).

  pcFileName = REPLACE(pcFileName,"<YEAR>"     , STRING(YEAR (TODAY),"9999") ).
  pcFileName = REPLACE(pcFileName,"<MONTH>"    , STRING(MONTH(TODAY),  "99") ).
  pcFileName = REPLACE(pcFileName,"<DAY>"      , STRING(DAY  (TODAY),  "99") ).
  pcFileName = REPLACE(pcFileName,"<HH>"       , ENTRY(1,STRING(TIME,"HH:MM:SS"),":" ) ).
  pcFileName = REPLACE(pcFileName,"<MM>"       , ENTRY(2,STRING(TIME,"HH:MM:SS"),":" ) ).
  pcFileName = REPLACE(pcFileName,"<SS>"       , ENTRY(3,STRING(TIME,"HH:MM:SS"),":" ) ).

  /* Get rid of annoying slashes */
  pcFileName = TRIM(pcFileName,'/\').

  /* Get rid of double slashes (except at the beginning for UNC paths) */
  pcFileName = SUBSTRING(pcFileName,1,1) + REPLACE(SUBSTRING(pcFileName,2),'\\','\').

  /* Sequences */
  pcFileName = resolveSequence(pcFileName).

  /* OS-vars */
  pcFileName = resolveOsVars(pcFileName).

  /* Make lower */
  pcFileName = LC(pcFileName).
  PUBLISH "debugInfo" (3, SUBSTITUTE("Dump to: &1", pcFileName)).

END PROCEDURE. /* getDumpFileName */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getFavourites) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE getFavourites Procedure
PROCEDURE getFavourites :
/* Extract favourites from config table into own tt
*/
  DEFINE OUTPUT PARAMETER TABLE FOR ttFavGroup.

  DEFINE BUFFER bfConfig   FOR ttConfig.
  DEFINE BUFFER btFavGroup FOR ttFavGroup.

  EMPTY TEMP-TABLE ttFavGroup.

  FOR EACH bfConfig
    WHERE bfConfig.cSection = 'DataDigger:Favourites'
      AND bfConfig.cSetting > "":

    CREATE btFavGroup.
    ASSIGN
      btFavGroup.cGroup  = bfConfig.cSetting
      btFavGroup.cTables = bfConfig.cValue.
  END.

  /* If no groups are found, create a default one */
  IF NOT CAN-FIND(FIRST btFavGroup) THEN
  DO:
    CREATE btFavGroup.
    ASSIGN btFavGroup.cGroup = 'MyFavourites'.
  END.

END PROCEDURE. /* getFavourites */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getFields) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE getFields Procedure
PROCEDURE getFields :
/* Fill the fields temp-table
  */
  DEFINE INPUT  PARAMETER pcDatabase  AS CHARACTER   NO-UNDO.
  DEFINE INPUT  PARAMETER pcTableName AS CHARACTER   NO-UNDO.
  DEFINE OUTPUT PARAMETER DATASET FOR dsFields.

  DEFINE VARIABLE cCacheFile         AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cPrimIndexFields   AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cQuery             AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cSelectedFields    AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cUniqueIndexFields AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cSDBName           AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE hBufferField       AS HANDLE      NO-UNDO.
  DEFINE VARIABLE hBufferFile        AS HANDLE      NO-UNDO.
  DEFINE VARIABLE hQuery             AS HANDLE      NO-UNDO.
  DEFINE VARIABLE iFieldExtent       AS INTEGER     NO-UNDO.
  DEFINE VARIABLE iFieldOrder        AS INTEGER     NO-UNDO.
  DEFINE VARIABLE lDataField         AS LOGICAL     NO-UNDO.
  DEFINE VARIABLE iDataOrder         AS INTEGER     NO-UNDO.
  DEFINE VARIABLE i                  AS INTEGER     NO-UNDO.

  DEFINE BUFFER bTable       FOR ttTable.
  DEFINE BUFFER bField       FOR ttField.
  DEFINE BUFFER bColumn      FOR ttColumn.
  DEFINE BUFFER bFieldCache  FOR ttFieldCache.
  DEFINE BUFFER bColumnCache FOR ttColumnCache.
  DEFINE BUFFER bTableFilter FOR ttTableFilter.

  {&timerStart}

  /* Clean up first */
  EMPTY TEMP-TABLE bField.
  EMPTY TEMP-TABLE bColumn.

  /* For dataservers, use the schema name [dataserver] */
  ASSIGN cSDBName = SDBNAME(pcDatabase).

  /* Return if no db connected */
  IF NUM-DBS = 0 THEN RETURN.

  /* caching */
  IF glCacheFieldDefs THEN
  DO:
    /* Find the table. Should exist. */
    FIND bTable WHERE bTable.cDatabase = pcDatabase AND bTable.cTableName = pcTableName NO-ERROR.
    IF NOT AVAILABLE bTable THEN RETURN.

    /* Verify whether the CRC is still the same. If not, kill the cache */
    PUBLISH "DD:Timer" ("start", 'getFields - step 1: verify CRC').
    CREATE BUFFER hBufferFile FOR TABLE cSDBName + "._File".

    hBufferFile:FIND-UNIQUE(SUBSTITUTE('where _file-name = &1 and _File._File-Number < 32768', QUOTER(pcTableName)),NO-LOCK).
    IF hBufferFile::_crc <> bTable.cCrc THEN
    DO:
      /* It seems that it is not possible to refresh the schema cache of the running
       * session. You just have to restart your session.
       */
      PUBLISH "debugInfo" (1, SUBSTITUTE("File CRC changed, kill cache and build new")).
      FOR EACH bFieldCache WHERE bFieldCache.cTableCacheId = bTable.cCacheId:
        DELETE bFieldCache.
      END.
      FOR EACH bColumnCache WHERE bColumnCache.cTableCacheId = bTable.cCacheId:
        DELETE bColumnCache.
      END.

      /* Get a fresh list of tables */
      RUN getTables(INPUT TABLE bTableFilter, OUTPUT TABLE bTable).

      /* Find the table back. Should exist. */
      FIND bTable WHERE bTable.cDatabase = pcDatabase AND bTable.cTableName = pcTableName NO-ERROR.
      IF NOT AVAILABLE bTable THEN RETURN.
    END.
    PUBLISH "DD:Timer" ("stop", 'getFields - step 1: verify CRC').

    /* First look in the memory-cache */
    IF CAN-FIND(FIRST bFieldCache WHERE bFieldCache.cTableCacheId = bTable.cCacheId) THEN
    DO:
      PUBLISH "DD:Timer" ("start", 'getFields - step 2: check memory cache').
      PUBLISH "debugInfo" (3, SUBSTITUTE("Get from memory-cache")).

      FOR EACH bFieldCache WHERE bFieldCache.cTableCacheId = bTable.cCacheId:
        CREATE bField.
        BUFFER-COPY bFieldCache TO bField.
      END.

      FOR EACH bColumnCache WHERE bColumnCache.cTableCacheId = bTable.cCacheId:
        CREATE bColumn.
        BUFFER-COPY bColumnCache TO bColumn.
      END.

      /* Update with settings from registry */
      RUN updateFields(INPUT pcDatabase, INPUT pcTableName, INPUT-OUTPUT TABLE bField).

      PUBLISH "DD:Timer" ("stop", 'getFields - step 2: check memory cache').
      RETURN.
    END.

    /* See if disk cache exists */
    cCacheFile = SUBSTITUTE('&1cache\&2.xml', getWorkFolder(), bTable.cCacheId).
    PUBLISH "debugInfo" (2, SUBSTITUTE("Cachefile: &1", cCacheFile)).

    IF SEARCH(cCacheFile) <> ? THEN
    DO:
      PUBLISH "DD:Timer" ("start", 'getFields - step 3: get from disk cache').
      PUBLISH "debugInfo" (3, SUBSTITUTE("Get from disk cache")).
      DATASET dsFields:READ-XML("file", cCacheFile, "empty", ?, ?, ?, ?).

      /* Add to memory cache, so the next time it's even faster */
      IF TEMP-TABLE bField:HAS-RECORDS THEN
      DO:
        PUBLISH "debugInfo" (3, SUBSTITUTE("Add to first-level cache")).
        FOR EACH bField {&TABLE-SCAN}:
          CREATE bFieldCache.
          BUFFER-COPY bField TO bFieldCache.
        END.

        FOR EACH bColumn {&TABLE-SCAN}:
          CREATE bColumnCache.
          BUFFER-COPY bColumn TO bColumnCache.
        END.
      END.

      /* Update with settings from registry */
      RUN updateFields(INPUT pcDatabase, INPUT pcTableName, INPUT-OUTPUT TABLE bField).

      PUBLISH "DD:Timer" ("stop", 'getFields - step 3: get from disk cache').
      RETURN.
    END.

    PUBLISH "debugInfo" (3, SUBSTITUTE("Not found in any cache, build tables...")).
  END.

  /*
   * If we get here, the table either cannot be found in the cache
   * or caching is disabled. Either way, fill the tt with fields
   */
  PUBLISH "DD:Timer" ("start", 'getFields - step 4: build cache').
  FIND bTable WHERE bTable.cDatabase = pcDatabase AND bTable.cTableName = pcTableName NO-ERROR.
  IF NOT AVAILABLE bTable THEN RETURN.

  CREATE BUFFER hBufferFile  FOR TABLE cSDBName + "._File".
  CREATE BUFFER hBufferField FOR TABLE cSDBName + "._Field".

  CREATE QUERY hQuery.
  hQuery:SET-BUFFERS(hBufferFile,hBufferField).

  cQuery = SUBSTITUTE("FOR EACH &1._File  WHERE &1._file._file-name = '&2' AND _File._File-Number < 32768 NO-LOCK, " +
                      "    EACH &1._Field OF &1._File NO-LOCK BY _ORDER"
                    , cSDBName
                    , pcTableName
                    ).

  hQuery:QUERY-PREPARE(cQuery).
  hQuery:QUERY-OPEN().
  hQuery:GET-FIRST().

  /* Get list of fields in primary index. */
  cPrimIndexFields = getIndexFields(cSDBName, pcTableName, "P").

  /* Get list of fields in all unique indexes. */
  cUniqueIndexFields = getIndexFields(cSDBName, pcTableName, "U").

  /* Get list of all previously selected fields */
  cSelectedFields = getRegistry(SUBSTITUTE("DB:&1",pcDatabase), SUBSTITUTE("&1:Fields",pcTableName)).

  /* If none selected, set mask to 'all' */
  IF cSelectedFields = ? THEN cSelectedFields = '*'.

  REPEAT WHILE NOT hQuery:QUERY-OFF-END:

    CREATE bField.
    ASSIGN
      iFieldOrder          = iFieldOrder + 1
      bField.cTableCacheId = bTable.cCacheId
      bField.cDatabase     = pcDatabase
      bField.cTablename    = pcTableName
      bField.cFieldName    = hBufferField:BUFFER-FIELD('_field-name'):BUFFER-VALUE

      bField.lShow         = CAN-DO(cSelectedFields, hBufferField:BUFFER-FIELD('_field-name'):BUFFER-VALUE)
      bField.iOrder        = iFieldOrder
      bField.iOrderOrg     = iFieldOrder

      bField.cFullName     = hBufferField:BUFFER-FIELD('_field-name'):BUFFER-VALUE
      bField.cDataType     = hBufferField:BUFFER-FIELD('_data-type'):BUFFER-VALUE
      bField.cInitial      = hBufferField:BUFFER-FIELD('_initial'):BUFFER-VALUE
      bField.cFormat       = hBufferField:BUFFER-FIELD('_format'):BUFFER-VALUE
      bField.cFormatOrg    = hBufferField:BUFFER-FIELD('_format'):BUFFER-VALUE
      bField.iWidth        = hBufferField:BUFFER-FIELD('_width'):BUFFER-VALUE
      bField.cLabel        = hBufferField:BUFFER-FIELD('_label'):BUFFER-VALUE
      bField.lPrimary      = CAN-DO(cPrimIndexFields, bField.cFieldName)
      bField.iExtent       = hBufferField:BUFFER-FIELD('_Extent'):BUFFER-VALUE
      bField.lMandatory    = hBufferField:BUFFER-FIELD('_mandatory'):BUFFER-VALUE
      bField.lUniqueIdx    = CAN-DO(cUniqueIndexFields,bField.cFieldName)

      /* New fields as per v19 */
      bField.cColLabel     = hBufferField:BUFFER-FIELD('_Col-label'):BUFFER-VALUE
      bField.iDecimals     = hBufferField:BUFFER-FIELD('_Decimals'):BUFFER-VALUE
      bField.iFieldRpos    = hBufferField:BUFFER-FIELD('_Field-rpos'):BUFFER-VALUE
      bField.cValExp       = hBufferField:BUFFER-FIELD('_ValExp'):BUFFER-VALUE
      bField.cValMsg       = hBufferField:BUFFER-FIELD('_ValMsg'):BUFFER-VALUE
      bField.cHelp         = hBufferField:BUFFER-FIELD('_Help'):BUFFER-VALUE
      bField.cDesc         = hBufferField:BUFFER-FIELD('_Desc'):BUFFER-VALUE
      bField.cViewAs       = hBufferField:BUFFER-FIELD('_View-as'):BUFFER-VALUE
      .
    ASSIGN
      bField.cXmlNodeName  = getXmlNodeName(bField.cFieldName)
      .

    /* Make a list of fields on table level */
    bTable.cFields = bTable.cFields + "," + bField.cFieldName.

    /* Some types should not be shown like CLOB BLOB and RAW */
    lDataField = (LOOKUP(bField.cDataType, 'clob,blob,raw') = 0).

    /* Create TT records for each column to show, except for CLOB / BLOB / RAW */
    IF lDataField = TRUE THEN
    DO iFieldExtent = (IF bField.iExtent = 0 THEN 0 ELSE 1) TO bField.iExtent:

      iDataOrder = iDataOrder + 1.

      CREATE bColumn.
      ASSIGN
        bColumn.cTableCacheId = bTable.cCacheId
        bColumn.cDatabase     = bField.cDatabase
        bColumn.cTableName    = bField.cTablename
        bColumn.cFieldName    = bField.cFieldName
        bColumn.iExtent       = iFieldExtent
        bColumn.cFullName     = bField.cFieldName + (IF iFieldExtent > 0 THEN SUBSTITUTE("[&1]", iFieldExtent) ELSE "")
        bColumn.iColumnNr     = iDataOrder
        bColumn.iOrder        = bField.iOrder
        bColumn.cLabel        = bField.cLabel
        .
      PUBLISH "debugInfo"(3,SUBSTITUTE("Field &1 created", bColumn.cFullName)).
    END. /* For each extent nr */

    hQuery:GET-NEXT().
  END.
  hQuery:QUERY-CLOSE().

  DELETE OBJECT hQuery.
  DELETE OBJECT hBufferField.
  DELETE OBJECT hBufferFile.

  /* Fieldlist */
  bTable.cFields = SUBSTRING(bTable.cFields,2).

  /* Add columns for recid/rowid */
  DO i = 1 TO 2:

    CREATE bField.
    ASSIGN
      iFieldOrder          = iFieldOrder + 1
      bField.cTableCacheId = bTable.cCacheId
      bField.cDatabase     = pcDatabase
      bField.cTablename    = pcTableName
      bField.cFieldName    = ENTRY(i,"RECID,ROWID")
      bField.lShow         = FALSE
      bField.iOrder        = iFieldOrder
      bField.iOrderOrg     = iFieldOrder
      bField.cFieldName    = bField.cFieldName
      bField.cFullName     = bField.cFieldName
      bField.cDataType     = 'character'
      bField.cInitial      = ''
      bField.cFormat       = ENTRY(i,"X(20),X(24)")
      bField.cFormatOrg    = bField.cFormat
      bField.cLabel        = bField.cFieldName
      bField.lPrimary      = NO
      bField.iExtent       = 0
      .

    iDataOrder = iDataOrder + 1.
    CREATE bColumn.
    ASSIGN
      bColumn.cTableCacheId = bField.cTableCacheId
      bColumn.cDatabase     = bField.cDatabase
      bColumn.cTableName    = bField.cTablename
      bColumn.cFieldName    = bField.cFieldName
      bColumn.iExtent       = 0
      bColumn.cFullName     = bField.cFieldName
      bColumn.iColumnNr     = iDataOrder
      bColumn.iOrder        = bField.iOrder
      bColumn.cLabel        = bField.cLabel
      .
  END.
  PUBLISH "DD:Timer" ("stop", 'getFields - step 4: build cache').

  /* Update the cache */
  IF glCacheFieldDefs THEN
  DO:
    /* Add to disk cache */
    PUBLISH "DD:Timer" ("start", 'getFields - step 5: save to disk').
    PUBLISH "debugInfo" (3, SUBSTITUTE("Add to second-level cache.")).
    DATASET dsFields:WRITE-XML( "file", cCacheFile, YES, ?, ?, NO, NO).

    /* Add to memory cache */
    PUBLISH "debugInfo" (3, SUBSTITUTE("Add to first-level cache.")).
    FOR EACH bField {&TABLE-SCAN}:
      CREATE bFieldCache.
      BUFFER-COPY bField TO bFieldCache.
    END.

    FOR EACH bColumn {&TABLE-SCAN}:
      CREATE bColumnCache.
      BUFFER-COPY bColumn TO bColumnCache.
    END.
    PUBLISH "DD:Timer" ("stop", 'getFields - step 5: save to disk').
  END.

  /* Update fields with settings from registry */
  RUN updateFields(INPUT pcDatabase, INPUT pcTableName, INPUT-OUTPUT TABLE bField).

  {&timerStop}

END PROCEDURE. /* getFields */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getMouseXY) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE getMouseXY Procedure
PROCEDURE getMouseXY :
/* Get the position of the mouse relative to the frame
  */
  DEFINE INPUT  PARAMETER phFrame  AS HANDLE  NO-UNDO.
  DEFINE OUTPUT PARAMETER piMouseX AS INTEGER NO-UNDO.
  DEFINE OUTPUT PARAMETER piMouseY AS INTEGER NO-UNDO.

  DEFINE VARIABLE lp   AS MEMPTR NO-UNDO.
  {&_proparse_prolint-nowarn(varusage)}
  DEFINE VARIABLE iRet AS INT64  NO-UNDO.

  SET-SIZE( LP ) = 16.

  {&_proparse_prolint-nowarn(varusage)}
  RUN GetCursorPos(INPUT GET-POINTER-VALUE(lp), OUTPUT iRet).

  RUN ScreenToClient ( INPUT phFrame:HWND, INPUT lp ).
  piMouseX = GET-LONG( lp, 1 ).
  piMouseY = GET-LONG( lp, 5 ).
  SET-SIZE( LP ) = 0.

  PUBLISH "debugInfo" (3, SUBSTITUTE("Mouse X/Y = &1 / &2", piMouseX, piMouseY)).

END PROCEDURE. /* getMouseXY */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getQueryTable) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE getQueryTable Procedure
PROCEDURE getQueryTable :
/* Get the ttQuery table
  * Note: This procedure just returns the table, no further logic needed.
  */
  DEFINE OUTPUT PARAMETER table FOR ttQuery.

END PROCEDURE. /* getQueryTable */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getRegistryTable) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE getRegistryTable Procedure
PROCEDURE getRegistryTable :
/* Return complete registry tt
  */
  DEFINE OUTPUT PARAMETER TABLE FOR ttConfig.

END PROCEDURE. /* getRegistryTable */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getTables) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE getTables Procedure
PROCEDURE getTables :
/* Fill ttTable with all currently connected databases.
  */
  DEFINE INPUT PARAMETER TABLE FOR ttTableFilter.
  DEFINE OUTPUT PARAMETER TABLE FOR ttTable.

  DEFINE VARIABLE cCacheFile       AS CHARACTER  NO-UNDO.
  DEFINE VARIABLE hDbBuffer        AS HANDLE     NO-UNDO.
  DEFINE VARIABLE hDbStatusBuffer  AS HANDLE     NO-UNDO.
  DEFINE VARIABLE hDbQuery         AS HANDLE     NO-UNDO.
  DEFINE VARIABLE iDatabase        AS INTEGER    NO-UNDO.
  DEFINE VARIABLE cCacheTimeStamp  AS CHARACTER  NO-UNDO.
  DEFINE VARIABLE cCacheDir        AS CHARACTER  NO-UNDO.
  DEFINE VARIABLE cSchemaCacheFile AS CHARACTER  NO-UNDO.
  DEFINE VARIABLE cOneCacheFile    AS CHARACTER  NO-UNDO.

  DEFINE BUFFER bTable    FOR ttTable.
  DEFINE BUFFER bTableXml FOR ttTableXml.

  {&timerStart}

  /* Dataserver support can be for:
   *
   * V9:   "PROGRESS,AS400,ORACLE,MSS,ODBC"
   * V10:  "PROGRESS,ORACLE,MSS,ODBC"        (from V10 no native support for AS400)
   * V11:  "PROGRESS,ORACLE,MSS,ODBC"
   * V12:  "PROGRESS,ORACLE,MSS"             (from V12 no ODBC support anymore)
   *
   */
  EMPTY TEMP-TABLE ttTable.
  CREATE WIDGET-POOL "metaInfo".

  #Database:
  DO iDatabase = 1 TO NUM-DBS:
    IF DBTYPE(iDatabase) <> "PROGRESS" THEN NEXT #Database.

    /* Compose name of the cache file. Use date/time of last schema change in the name */
    IF glCacheTableDefs THEN
    DO:
      CREATE BUFFER hDbStatusBuffer FOR TABLE LDBNAME(iDatabase) + "._DbStatus" IN WIDGET-POOL "metaInfo".
      hDbStatusBuffer:FIND-FIRST("",NO-LOCK).

      ASSIGN
        cCacheTimeStamp = REPLACE(REPLACE(hDbStatusBuffer::_dbstatus-cachestamp," ","_"),":","")
        cCacheFile = SUBSTITUTE("&1cache\db.&2.&3.xml", getWorkFolder(), LDBNAME(iDatabase), cCacheTimeStamp ).

      DELETE OBJECT hDbStatusBuffer.
    END.

    /* If caching enabled and there is a cache file, read it */
    IF glCacheTableDefs AND SEARCH(cCacheFile) <> ? THEN
    DO:
      PUBLISH "debugInfo" (3, SUBSTITUTE("Get table list from cache file &1", cCacheFile)).
      TEMP-TABLE ttTable:READ-XML("file", cCacheFile, "APPEND", ?, ?, ?, ?).

      cCacheDir = SUBSTITUTE( "&1cache", getWorkFolder() ).
      INPUT FROM OS-DIR(cCacheDir).
      #ReadSchemaCache:
      REPEAT:
        IMPORT cSchemaCacheFile.

        IF cSchemaCacheFile BEGINS SUBSTITUTE("db.&1;", LDBNAME(iDatabase))
         AND ENTRY(NUM-ENTRIES(cSchemaCacheFile, ".") - 1, cSchemaCacheFile, ".") = ENTRY (NUM-ENTRIES(cCacheFile, ".") - 1, cCacheFile, ".")  /* Check timestamp */
        THEN
        DO:
          cOneCacheFile = SUBSTITUTE( "&1\&2", cCacheDir, cSchemaCacheFile).
          TEMP-TABLE ttTable:READ-XML("file", cOneCacheFile, "APPEND", ?, ?, ?, ?).
        END.
      END.
      INPUT CLOSE.
    END.

    /* Otherwise build it */
    ELSE
    DO:
      CREATE ALIAS 'dictdb' FOR DATABASE VALUE(LDBNAME(iDatabase)).
      RUN getSchema.p(INPUT TABLE ttTable BY-REFERENCE).

      /* Save cache file for next time */
      IF glCacheTableDefs THEN
      DO:
        /* Move the tables of the current db to a separate tt so we can dump it. */
        EMPTY TEMP-TABLE ttTableXml.

        CREATE QUERY hDbQuery IN WIDGET-POOL "metaInfo".
        CREATE BUFFER hDbBuffer FOR TABLE LDBNAME(iDatabase) + "._Db" IN WIDGET-POOL "metaInfo".

        hDbQuery:SET-BUFFERS(hDbBuffer).
        hDbQuery:QUERY-PREPARE("FOR EACH _Db NO-LOCK WHERE _Db._Db-local = TRUE").
        hDbQuery:QUERY-OPEN().

        #DB:
        REPEAT:
          hDbQuery:GET-NEXT().
          IF hDbQuery:QUERY-OFF-END THEN LEAVE #DB.

          FOR EACH bTable
            WHERE bTable.cDatabase = (IF hDbBuffer::_Db-slave THEN hDbBuffer::_Db-name ELSE LDBNAME(iDatabase)):
            CREATE bTableXml.
            BUFFER-COPY bTable TO bTableXml.
          END.
        END.

        hDbQuery:QUERY-CLOSE().
        DELETE OBJECT hDbQuery.
        DELETE OBJECT hDbBuffer.

        TEMP-TABLE ttTableXml:WRITE-XML("file", cCacheFile, YES, ?, ?, NO, NO).
        EMPTY TEMP-TABLE ttTableXml.

        /* Support Dataservers */
        FOR EACH bTable
          WHERE bTable.cSchemaHolder = LDBNAME(iDatabase)
          BREAK BY bTable.cDatabase
                BY bTable.cTableName:

          IF FIRST-OF(bTable.cDatabase) THEN
          DO:
            cCacheFile  = SUBSTITUTE( "&1cache\db.&2;&3.&4.xml"
                                    , getWorkFolder()
                                    , LDBNAME(iDatabase)
                                    , bTable.cDatabase
                                    , cCacheTimeStamp
                                    ).
            EMPTY TEMP-TABLE bTableXml.
          END.

          CREATE bTableXml.
          BUFFER-COPY bTable TO bTableXml.

          IF LAST-OF(bTable.cDatabase) THEN
          DO:
            TEMP-TABLE bTableXml:WRITE-XML("file", cCacheFile, YES, ?, ?, NO, NO).
            EMPTY TEMP-TABLE bTableXml.
          END. /* IF LAST-OF */
        END. /* FOR EACH bTable */
      END. /* IF glCacheTableDefs THEN */
    END. /* tt empty */
  END. /* 1 to num-dbs */

  DELETE WIDGET-POOL "metaInfo".

  /* Apply filter to collection of tables */
  RUN getTablesFiltered(INPUT TABLE ttTableFilter, OUTPUT TABLE ttTable).

  /* Get table properties from the INI file */
  RUN getTableStats(INPUT-OUTPUT TABLE ttTable).

  {&timerStop}

END PROCEDURE. /* getTables */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getTablesFiltered) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE getTablesFiltered Procedure
PROCEDURE getTablesFiltered :
/* Determine whether tables in the ttTable are visible given a user defined filter
  */
  {&timerStart}
  DEFINE INPUT PARAMETER TABLE FOR ttTableFilter.
  DEFINE OUTPUT PARAMETER TABLE FOR ttTable.

  DEFINE VARIABLE cSearchFld  AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cThisField  AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE iSearch     AS INTEGER     NO-UNDO.
  DEFINE VARIABLE iField      AS INTEGER     NO-UNDO.
  DEFINE VARIABLE lRejected   AS LOGICAL     NO-UNDO.
  DEFINE VARIABLE lFieldFound AS LOGICAL     NO-UNDO.
  DEFINE VARIABLE lNormal     AS LOGICAL     NO-UNDO.
  DEFINE VARIABLE lSchema     AS LOGICAL     NO-UNDO.
  DEFINE VARIABLE lVst        AS LOGICAL     NO-UNDO.
  DEFINE VARIABLE lSql        AS LOGICAL     NO-UNDO.
  DEFINE VARIABLE lOther      AS LOGICAL     NO-UNDO.
  DEFINE VARIABLE lHidden     AS LOGICAL     NO-UNDO.
  DEFINE VARIABLE lFrozen     AS LOGICAL     NO-UNDO.
  DEFINE VARIABLE cNameShow   AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cNameHide   AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cFieldShow  AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cFieldHide  AS CHARACTER   NO-UNDO.

  /* This table **SHOULD** exist and have exactly 1 record */
  FIND ttTableFilter NO-ERROR.
  IF NOT AVAILABLE ttTableFilter THEN RETURN.

  ASSIGN
    lNormal    = ttTableFilter.lShowNormal
    lSchema    = ttTableFilter.lShowSchema
    lVst       = ttTableFilter.lShowVst
    lSql       = ttTableFilter.lShowSql
    lOther     = ttTableFilter.lShowOther
    lHidden    = ttTableFilter.lShowHidden
    lFrozen    = ttTableFilter.lShowFrozen
    cNameShow  = ttTableFilter.cTableNameShow
    cNameHide  = ttTableFilter.cTableNameHide
    cFieldShow = ttTableFilter.cTableFieldShow
    cFieldHide = ttTableFilter.cTableFieldHide
    .

  /* Reset the filters to sane values if needed */
  IF cNameShow  = ''  OR cNameShow  = ? THEN cNameShow  = '*'.
  IF cNameHide  = '*' OR cNameHide  = ? THEN cNameHide  = '' .
  IF cFieldShow = '*' OR cFieldShow = ? THEN cFieldShow = ''.
  IF cFieldHide = '*' OR cFieldHide = ? THEN cFieldHide = ''.

  /* Move elements starting with "!" from pos-list to neg-list */
  RUN correctFilterList(INPUT-OUTPUT cNameShow, INPUT-OUTPUT cNameHide).
  RUN correctFilterList(INPUT-OUTPUT cFieldShow, INPUT-OUTPUT cFieldHide).

  #Table:
  FOR EACH ttTable {&TABLE-SCAN}:
    /* Init table to false until proven otherwise */
    ASSIGN ttTable.lShowInList = FALSE.

    /* Check against filter-to-hide */
    IF CAN-DO(cNameHide,ttTable.cTableName) THEN NEXT #Table.

    /* Check against filter-to-show */
    IF NOT CAN-DO(cNameShow,ttTable.cTableName) THEN NEXT #Table.

    /* User tables          : _file-number > 0   AND _file-number < 32000
     * Schema tables        : _file-number > -80 AND _file-number < 0
     * Virtual system tables: _file-number < -16384
     * SQL catalog tables   : _file-name BEGINS "_sys"
     */
    IF NOT lNormal AND ttTable.cCategory = 'Normal' THEN NEXT #Table.
    IF NOT lSchema AND ttTable.cCategory = 'Schema' THEN NEXT #Table.
    IF NOT lVst    AND ttTable.cCategory = 'VST'    THEN NEXT #Table.
    IF NOT lSql    AND ttTable.cCategory = 'SQL'    THEN NEXT #Table.
    IF NOT lOther  AND ttTable.cCategory = 'Other'  THEN NEXT #Table.

    /* Handling for Hidden and Frozen apply only to user tables otherwise it will be too confusing
     * because Schema, VST and SQL tables are all by default hidden and frozen.
     */
    IF NOT lHidden AND ttTable.cCategory = 'Application' AND ttTable.lHidden = TRUE THEN NEXT #Table.
    IF NOT lFrozen AND ttTable.cCategory = 'Application' AND ttTable.lFrozen = TRUE THEN NEXT #Table.

    /* Fields that must be in the list */
    DO iSearch = 1 TO NUM-ENTRIES(cFieldShow):
      cSearchFld = ENTRY(iSearch,cFieldShow).

      /* If no wildcards used, we can simply CAN-DO */
      IF INDEX(cSearchFld,"*") = 0 THEN
      DO:
        IF NOT CAN-DO(ttTable.cFields, cSearchFld) THEN NEXT #Table.
      END.
      ELSE
      DO:
        lFieldFound = FALSE.

        #Field:
        DO iField = 1 TO NUM-ENTRIES(ttTable.cFields):
          cThisField = ENTRY(iField,ttTable.cFields).
          IF CAN-DO(cSearchFld,cThisField) THEN
          DO:
            lFieldFound = TRUE.
            LEAVE #Field.
          END.
        END.
        IF NOT lFieldFound THEN NEXT #Table.
      END.
    END.

    /* Fields that may not be in the list */
    DO iSearch = 1 TO NUM-ENTRIES(cFieldHide):
      cSearchFld = ENTRY(iSearch,cFieldHide).

      /* If no wildcards used, we can simply CAN-DO */
      IF INDEX(cSearchFld,"*") = 0 THEN
      DO:
        IF CAN-DO(ttTable.cFields, cSearchFld) THEN NEXT #Table.
      END.
      ELSE
      DO:
        lRejected = FALSE.
        #Field:
        DO iField = 1 TO NUM-ENTRIES(ttTable.cFields):
          cThisField = ENTRY(iField,ttTable.cFields).
          IF CAN-DO(cSearchFld,cThisField) THEN
          DO:
            lRejected = TRUE.
            LEAVE #Field.
          END.
        END. /* do iField */
        IF lRejected THEN NEXT #Table.
      END. /* else */
    END. /* do iSearch */

    /* If we get here, we should add the table */
    ASSIGN ttTable.lShowInList = TRUE.
  END. /* for each ttTable */

  {&timerStop}
END PROCEDURE. /* getTablesFiltered */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getTableStats) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE getTableStats Procedure
PROCEDURE getTableStats :
/* Get table statistics from the INI file
  */
  DEFINE INPUT-OUTPUT PARAMETER table FOR ttTable.

  DEFINE VARIABLE cIniFile    AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cLine       AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cSection    AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cDatabase   AS CHARACTER   NO-UNDO.

  /* Read the ini file as plain text and parse the lines.
   *
   * The normal way would be to do a FOR-EACH on the _file table and
   * retrieve the information needed. But if you have a large database
   * (or a lot of databases), this becomes VERY slow. Searching the
   * other way around by parsing the INI is a lot faster.
   */
  {&timerStart}

  cIniFile = SUBSTITUTE('&1DataDigger-&2.ini', getWorkFolder(), getUserName() ).
  IF SEARCH(cIniFile) = ? THEN RETURN.

  INPUT FROM VALUE(cIniFile).

  #ReadLine:
  REPEAT:
    /* Sometimes lines get screwed up and are waaaay too long
     * for the import statement. So just ignore those.
     */
    IMPORT UNFORMATTED cLine NO-ERROR.
    IF ERROR-STATUS:ERROR THEN NEXT #ReadLine.

    /* Find DB sections */
    IF cLine MATCHES '[DB:*]' THEN
    DO:
      cSection = TRIM(cLine,'[]').
      cDatabase = ENTRY(2,cSection,":").
    END.

    /* Only process lines of database-sections */
    IF NOT cSection BEGINS "DB:" THEN NEXT #ReadLine.

    /* Only process setting lines */
    IF NOT cLine MATCHES '*:*=*' THEN NEXT #ReadLine.

    /* Filter out some settings */
    IF cLine MATCHES "*:QueriesServed=*" THEN
    DO:
      FIND FIRST ttTable
        WHERE ttTable.cDatabase = cDatabase
          AND ttTable.cTableName = ENTRY(1,cLine,':') NO-ERROR.

      IF AVAILABLE ttTable THEN
      DO:
        ttTable.iNumQueries = INTEGER(ENTRY(2,cLine,'=')) NO-ERROR.
        IF ttTable.iNumQueries = ? THEN ttTable.iNumQueries = 0.
      END.
    END. /* queriesServed */

    ELSE
    IF cLine MATCHES "*:LastUsed=*" THEN
    DO:
      FIND FIRST ttTable
        WHERE ttTable.cDatabase = cDatabase
          AND ttTable.cTableName = ENTRY(1,cLine,':') NO-ERROR.

      IF AVAILABLE ttTable THEN
        ttTable.tLastUsed = DATETIME(ENTRY(2,cLine,'=')) NO-ERROR.

    END. /* lastUsed */

  END. /* repeat */
  INPUT CLOSE.

  {&timerStop}

END PROCEDURE. /* getTableStats */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-initTableFilter) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE initTableFilter Procedure
PROCEDURE initTableFilter :
/* Set table filter values back to their initial values
  */
  DEFINE INPUT-OUTPUT PARAMETER TABLE FOR ttTableFilter.

  EMPTY TEMP-TABLE ttTableFilter.
  CREATE ttTableFilter.

  /* Set visibility of schema tables */
  ttTableFilter.lShowSchema = LOGICAL(getRegistry('DataDigger','ShowHiddenTables')).
  IF ttTableFilter.lShowSchema = ? THEN ttTableFilter.lShowSchema = NO.

END PROCEDURE. /* initTableFilter */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-loadSettings) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE loadSettings Procedure
PROCEDURE loadSettings :
/* Load settings from ini files
*/
  DEFINE VARIABLE lValue AS LOGICAL   NO-UNDO.

  /* Help file is least important, so read that first */
  RUN readConfigFile( SUBSTITUTE("&1DataDiggerHelp.ini", getProgramDir() ), FALSE).

  /* General DD settings (always in program folder) */
  RUN readConfigFile( SUBSTITUTE("&1DataDigger.ini", getProgramDir() ), FALSE).

  /* Per-user settings */
  RUN readConfigFile( SUBSTITUTE("&1DataDigger-&2.ini", getWorkFolder(), getUserName() ), TRUE).

  /* When all ini-files have been read, we can determine whether
   * caching needs to be enabled
   */
  lValue = LOGICAL(getRegistry("DataDigger:Cache","TableDefs")) NO-ERROR.
  IF lValue <> ? THEN ASSIGN glCacheTableDefs = lValue.

END PROCEDURE. /* loadSettings */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-lockWindow) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE lockWindow Procedure
PROCEDURE lockWindow :
/* Lock / unlock updates that Windows does to windows.
  */
  DEFINE INPUT PARAMETER phWindow AS HANDLE  NO-UNDO.
  DEFINE INPUT PARAMETER plLock   AS LOGICAL NO-UNDO.

  {&_proparse_prolint-nowarn(varusage)}
  DEFINE VARIABLE iRet AS INTEGER NO-UNDO.
  DEFINE BUFFER ttWindowLock FOR ttWindowLock.

  {&timerStart}
  PUBLISH "debugInfo" (3, SUBSTITUTE("Window &1, lock: &2", phWindow:TITLE, STRING(plLock,"ON/OFF"))).

  IF NOT VALID-HANDLE(phWindow) THEN RETURN.

  /* Find window in our tt of locked windows */
  FIND ttWindowLock WHERE ttWindowLock.hWindow = phWindow NO-ERROR.
  IF NOT AVAILABLE ttWindowLock THEN
  DO:
    /* If we try to unlock a window thats not in the tt, just go back */
    IF NOT plLock THEN RETURN.

    /* Otherwise create a tt record for it */
    CREATE ttWindowLock.
    ttWindowLock.hWindow = phWindow.
  END.

  /* Because commands to lock or unlock may be nested, keep track
   * of the number of locks/unlocks using a semaphore.
   *
   * The order of commands may be:
   * lockWindow(yes). -> actually lock the window
   * lockWindow(yes). -> do nothing
   * lockWindow(yes). -> do nothing
   * lockWindow(no).  -> do nothing
   * lockWindow(no).  -> do nothing
   * lockWindow(yes). -> do nothing
   * lockWindow(no).  -> do nothing
   * lockWindow(no).  -> actually unlock the window
   */
  IF plLock THEN
    ttWindowLock.iLockCounter = ttWindowLock.iLockCounter + 1.
  ELSE
    ttWindowLock.iLockCounter = ttWindowLock.iLockCounter - 1.

  PUBLISH "debugInfo" (3, SUBSTITUTE("Lock counter: &1", ttWindowLock.iLockCounter)).

  /* Now, only lock when the semaphore is increased to 1 */
  IF plLock AND ttWindowLock.iLockCounter = 1 THEN
  DO:
    {&_proparse_prolint-nowarn(varusage)}
    RUN SendMessageA( phWindow:HWND /* {&window-name}:hwnd */
                    , {&WM_SETREDRAW}
                    , 0
                    , 0
                    , OUTPUT iRet
                    ).
  END.

  /* And only unlock after the last unlock command */
  ELSE IF ttWindowLock.iLockCounter <= 0 THEN
  DO:
    {&_proparse_prolint-nowarn(varusage)}
    RUN SendMessageA( phWindow:HWND /* {&window-name}:hwnd */
                    , {&WM_SETREDRAW}
                    , 1
                    , 0
                    , OUTPUT iRet
                    ).

    {&_proparse_prolint-nowarn(varusage)}
    RUN RedrawWindow( phWindow:HWND /* {&window-name}:hwnd */
                    , 0
                    , 0
                    , {&RDW_ALLCHILDREN} + {&RDW_ERASE} + {&RDW_INVALIDATE}
                    , OUTPUT iRet
                    ).

    /* Don't delete, creating records is more expensive than re-use, so just reset */
    ttWindowLock.iLockCounter = 0.
  END.

  {&timerStop}

END PROCEDURE. /* lockWindow */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-readConfigFile) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE readConfigFile Procedure
PROCEDURE readConfigFile :
/* Read the ini-file and create tt records for it
  */
  DEFINE INPUT PARAMETER pcConfigFile   AS CHARACTER NO-UNDO.
  DEFINE INPUT PARAMETER plUserSettings AS LOGICAL   NO-UNDO.

  DEFINE VARIABLE cFile      AS LONGCHAR    NO-UNDO.
  DEFINE VARIABLE cLine      AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cChunk     AS LONGCHAR    NO-UNDO.
  DEFINE VARIABLE cSection   AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cTrimChars AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE iLine      AS INTEGER     NO-UNDO.

  {&timerStart}
  DEFINE BUFFER bfConfig FOR ttConfig.

  /* Read file in 1 pass to memory */
  IF SEARCH(pcConfigFile) = ? THEN RETURN.
  COPY-LOB FILE pcConfigFile TO cFile NO-CONVERT NO-ERROR.
  IF ERROR-STATUS:ERROR THEN cFile = readFile(pcConfigFile).

  cTrimChars = " " + CHR(1) + "~r". /* space / chr-1 / LF */

  /* Process line by line */
  #LineLoop:
  DO iLine = 1 TO NUM-ENTRIES(cFile,"~n"):

    cChunk = ENTRY(iLine,cFile,"~n").
    cChunk = SUBSTRING(cChunk, 1,20000). /* trim very long lines */
    cLine = TRIM(cChunk, cTrimChars).    /* remove junk */

    /* Section line */
    IF cLine MATCHES "[*]" THEN
    DO:
      cSection = TRIM(cLine,"[]").
      NEXT #LineLoop.
    END.

    /* Ignore weird settings within [DB:xxxx] sections */
    IF cSection BEGINS 'DB:'
      AND NUM-ENTRIES( TRIM(ENTRY(1,cLine,"=")), ':') = 1 THEN NEXT #LineLoop.

    /* Config line */
    FIND bfConfig
      WHERE bfConfig.cSection = cSection
        AND bfConfig.cSetting = TRIM(ENTRY(1,cLine,"=")) NO-ERROR.

    IF NOT AVAILABLE bfConfig THEN
    DO:
      CREATE bfConfig.
      ASSIGN
        bfConfig.cSection = cSection
        bfConfig.cSetting = TRIM(ENTRY(1,cLine,"="))
        .
    END.

    /* Config line /might/ already exist. This can happen if you have
     * the same setting in multiple .ini files.
     */
    ASSIGN
      bfConfig.cValue = TRIM(SUBSTRING(cLine, INDEX(cLine,"=") + 1))
      bfConfig.lUser  = plUserSettings.
  END.

  {&timerStop}
END PROCEDURE. /* readConfigFile */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-resetAnswers) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE resetAnswers Procedure
PROCEDURE resetAnswers :
/* Reset answers to all 'do not ask again' questions
*/
  {&timerStart}
  DEFINE BUFFER bfConfig FOR ttConfig.

  FOR EACH bfConfig
    WHERE bfConfig.cSection = 'DataDigger:Help'
      AND (bfConfig.cSetting MATCHES '*:hidden' OR bfConfig.cSetting MATCHES '*:answer'):
    setRegistry(bfConfig.cSection, bfConfig.cSetting, ?).
  END. /* for each bfConfig */

  RUN flushRegistry.

  {&timerStop}

END PROCEDURE. /* resetAnswers */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-resizeFilterFields) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE resizeFilterFields Procedure
PROCEDURE resizeFilterFields :
/* Redraw the browse filter fields
  */
  DEFINE INPUT PARAMETER phLeadButton   AS HANDLE      NO-UNDO.
  DEFINE INPUT PARAMETER pcFilterFields AS CHARACTER   NO-UNDO.
  DEFINE INPUT PARAMETER pcButtons      AS CHARACTER   NO-UNDO.
  DEFINE INPUT PARAMETER phBrowse       AS HANDLE      NO-UNDO.

  DEFINE VARIABLE iField        AS INTEGER NO-UNDO.
  DEFINE VARIABLE iButton       AS INTEGER NO-UNDO.
  DEFINE VARIABLE iCurrentPos   AS INTEGER NO-UNDO.
  DEFINE VARIABLE iRightEdge    AS INTEGER NO-UNDO.
  DEFINE VARIABLE iWidth        AS INTEGER NO-UNDO.
  DEFINE VARIABLE hColumn       AS HANDLE  NO-UNDO.
  DEFINE VARIABLE hButton       AS HANDLE  NO-UNDO.
  DEFINE VARIABLE hFilterField  AS HANDLE  NO-UNDO.
  DEFINE VARIABLE iFilter       AS INTEGER NO-UNDO.

  {&timerStart}

  /* To prevent drawing error, make all fields small */
  PUBLISH "DD:Timer" ("start", "resizeFilterFields:makeSmall").
  DO iField = 1 TO NUM-ENTRIES(pcFilterFields):
    hFilterField = HANDLE(ENTRY(iField,pcFilterFields)).
    hFilterField:VISIBLE      = NO.
    hFilterField:X            = phBrowse:X.
    hFilterField:Y            = phBrowse:Y - 23.
    hFilterField:WIDTH-PIXELS = 1.
  END.
  PUBLISH "DD:Timer" ("stop", "resizeFilterFields:makeSmall").

  /* Start by setting the buttons at the proper place. Do this right to left */
  PUBLISH "DD:Timer" ("start", "resizeFilterFields:reposition").
  ASSIGN iRightEdge = phBrowse:X + phBrowse:WIDTH-PIXELS.
  DO iButton = NUM-ENTRIES(pcButtons) TO 1 BY -1:
    hButton = HANDLE(ENTRY(iButton,pcButtons)).
    hButton:X = iRightEdge - hButton:WIDTH-PIXELS.
    hButton:Y = phBrowse:Y - 23. /* filter buttons close to the browse */
    iRightEdge = hButton:X + 0. /* A little margin between buttons */
  END.
  PUBLISH "DD:Timer" ("stop", "resizeFilterFields:reposition").

  /* The left side of the left button is the maximum point
   * Fortunately, this value is already in iRightEdge.
   * Resize and reposition the fields from left to right,
   * use the space between browse:x and iRightEdge
   */

  /* Take the left side of the first visible column as a starting point. */
  PUBLISH "DD:Timer" ("start", "resizeFilterFields:firstVisibleColumn").
  firstVisibleColumn:
  DO iField = 1 TO phBrowse:NUM-COLUMNS:
    hColumn = phBrowse:GET-BROWSE-COLUMN(iField):HANDLE.

    IF hColumn:X > 0 AND hColumn:VISIBLE THEN
    DO:
      iCurrentPos = phBrowse:X + hColumn:X.
      LEAVE firstVisibleColumn.
    END.
  END.
  PUBLISH "DD:Timer" ("stop", "resizeFilterFields:firstVisibleColumn").

  PUBLISH "DD:Timer" ("start", "resizeFilterFields:#Field").
  #Field:
  DO iField = 1 TO phBrowse:NUM-COLUMNS:

    hColumn = phBrowse:GET-BROWSE-COLUMN(iField):handle.

    /* Some types cannot have a filter */
    IF hColumn:DATA-TYPE = 'raw' THEN NEXT #Field.

    iFilter = iFilter + 1.
    IF iFilter > NUM-ENTRIES(pcFilterFields) THEN LEAVE #Field.

    /* Determine the handle of the filterfield */
    hFilterField = HANDLE(ENTRY(iFilter, pcFilterFields)).

    /* If the column is hidden, make the filter hidden and go to the next */
    IF NOT hColumn:VISIBLE THEN
    DO:
      hFilterField:VISIBLE = NO.
      NEXT #Field.
    END.

    /* Where *are* we ?? */
    iCurrentPos = phBrowse:X + hColumn:X.

    /* If the columns have been resized, some columns might have fallen off the screen */
    IF hColumn:X < 1 THEN NEXT #Field.

    /* Does it fit on the screen? */
    IF iCurrentPos >= iRightEdge - 5 THEN LEAVE #Field. /* accept some margin */

    /* Where will this field end? And does it fit? */
    iWidth = hColumn:WIDTH-PIXELS + 4.
    IF iCurrentPos + iWidth > iRightEdge THEN iWidth = iRightEdge - iCurrentPos.

    /* Ok, seems to fit */
    hFilterField:X            = iCurrentPos.
    hFilterField:WIDTH-PIXELS = iWidth.
    iCurrentPos               = iCurrentPos + iWidth.
    hFilterField:VISIBLE      = phBrowse:VISIBLE. /* take over the visibility of the browse */
  END.
  PUBLISH "DD:Timer" ("stop", "resizeFilterFields:#Field").

  /* Place lead-button at the utmost left */
  IF VALID-HANDLE(phLeadButton) THEN
    ASSIGN
      phLeadButton:X = phBrowse:X
      phLeadButton:Y = phBrowse:Y - 23.

  {&timerStop}

END PROCEDURE. /* resizeFilterFields */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-restoreWindowPos) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE restoreWindowPos Procedure
PROCEDURE restoreWindowPos :
/* Restore position / size of a window
  */
  DEFINE INPUT PARAMETER phWindow     AS HANDLE      NO-UNDO.
  DEFINE INPUT PARAMETER pcWindowName AS CHARACTER   NO-UNDO.

  DEFINE VARIABLE iValue AS INTEGER     NO-UNDO.

  iValue = INTEGER(getRegistry(pcWindowName, 'Window:x' )).
  IF iValue = ? THEN iValue = INTEGER(getRegistry('DataDigger', 'Window:x' )) + 50.
  ASSIGN phWindow:X = iValue NO-ERROR.

  iValue = INTEGER(getRegistry(pcWindowName, 'Window:y' )).
  IF iValue = ? THEN iValue = INTEGER(getRegistry('DataDigger', 'Window:y' )) + 50.
  IF iValue <> ? THEN ASSIGN phWindow:Y = iValue NO-ERROR.

  iValue = INTEGER(getRegistry(pcWindowName, 'Window:height' )).
  IF iValue = ? OR iValue = 0 THEN iValue = INTEGER(getRegistry('DataDigger', 'Window:height' )) - 100.
  ASSIGN phWindow:HEIGHT-PIXELS = iValue NO-ERROR.

  iValue = INTEGER(getRegistry(pcWindowName, 'Window:width' )).
  IF iValue = ? OR iValue = 0 THEN iValue = INTEGER(getRegistry('DataDigger', 'Window:width' )) - 100.
  ASSIGN phWindow:WIDTH-PIXELS = iValue NO-ERROR.

  /* Force a redraw */
  APPLY 'window-resized' TO phWindow.

END PROCEDURE. /* restoreWindowPos */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-saveConfigFileSorted) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE saveConfigFileSorted Procedure
PROCEDURE saveConfigFileSorted :
/* Save settings file sorted
  */
  DEFINE VARIABLE cUserConfigFile AS CHARACTER NO-UNDO.
  DEFINE BUFFER bfConfig FOR ttConfig.

  {&timerStart}

  /* Clean up rubbish settings data */
  FOR EACH bfConfig
    WHERE bfConfig.cSetting = '' OR bfConfig.cSetting = ?
       OR bfConfig.cValue   = '' OR bfConfig.cValue   = ?:
    DELETE bfConfig.
  END.

  cUserConfigFile = SUBSTITUTE("&1DataDigger-&2.ini", getWorkFolder(), getUserName() ).
  OUTPUT TO VALUE(cUserConfigFile).

  FOR EACH bfConfig
    WHERE bfConfig.lUser = TRUE
    BREAK BY (bfConfig.cSection BEGINS "DataDigger") DESCENDING
          BY bfConfig.cSection
          BY bfConfig.cSetting:

    IF FIRST-OF(bfConfig.cSection) THEN PUT UNFORMATTED SUBSTITUTE("[&1]",bfConfig.cSection) SKIP.
    PUT UNFORMATTED SUBSTITUTE("&1=&2",bfConfig.cSetting, bfConfig.cValue) SKIP.
    IF LAST-OF(bfConfig.cSection) THEN PUT UNFORMATTED SKIP(1).
  END.

  OUTPUT CLOSE.
  glDirtyCache = FALSE.

  {&timerStop}
END PROCEDURE. /* saveConfigFileSorted */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-saveQuery) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE saveQuery Procedure
PROCEDURE saveQuery :
/* Save a single query to the INI file.
  */
  DEFINE INPUT  PARAMETER pcDatabase     AS CHARACTER   NO-UNDO.
  DEFINE INPUT  PARAMETER pcTable        AS CHARACTER   NO-UNDO.
  DEFINE INPUT  PARAMETER pcQuery        AS CHARACTER   NO-UNDO.

  DEFINE VARIABLE cQuery AS CHARACTER NO-UNDO.
  DEFINE VARIABLE iNewNr AS INTEGER   NO-UNDO.

  DEFINE BUFFER bQuery FOR ttQuery.

  {&timerStart}

  /* Prepare query for saving in ini-file */
  cQuery = pcQuery.
  cQuery = REPLACE(cQuery,'~n',CHR(1)).
  cQuery = REPLACE(cQuery,{&QUERYSEP},CHR(1)).
  IF cQuery = '' THEN RETURN.

  /* Get the table with queries again, because they might be
   * changed if the user has more than one window open.
   */
  RUN collectQueryInfo(pcDatabase, pcTable).

  /* Save current query in the tt. If it already is in the
   * TT then just move it to the top
   */
  FIND bQuery
    WHERE bQuery.cDatabase = pcDatabase
      AND bQuery.cTable    = pcTable
      AND bQuery.cQueryTxt = cQuery NO-ERROR.

  IF AVAILABLE bQuery THEN
  DO:
    ASSIGN bQuery.iQueryNr = 0.
  END.
  ELSE
  DO:
    CREATE bQuery.
    ASSIGN bQuery.cDatabase = pcDatabase
          bQuery.cTable    = pcTable
          bQuery.iQueryNr  = 0
          bQuery.cQueryTxt = cQuery.
  END.

  /* The ttQuery temp-table is already filled, renumber it */
  #QueryLoop:
  REPEAT PRESELECT EACH bQuery
    WHERE bQuery.cDatabase = pcDatabase
      AND bQuery.cTable    = pcTable
      BY bQuery.iQueryNr:

    FIND NEXT bQuery NO-ERROR.
    IF NOT AVAILABLE bQuery THEN LEAVE #QueryLoop.
    ASSIGN
      iNewNr          = iNewNr + 1
      bQuery.iQueryNr = iNewNr.
  END.

  /* And save it to the INI-file */
  RUN saveQueryTable(table bQuery, pcDatabase, pcTable).

  {&timerStop}
END PROCEDURE. /* saveQuery */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-saveQueryTable) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE saveQueryTable Procedure
PROCEDURE saveQueryTable :
/* Save the queries in the TT to the INI file with a max of MaxQueryHistory
  */
  DEFINE INPUT  PARAMETER table FOR ttQuery.
  DEFINE INPUT  PARAMETER pcDatabase     AS CHARACTER   NO-UNDO.
  DEFINE INPUT  PARAMETER pcTable        AS CHARACTER   NO-UNDO.

  DEFINE VARIABLE iMaxQueryHistory AS INTEGER NO-UNDO.
  DEFINE VARIABLE iQuery           AS INTEGER NO-UNDO.
  DEFINE VARIABLE cSetting         AS CHARACTER NO-UNDO.

  DEFINE BUFFER bQuery FOR ttQuery.

  {&timerStart}

  iMaxQueryHistory = INTEGER(getRegistry("DataDigger", "MaxQueryHistory" )).
  IF iMaxQueryHistory = 0 THEN RETURN. /* no query history wanted */

  /* If it is not defined use default setting */
  IF iMaxQueryHistory = ? THEN iMaxQueryHistory = 10.

  iQuery = 1.

  #SaveQuery:
  FOR EACH bQuery
    WHERE bQuery.cDatabase = pcDatabase
      AND bQuery.cTable    = pcTable
      BY bQuery.iQueryNr:

    cSetting = bQuery.cQueryTxt.
    IF cSetting = '' THEN NEXT #SaveQuery.

    setRegistry( SUBSTITUTE("DB:&1", pcDatabase)
              , SUBSTITUTE('&1:query:&2', pcTable, iQuery)
              , cSetting).
    iQuery = iQuery + 1.
    IF iQuery > iMaxQueryHistory THEN LEAVE #SaveQuery.
  END.

  /* Delete higher nrs than MaxQueryHistory */
  DO WHILE iQuery <= iMaxQueryHistory:

    setRegistry( SUBSTITUTE("DB:&1", pcDatabase)
              , SUBSTITUTE('&1:query:&2', pcTable, iQuery)
              , ?).
    iQuery = iQuery + 1.
  END. /* iQuery .. MaxQueryHistory */

  {&timerStop}
END PROCEDURE. /* saveQueryTable */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-saveWindowPos) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE saveWindowPos Procedure
PROCEDURE saveWindowPos :
/* Save position / size of a window
  */
  DEFINE INPUT PARAMETER phWindow     AS HANDLE      NO-UNDO.
  DEFINE INPUT PARAMETER pcWindowName AS CHARACTER   NO-UNDO.

  setRegistry(pcWindowName, "Window:x"     , STRING(phWindow:X) ).
  setRegistry(pcWindowName, "Window:y"     , STRING(phWindow:Y) ).
  setRegistry(pcWindowName, "Window:height", STRING(phWindow:HEIGHT-PIXELS) ).
  setRegistry(pcWindowName, "Window:width" , STRING(phWindow:WIDTH-PIXELS) ).

END PROCEDURE. /* saveWindowPos */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-setCaching) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE setCaching Procedure
PROCEDURE setCaching :
/* Set the cache vars for the library
  */
  glCacheTableDefs = LOGICAL( getRegistry("DataDigger:Cache","TableDefs") ).
  glCacheFieldDefs = LOGICAL( getRegistry("DataDigger:Cache","FieldDefs") ).

END PROCEDURE. /* setCaching */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-setFavourite) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE setFavourite Procedure
PROCEDURE setFavourite :
/* Set / unset / toggle a table as favourite
*/
  DEFINE INPUT PARAMETER pcTable     AS CHARACTER NO-UNDO.
  DEFINE INPUT PARAMETER pcGroupName AS CHARACTER NO-UNDO.
  DEFINE INPUT PARAMETER plFavourite AS LOGICAL   NO-UNDO.

  DEFINE VARIABLE i     AS INTEGER   NO-UNDO.
  DEFINE VARIABLE cList AS CHARACTER NO-UNDO.

  cList = getRegistry("DataDigger:Favourites", pcGroupName).
  IF cList = ? THEN cList = ''.
  i = LOOKUP(pcTable, cList).

  /* Toggle setting? */
  IF plFavourite = ? THEN plFavourite = (i = 0).

  /* Add to favourites */
  IF NOT plFavourite AND i > 0 THEN
  DO:
    ENTRY(i, cList) = ''.
    cList = REPLACE(cList,',,',',').
    cList = TRIM(cList,',').
  END.

  /* Remove from favourites */
  IF plFavourite AND i = 0 THEN
    cList = TRIM(SUBSTITUTE('&1,&2', cList, pcTable),',').

  setRegistry("DataDigger:Favourites", pcGroupName, cList).

END PROCEDURE. /* setFavourite */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-setLabelPosition) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE setLabelPosition Procedure
PROCEDURE setLabelPosition :
/* Correct the position of the label for larger fonts
  */
  DEFINE INPUT PARAMETER phWidget AS HANDLE NO-UNDO.

  /* Move horizontally far enough from the widget */
  phWidget:SIDE-LABEL-HANDLE:X = phWidget:X
    - FONT-TABLE:GET-TEXT-WIDTH-PIXELS(phWidget:SIDE-LABEL-HANDLE:SCREEN-VALUE, phWidget:FRAME:FONT)
    - (IF phWidget:TYPE = 'fill-in' THEN 5 ELSE 0)
    .

END PROCEDURE. /* setLabelPosition */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-setSortArrow) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE setSortArrow Procedure
PROCEDURE setSortArrow :
/* Set the sorting arrow on a browse
  */
  DEFINE INPUT PARAMETER phBrowse    AS HANDLE    NO-UNDO.
  DEFINE INPUT PARAMETER pcSortField AS CHARACTER NO-UNDO.
  DEFINE INPUT PARAMETER plAscending AS LOGICAL   NO-UNDO.

  DEFINE VARIABLE iColumn    AS INTEGER   NO-UNDO.
  DEFINE VARIABLE hColumn    AS HANDLE    NO-UNDO.
  DEFINE VARIABLE lSortFound AS LOGICAL   NO-UNDO.

  {&timerStart}

  DO iColumn = 1 TO phBrowse:NUM-COLUMNS:
    hColumn = phBrowse:GET-BROWSE-COLUMN(iColumn).

    /* If you apply the sort to the same column, the order
     * of sorting is inverted.
     */
    IF hColumn:NAME = pcSortField THEN
    DO:
      phBrowse:SET-SORT-ARROW(iColumn, plAscending ).
      lSortFound = TRUE.

      /* Setting is one of: ColumnSortFields | ColumnSortIndexes | ColumnSortTables */
      setRegistry( 'DataDigger'
                , SUBSTITUTE('ColumnSort&1', SUBSTRING(phBrowse:NAME,3))
                , SUBSTITUTE('&1,&2',iColumn, plAscending)
                ).
    END.
    ELSE
      phBrowse:SET-SORT-ARROW(iColumn, ? ). /* erase existing arrow */
  END.

  /* If no sort is found, delete setting */
  IF NOT lSortFound THEN
    setRegistry( 'DataDigger', SUBSTITUTE('ColumnSort&1', SUBSTRING(phBrowse:NAME,3)), ?).

  {&timerStop}

END PROCEDURE. /* setSortArrow */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-setTransparency) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE setTransparency Procedure
PROCEDURE setTransparency :
/* Set transparency level for a frame, using Windows api
  */
  DEFINE INPUT  PARAMETER phFrame AS HANDLE     NO-UNDO.
  DEFINE INPUT  PARAMETER piLevel AS INTEGER    NO-UNDO.

  &SCOPED-DEFINE GWL_EXSTYLE         -20
  &SCOPED-DEFINE WS_EX_LAYERED       524288
  &SCOPED-DEFINE LWA_ALPHA           2
  &SCOPED-DEFINE WS_EX_TRANSPARENT   32

  {&_proparse_prolint-nowarn(varusage)}
  DEFINE VARIABLE stat AS INTEGER    NO-UNDO.

  /* Set WS_EX_LAYERED on this window  */
  {&_proparse_prolint-nowarn(varusage)}
  RUN SetWindowLongA(phFrame:HWND, {&GWL_EXSTYLE}, {&WS_EX_LAYERED}, OUTPUT stat).

  /* Make this window transparent (0 - 255) */
  {&_proparse_prolint-nowarn(varusage)}
  RUN SetLayeredWindowAttributes(phFrame:HWND, 0, piLevel, {&LWA_ALPHA}, OUTPUT stat).

END PROCEDURE. /* setTransparency */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-setXmlNodeNames) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE setXmlNodeNames Procedure
PROCEDURE setXmlNodeNames :
/* Set the XML-NODE-NAMES of all fields in a buffer
  */
  DEFINE INPUT PARAMETER phTable AS HANDLE NO-UNDO.
  DEFINE VARIABLE iField AS INTEGER NO-UNDO.

  DO iField = 1 TO phTable:NUM-FIELDS:
    phTable:BUFFER-FIELD(iField):XML-NODE-NAME = getXmlNodeName(phTable:BUFFER-FIELD(iField):NAME).
  END.

END PROCEDURE. /* setXmlNodeNames */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-showHelp) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE showHelp Procedure
PROCEDURE showHelp :
/* Show a help message and save answer to ini
  */
  DEFINE INPUT PARAMETER pcTopic   AS CHARACTER   NO-UNDO.
  DEFINE INPUT PARAMETER pcStrings AS CHARACTER   NO-UNDO.

  DEFINE VARIABLE cButtons       AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cMessage       AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cPrg           AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cTitle         AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cType          AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cUrl           AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cCanHide       AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE iButtonPressed AS INTEGER     NO-UNDO.
  DEFINE VARIABLE lDontShowAgain AS LOGICAL     NO-UNDO.
  DEFINE VARIABLE lCanHide       AS LOGICAL     NO-UNDO.
  DEFINE VARIABLE lHidden        AS LOGICAL     NO-UNDO.
  DEFINE VARIABLE iString        AS INTEGER     NO-UNDO.
  DEFINE VARIABLE cUserString    AS CHARACTER   NO-UNDO EXTENT 9.

  /* If no message, then just return */
  cMessage = getRegistry('DataDigger:Help', pcTopic + ':message').

  /* What to start? */
  cUrl = getRegistry('DataDigger:Help', pcTopic + ':url').
  cPrg = getRegistry('DataDigger:Help', pcTopic + ':program').
  cCanHide = getRegistry('DataDigger:Help', pcTopic + ':canHide').
  cCanHide = TRIM(cCanHide).
  lCanHide = LOGICAL(cCanHide) NO-ERROR.
  IF lCanHide = ? THEN lCanHide = TRUE.

  IF cMessage = ? THEN
  DO:
    IF cUrl = ? AND cPrg = ? THEN RETURN.
    lHidden        = YES. /* suppress empty text window */
    iButtonPressed = 1.   /* forces to start the url or prog */
  END.

  /* If type is unknown, set to QUESTION if there is a question mark in the message */
  cType    = getRegistry('DataDigger:Help', pcTopic + ':type').
  IF cType = ? THEN cType = (IF cMessage MATCHES '*?*' THEN 'Question' ELSE 'Message').

  /* If no button labels defined, set them based on message type */
  cButtons = getRegistry('DataDigger:Help', pcTopic + ':buttons').
  IF cButtons = ? THEN cButtons = (IF cType = 'Question' THEN '&Yes,&No,&Cancel' ELSE '&Ok').

  /* If title is empty, set it to the type of the message */
  cTitle   = getRegistry('DataDigger:Help', pcTopic + ':title').
  IF cTitle = ? THEN cTitle = cType.

  /* If hidden has strange value, set it to NO */
  lHidden = LOGICAL(getRegistry('DataDigger:Help', pcTopic + ':hidden')) NO-ERROR.
  IF lHidden = ? THEN lHidden = NO.

  /* If ButtonPressed has strange value, set hidden to NO */
  iButtonPressed = INTEGER( getRegistry('DataDigger:Help',pcTopic + ':answer') ) NO-ERROR.
  IF iButtonPressed = ? THEN lHidden = NO.

  /* if we have no message, but we do have an URL or prog, then
   * dont show an empty message box.
   */
  IF cMessage = ? THEN
    ASSIGN
      lHidden        = YES /* suppress empty text window */
      iButtonPressed = 1.   /* forces to start the url or prog */

  /* Fill in strings in message */
  DO iString = 1 TO NUM-ENTRIES(pcStrings):
    cUserString[iString] = ENTRY(iString,pcStrings).
  END.

  cMessage = SUBSTITUTE( cMessage
                      , cUserString[1]
                      , cUserString[2]
                      , cUserString[3]
                      , cUserString[4]
                      , cUserString[5]
                      , cUserString[6]
                      , cUserString[7]
                      , cUserString[8]
                      , cUserString[9]
                      ).

  /* If not hidden, show the message and let the user choose an answer */
  IF NOT lHidden THEN
  DO:
    RUN VALUE( getProgramDir() + 'dQuestion.w')
      ( INPUT cTitle
      , INPUT cMessage
      , INPUT cButtons
      , INPUT lCanHide
      , OUTPUT iButtonPressed
      , OUTPUT lDontShowAgain
      ).

    IF lDontShowAgain THEN
      setRegistry('DataDigger:Help', pcTopic + ':hidden', 'yes').
  END.

  /* Start external things if needed */
  IF iButtonPressed = 1 THEN
  DO:
    IF cUrl <> ? THEN OS-COMMAND NO-WAIT START (cUrl).
    IF cPrg <> ? THEN RUN VALUE(cPrg) NO-ERROR.
  END.

  /* Save answer */
  setRegistry('DataDigger:Help',pcTopic + ':answer', STRING(iButtonPressed)).

END PROCEDURE. /* showHelp */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-showScrollbars) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE showScrollbars Procedure
PROCEDURE showScrollbars :
/* Hide or show scrollbars the hard way
  */
  DEFINE INPUT PARAMETER ip-Frame      AS HANDLE  NO-UNDO.
  DEFINE INPUT PARAMETER ip-horizontal AS LOGICAL NO-UNDO.
  DEFINE INPUT PARAMETER ip-vertical   AS LOGICAL NO-UNDO.

  {&_proparse_prolint-nowarn(varusage)}
  DEFINE VARIABLE iv-retint AS INTEGER NO-UNDO.

  {&timerStart}

  IF NOT VALID-HANDLE(ip-Frame) OR ip-Frame:HWND = ? THEN RETURN.

  &scoped-define SB_HORZ 0
  &scoped-define SB_VERT 1
  &scoped-define SB_BOTH 3
  &scoped-define SB_THUMBPOSITION 4

  {&_proparse_prolint-nowarn(varusage)}
  RUN ShowScrollBar ( ip-Frame:HWND,
                      {&SB_HORZ},
                      IF ip-horizontal THEN -1 ELSE 0,
                      OUTPUT iv-retint ).

  {&_proparse_prolint-nowarn(varusage)}
  RUN ShowScrollBar ( ip-Frame:HWND,
                      {&SB_VERT},
                      IF ip-vertical  THEN -1 ELSE 0,
                      OUTPUT iv-retint ).

  &undefine SB_HORZ
  &undefine SB_VERT
  &undefine SB_BOTH
  &undefine SB_THUMBPOSITION

  {&timerStop}
END PROCEDURE. /* ShowScrollbars */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-unlockWindow) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE unlockWindow Procedure
PROCEDURE unlockWindow :
/* Force a window to unlock
  */
  DEFINE INPUT PARAMETER phWindow AS HANDLE  NO-UNDO.

  {&_proparse_prolint-nowarn(varusage)}
  DEFINE VARIABLE iRet AS INTEGER NO-UNDO.
  DEFINE BUFFER ttWindowLock FOR ttWindowLock.

  PUBLISH "debugInfo" (3, SUBSTITUTE("Window &1, force to unlock", phWindow:TITLE)).

  /* Find window in our tt of locked windows */
  FIND ttWindowLock WHERE ttWindowLock.hWindow = phWindow NO-ERROR.
  IF NOT AVAILABLE ttWindowLock THEN RETURN.

  IF ttWindowLock.iLockCounter > 0 THEN
  DO:
    {&_proparse_prolint-nowarn(varusage)}
    RUN SendMessageA(phWindow:HWND, {&WM_SETREDRAW}, 1, 0, OUTPUT iRet).

    {&_proparse_prolint-nowarn(varusage)}
    RUN RedrawWindow(phWindow:HWND, 0, 0, {&RDW_ALLCHILDREN} + {&RDW_ERASE} + {&RDW_INVALIDATE}, OUTPUT iRet).

    DELETE ttWindowLock.
  END.

END PROCEDURE. /* unlockWindow */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-updateFields) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE updateFields Procedure
PROCEDURE updateFields :
/* Update the fields temp-table with settings from registry
  */
  DEFINE INPUT PARAMETER pcDatabase    AS CHARACTER   NO-UNDO.
  DEFINE INPUT PARAMETER pcTableName   AS CHARACTER   NO-UNDO.
  DEFINE INPUT-OUTPUT PARAMETER TABLE FOR ttField.

  DEFINE VARIABLE cCustomFormat      AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cSelectedFields    AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cFieldOrder        AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE iColumnOrder       AS INTEGER     NO-UNDO.
  DEFINE VARIABLE iFieldOrder        AS INTEGER     NO-UNDO.
  DEFINE VARIABLE iMaxExtent         AS INTEGER     NO-UNDO.
  DEFINE VARIABLE lRecRowAtEnd       AS LOGICAL     NO-UNDO.

  DEFINE BUFFER bField FOR ttField.
  DEFINE BUFFER bColumn FOR ttColumn.

  {&timerStart}
  PUBLISH "debugInfo" (1, SUBSTITUTE("Update field definitions for &1.&2", pcDatabase, pcTableName)).

  /* Get list of all previously selected fields */
  cSelectedFields = getRegistry(SUBSTITUTE("DB:&1",pcDatabase), SUBSTITUTE("&1:fields",pcTableName)).
  IF cSelectedFields = ? THEN cSelectedFields = '!RECID,!ROWID,*'.

  /* Get field ordering */
  cFieldOrder = getRegistry(SUBSTITUTE('DB:&1',pcDatabase), SUBSTITUTE('&1:fieldOrder',pcTableName)).

  /* RECID and ROWID at the end? */
  IF cFieldOrder <> ? THEN
  DO:
    lRecRowAtEnd = LOOKUP("ROWID", cFieldOrder) > NUM-ENTRIES(cFieldOrder) - 2 AND LOOKUP("RECID", cFieldOrder) > NUM-ENTRIES(cFieldOrder) - 2.
    PUBLISH "debugInfo" (2, SUBSTITUTE("Field order for table &1: &2", pcTableName, cFieldOrder)).
    PUBLISH "debugInfo" (3, SUBSTITUTE("Rowid/recid at the end for table &1: &2", pcTableName, lRecRowAtEnd)).
  END.

  FOR EACH bField {&TABLE-SCAN}:

    /* Due to a bug the nr of decimals may be set on non-decimal fields
     * See PKB P185263 (article 18087) for more information
     * http://knowledgebase.progress.com/articles/Article/P185263
     */
    IF bField.cDataType <> 'DECIMAL' THEN bField.iDecimals = ?.

    /* Was this field selected? */
    bField.lShow = CAN-DO(cSelectedFields, bField.cFullName).

    /* Customization option for the user to show/hide certain fields */
    PUBLISH "DD:Timer" ("start", 'customShowField').
    PUBLISH 'customShowField' (pcDatabase, pcTableName, bField.cFieldName, INPUT-OUTPUT bField.lShow).
    PUBLISH "DD:Timer" ("stop", 'customShowField').

    /* Customization option for the user to adjust the format */
    PUBLISH "DD:Timer" ("start", 'customFormat').
    PUBLISH 'customFormat' (pcDatabase, pcTableName, bField.cFieldName, bField.cDatatype, INPUT-OUTPUT bField.cFormat).
    PUBLISH "DD:Timer" ("stop", 'customFormat').

    /* Restore changed field format. */
    cCustomFormat = getRegistry(SUBSTITUTE("DB:&1",pcDatabase), SUBSTITUTE("&1.&2:format",pcTableName,bField.cFieldName) ).
    IF cCustomFormat <> ? THEN bField.cFormat = cCustomFormat.

    /* Restore changed field order. */
    bField.iOrder = LOOKUP(bField.cFullName,cFieldOrder).
    IF bField.iOrder = ? THEN bField.iOrder = bField.iOrderOrg.

    /* Keep track of highest nr */
    iFieldOrder = MAXIMUM(iFieldOrder,bField.iOrder).

  END. /* f/e bField */

  /* Only show first X of an extent */
  iMaxExtent = INTEGER(getRegistry("DataDigger","MaxExtent")) NO-ERROR.
  IF iMaxExtent = ? THEN iMaxExtent = 100.
  IF iMaxExtent > 0 THEN
  FOR EACH bColumn WHERE bColumn.iExtent > iMaxExtent:
    DELETE bColumn.
  END.

  IF CAN-FIND(FIRST bField WHERE bField.iOrder = 0) THEN
  DO:
    /* Set new fields (no order assigned) at the end */
    FOR EACH bField WHERE bField.iOrder = 0 BY bField.iFieldRpos:
      ASSIGN
        iFieldOrder   = iFieldOrder + 1
        bField.iOrder = iFieldOrder.
    END.

    /* If RECID+ROWID should be at the end then re-assign them */
    IF lRecRowAtEnd THEN
    FOR EACH bField
      WHERE bField.cFieldName = "RECID" OR bField.cFieldName = "ROWID" BY bField.iOrder:
      ASSIGN
        iFieldOrder   = iFieldOrder + 1
        bField.iOrder = iFieldOrder.
    END.
  END.

  /* Reorder fields to get rid of gaps */
  iFieldOrder = 0.
  #FieldLoop:
  REPEAT PRESELECT EACH bField BY bField.iOrder:
    FIND NEXT bField NO-ERROR.
    IF NOT AVAILABLE bField THEN LEAVE #FieldLoop.
    ASSIGN
      iFieldOrder   = iFieldOrder + 1
      bField.iOrder = iFieldOrder.
  END.

  /* Assign order nrs to columns to handle extents */
  iColumnOrder = 0.
  FOR EACH bField BY bField.iOrder:
    FOR EACH bColumn WHERE bColumn.cFieldName =  bField.cFieldName BY bColumn.cFieldName:
      iColumnOrder = iColumnOrder + 1.
      bColumn.iColumnNr = iColumnOrder.
    END.
  END.

  {&timerStop}
END PROCEDURE. /* updateFields */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-updateMemoryCache) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _PROCEDURE updateMemoryCache Procedure
PROCEDURE updateMemoryCache :
/* Update the memory cache with current settings
  */
  DEFINE INPUT PARAMETER pcDatabase  AS CHARACTER NO-UNDO.
  DEFINE INPUT PARAMETER pcTableName AS CHARACTER NO-UNDO.
  DEFINE INPUT PARAMETER TABLE FOR ttField.
  DEFINE INPUT PARAMETER TABLE FOR ttColumn.

  DEFINE BUFFER bField  FOR ttField.
  DEFINE BUFFER bColumn FOR ttColumn.
  DEFINE BUFFER bFieldCache  FOR ttFieldCache.
  DEFINE BUFFER bColumnCache FOR ttColumnCache.

  PUBLISH "debugInfo" (2, SUBSTITUTE("Update first-level cache for &1.&2", pcDatabase, pcTableName)).

  /* Delete old */
  FOR EACH bFieldCache
    WHERE bFieldCache.cDatabase  = pcDatabase
      AND bFieldCache.cTableName = pcTableName:

    DELETE bFieldCache.
  END.

  FOR EACH bColumnCache
    WHERE bColumnCache.cDatabase  = pcDatabase
      AND bColumnCache.cTableName = pcTableName:

    DELETE bColumnCache.
  END.

  /* Create new */
  FOR EACH bField {&TABLE-SCAN}:
    CREATE bFieldCache.
    BUFFER-COPY bField TO bFieldCache.
  END.

  FOR EACH bColumn {&TABLE-SCAN}:
    CREATE bColumnCache.
    BUFFER-COPY bColumn TO bColumnCache.
  END.

END PROCEDURE. /* updateMemoryCache */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

/* ************************  Function Implementations ***************** */

&IF DEFINED(EXCLUDE-addConnection) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION addConnection Procedure
FUNCTION addConnection RETURNS LOGICAL
  ( pcDatabase AS CHARACTER
  , pcSection  AS CHARACTER ) :
  /* Add a connection to the temp-table
  */
  IF NOT CAN-FIND(ttDatabase WHERE ttDatabase.cLogicalName = pcDatabase) THEN
  DO:
    CREATE ttDatabase.
    ASSIGN
      ttDatabase.cLogicalName  = pcDatabase
      ttDatabase.cSection      = pcSection
      .
  END.
  RETURN TRUE.

END FUNCTION.

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-formatQueryString) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION formatQueryString Procedure
FUNCTION formatQueryString RETURNS CHARACTER
  ( INPUT pcQueryString AS CHARACTER
  , INPUT plExpanded    AS LOGICAL ) :
  /* Return a properly formatted query string
  */
  DEFINE VARIABLE cReturnValue AS CHARACTER   NO-UNDO.

  {&timerStart}
  cReturnValue = pcQueryString.
  IF cReturnValue <> '' AND cReturnValue <> ? THEN
  DO:
    /* There might be chr(1) chars in the text (if read from ini, for example)
     * Replace these with normal CRLF, then proceed
     */
    cReturnValue = REPLACE(cReturnValue,CHR(1),'~n').

    IF plExpanded THEN
      cReturnValue = REPLACE(cReturnValue, {&QUERYSEP}, '~n').
    ELSE
      cReturnValue = REPLACE(cReturnValue, '~n', {&QUERYSEP}).
  END.

  RETURN cReturnValue.
  {&timerStop}

END FUNCTION. /* formatQueryString */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getColor) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getColor Procedure
FUNCTION getColor RETURNS INTEGER
  ( pcName AS CHARACTER ) :
  /* Return the color number for a color name
   */
  DEFINE BUFFER bColor FOR ttColor.

  FIND bColor WHERE bColor.cName = pcName NO-ERROR.
  IF NOT AVAILABLE bColor THEN
    RETURN setColor(pcName,?).
  ELSE
    RETURN bColor.iColor.   /* Function return value. */

END FUNCTION. /* getColor */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getColorByRGB) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getColorByRGB Procedure
FUNCTION getColorByRGB RETURNS INTEGER
  ( piRed   AS INTEGER
  , piGreen AS INTEGER
  , piBlue  AS INTEGER
  ):
  /* Return the color number for a RGB combination
   * if needed, add color to color table.
   */
  DEFINE VARIABLE i AS INTEGER NO-UNDO.

  /* See if already exists */
  DO i = 0 TO COLOR-TABLE:NUM-ENTRIES - 1:
    IF    COLOR-TABLE:GET-RED-VALUE(i)   = piRed
      AND COLOR-TABLE:GET-GREEN-VALUE(i) = piGreen
      AND COLOR-TABLE:GET-BLUE-VALUE(i)  = piBlue THEN RETURN i.
  END.

  /* Define new color */
  i = COLOR-TABLE:NUM-ENTRIES.
  COLOR-TABLE:NUM-ENTRIES = COLOR-TABLE:NUM-ENTRIES + 1.
  COLOR-TABLE:SET-DYNAMIC(i, TRUE).
  COLOR-TABLE:SET-RED-VALUE  (i, piRed  ).
  COLOR-TABLE:SET-GREEN-VALUE(i, piGreen).
  COLOR-TABLE:SET-BLUE-VALUE (i, piBlue ).

  RETURN i.

END FUNCTION. /* getColorByRGB */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getColumnLabel) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getColumnLabel Procedure
FUNCTION getColumnLabel RETURNS CHARACTER
  ( INPUT phFieldBuffer AS HANDLE ):
  /* Return column label, based on settings
  */
  DEFINE VARIABLE cColumnLabel AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cTemplate    AS CHARACTER   NO-UNDO.

  {&timerStart}

  cTemplate = getRegistry("DataDigger","ColumnLabelTemplate").
  IF cTemplate = ? OR cTemplate = "" THEN cTemplate = "&1".

  cColumnLabel = SUBSTITUTE(cTemplate
                          , phFieldBuffer::cFullName
                          , phFieldBuffer::iOrder
                          , phFieldBuffer::cLabel
                          ).
  RETURN cColumnLabel.
  {&timerStop}

END FUNCTION. /* getColumnLabel */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getColumnWidthList) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getColumnWidthList Procedure
FUNCTION getColumnWidthList RETURNS CHARACTER
  ( INPUT phBrowse AS HANDLE ):
  /* returns a list of all fields and their width like:
   * custnum:12,custname:20,city:12
   */
  DEFINE VARIABLE cWidthList AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE hColumn    AS HANDLE      NO-UNDO.
  DEFINE VARIABLE iColumn    AS INTEGER     NO-UNDO.

  {&timerStart}

  DO iColumn = 1 TO phBrowse:NUM-COLUMNS:

    hColumn = phBrowse:GET-BROWSE-COLUMN(iColumn).
    cWidthList = SUBSTITUTE('&1,&2:&3'
                          , cWidthList
                          , hColumn:NAME
                          , hColumn:WIDTH-PIXELS
                          ).
  END.

  RETURN TRIM(cWidthList,',').
  {&timerStop}

END FUNCTION. /* getColumnWidthList */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getDatabaseList) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getDatabaseList Procedure
FUNCTION getDatabaseList RETURNS CHARACTER:
  /* Return a comma separated list of all connected databases
  */
  DEFINE VARIABLE cDatabaseList    AS CHARACTER  NO-UNDO.
  DEFINE VARIABLE cSchemaHolders   AS CHARACTER  NO-UNDO.
  DEFINE VARIABLE iCount           AS INTEGER    NO-UNDO.
  DEFINE VARIABLE cDbType          AS CHARACTER  NO-UNDO.
  DEFINE VARIABLE cLogicalDbName   AS CHARACTER  NO-UNDO.
  DEFINE VARIABLE iPos             AS INTEGER    NO-UNDO.

  DEFINE BUFFER bDataserver FOR ttDataserver.

  {&timerStart}

  /* Support Dataservers */
  IF gcSaveDatabaseList <> ""
   AND PROGRAM-NAME(2) BEGINS "initializeObjects " THEN RETURN gcSaveDatabaseList.

  /* Make a list of schema holders */
  #Db:
  DO iCount = 1 TO NUM-DBS:
    ASSIGN
      cDbType        = DBTYPE(iCount)
      cLogicalDbName = LDBNAME(iCount).

    IF cDbType <> 'PROGRESS' THEN
      cSchemaHolders = cSchemaHolders + ',' + SDBNAME(iCount).

    cDbType = DBTYPE(iCount).
    IF cDbType <> "PROGRESS" THEN NEXT #Db.

    cDatabaseList = cDatabaseList + ',' + cLogicalDbName.
  END.

  /* Build list of all databases. Skip if already in the list of schemaholders  */
  #Db:
  DO iCount = 1 TO NUM-DBS:
    ASSIGN
      cDbType         = DBTYPE(iCount)
      cLogicalDbName  = LDBNAME(iCount).

    IF LOOKUP(LDBNAME(iCount), cSchemaHolders) > 0 OR cDbType <> "PROGRESS" THEN NEXT #Db.

    CREATE ALIAS dictdb FOR DATABASE VALUE(cLogicalDbName).
    RUN getDataserver.p
      ( INPUT              cLogicalDbName
      , INPUT-OUTPUT       giDataserverNr
      , INPUT-OUTPUT TABLE bDataserver
      ).
    DELETE ALIAS dictdb.
  END.

  /* Support dataservers */
  FOR EACH bDataserver BY bDataserver.cLDbNameSchema:
    /* Remove schemaholder from database list */
    IF bDataserver.lDontShowSchemaHr THEN
    DO:
      iPos = LOOKUP(bDataserver.cLDbNameSchema, cDatabaseList).
      IF iPos > 0
       AND NOT CAN-FIND(FIRST ttTable WHERE ttTable.cDatabase = bDataserver.cLDbNameSchema
                                        AND ttTable.lHidden   = NO) THEN
      DO:
        ENTRY(iPos, cDatabaseList) = "".
        cDatabaseList = TRIM(REPLACE(cDatabaseList, ",,", ","), ",").
      END.
    END.

    /* Add dataserver to database list */
    iPos = LOOKUP(bDataserver.cLDbNameDataserver, cDatabaseList).
    IF bDataserver.lConnected THEN
    DO:
      IF iPos = 0 THEN cDatabaseList = TRIM(cDatabaseList + "," + bDataserver.cLDbNameDataserver, ",").
    END. /* IF bDataserver.lConnected */

    ELSE
    DO:
      IF iPos > 0 THEN
      DO:
        ENTRY(iPos, cDatabaseList) = "".
        cDatabaseList = TRIM(REPLACE(cDatabaseList, ",,", ","), ",").
      END. /* IF iPos > 0 */
    END. /* else */
  END. /* FOR EACH bDataserver */

  ASSIGN
    cDatabaseList      = TRIM(cDatabaseList, ',')
    gcSaveDatabaseList = cDatabaseList.

  RETURN cDatabaseList.

  {&timerStop}
END FUNCTION. /* getDatabaseList */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getEscapedData) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getEscapedData Procedure
FUNCTION getEscapedData RETURNS CHARACTER
  ( pcTarget AS CHARACTER
  , pcString AS CHARACTER ) :
  /* Return html- or 4gl-safe string
  */
  DEFINE VARIABLE cOutput AS CHARACTER NO-UNDO.
  DEFINE VARIABLE iTmp    AS INTEGER   NO-UNDO.

  {&timerStart}

  /* Garbage in, garbage out  */
  cOutput = pcString.

  CASE pcTarget:
    WHEN "HTML" THEN
    DO:
      cOutput = REPLACE(cOutput,"<","&lt;").
      cOutput = REPLACE(cOutput,">","&gt;").
    END.

    WHEN "4GL" THEN
    DO:
      /* Replace single quotes because we are using them for 4GL separating too */
      cOutput = REPLACE(cOutput, "'", "~~'").

      /* Replace CHR's 1 till 13  */
      DO iTmp = 1 TO 13:
        cOutput = REPLACE(cOutput, CHR(iTmp), "' + chr(" + string(iTmp) + ") + '").
      END.
    END.
  END CASE.

  RETURN cOutput.
  {&timerStop}

END FUNCTION. /* getEscapedData */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getFieldList) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getFieldList Procedure
FUNCTION getFieldList RETURNS CHARACTER
  ( pcDatabase AS CHARACTER
  , pcFile     AS CHARACTER
  ):
  /* Return a comma separated list of all fields of a table
  */
  DEFINE VARIABLE hQuery  AS HANDLE    NO-UNDO.
  DEFINE VARIABLE hFile   AS HANDLE    NO-UNDO.
  DEFINE VARIABLE hField  AS HANDLE    NO-UNDO.
  DEFINE VARIABLE cFields AS CHARACTER NO-UNDO.

  CREATE BUFFER hFile FOR TABLE pcDatabase + "._file".
  CREATE BUFFER hField FOR TABLE pcDatabase + "._field".

  CREATE QUERY hQuery.
  hQuery:SET-BUFFERS(hFile,hField).
  hQuery:QUERY-PREPARE(SUBSTITUTE('FOR EACH _File WHERE _File-name = &1, EACH _Field OF _File', QUOTER(pcFile))).
  hQuery:QUERY-OPEN().

  #CollectFields:
  REPEAT:
    hQuery:GET-NEXT().
    IF hQuery:QUERY-OFF-END THEN LEAVE #CollectFields.
    cFields = cFields + "," + hField::_Field-name.
  END. /* #CollectFields */

  hQuery:QUERY-CLOSE().
  DELETE OBJECT hField.
  DELETE OBJECT hFile.
  DELETE OBJECT hQuery.

  RETURN TRIM(cFields, ",").

END FUNCTION. /* getFieldList */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getFileCategory) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getFileCategory Procedure
FUNCTION getFileCategory RETURNS CHARACTER
  ( piFileNumber AS INTEGER
  , pcFileName   AS CHARACTER
  ) :
  /* Based on table name and -number, return the category for a table
   *
   * Application tables   : _file-number > 0   AND _file-number < 32000
   * Schema tables        : _file-number > -80 AND _file-number < 0
   * Virtual system tables: _file-number < -16384
   * SQL catalog tables   : _file-name BEGINS "_sys"
   * Other tables         : _file-number >= -16384 AND _file-number <= -80
   */
  IF piFileNumber > 0       AND piFileNumber < 32000 THEN RETURN 'Normal'.
  IF piFileNumber > -80     AND piFileNumber < 0     THEN RETURN 'Schema'.
  IF piFileNumber < -16384                           THEN RETURN 'VST'.
  IF pcFileName BEGINS '_sys'                        THEN RETURN 'SQL'.
  IF piFileNumber >= -16384 AND piFileNumber <= -80  THEN RETURN 'Other'.

  RETURN ''.   /* Function return value. */

END FUNCTION. /* getFileCategory */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getFont) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getFont Procedure
FUNCTION getFont RETURNS INTEGER
  ( pcName AS CHARACTER ) :
  /* Return the fontnumber for the type given
  */
  DEFINE BUFFER bFont FOR ttFont.

  {&timerStart}

  FIND bFont WHERE bFont.cName = pcName NO-ERROR.
  IF AVAILABLE bFont THEN RETURN bFont.iFont.

  CREATE bFont.
  ASSIGN bFont.cName = pcName.

  bFont.iFont = INTEGER(getRegistry('DataDigger:Fonts',pcName)) NO-ERROR.

  IF bFont.iFont = ? OR bFont.iFont > 23 THEN
  CASE pcName:
    WHEN 'Default' THEN bFont.iFont = 4.
    WHEN 'Fixed'   THEN bFont.iFont = 0.
  END CASE.

  RETURN bFont.iFont.   /* Function return value. */
  {&timerStop}

END FUNCTION. /* getFont */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getImagePath) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getImagePath Procedure
FUNCTION getImagePath RETURNS CHARACTER
  ( pcImage AS CHARACTER ) :
  /* Return the image path + icon set name
  */
  {&timerStart}
  RETURN SUBSTITUTE('&1Image/default_&2', getProgramDir(), pcImage).
  {&timerStop}

END FUNCTION. /* getImagePath */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getIndexFields) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getIndexFields Procedure
FUNCTION getIndexFields RETURNS CHARACTER
  ( INPUT pcDatabaseName AS CHARACTER
  , INPUT pcTableName    AS CHARACTER
  , INPUT pcFlags        AS CHARACTER
  ) :
  /* Return the index fields of a table.
  */
  DEFINE VARIABLE cWhere            AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE hQuery            AS HANDLE      NO-UNDO.
  DEFINE VARIABLE hFieldBuffer      AS HANDLE      NO-UNDO.
  DEFINE VARIABLE hFileBuffer       AS HANDLE      NO-UNDO.
  DEFINE VARIABLE hIndexBuffer      AS HANDLE      NO-UNDO.
  DEFINE VARIABLE hIndexFieldBuffer AS HANDLE      NO-UNDO.
  DEFINE VARIABLE cFieldList        AS CHARACTER   NO-UNDO.

  {&timerStart}

  CREATE BUFFER hFileBuffer       FOR TABLE pcDatabaseName + "._File".
  CREATE BUFFER hIndexBuffer      FOR TABLE pcDatabaseName + "._Index".
  CREATE BUFFER hIndexFieldBuffer FOR TABLE pcDatabaseName + "._Index-Field".
  CREATE BUFFER hFieldBuffer      FOR TABLE pcDatabaseName + "._Field".

  CREATE QUERY hQuery.
  hQuery:SET-BUFFERS(hFileBuffer,hIndexBuffer,hIndexFieldBuffer,hFieldBuffer).

  {&_proparse_ prolint-nowarn(longstrings)}
  cWhere = SUBSTITUTE("FOR EACH &1._file WHERE &1._file._file-name = &2 AND _File._File-Number < 32768, ~
                          EACH &1._index       OF &1._file WHERE TRUE &3 &4,  ~
                          EACH &1._index-field OF &1._index,            ~
                          EACH &1._field       OF &1._index-field"
                    , pcDatabaseName
                    , QUOTER(pcTableName)
                    , (IF CAN-DO(pcFlags,"U") THEN "AND _index._unique = true" ELSE "")
                    , (IF CAN-DO(pcFlags,"P") THEN "AND recid(_index) = _file._prime-index" ELSE "")
                    ).

  IF hQuery:QUERY-PREPARE (cWhere) THEN
  DO:
    hQuery:QUERY-OPEN().
    hQuery:GET-FIRST(NO-LOCK).
    REPEAT WHILE NOT hQuery:QUERY-OFF-END:
      cFieldList = cFieldList + "," + trim(hFieldBuffer:BUFFER-FIELD("_field-name"):string-value).
      hQuery:GET-NEXT(NO-LOCK).
    END.
  END.

  cFieldList = TRIM(cFieldList, ",").

  hQuery:QUERY-CLOSE.

  DELETE OBJECT hFileBuffer.
  DELETE OBJECT hIndexBuffer.
  DELETE OBJECT hIndexFieldBuffer.
  DELETE OBJECT hFieldBuffer.
  DELETE OBJECT hQuery.

  RETURN cFieldList.   /* Function return value. */
  {&timerStop}
END FUNCTION. /* getIndexFields */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getKeyList) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getKeyList Procedure
FUNCTION getKeyList RETURNS CHARACTER
  ( /* parameter-definitions */ ) :
  /* Return a list of special keys pressed
  */
  DEFINE VARIABLE mKeyboardState AS MEMPTR    NO-UNDO.
  {&_proparse_prolint-nowarn(varusage)}
  DEFINE VARIABLE iReturnValue   AS INT64     NO-UNDO.
  DEFINE VARIABLE cKeyList       AS CHARACTER NO-UNDO.

  SET-SIZE(mKeyboardState) = 256.

  /* Get the current state of the keyboard */
  {&_proparse_prolint-nowarn(varusage)}
  RUN GetKeyboardState(GET-POINTER-VALUE(mKeyboardState), OUTPUT iReturnValue) NO-ERROR.

  /* try to suppress error: 'C' Call Stack has been compromised after calling  in  (6069) */
  IF NOT ERROR-STATUS:ERROR THEN
  DO:
    IF GET-BITS(GET-BYTE(mKeyboardState, 1 + 16), 8, 1) = 1 THEN cKeyList = TRIM(cKeyList + ",SHIFT",",").
    IF GET-BITS(GET-BYTE(mKeyboardState, 1 + 17), 8, 1) = 1 THEN cKeyList = TRIM(cKeyList + ",CTRL",",").
    IF GET-BITS(GET-BYTE(mKeyboardState, 1 + 18), 8, 1) = 1 THEN cKeyList = TRIM(cKeyList + ",ALT",",").
  END.

  SET-SIZE(mKeyboardState) = 0.
  RETURN cKeyList.   /* Function return value. */

END FUNCTION. /* getKeyList */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getLinkInfo) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getLinkInfo Procedure
FUNCTION getLinkInfo RETURNS CHARACTER
  ( INPUT pcFieldName AS CHARACTER
  ):
  /* Save name/value of a field.
  */
  DEFINE BUFFER bLinkInfo FOR ttLinkInfo.
  {&timerStart}
  FIND bLinkInfo WHERE bLinkInfo.cField = pcFieldName NO-ERROR.

  RETURN (IF AVAILABLE bLinkInfo THEN bLinkInfo.cValue ELSE "").
  {&timerStop}
END FUNCTION. /* getLinkInfo */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getMaxLength) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getMaxLength Procedure
FUNCTION getMaxLength RETURNS INTEGER
  ( cFieldList AS CHARACTER ) :
  /* Return the length of the longest element in a comma separated list
  */
  DEFINE VARIABLE iField     AS INTEGER NO-UNDO.
  DEFINE VARIABLE iMaxLength AS INTEGER NO-UNDO.
  {&timerStart}

  /* Get max field length */
  DO iField = 1 TO NUM-ENTRIES(cFieldList):
    iMaxLength = MAXIMUM(iMaxLength,LENGTH(ENTRY(iField,cFieldList))).
  END.

  RETURN iMaxLength.   /* Function return value. */
  {&timerStop}
END FUNCTION. /* getMaxLength */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getOsErrorDesc) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getOsErrorDesc Procedure
FUNCTION getOsErrorDesc RETURNS CHARACTER
  (INPUT piOsError AS INTEGER):
  /* Return string for os-error
  */
  CASE piOsError:
    WHEN   0 THEN RETURN "No error                 ".
    WHEN   1 THEN RETURN "Not owner                ".
    WHEN   2 THEN RETURN "No such file or directory".
    WHEN   3 THEN RETURN "Interrupted system call  ".
    WHEN   4 THEN RETURN "I/O error                ".
    WHEN   5 THEN RETURN "Bad file number          ".
    WHEN   6 THEN RETURN "No more processes        ".
    WHEN   7 THEN RETURN "Not enough core memory   ".
    WHEN   8 THEN RETURN "Permission denied        ".
    WHEN   9 THEN RETURN "Bad address              ".
    WHEN  10 THEN RETURN "File exists              ".
    WHEN  11 THEN RETURN "No such device           ".
    WHEN  12 THEN RETURN "Not a directory          ".
    WHEN  13 THEN RETURN "Is a directory           ".
    WHEN  14 THEN RETURN "File table overflow      ".
    WHEN  15 THEN RETURN "Too many open files      ".
    WHEN  16 THEN RETURN "File too large           ".
    WHEN  17 THEN RETURN "No space left on device  ".
    WHEN  18 THEN RETURN "Directory not empty      ".
    OTHERWISE RETURN "Unmapped error           ".
  END CASE.

END FUNCTION. /* getOsErrorDesc */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getProgramDir) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getProgramDir Procedure
FUNCTION getProgramDir RETURNS CHARACTER
  ( /* parameter-definitions */ ) :
  /* Return the DataDigger install dir, including a backslash
  */

  /* Cached the value in a global var (about 100x as fast) */
  IF gcProgramDir = '' THEN
  DO:
    /* this-procedure:file-name will return the .p name without path when the
     * procedure us run without full path. We need to seek it in the propath.
     */
    FILE-INFO:FILE-NAME = THIS-PROCEDURE:FILE-NAME.
    IF FILE-INFO:FULL-PATHNAME = ? THEN
    DO:
      IF SUBSTRING(THIS-PROCEDURE:FILE-NAME,LENGTH(THIS-PROCEDURE:FILE-NAME) - 1, 2) = ".p" THEN
        FILE-INFO:FILE-NAME = SUBSTRING(THIS-PROCEDURE:FILE-NAME,1,LENGTH(THIS-PROCEDURE:FILE-NAME) - 2) + ".r".
    END.

    gcProgramDir = SUBSTRING(FILE-INFO:FULL-PATHNAME,1,R-INDEX(FILE-INFO:FULL-PATHNAME,'\')).
    PUBLISH "message"(50,gcProgramDir).
  END.

  RETURN gcProgramDir.

END FUNCTION. /* getProgramDir */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getQuery) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getQuery Procedure
FUNCTION getQuery RETURNS CHARACTER
  ( INPUT pcDatabase AS CHARACTER
  , INPUT pcTable    AS CHARACTER
  , INPUT piQuery    AS INTEGER
  ) :
  /* Get previously used query nr <piQuery>
  */
  DEFINE BUFFER bQuery FOR ttQuery.

  FIND bQuery
    WHERE bQuery.cDatabase = pcDatabase
      AND bQuery.cTable    = pcTable
      AND bQuery.iQueryNr  = piQuery NO-ERROR.

  IF AVAILABLE bQuery THEN
    RETURN bQuery.cQueryTxt.
  ELSE
    RETURN ?.

END FUNCTION. /* getQuery */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getReadableQuery) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getReadableQuery Procedure
FUNCTION getReadableQuery RETURNS CHARACTER
  ( INPUT pcQuery AS CHARACTER ):
  /* Return a query as a string that is readable for humans.
  */
  DEFINE VARIABLE hQuery AS HANDLE NO-UNDO.

  /* Accept query or query-handle */
  hQuery = WIDGET-HANDLE(pcQuery) NO-ERROR.
  IF VALID-HANDLE( hQuery ) THEN
  DO:
    hQuery = WIDGET-HANDLE(pcQuery).
    pcQuery = hQuery:PREPARE-STRING.
  END.

  pcQuery = REPLACE(pcQuery,' EACH ' ,' EACH ').
  pcQuery = REPLACE(pcQuery,' FIRST ',' FIRST ').
  pcQuery = REPLACE(pcQuery,' WHERE ',  '~n  WHERE ').
  pcQuery = REPLACE(pcQuery,' AND '  ,  '~n    AND ').
  pcQuery = REPLACE(pcQuery,' BY '   ,  '~n     BY ').
  pcQuery = REPLACE(pcQuery,' FIELDS ()','').
  pcQuery = REPLACE(pcQuery,'FOR EACH ' ,'FOR EACH ').
  pcQuery = REPLACE(pcQuery,' NO-LOCK',  ' NO-LOCK').
  pcQuery = REPLACE(pcQuery,' INDEXED-REPOSITION',  '').

  pcQuery = pcQuery + '~n'.

  RETURN pcQuery.
END FUNCTION. /* getReadableQuery */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getRegistry) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getRegistry Procedure
FUNCTION getRegistry RETURNS CHARACTER
    ( pcSection AS CHARACTER
    , pcKey     AS CHARACTER
    ) :
  /* Get a value from the registry.
  */
  {&timerStart}
  DEFINE BUFFER bDatabase FOR ttDatabase.
  DEFINE BUFFER bConfig   FOR ttConfig.

  /* If this is a DB-specific section then replace db name if needed */
  IF pcSection BEGINS "DB:" THEN
  DO:
    FIND bDatabase WHERE bDatabase.cLogicalName = ENTRY(2,pcSection,":") NO-ERROR.
    IF AVAILABLE bDatabase THEN pcSection = "DB:" + bDatabase.cSection.
  END.

  /* Load settings if there is nothing in the config table */
  IF NOT TEMP-TABLE ttConfig:HAS-RECORDS THEN
    RUN loadSettings.

  /* Search in settings tt */
  FIND bConfig WHERE bConfig.cSection = pcSection AND bConfig.cSetting = pcKey NO-ERROR.

  RETURN ( IF AVAILABLE bConfig THEN bConfig.cValue ELSE ? ).
  {&timerStop}
END FUNCTION. /* getRegistry */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getSchemaHolder) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getSchemaHolder Procedure
FUNCTION getSchemaHolder RETURNS CHARACTER
  ( INPUT pcDataSrNameOrDbName AS CHARACTER
  ):
  DEFINE BUFFER bDataserver FOR ttDataserver.

  FIND bDataserver WHERE bDataserver.cLDBNameDataserver = pcDataSrNameOrDbName NO-ERROR.
  RETURN (IF AVAILABLE bDataserver THEN bDataserver.cLDBNameSchema ELSE pcDataSrNameOrDbName).

END FUNCTION. /* getSchemaHolder */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getStackSize) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getStackSize Procedure
FUNCTION getStackSize RETURNS INTEGER():
  /* Return value of the -s session setting
  */
  DEFINE VARIABLE cList      AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cParm      AS CHARACTER   CASE-SENSITIVE NO-UNDO.
  DEFINE VARIABLE cSetting   AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cValue     AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE iParm      AS INTEGER     NO-UNDO.
  DEFINE VARIABLE iStackSize AS INTEGER     NO-UNDO.

  cList = SESSION:STARTUP-PARAMETERS.

  DO iParm = 1 TO NUM-ENTRIES(cList):
    cSetting = ENTRY(iParm,cList) + " ".
    cParm    = ENTRY(1,cSetting," ").
    cValue   = ENTRY(2,cSetting," ").

    IF cParm = "-s" THEN
    DO:
      iStackSize = INTEGER(cValue) NO-ERROR.
      IF ERROR-STATUS:ERROR THEN iStackSize = 0.
    END.
  END.

  /* If not defined, report the default */
  IF iStackSize = 0 THEN iStackSize = 40.

  RETURN iStackSize.
END FUNCTION. /* getStackSize */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getTableDesc) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getTableDesc Procedure
FUNCTION getTableDesc RETURNS CHARACTER
  ( INPUT pcDatabase AS CHARACTER
  , INPUT pcTable    AS CHARACTER
  ) :
  DEFINE BUFFER bTable FOR ttTable.

  FIND bTable
    WHERE bTable.cDatabase  = pcDatabase
      AND bTable.cTableName = pcTable NO-ERROR.

  RETURN (IF AVAILABLE bTable THEN bTable.cTableDesc ELSE '').

END FUNCTION. /* getTableDesc */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getTableLabel) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getTableLabel Procedure
FUNCTION getTableLabel RETURNS CHARACTER
  ( INPUT  pcDatabase AS CHARACTER
  , INPUT  pcTable    AS CHARACTER
  ) :
  DEFINE BUFFER bTable FOR ttTable.

  FIND bTable
    WHERE bTable.cDatabase  = pcDatabase
      AND bTable.cTableName = pcTable NO-ERROR.

  RETURN (IF AVAILABLE bTable AND bTable.cTableLabel <> ? THEN bTable.cTableLabel ELSE '').

END FUNCTION. /* getTableLabel */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getTableList) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getTableList Procedure
FUNCTION getTableList RETURNS CHARACTER
  ( INPUT  pcDatabaseFilter AS CHARACTER
  , INPUT  pcTableFilter    AS CHARACTER
  ) :
  /* Get a filtered list of all tables in the current database
  */
  DEFINE VARIABLE cTableList  AS CHARACTER   NO-UNDO.
  DEFINE VARIABLE cQuery      AS CHARACTER   NO-UNDO.

  DEFINE BUFFER bTable FOR ttTable.
  DEFINE QUERY qTable FOR bTable.

  {&timerStart}
  IF pcDatabaseFilter = '' OR pcDatabaseFilter = ? THEN pcDatabaseFilter = '*'.

  /* Build query */
  cQuery = SUBSTITUTE('for each bTable where cDatabase matches &1', QUOTER(pcDatabaseFilter)).
  cQuery = SUBSTITUTE("&1 and cTableName matches &2", cQuery, QUOTER(pcTableFilter )).

  QUERY qTable:QUERY-PREPARE( SUBSTITUTE('&1 by cTableName', cQuery)).
  QUERY qTable:QUERY-OPEN.
  QUERY qTable:GET-FIRST.

  /* All fields */
  REPEAT WHILE NOT QUERY qTable:QUERY-OFF-END:
    cTableList = cTableList + "," + bTable.cTableName.
    QUERY qTable:GET-NEXT.
  END.
  QUERY qTable:QUERY-CLOSE.

  cTableList = LEFT-TRIM(cTableList, ",").

  RETURN cTableList.   /* Function return value. */
  {&timerStop}
END FUNCTION. /* getTableList */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getUserName) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getUserName Procedure
FUNCTION getUserName RETURNS CHARACTER
  ( /* parameter-definitions */ ) :
  /* Return login name of user
  */
  DEFINE VARIABLE cUserName AS LONGCHAR   NO-UNDO.
  DEFINE VARIABLE intResult AS INTEGER    NO-UNDO.
  DEFINE VARIABLE intSize   AS INTEGER    NO-UNDO.
  DEFINE VARIABLE mUserId   AS MEMPTR     NO-UNDO.

  {&startTimer}

  /* Otherwise determine the value */
  SET-SIZE(mUserId) = 256.
  intSize = 255.

  RUN GetUserNameA(INPUT mUserId, INPUT-OUTPUT intSize, OUTPUT intResult).
  COPY-LOB mUserId FOR (intSize - 1) TO cUserName NO-CONVERT.

  IF intResult <> 1 OR cUserName = "" OR cUserName = ? THEN
    cUserName = "default".
  ELSE
    cUserName = REPLACE(cUserName,".","").

  RETURN STRING(cUserName). /* Function return value. */

  {&stopTimer}
END FUNCTION. /* getUserName */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getWidgetUnderMouse) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getWidgetUnderMouse Procedure
FUNCTION getWidgetUnderMouse RETURNS HANDLE
  ( phFrame AS HANDLE ) :
  /* Return the handle of the widget that is currently under the mouse cursor
  */
  DEFINE VARIABLE hWidget AS HANDLE  NO-UNDO.
  DEFINE VARIABLE iMouseX AS INTEGER NO-UNDO.
  DEFINE VARIABLE iMouseY AS INTEGER NO-UNDO.

  {&timerStart}
  hWidget = phFrame:FIRST-CHILD:first-child.
  RUN getMouseXY(INPUT phFrame, OUTPUT iMouseX, OUTPUT iMouseY).

  REPEAT WHILE VALID-HANDLE(hWidget):

    IF hWidget:TYPE <> "RECTANGLE"
      AND iMouseX >= hWidget:X
      AND iMouseX <= hWidget:X + hWidget:WIDTH-PIXELS
      AND iMouseY >= hWidget:Y
      AND iMouseY <= hWidget:Y + hWidget:HEIGHT-PIXELS THEN RETURN hWidget.

    hWidget = hWidget:NEXT-SIBLING.
  END.

  RETURN ?.
  {&timerStop}
END FUNCTION. /* getWidgetUnderMouse */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getWorkFolder) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getWorkFolder Procedure
FUNCTION getWorkFolder RETURNS CHARACTER
  ( /* parameter-definitions */ ) :

  /* Cached the value in a global var  */
  IF gcWorkFolder = '' THEN
  DO:
    gcWorkFolder = getRegistry("DataDigger", "WorkFolder").

    /* Possibility to specify where DD files are created */
    IF gcWorkFolder = ? OR gcWorkFolder = '' THEN
      gcWorkFolder = getProgramDir().
    ELSE
    DO:
      gcWorkFolder = RIGHT-TRIM(gcWorkFolder,'/\') + '\'.
      gcWorkFolder = resolveOsVars(gcWorkFolder).
      RUN createFolder(gcWorkFolder).

      FILE-INFO:FILE-NAME = gcWorkFolder.
      IF FILE-INFO:FULL-PATHNAME = ? THEN gcWorkFolder = getProgramDir().
    END.
  END.

  RETURN gcWorkFolder.

END FUNCTION. /* getWorkFolder */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-getXmlNodeName) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION getXmlNodeName Procedure
FUNCTION getXmlNodeName RETURNS CHARACTER
  ( pcFieldName AS CHARACTER ) :
  /* Return a name that is safe to use in XML output
  */
  pcFieldName = REPLACE(pcFieldName,'%', '_').
  pcFieldName = REPLACE(pcFieldName,'#', '_').

  RETURN pcFieldName.

END FUNCTION. /* getXmlNodeName */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-isDataServer) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION isDataServer Procedure
FUNCTION isDataServer RETURNS LOGICAL
  ( INPUT pcDataSrNameOrDbName AS CHARACTER
  ):
  RETURN CAN-FIND(ttDataserver WHERE ttDataserver.cLDBNameDataserver = pcDataSrNameOrDbName).

END FUNCTION. /* isDataServer */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-isDefaultFontsChanged) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION isDefaultFontsChanged Procedure
FUNCTION isDefaultFontsChanged RETURNS LOGICAL
  ( /* parameter-definitions */ ) :
  /* Returns whether the default fonts 0-7 were changed.
  */
  DEFINE VARIABLE cFontSize AS CHARACTER NO-UNDO EXTENT 8.
  DEFINE VARIABLE i         AS INTEGER   NO-UNDO.

  /* These are the expected fontsizes of the text 'DataDigger' */
  cFontSize[1] = '70/14'. /* font0 */
  cFontSize[2] = '54/13'. /* font1 */
  cFontSize[3] = '70/14'. /* font2 */
  cFontSize[4] = '70/14'. /* font3 */
  cFontSize[5] = '54/13'. /* font4 */
  cFontSize[6] = '70/16'. /* font5 */
  cFontSize[7] = '65/13'. /* font6 */
  cFontSize[8] = '54/13'. /* font7 */

  checkFont:
  DO i = 0 TO 7:
    IF cFontSize[i + 1] <> SUBSTITUTE('&1/&2'
                                    , FONT-TABLE:GET-TEXT-WIDTH-PIXELS('DataDigger',i)
                                    , FONT-TABLE:GET-TEXT-HEIGHT-PIXELS(i)
                                    ) THEN RETURN TRUE.
  END. /* checkFont */

  RETURN FALSE.

END FUNCTION. /* isDefaultFontsChanged */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-isFileLocked) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION isFileLocked Procedure
FUNCTION isFileLocked RETURNS LOGICAL
  ( pcFileName AS CHARACTER ) :
  /* Check whether a file is locked on the file system
  */
  DEFINE VARIABLE iFileHandle   AS INTEGER NO-UNDO.
  {&_proparse_prolint-nowarn(varusage)}
  DEFINE VARIABLE nReturn       AS INTEGER NO-UNDO.

  /* Try to lock the file agains writing */
  RUN CreateFileA ( INPUT pcFileName
                  , INPUT {&GENERIC_WRITE}
                  , {&FILE_SHARE_READ}
                  , 0
                  , {&OPEN_EXISTING}
                  , {&FILE_ATTRIBUTE_NORMAL}
                  , 0
                  , OUTPUT iFileHandle
                  ).

  /* Release file handle */
  {&_proparse_prolint-nowarn(varusage)}
  RUN CloseHandle (INPUT iFileHandle, OUTPUT nReturn).

  RETURN (iFileHandle = -1).

END FUNCTION. /* isFileLocked */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-isMouseOver) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION isMouseOver Procedure
FUNCTION isMouseOver RETURNS LOGICAL
  ( phWidget AS HANDLE ) :
  /* Return whether the mouse is currently over a certain widget
  */
  DEFINE VARIABLE iMouseX AS INTEGER   NO-UNDO.
  DEFINE VARIABLE iMouseY AS INTEGER   NO-UNDO.

  IF NOT VALID-HANDLE(phWidget) THEN RETURN FALSE.
  RUN getMouseXY(INPUT phWidget:FRAME, OUTPUT iMouseX, OUTPUT iMouseY).

  RETURN (    iMouseX >= phWidget:X
          AND iMouseX <= phWidget:X + phWidget:WIDTH-PIXELS
          AND iMouseY >= phWidget:Y
          AND iMouseY <= phWidget:Y + phWidget:HEIGHT-PIXELS ).

END FUNCTION. /* isMouseOver */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-isTableFilterUsed) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION isTableFilterUsed Procedure
FUNCTION isTableFilterUsed RETURNS LOGICAL
  ( INPUT TABLE ttTableFilter ) :
  /* Returns whether any setting is used for table filtering
  */
  FIND ttTableFilter NO-ERROR.
  IF NOT AVAILABLE ttTableFilter THEN RETURN FALSE.

  /* Main toggles */
  IF   ttTableFilter.lShowNormal = FALSE
    OR ttTableFilter.lShowSchema <> LOGICAL(getRegistry('DataDigger','ShowHiddenTables'))
    OR ttTableFilter.lShowVst    = TRUE
    OR ttTableFilter.lShowSql    = TRUE
    OR ttTableFilter.lShowOther  = TRUE
    OR ttTableFilter.lShowHidden = TRUE
    OR ttTableFilter.lShowFrozen = TRUE THEN RETURN TRUE.

  /* Show these tables */
  IF   ttTableFilter.cTableNameShow <> ?
    AND ttTableFilter.cTableNameShow <> ''
    AND ttTableFilter.cTableNameShow <> '*' THEN RETURN TRUE.

  /* But hide these */
  IF   ttTableFilter.cTableNameHide <> ?
    AND ttTableFilter.cTableNameHide <> '' THEN RETURN TRUE.

  /* Show only tables that contain all of these fields */
  IF    ttTableFilter.cTableFieldShow <> ?
    AND ttTableFilter.cTableFieldShow <> ''
    AND ttTableFilter.cTableFieldShow <> '*' THEN RETURN TRUE.

  /* But hide tables that contain any of these */
  IF    ttTableFilter.cTableFieldHide <> ?
    AND ttTableFilter.cTableFieldHide <> '' THEN RETURN TRUE.

  /* else */
  RETURN FALSE.

END FUNCTION. /* isTableFilterUsed */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-isValidCodePage) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION isValidCodePage Procedure
FUNCTION isValidCodePage RETURNS LOGICAL
  (pcCodepage AS CHARACTER):
  /* Returns whether pcCodePage is valid
  */
  {&_proparse_prolint-nowarn(varusage)}
  DEFINE VARIABLE cDummy AS LONGCHAR NO-UNDO.

  IF pcCodePage = '' THEN RETURN TRUE.

  FIX-CODEPAGE(cDummy) = pcCodepage NO-ERROR.
  RETURN NOT ERROR-STATUS:ERROR.

END FUNCTION. /* isValidCodePage */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-readFile) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION readFile Procedure
FUNCTION readFile RETURNS LONGCHAR
  (pcFilename AS CHARACTER):
  /* Read contents of a file as a longchar.
  */
  DEFINE VARIABLE cContent AS LONGCHAR  NO-UNDO.
  DEFINE VARIABLE cLine    AS CHARACTER NO-UNDO.

  IF SEARCH(pcFilename) <> ? THEN
  DO:
    INPUT FROM VALUE(pcFilename).
    REPEAT:
      IMPORT UNFORMATTED cLine.
      cContent = cContent + "~n" + cLine.
    END.
    INPUT CLOSE.
  END.

  RETURN cContent.
END FUNCTION. /* readFile */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-removeConnection) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION removeConnection Procedure
FUNCTION removeConnection RETURNS LOGICAL
  ( pcDatabase AS CHARACTER ) :
  /* Remove record from connection temp-table
  */
  DEFINE BUFFER bfDatabase FOR ttDatabase.
  FIND bfDatabase WHERE bfDatabase.cLogicalName = pcDatabase NO-ERROR.
  IF AVAILABLE bfDatabase THEN DELETE bfDatabase.
  RETURN TRUE.

END FUNCTION. /* removeConnection */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-resolveOsVars) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION resolveOsVars Procedure
FUNCTION resolveOsVars RETURNS CHARACTER
  ( pcString AS CHARACTER ) :

  /* Return a string with OS vars resolved
  */
  DEFINE VARIABLE i AS INTEGER NO-UNDO.

  DO i = 1 TO NUM-ENTRIES(pcString,'%'):
    IF i MODULO 2 = 0
      AND OS-GETENV(ENTRY(i,pcString,'%')) <> ? THEN
      ENTRY(i,pcString,'%') = OS-GETENV(ENTRY(i,pcString,'%')).
  END.

  pcString = REPLACE(pcString,'%','').
  RETURN pcString.
END FUNCTION. /* resolveOsVars */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-resolveSequence) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION resolveSequence Procedure
FUNCTION resolveSequence RETURNS CHARACTER
  ( pcString AS CHARACTER ) :
  /* Return a string where sequence nr for file is resolved
  */
  DEFINE VARIABLE iFileNr    AS INTEGER   NO-UNDO.
  DEFINE VARIABLE cSeqMask   AS CHARACTER NO-UNDO .
  DEFINE VARIABLE cSeqFormat AS CHARACTER NO-UNDO .
  DEFINE VARIABLE cFileName  AS CHARACTER NO-UNDO.

  cFileName = pcString.

  /* User can specify a sequence for the file. The length of
   * the tag sets the format: <###> translates to a 3-digit nr
   * Special case is <#> which translates to no leading zeros
   */
  IF    INDEX(cFileName,'<#') > 0
    AND index(cFileName,'#>') > 0 THEN
  DO:
    cSeqMask = SUBSTRING(cFileName,INDEX(cFileName,'<#')). /* <#####>tralalala */
    cSeqMask = SUBSTRING(cSeqMask,1,INDEX(cSeqMask,'>')). /* <#####> */
    cSeqFormat = TRIM(cSeqMask,'<>'). /* ##### */
    cSeqFormat = REPLACE(cSeqFormat,'#','9').
    IF cSeqFormat = '9' THEN cSeqFormat = '>>>>>>>>>9'.

    setFileNr:
    REPEAT:
      iFileNr = iFileNr + 1.
      IF SEARCH(REPLACE(cFileName,cSeqMask,TRIM(STRING(iFileNr,cSeqFormat)))) = ? THEN
      DO:
        cFileName = REPLACE(cFileName,cSeqMask,TRIM(STRING(iFileNr,cSeqFormat))).
        LEAVE setFileNr.
      END.
    END.
  END.

  RETURN cFileName.

END FUNCTION. /* resolveSequence */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-setColor) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION setColor Procedure
FUNCTION setColor RETURNS INTEGER
  ( pcName  AS CHARACTER
  , piColor AS INTEGER) :
  /* Set color nr in the color tt
   */
  DEFINE BUFFER bColor FOR ttColor.

  FIND bColor WHERE bColor.cName = pcName NO-ERROR.
  IF NOT AVAILABLE bColor THEN
  DO:
    CREATE bColor.
    ASSIGN bColor.cName = pcName.
  END.

  /* Set to default value from settings */
  IF piColor = ? THEN
  DO:
    piColor = INTEGER(getRegistry('DataDigger:Colors', pcName)) NO-ERROR.
    IF ERROR-STATUS:ERROR THEN piColor = ?.
  END.

  bColor.iColor = piColor.
  RETURN bColor.iColor.

END FUNCTION. /* setColor */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-setColumnWidthList) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION setColumnWidthList Procedure
FUNCTION setColumnWidthList RETURNS LOGICAL
  ( INPUT phBrowse    AS HANDLE
  , INPUT pcWidthList AS CHARACTER):
  /* Set all specified columns in pcWidthList to a specified width
  */
  DEFINE VARIABLE cColumnName  AS CHARACTER NO-UNDO.
  DEFINE VARIABLE cListItem    AS CHARACTER NO-UNDO.
  DEFINE VARIABLE hColumn      AS HANDLE    NO-UNDO.
  DEFINE VARIABLE iColumnWidth AS INTEGER   NO-UNDO.
  DEFINE VARIABLE i            AS INTEGER   NO-UNDO.
  DEFINE VARIABLE j            AS INTEGER   NO-UNDO.

  DO i = 1 TO NUM-ENTRIES(pcWidthList):
    cListItem    = ENTRY(i,pcWidthList).
    cColumnName  = ENTRY(1,cListItem,':') NO-ERROR.
    iColumnWidth = INTEGER(ENTRY(2,cListItem,':')) NO-ERROR.

    DO j = 1 TO phBrowse:NUM-COLUMNS:
      hColumn = phBrowse:GET-BROWSE-COLUMN(j).
      IF hColumn:NAME = cColumnName THEN
        hColumn:WIDTH-PIXELS = iColumnWidth.
    END. /* j */
  END. /* i */

  RETURN TRUE.
END FUNCTION. /* setColumnWidthList */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-setLinkInfo) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION setLinkInfo Procedure
FUNCTION setLinkInfo RETURNS LOGICAL
  ( INPUT pcFieldName AS CHARACTER
  , INPUT pcValue     AS CHARACTER
  ):
  /* Save name/value of a field.
  */
  DEFINE BUFFER bLinkInfo FOR ttLinkInfo.
  {&timerStart}

  PUBLISH "debugInfo" (2, SUBSTITUTE("Set linkinfo for field &1 to &2", pcFieldName, pcValue)).

  FIND bLinkInfo WHERE bLinkInfo.cField = pcFieldName NO-ERROR.
  IF NOT AVAILABLE bLinkInfo THEN
  DO:
    CREATE bLinkInfo.
    ASSIGN bLinkInfo.cField = pcFieldName.
  END.

  bLinkInfo.cValue = TRIM(pcValue).

  RETURN TRUE.   /* Function return value. */
  {&timerStop}

END FUNCTION. /* setLinkInfo */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF

&IF DEFINED(EXCLUDE-setRegistry) = 0 &THEN

&ANALYZE-SUSPEND _UIB-CODE-BLOCK _FUNCTION setRegistry Procedure
FUNCTION setRegistry RETURNS CHARACTER
  ( pcSection AS CHARACTER
  , pcKey     AS CHARACTER
  , pcValue   AS CHARACTER
  ) :
  /* Set a value in the registry.
  */
  {&timerStart}
  DEFINE BUFFER bfConfig FOR ttConfig.

  FIND bfConfig
    WHERE bfConfig.cSection = pcSection
      AND bfConfig.cSetting = pcKey NO-ERROR.

  IF NOT AVAILABLE bfConfig THEN
  DO:
    CREATE bfConfig.
    ASSIGN
      bfConfig.cSection = pcSection
      bfConfig.cSetting = pcKey.

    glDirtyCache = TRUE.
  END.

  IF pcValue = ? OR TRIM(pcValue) = '' THEN
  DO:
    DELETE bfConfig.
    glDirtyCache = TRUE.
  END.
  ELSE
  DO:
    ASSIGN
      bfConfig.lUser  = TRUE
      bfConfig.cValue = pcValue.

    IF bfConfig.cValue <> pcValue THEN glDirtyCache = TRUE.
  END.

  RETURN "". /* Function return value. */
  {&timerStop}

END FUNCTION. /* setRegistry */

/* _UIB-CODE-BLOCK-END */
&ANALYZE-RESUME

&ENDIF