///////////////////////////////////////////////////////////////////////////////
// Filename:    Animate.CC                                                   //
// Written for: dBASE (32-bit versions)                                      //
// Written by:  Jim Sare - dBASE Inc. R&D Group                              //
// Copyright:   Copyright © dBASE Inc. 2002                                  //
// Run From:    :FormControls:Animate.CC                                     //
// Purpose:     To display single-stream AVI video animations in an          //
//              independent execution thread in a dBASE form.                //
// Overview:    This class surfaces the Animate control class in the Windows //
//              common controls library. The Animate control plays AVI files //
//              that are single-stream video-only AVI files. The Animate     //
//              control creates a separate execution thread that allows the  //
//              control to continue playing an AVI file while a dBL          //
//              application is performing other tasks. This provides a means //
//              to indicate system activity to the user concurrent with the  //
//              application performing tasks which take unknown extended     //
//              periods of time to complete. Examples of usage of this       //
//              facility are in the Windows Explorer's Find dialog which     //
//              displays a moving magnifying glass scanning a monitor, or    //
//              the flying sheets of paper that display during copy, move,   //
//              and delete operations.                                       //
//              Other possible uses for animation are to display animated    //
//              graphics on application Help|About screens, forms used as    //
//              application startup splashes, etc.                           //
//              Some AVI files are installed with dBASE which can be used    //
//              with this class in dBL applications. There are also many     //
//              other AVI files available on the internet which are single-  //
//              stream video that are compatible with the Animate control.   //
//              Or AVI files can be created using many graphics editing      //
//              programs available on the internet and commercially.         //
// Usage:       Place an Animate object on a form in the Form Designer.      //
//              Use the Animate.Open method after the form is opened to      //
//              start the animation. See AnimateDemo.WFM in the \Samples     //
//              folder for an example of how to use the Animate class.       //
// Properties:  See the comments in the code.                                //
// Methods:     See the comments in the code.                                //
///////////////////////////////////////////////////////////////////////////////
// Versioning and History comments:
// #define ANIMATE_VERSION  "1000"  // 04/23/1998 - Original version.
// #define ANIMATE_VERSION  "1001"  // 04/16/2002 - Cleanup and commenting.
// #define ANIMATE_VERSION  "1002"  // 04/17/2002 - Add SourceAlias support.
// #define ANIMATE_VERSION  "1003"  // 04/19/2002 - Minor correction for
//                                                  animation window positioning.
// #define ANIMATE_VERSION  "1004"  // 04/25/2002 - Added pixel conversion code
//                                                    and Version property.
//#define ANIMATE_VERSION  "1005"  // 10/25/2002 - Added call to PlayAllRepeat in
//                                                    Open method to fix problem with
//                                  //                animation not starting correctly
//                                  //                in long dBL code string or loop.
#define ANIMATE_VERSION  "1006"  // March 4, 2003 - Modified code in 'AliasToPath'
                                    //              routine to deal with runtime
                                    //              issue -- QAID: 3936
                                    //              This fix handles DEO, as well
                                    //              as the AVI being in the same
                                    //              folder as the EXE.
                                    //              If the AVI is not found, this 
                                    //              doesn't display anything.

#define STRINGFROMFOURCC(x, n)   (Chr(x.GetByte(n + 0)) + Chr(x.GetByte(n + 1)) +;
                                  Chr(x.GetByte(n + 2)) + Chr(x.GetByte(n + 3)))
#define USTRINGFROMFOURCC(x, n)  Upper(STRINGFROMFOURCC(x, n))
#define WORDFROMTWOBYTE(x, n)    Int(x.GetByte(n + 0) +;
                                     BitLShift(x.GetByte(n + 1), 8))
#define DWORDFROMFOURBYTE(x, n)  Int(x.GetByte(n + 0) +;
                                     BitLShift(x.GetByte(n + 1), 8) +;
                                     BitLShift(x.GetByte(n + 2), 16) +;
                                     BitLShift(x.GetByte(n + 3), 24))

#define AVIF_HASINDEX            0x00000010
#define AVIF_MUSTUSEINDEX        0x00000020
#define AVIF_ISINTERLEAVED       0x00000100
#define AVIF_TRUSTCKTYPE         0x00000800
#define AVIF_WASCAPTUREFILE      0x00010000
#define AVIF_COPYRIGHTED         0x00020000

CLASS Animate(o, n) of PaintBox(o, n) Custom

   If Type("dB_CreateWindowExU12") # "FP"
      extern CHANDLE dB_CreateWindowExU12(CULONG, CSTRING, CSTRING, CULONG,;
                                          CINT, CINT, CINT, CINT,;
                                          CHANDLE, CHANDLE, CHANDLE, CULONG) User32;
                From "CreateWindowExA"
   EndIf
   If Type("dB_DestroyWindow") # "FP"
      extern CLOGICAL dB_DestroyWindow(CHANDLE) User32;
                From "DestroyWindow"
   EndIf
   If Type("dB_GetClientRect") # "FP"
      extern CLOGICAL dB_GetClientRect(CHANDLE, CPTR) User32;
              From "GetClientRect"
   EndIf
   If Type("dB_GetWindowLong") # "FP"
      extern CLONG dB_GetWindowLong(CHANDLE, CINT) User32;
               From "GetWindowLongA"
   EndIf
      #define GWL_HINSTANCE             -6
   If Type("dB_InitCommonControls") # "FP"
      extern CVOID dB_InitCommonControls() ComCtl32;
               From "InitCommonControls"
   EndIf
   If Type("dB_SendMessageC4") # "FP"
      extern CULONG dB_SendMessageC4(CHANDLE, CUINT, CULONG, CSTRING) User32;
               From "SendMessageA"
   EndIf
   If Type("dB_SendMessageU4") # "FP"
      extern CULONG dB_SendMessageU4(CHANDLE, CUINT, CULONG, CULONG) User32;
               From "SendMessageA"
   EndIf

      #define ANIMATE_CLASS             "SysAnimate32"
      #define Animate_Create(hwndP, id, dwStyle, hInstance);
                        dB_CreateWindowExU12(0, ANIMATE_CLASS, Chr(0), dwStyle,;
                                             0, 0, 0, 0, hwndP, id, hInstance, 0)

      #define WS_CHILD                  0x40000000
      #define WS_VISIBLE                0x10000000

      #define WS_EX_NOPARENTNOTIFY      0x00000004

   If Type("dB_SetWindowPos") # "FP"
      extern CLOGICAL dB_SetWindowPos(CHANDLE, CHANDLE,;
                                      CINT, CINT, CINT, CINT, CINT) User32;
               From "SetWindowPos"
   EndIf
      #define SWP_NOZORDER                4

      #define WM_USER                   0x0400

      #define ACM_OPEN                  WM_USER + 100
      #define ACM_PLAY                  WM_USER+101
      #define ACM_STOP                  WM_USER+102

      #define ACS_CENTER                0x0001
      #define ACS_TRANSPARENT           0x0002
      #define ACS_AUTOPLAY              0x0004

   // Ensure the Windows CommonControls library is initialized.
   dB_InitCommonControls()

   // Standard property members.
   this.borderStyle :=                   3     // None
   this.Enabled :=                       false
   this.ColorNormal :=                   Left(this.ColorNormal, At("/", this.ColorNormal)) + this.Parent.ColorNormal
   this.Transparent :=                   true

   // Published custom property members representing AVI file characteristics.
   this.Flags =                          0
   this.Copyright =                      false
   this.Format =                         ""
   this.Frames =                         0
   this.FramesPerSecond =                0
   this.ImageWidth =                     0
   this.ImageHeight =                    0
   this.ImageColors =                    0
   this.TransferRate =                   0
   this.Version =                        ANIMATE_VERSION

   // Published custom property members representing functional characteristics and controls.
   this.AutoPlay =                       true  // AutoPlay on Open of AVI file.
   this.Center =                         true  // Center AVI display in control.
   this.TransparentImageBackground =     true  // Play AVI using transparent background.

   // PROTECTed custom property members to store critical or control variables.
   // Instance handle of the currently running dBASE session.
   this.hInstance =                      Int(dB_GetWindowLong(_app.FrameWin.hWND, GWL_HINSTANCE))
   this.hAnimate =                       0     // Handle of the Animation window.
   this.xRECT =                          Replicate(Chr(0), 8)
   // NOTE: The following 3 members store the characteristics for the current
   //       Animation window. When a subsequent AVI file is OPEN()ed, the
   //       corresponding published properties are checked against the following
   //       3 members, and if any values are different, a new Animation window
   //       is created according to the published property member values.
   this.protAutoPlay =                   Null
   this.protCenter =                     Null
   this.protTransparentImageBackground = Null

   // PROTECTed data members
   PROTECT hAnimate, hInstance, xRECT,;
           protAutoPlay, protCenter, protTransparentImageBackground

   FUNCTION CreateAnimationWindow
      // Creates an independently-threaded Animation window as a child of
      // this PaintBox and sizes the window to fill the PaintBox area.
      LOCAL nWidth, nHeight

      this.hAnimate := Animate_Create(this.hWND, 1,;
                                      iIf(this.AutoPlay, ACS_AUTOPLAY, 0) +;
                                      iIf(this.Center, ACS_CENTER, 0) +;
                                      iIf(this.TransparentImageBackground, ACS_TRANSPARENT, 0) +;
                                      WS_CHILD + WS_VISIBLE,;
                                      this.hInstance)
      dB_GetClientRect(this.hWND, this.xRECT)
      nWidth = this.xRECT.getByte(8) +;
               bitLShift(this.xRECT.getByte(9), 8) +;
               bitLShift(this.xRECT.getByte(10), 16) +;
               bitLShift(this.xRECT.getByte(11), 24)
      nHeight = this.xRECT.getByte(12) +;
                bitLShift(this.xRECT.getByte(13), 8) +;
                bitLShift(this.xRECT.getByte(14), 16) +;
                bitLShift(this.xRECT.getByte(15), 24)
      dB_SetWindowPos(this.hAnimate, 0, 2, 2, nWidth - 4, nHeight - 4, SWP_NOZORDER)
      If this.hAnimate # 0
         this.protAutoPlay := this.AutoPlay
         this.protCenter := this.Center
         this.protTransparentImageBackground := this.TransparentImageBackground
      EndIf
      RETURN this.hAnimate # 0

   FUNCTION Release
      // Closes currently playing AVI file (if any), destroys the Animation
      // window, and releases the Animation object that was based on this class.

      If this.hAnimate # 0
         this.Close()
         this.DestroyAnimationWindow()
         this.hAnimate := 0
      EndIf
      RETURN SUPER::Release()

   FUNCTION DestroyAnimationWindow
      // Destroys the Animation window
      LOCAL lRet

      If this.hAnimate = 0
         RETURN True
      EndIf
      lRet = dB_DestroyWindow(this.hAnimate)
      If lRet
         this.hAnimate := 0
      EndIf
      RETURN lRet

   FUNCTION Open(cName)
      // Opens .AVI file passed as cName, extracts AVI characteristics, and
      // creates an animation player window if necessary. Can handle AVI
      // filenames using a SourceAlias (:<SourceAlias>:<AVIFilename.AVI>
      LOCAL cAlias, cKey, lFound, cFile, oFile, xIn, nSize, lRet

      // Initialize AVI characteristics property members.
      this.Visible :=         False
      this.Flags :=           0
      this.Copyright :=       False
      this.Format :=          ""
      this.Frames :=          0
      this.FramesPerSecond := 0
      this.ImageWidth :=      0
      this.ImageHeight :=     0
      this.ImageColors :=     0
      this.TransferRate :=    0
      // If closing an already opened AVI file.
      If Empty(cName)
         lRet = dB_SendMessageC4(this.hAnimate, ACM_OPEN, 0, "") # 0
         this.Visible := True
         RETURN lRet
      EndIf
      // Animation window must be destroyed and recreated if AutoPlay, Center,
      // or TransparentImageBackground are changed.
      If (this.protAutoPlay # this.AutoPlay) Or;
                  (this.protCenter # this.Center) Or;
                  (this.protTransparentImageBackground # this.TransparentImageBackground)
         this.DestroyAnimationWindow()
      EndIf
      // Create Animation window if necessary.
      If (this.hAnimate = 0) and (not this.CreateAnimationWindow())
         RETURN False
      EndIf
      cFile = this.UnaliasFilespec(cName)
      // Read and parse AVI file to extract AVI characteristics.
      Try
         oFile = New File()
         oFile.Open(cFile, "RB")
         xIn = oFile.Read(8)
         If USTRINGFROMFOURCC(xIn, 0) # "RIFF"
            oFile.Close()
            RETURN False
         EndIf
         xIn := oFile.Read(4)
         If USTRINGFROMFOURCC(xIn, 0) # "AVI "
            oFile.Close()
            RETURN False
         EndIf
         xIn := oFile.Read(8)
         If USTRINGFROMFOURCC(xIn, 0) # "LIST"
            oFile.Close()
            RETURN False
         EndIf
         xIn := oFile.Read(4)
         If USTRINGFROMFOURCC(xIn, 0) # "HDRL"
            oFile.Close()
            RETURN False
         EndIf
         xIn := oFile.Read(8)
         If USTRINGFROMFOURCC(xIn, 0) # "AVIH"
            oFile.Close()
            RETURN False
         EndIf
         nSize = DWORDFROMFOURBYTE(xIn, 4)
         xIn := oFile.Read(nSize)
         this.FramesPerSecond := 1000000 / DWORDFROMFOURBYTE(xIn, 0)
         this.TransferRate := DWORDFROMFOURBYTE(xIn, 4)
         this.Flags := DWORDFROMFOURBYTE(xIn, 12)
         this.Copyright := BitAnd(this.Flags, AVIF_COPYRIGHTED) # 0
         this.Frames := DWORDFROMFOURBYTE(xIn, 16)
         this.ImageWidth := DWORDFROMFOURBYTE(xIn, 32)
         this.ImageHeight := DWORDFROMFOURBYTE(xIn, 36)
         xIn := oFile.Read(8)
         If USTRINGFROMFOURCC(xIn, 0) # "LIST"
            oFile.Close()
            RETURN False
         EndIf
         xIn := oFile.Read(4)
         If USTRINGFROMFOURCC(xIn, 0) # "STRL"
            oFile.Close()
            RETURN False
         EndIf
         xIn := oFile.Read(8)
         If USTRINGFROMFOURCC(xIn, 0) # "STRH"
            oFile.Close()
            RETURN False
         EndIf
         nSize := DWORDFROMFOURBYTE(xIn, 4)
         xIn := oFile.Read(nSize)
         If USTRINGFROMFOURCC(xIn, 0) # "VIDS"
            oFile.Close()
            RETURN False
         EndIf
         this.Format := USTRINGFROMFOURCC(xIn, 4)
         xIn := oFile.Read(8)
         If USTRINGFROMFOURCC(xIn, 0) # "STRF"
            oFile.Close()
            RETURN False
         EndIf
         nSize := DWORDFROMFOURBYTE(xIn, 4)
         xIn := oFile.Read(nSize)
         this.ImageColors := Int(2 ^ WORDFROMTWOBYTE(xIn, 14))
         oFile.Close()
      Catch(exception e)
         Try
            oFile.Close()
         Catch(exception e)
            // Discard any errors.
         EndTry
         RETURN False
      EndTry
      // AVI characteristics are extracted.
      // Now play the AVI file.
      lRet = dB_SendMessageC4(this.hAnimate, ACM_OPEN, 0, cFile) # 0
      this.Visible := True
      this.PlayAllRepeat()
      RETURN lRet

   FUNCTION Close
      // Stops the playing of an AVI file.

      RETURN this.Open("")

   FUNCTION PlayAllRepeat
      // Plays all frames of an Open()ed AVI file repeatedly.

      RETURN this.PlayFromTo(0, -1, -1)

   FUNCTION PlayFrom(nFrom, nRepeat)
      // Play AVI file from frame (nFrom) through the last frame.
      //   and repeats nRepeat times.
      // Passing -1 in nRepeate causes it to play repeatedly.

      RETURN this.PlayFromTo(nFrom, -1, nRepeat)

   FUNCTION PlayFromTo(nFrom, nTo, nRepeat)
      // Play AVI file from frame (nFrom) through frame (nTo) for nRepeat times.
      // Passing -1 in nRepeate causes it to play repeatedly.

      RETURN dB_SendMessageU4(this.hAnimate, ACM_PLAY, nRepeat, nFrom + BitLShift(nTo, 16)) # 0

   FUNCTION Stop
      // Stops playing the current AVI file.

      RETURN dB_SendMessageU4(this.hAnimate, ACM_STOP, 0, 0) # 0

   FUNCTION Seek(nFrame)
      // Seeks to the specified frame.

      RETURN this.PlayFromTo(nFrame, nFrame, 1)

   FUNCTION UnaliasFilespec(c)

      // c = \Dir\File.Ext or c = D:\Dir\File.Ext or c = File.Ext, etc.
      If (At(":", c) = 0) or (At(":", c) = 2) // Potential SourceAlias?
         RETURN c                             // Not aliased. Return same.
      EndIf
      // Valid SourceAlias must be at least :x:
      If (Not At(":", c) = 1) Or (Not At(":", c, 2) > 2) // Valid SourceAlias?
         RETURN ""                            // Not a valid SourceAlias.
      EndIf
      RETURN this.AliasToPath(c) + "\" + SubStr(c, At(":", c, 2) + 1)

   FUNCTION AliasToPath(c)
      LOCAL cAlias, lFound, cKey
      private cFile // needed for .EXE code ... (vers. 1006)
      If (At(":", c) = 1) And (At(":", c, 2) > 2)
         cAlias = SubStr(c, 2, At(":", c, 2) - 2)
         cFile = right( c, len(c) - rat(":",c) )
      ElseIf (At(":", c) = 0) And (At("\", c) = 0) And (At(".", c) = 0)
         cAlias = c
         cFile = c
      Else
         RETURN ""
      EndIf

      /* 
         Modifications based on suggestions by Brian Johnson,
         dbase.programming newsgroup, March 1, 2003
         This is a fix for QAID: 3936, where the animate
         class returns an error in the runtime environment,
         as it is not able to find the AVI file. That's because
         Source Aliases are not supported in the runtime.
         This should work with DEO, as well as the AVI
         being in the current folder.
      */
      if not "RUNTIME" $ upper( version( 1 ) )
         If _app.SourceAliases.IsKey(cAlias)     // If key exists, return path.
            RETURN _app.SourceAliases[cAlias]
         EndIf
         lFound = False                          // Try to find path based on case
         cKey = _app.SourceAliases.FirstKey      //    insensitive alias match.
         Do While Not Empty(cKey)
            If Upper(cAlias) == Upper(cKey)
               lFound := True
               EXIT
            EndIf
            cKey := _app.SourceAliases.NextKey(cKey)
         EndDo
         RETURN IIf(lFound, _app.SourceAliases[cKey], "")
      else
         // This code is the additional code (vers. 1006), 
         // it will attempt to find the .AVI file in the 
         // DEO path, then in the folder that the
         // executable is in. 
         local cEXE, cINI, fINI, lFound, cLine, cPath,;
               cFullPath

         cEXE = _app.exeName
         cINI = left( cEXE, rat( ".", cEXE ) -1 )+".ini"
         fINI = new File()
         fINI.open( cINI, "r" )

         // Look for DEO entries, if any exist, then we need
         // to check those *first*, and if the file being looked
         // for is not there, then we want to see if it's in the
         // same path as the .exe:

         // First, look for the DEO section [ObjectPath]:
         lFound = false
         do while not fINI.eof() // loop until we hit EOF
            cLine = fINI.gets() // read a line
            if cLine == "[ObjectPath]"
               lFound := true
               exit
            endif
         enddo

         // if lFound is true, we have a DEO section in the
         // .INI file, so we need to check any paths there
         // for the file.
         if lFound 
            lFound := false // reset to false for new search
            do while not fINI.eof()
               cLine = fINI.gets()
               cPath = right( cLine, len(cLine)-at("=", cLine ) )
               cFullPath = cPath+"\"+cFile
               if file( cFullPath )
                  lFound := true
                  RETURN (cPath)
               endif
            enddo

         endif

         // If file is not in DEO path, then check local 
         // folder (one EXE is in):
         if not lFound
            cPath = set("dire")
            cPath+="\"+cFile
            if file( cPath )
               RETURN set("dire") // current folder
            endif

         endif
         // in case of problems
         RETURN set("dire") // current folder
      endif

ENDCLASS  // Animate of PaintBox

