Skip to main content

Notice

Please note that most of the software linked on this forum is likely to be safe to use. If you are unsure, feel free to ask in the relevant topics, or send a private message to an administrator or moderator. To help curb the problems of false positives, or in the event that you do find actual malware, you can contribute through the article linked here.
Topic: [how not to post] From: Do I need Columns UI to switch images in the a (Read 1528 times) previous topic - next topic
0 Members and 1 Guest are viewing this topic.

[how not to post] From: Do I need Columns UI to switch images in the a

I have attempted to follow these instructions, but when I add the script, which is a text fiel to the WSH mod panel I get an error message

Scripting Engine Initialization Failed (WSH Artwork Viewer v1.2 by EIKO, mod by r0lZ, CODE: 0x80020101)
Check the console for more information (Always caused by unexcepted script error).

Here is the script I have imported


// ==PREPROCESSOR==
// @name "WSH Artwork Viewer"
// @version "1.2"
// @author "EIKO, mod by r0lZ"
// ==/PREPROCESSOR==

// Version 1.1:
// - Modified by r0lZ to adapt it to WSHPM v1.5.0.
//  (Replaced CreateTimerTimeout(), CreateTimerInterval() and KillTimer() with new methods.)
// Version 1.2:
// - Workaround for ApplyMask() bug in WSHPM v1.5.0: the reflection was not shown.
//  (see the comments "r0lZ: workaround for ApplyMask() bug in v1.5.0" in the code)
// - Forgot to change CursorFollowAutoTimer.Dispose() (caused crashes)
//
// To do:
// - Change the method to build the menu to use AppendTo() instead of AppendMenuItem(MF_POPUP...)

// ━━━━━━━━━━━━━━━━ flags ━━━━━━━━━━━━━━━━
// Used in SetSmoothingMode()
// For more information, see: http://msdn.microsoft.com/en-us/library/ms534173(VS.85).aspx
var SmoothingMode = {
   Invalid: -1,
   Default: 0,
   HighSpeed: 1,
   HighQuality: 2,
   None: 3,
   AntiAlias: 4
};

// Used in SetInterpolationMode()
// For more information, see: http://msdn.microsoft.com/en-us/library/ms534141(VS.85).aspx
var InterpolationMode = {
   Invalid: -1,
   Default: 0,
   LowQuality: 1,
   HighQuality: 2,
   Bilinear: 3,
   Bicubic: 4,
   NearestNeighbor: 5,
   HighQualityBilinear: 6,
   HighQualityBicubic: 7
};

// Used in SetTextRenderingHint()
var TextRenderingHint = {
   SystemDefault: 0, // Glyph with system default rendering hint
   SingleBitPerPixelGridFit: 1, // Glyph bitmap with hinting
   SingleBitPerPixel: 2, // Glyph bitmap without hinting
   AntiAliasGridFit: 3, // Glyph anti-alias bitmap with hinting
   AntiAlias: 4, // Glyph anti-alias bitmap without hinting
   ClearTypeGridFit: 5 // Glyph ClearType bitmap with hinting
};

// Used in gdi.Font(), you can combine these flags using '|' operator
var FontStyle = {
   Regular: 0,
   Bold: 1,
   Italic: 2,
   BoldItalic: 3,
   Underline: 4,
   Strikeout: 8
};

// h_align/v_align:
// FYI: http://msdn.microsoft.com/en-us/library/sy...ent(VS.71).aspx
var StringAlignment = {
   Near: 0,
   Center: 1,
   Far: 2
};

// FYI: http://msdn.microsoft.com/en-us/library/sy...ing(VS.71).aspx
var StringTrimming = {
    None: 0,
    Character: 1,
    Word: 2,
    EllipsisCharacter: 3,
    EllipsisWord: 4,
    EllipsisPath: 5
};

// FYI: http://msdn.microsoft.com/en-us/library/sy...ags(VS.71).aspx
var StringFormatFlags = {
    DirectionRightToLeft: 0x00000001,
    DirectionVertical : 0x00000002,
    NoFitBlackBox: 0x00000004,
    DisplayFormatControl: 0x00000020,
    NoFontFallback: 0x00000400,
    MeasureTrailingSpaces: 0x00000800,
    NoWrap: 0x00001000,
    LineLimit: 0x00002000,
    NoClip: 0x00004000,
    BypassGDI: 0x80000000
}

// Flags, used with GdiDrawText()
var DT_TOP = 0x00000000;
var DT_LEFT = 0x00000000;
var DT_CENTER = 0x00000001;
var DT_RIGHT = 0x00000002;
var DT_VCENTER = 0x00000004;
var DT_BOTTOM = 0x00000008;
var DT_CENTER = 0x00000001;
var DT_VCENTER = 0x00000004;
var DT_SINGLELINE = 0x00000020;

// Used in RotateFlip()
var RotateFlipType = {RotateNoneFlipY: 6};

// Used in window.SetCursor()
var IDC_ARROW = 32512;
var IDC_HAND = 32649;

// Flags of Menu
var MF_SEPARATOR = 0x00000800;
var MF_GRAYED = 0x00000001;
var MF_UNCHECKED = 0x00000000;
var MF_CHECKED = 0x00000008;
var MF_STRING = 0x00000000;
var MF_POPUP = 0x00000010;

function StringFormat(h_align, v_align, trim, flag) {
   return ((h_align << 28) | (v_align << 24) | (trim << 20) | flag);
}

function RGBA(r, g, b, a) {
   return ((a << 24) | (r << 16) | (g << 8) | (b));
}
function getRed(color) {return ((color >> 16) & 0xff);}
function getGreen(color) {return ((color >> 8) & 0xff);}
function getBlue(color) {return (color & 0xff);}
function getAlpha(color) {return ((color >> 24) & 0xff);}

var FSO = new ActiveXObject("Scripting.FileSystemObject");
var WshShell = new ActiveXObject("WScript.Shell");
var ShellObj = new ActiveXObject("Shell.Application");

// User defined flags
var CursorFollowMode = {
   Auto : 1,
   OnlyStoped : 2,
   Always : 3,
   Never : 4
};

var ImageSort = {
   Default : 1,
   Random : 2,
   Shuffle : 3
};

var ChangeMode = {
   Fade : 0,
   Up: 1,
   Down: 2,
   Left: 3,
   Right: 4
};

// Used with Image.Buttons
var ButtonStates= {
   Normal : 0,
   Hover : 1,   
   Down : 2,   
   Off : 3,
   None: 4
};
// Used with Image.Buttons
var AnimationSteps = {
   toNormal : 20,
   toHover: 40,
   toDown: 60,
   toNone: 80
};
var ButtonOpacity = 128;
var ButtonVisible = false;

var Properties = new GetProperties();
var PathCache = (Properties.Image.PathCacheCapacity > 0) ? new PathCacheFunc (Properties) : null;
var ImageCache = (Properties.Image.ImageCacheCapacity > 0) ? new ImageCacheFunc (Properties) : null;
var PopupMenu = new PopupMenuFunc(Properties);

var Image = new Images(Properties);   
/* ━━━━━━━━━━━━━━━━ Main Function Images ━━━━━━━━━━━━━━━━*/   
function Images (Prop){   
   this.x = 0;
   this.y = 0;
   this.width = window.Width;
   this.height = window.Height;
   
   this.metadb = null;
   this.Group = null;
   this.PathFormat = null;
   
   this.Items = new Array();
   this.Index = 0;
   this.RawBitmap = null;
   this.CurRawBitmap = null;
   
   this.Anix = this.x;
   this.Aniy = this.y;
   this.Anix2 = this.x;
   this.Aniy2 = this.y;
   this.opacity = 255;
   this.AniStep = 0;   
   
   this.ChangeTimerID = 0;
   this.AutoCycleTimerID = 0;
   this.GetImageTimerID = 0;
   this.ResizeCacheTimerID = 0;
   
   this.ChangeByAutoCycle = null;
   this.NeedGetImageTimer = null;
   
   // Caption   
   if (Prop.Image.Caption.Visible)
      this.Caption = new ScrollString("", Prop.Image.Caption.Font, RGBA(0,0,0), this.x, this.height-Prop.Image.Caption.Font.Height, this.width, Prop.Image.Caption.Font.Height+5);
   
   // Buttons
   if (FSO.FolderExists(Prop.Panel.WorkDirectory) && Prop.Image.Buttons.Visible){
      var btnsx = Math.floor((this.width - 68)/ 2);   
      if (this.Caption)
         var btnsy = this.Caption.y -15
      else
         var btnsy = this.height - 13;
      this.Buttons = new Buttons(btnsx, btnsy);
   }
   
   this.OnPaint = function (gr){      
      this.RawBitmap && gr.GdiAlphaBlend(this.RawBitmap, this.Anix, this.Aniy, this.RawBitmap.Width, this.RawBitmap.Height, 0, 0, this.RawBitmap.Width, this.RawBitmap.Height, this.opacity);   
      if (!this.Changed){
         var CurOpacity = Prop.ChangeAnimation.Mode == ChangeMode.Fade ? 255-this.opacity : this.opacity;
         this.CurRawBitmap && gr.GdiAlphaBlend(this.CurRawBitmap, this.Anix2, this.Aniy2, this.CurRawBitmap.Width, this.CurRawBitmap.Height, 0, 0, this.CurRawBitmap.Width, this.CurRawBitmap.Height, CurOpacity);
      }
      this.Caption && this.Caption.draw(gr);
      this.Buttons && this.Buttons.OnPaint(gr);
   }
   
   this.OnNewTrack = function(metadb){
      // If PopupMenu showing, pause this procedure and backup new metadb, this process will continue after exit the menu.
      if (PopupMenu.Showing){
         PopupMenu.MetadbBackup = metadb;
         return;
      }
         
      this.metadb = metadb;
      if (!metadb){
            this.Group = "";
            this.PathFormat = "";
         }
      else{
            var NewGroup = fb.TitleFormat(Prop.Image.GroupFormat).EvalWithMetadb(metadb);
            // If %album% or %artist% == "?", CalcPathFmt () will replace "%album%" with "^", because "?" is wildcard.
            var NewPathFormat = CalcPathFmt(Prop.Image.SourceFormat, metadb);
            
            // The same Group, and there is no "?" in GroupFormat, and prev and new track both no embed, then only refresh Caption, and return.
            if (NewGroup.indexOf("?") < 0 &&
                  NewGroup == this.Group &&
                  (this.PathFormat.indexOf("file://") < 0 && NewPathFormat.indexOf("file://") < 0)
               )
            {
               if (SetCaptionTxt()) window.RepaintRect(this.Caption.x, this.Caption.y, this.Caption.width, this.Caption.height);               
               // If needed to Back to playing.
               CreateCursorFollowAutoTimer();
               return;
            }
            
            // The same PathFormat, if it is not the same track, or the track's tag is changed, will refresh Caption, and return.
            if (NewPathFormat == this.PathFormat)  {
               if (SetCaptionTxt()) window.RepaintRect(this.Caption.x, this.Caption.y, this.Caption.width, this.Caption.height);
               // If needed to Back to playing.
               CreateCursorFollowAutoTimer();
               return;
            }
            
            // New group, New Track...
            this.Group = NewGroup;
            this.PathFormat = NewPathFormat;
      }
      
      //fb.trace("On New Track!");
      
      /* When on new track, skip GetImgTimer, after paint first image, it will be resumed in OnTimer(id).*/
      KillGetImageTimer();
      this.NeedGetImageTimer = true;
      KillAutoCycleTimer();
      KillChangeTimer();
      
      this.Items = GetImageItems(this.PathFormat);
      var CacheItem = ImageCache ? ImageCache.Search(this.Items[0].path) : null;
      if (CacheItem)
         this.Items[0].RawBitmap = CacheItem.RawBitmap
      else {
         GetImg(this.Items[0], Prop, this.width, this.height);
         ImageCache.AddItem(this.Items[0]);                     // Save the first one to ImageCache.
      }
      
      this.Buttons && this.Buttons.SetState();
      
      // Display one or first image.
      if (Prop.Image.Sort == ImageSort.Random)
         this.Index = RandomIndex()
      else
         this.Index = 0;
      CollectGarbage();         // Release memory.   
      this.ChangeReady();      
   }
   
   this.Sort = function (ASortMode){
      if (ASortMode == Prop.Image.Sort) return;
      
      // Sort, if from Shuffle to Default or Random, need to ReLoad and Sort, the list order will be normal as Default.
      var NeedReSortByDefault;
      if (Prop.Image.Sort == ImageSort.Shuffle)
         NeedReSortByDefault = true;         
      
      Prop.Image.Sort = ASortMode;
      window.SetProperty("B03.Image.Sort", Prop.Image.Sort);
      
      if (NeedReSortByDefault)
         this.Items = ReSortByDefault ();
      // Sort, from Default or Randow to Shuffle, Sort it by random.
      if (Prop.Image.Sort == ImageSort.Shuffle)
         this.Items = this.Items.sort(function(){return (Math.random() > 0.5) ? -1 : 1;});
         
      // Display first image.
      if (Prop.Image.Sort == ImageSort.Random)
         this.Index = RandomIndex()
      else
         this.Index = 0;
      CollectGarbage();         // Release memory.   
      this.ChangeReady();
   }
   
   // first
   this.First = function (){
      if (this.Index == 0) return;
      this.Prev(0);
   }
   
   // last
   this.Last = function (){
      if (this.Index == this.Items.length-1) return;
      this.Next(this.Items.length-1);
   }   
   
   // prev
   this.Prev = function (index){
      if (this.Items.length <= 1) return;

      this.Index = typeof(index) == "number" ?  index : this.Index - 1;
      if (this.Index < 0)
         this.Index = this.Items.length - 1;         
      
      // For give a different action from this.next(); so save current ChangeMode,
      // The original state will be restored after image changed, in function Image.OnTimer();      
      if (Prop.ChangeAnimation.Enable && Prop.ChangeAnimation.Mode != ChangeMode.Fade){
         this.CurMode_OnAction = Prop.ChangeAnimation.Mode;
         
         if (Prop.ChangeAnimation.Mode == ChangeMode.Up || Prop.ChangeAnimation.Mode == ChangeMode.Down)
            Prop.ChangeAnimation.Mode = ChangeMode.Down;
         else   if (Prop.ChangeAnimation.Mode == ChangeMode.Left || Prop.ChangeAnimation.Mode == ChangeMode.Right)
            Prop.ChangeAnimation.Mode = ChangeMode.Right;
      }
            
      this.ChangeReady();
   }
   
   // next
   this.SetNextImgIndex = function (index){
      if (this.Items.length <= 1) return false;
      
      this.Index = typeof(index) == "number" ?  index : (this.Index + 1);
      if (this.Index >= this.Items.length)
         this.Index = 0;   
      
      return true;
   }
   this.Next = function (index){
      if (!this.SetNextImgIndex(index)) return;
      
      // For give a different action from this.prev(); so save current ChangeMode,
      // The original state will be restored after image changed, in function Image.OnTimer();
      if (Prop.ChangeAnimation.Enable && Prop.ChangeAnimation.Mode != ChangeMode.Fade){
         this.CurMode_OnAction = Prop.ChangeAnimation.Mode;
         
         if (Prop.ChangeAnimation.Mode == ChangeMode.Up || Prop.ChangeAnimation.Mode == ChangeMode.Down)
            Prop.ChangeAnimation.Mode = ChangeMode.Up;
         else   if (Prop.ChangeAnimation.Mode == ChangeMode.Left || Prop.ChangeAnimation.Mode == ChangeMode.Right)
            Prop.ChangeAnimation.Mode = ChangeMode.Left;   
      }
            
      this.ChangeReady();
   }      
   
   // Start or Pause AutoCycle
   this.AutoCycle = function(){
      Prop.Cycle.Enable = !Prop.Cycle.Enable;
      window.SetProperty("N01.Cycle.Enable", Prop.Cycle.Enable);
      if (Prop.Cycle.Enable){
         var index;
         if (Prop.Image.Sort == ImageSort.Random)
            index = RandomIndex();         
         if (this.SetNextImgIndex(index))
            this.ChangeReady();
      }
      else
         KillAutoCycleTimer();
   }
   
   // Reflection or Stretch or Refresh...
   this.Refresh = function(){
      GetImg(this.Items[this.Index], Prop, this.width, this.height);
      this.ChangeReady();
   }
   
   // Change Animation Mode
   this.SetChangeMode = function (AChangeMode){
      if (AChangeMode == Prop.ChangeAnimation.Mode) return;
      Prop.ChangeAnimation.Mode = AChangeMode;
      window.SetProperty("M02.Change.Animation.Mode", Prop.ChangeAnimation.Mode);
      if (this.SetNextImgIndex())
         this.ChangeReady()
      else
         this.Refresh();         // If there is no another image, still give a demo to show how the new ChangeMode works on.
   }
   
   this.ChangeReady = function (){
      // If the Change is not made by AutoCycleTimer, keep time and back to playing.
      if (!this.ChangeByAutoCycle && CursorFollowAutoTimerID){
         KillCursorFollowAutoTimer();
         NeedCursorFollowAutoTimer = true;
      }
      
      this.Anix = this.x;
      this.Aniy = this.y;
      this.Anix2 = this.x;
      this.Aniy2 = this.y;
      
      // If no image or wrong size, then search it in ImageCache, or re get it.
      if ((!this.Items[this.Index].RawBitmap) || (this.Items[this.Index].RawBitmap.Width != this.width)){
         var CacheItem = ImageCache ? ImageCache.Search(this.Items[this.Index].path) : null;      
         if (CacheItem)
            this.Items[this.Index].RawBitmap = CacheItem.RawBitmap
         else
            GetImg(this.Items[this.Index], Prop, this.width, this.height);
      }
            
      if (Prop.ChangeAnimation.Enable){
            this.CurRawBitmap = this.RawBitmap;
            this.RawBitmap = this.Items[this.Index].RawBitmap;               
            
            switch (Prop.ChangeAnimation.Mode ){
            case ChangeMode.Fade :
                  this.opacity = 0;
                  this.AniStep = Math.ceil(255 * Prop.ChangeAnimation.RefreshInterval / Prop.ChangeAnimation.Duration);                  
                  break;
            case ChangeMode.Up :
                  this.opacity = 255;
                  this.Aniy = this.height;
                  this.AniStep = Math.ceil(this.height * Prop.ChangeAnimation.RefreshInterval / Prop.ChangeAnimation.Duration);                  
                  break;
            case ChangeMode.Down :
                  this.opacity = 255;
                  this.Aniy = this.y - this.height;
                  this.AniStep = Math.ceil(this.height * Prop.ChangeAnimation.RefreshInterval / Prop.ChangeAnimation.Duration);                  
                  break;
            case ChangeMode.Left :
                  this.opacity = 255;
                  this.Anix = this.width;
                  this.AniStep = Math.ceil(this.width * Prop.ChangeAnimation.RefreshInterval / Prop.ChangeAnimation.Duration);                  
                  break;
            case ChangeMode.Right :
                  this.opacity = 255;
                  this.Anix = this.x - this.width;
                  this.AniStep = Math.ceil(this.width * Prop.ChangeAnimation.RefreshInterval / Prop.ChangeAnimation.Duration);                  
                  //break;
            }
            this.Changed = false;
            KillChangeTimer();               
            this.ChangeTimerID = window.SetInterval(on_timer, Prop.ChangeAnimation.RefreshInterval);         
      }
      else{
            this.RawBitmap = this.Items[this.Index].RawBitmap;
            this.opacity = 255;
            this.Changed = true;
            this.ChangeImage();
               
            // AutoCycle            
            if (Prop.Cycle.Enable && this.Items.length > 1 && !PopupMenu.Showing){
               KillAutoCycleTimer();
               this.AutoCycleTimerID = window.SetTimeout(on_timer, Prop.Cycle.Period);
            }
               
            // Get Other Images of the same track.
            if (this.NeedGetImageTimer ){
               this.NeedGetImageTimer  = null;
               this.iGet = 1;
               this.GetImageTimerID = window.SetInterval(on_timer, 200);
            }
            
            // If needed to Back to playing.
            CreateCursorFollowAutoTimer();
      }
      CollectGarbage();         // Release memory.      
   }
   
   this.ChangeImage = function (){
      this.Caption && SetCaptionTxt();
      window.Repaint();
   }
   
   this.OnTimer = function (id){
      // 1. Change Image;
      if (this.ChangeTimerID && id == this.ChangeTimerID){
            switch (Prop.ChangeAnimation.Mode ){
            case ChangeMode.Fade :
                  if (this.opacity < 255){
                     this.opacity = Math.min(this.opacity + this.AniStep, 255);
                     this.ChangeImage();
                  }
                  else
                     this.Changed = true;
                  break;
            case ChangeMode.Up :
                  this.Anix = this.x;
                  this.Anix2 = this.x;
                  if (this.Aniy > this.y){
                     this.Aniy = Math.max(this.Aniy - this.AniStep, this.y);
                     this.Aniy2 = this.Aniy - this.height;
                     this.ChangeImage();
                  }
                  else
                     this.Changed = true;
                  break;
            case ChangeMode.Down :
                  this.Anix = this.x;
                  this.Anix2 = this.x;
                  if (this.Aniy < this.y){
                     this.Aniy = Math.min(this.Aniy + this.AniStep, this.y);
                     this.Aniy2 = this.Aniy + this.height;
                     this.ChangeImage();
                  }
                  else
                     this.Changed = true;
                  break;
            case ChangeMode.Left :
                  this.Aniy = this.y;
                  this.Aniy2 = this.y;
                  if (this.Anix > this.x){
                     this.Anix = Math.max(this.Anix - this.AniStep, this.x);
                     this.Anix2 = this.Anix - this.width;
                     this.ChangeImage();
                  }
                  else
                     this.Changed = true;
                  break;
            case ChangeMode.Right :
                  this.Aniy = this.y;
                  this.Aniy2 = this.y;
                  if (this.Anix < this.x){
                     this.Anix = Math.min(this.Anix + this.AniStep, this.x);
                     this.Anix2 = this.Anix + this.width;
                     this.ChangeImage();
                  }
                  else
                     this.Changed = true;
                  break;
               }
               
            if (this.Changed){
               KillChangeTimer();
               
               // Restored original state of ChangeAnimation.Mode
               if (this.CurMode_OnAction){
                  Prop.ChangeAnimation.Mode = window.GetProperty("M02.Change.Animation.Mode", this.CurMode_OnAction);
                  this.CurMode_OnAction = null;
               }
               
               // AutoCycle               
               if (Prop.Cycle.Enable && this.Items.length > 1 && !PopupMenu.Showing){
                  KillAutoCycleTimer();
                  this.AutoCycleTimerID = window.SetTimeout(on_timer, Prop.Cycle.Period);
               }
               
               // Get Other Images of the same track.
               if (this.NeedGetImageTimer ){
                  this.NeedGetImageTimer  = null;
                  this.iGet = 1;
                  this.GetImageTimerID = window.SetInterval(on_timer, 200);
               }
               
               // If needed to Back to playing.
               CreateCursorFollowAutoTimer();
            }         
      }
      
      // 2. Auto Cycle;
      else if (this.AutoCycleTimerID && id == this.AutoCycleTimerID){
            KillAutoCycleTimer();
            this.ChangeByAutoCycle = true;
            var index;
            if (Prop.Image.Sort == ImageSort.Random)
               index = RandomIndex();         
            if (this.SetNextImgIndex(index))
               this.ChangeReady();
            if (Prop.Image.Sort == ImageSort.Shuffle && this.Index == this.Items.length-1){
               this.Items = this.Items.sort(function(){return (Math.random() > 0.5) ? -1 : 1;});   
            }
            this.ChangeByAutoCycle = null;
      }
      
      // 3. Re Get Image, as on new track or resize;
      else if (this.GetImageTimerID && id == this.GetImageTimerID){
         //fb.trace("iGet = "+this.iGet);
         if (this.iGet >= this.Items.length){
            KillGetImageTimer();
            return;
         }
         GetImg (this.Items[this.iGet], Prop, this.width, this.height);
         this.iGet ++;
      }
      
      // 4. Re Get Image of Cache, as on resize; Condition if (ImageCache) has tested as this timer is created;
      else if (this.ResizeCacheTimer && id == this.ResizeCacheTimerID){
         //fb.trace("iResize = "+this.iResize);
         if (this.iResize >= ImageCache.length || !ImageCache[this.iResize].path){
            KillResizeCacheTimer();
            return;
         }
         GetImg (ImageCache[this.iResize], Prop, this.width, this.height);
         this.iResize ++;
      }
      
      // 5. AutoScroll Caption, if Caption visible is true;
      else if (this.Caption && this.Caption.timer && id == this.Caption.timer.ID){
         this.Caption.OnTimer(id);
      }
      
      // 6. If CursorFollowMode == Auto, go back to palying cover;
      else if (CursorFollowAutoTimerID && id == CursorFollowAutoTimerID){
            KillCursorFollowAutoTimer();
            if (fb.IsPlaying || fb.IsPaused) {
               on_playback_new_track();
               SkipFocus = null;
            }
      }
      
      // 7. Refresh Buttons, as the Buttons visible is true or on MouseMove.
      else if (this.Buttons){
         this.Buttons.OnTimer(id);
      }
      CollectGarbage();         // Release memory.   
   }
   
   this.ResizeAll = function (){
      // When window OnResize or Image.Stretch changed, All images on the list of current items and cache items need to resize.
      KillGetImageTimer;
      this.iGet = 0;
      this.GetImageTimerID = window.SetInterval(on_timer, 200);
      
      if (!ImageCache) return;
      KillResizeCacheTimer();
      this.iResize = 0;
      this.ResizeCacheTimerID = window.SetInterval(on_timer, 200);
   }   
}
   
/* ━━━━━━━━━━━━━━━━ Set Cursor Follow Mode Function ━━━━━━━━━━━━━━━━
Entrance, To get the Image.metadb
*/
var CursorFollowAutoTimerID, NeedCursorFollowAutoTimer;
function SetCursorFollowMode(AFollowMode){
   if (AFollowMode != Properties.Panel.CursorFollowMode) {
      Properties.Panel.CursorFollowMode = AFollowMode;
      window.SetProperty("A03.Panel.CursorFollowMode", AFollowMode);
      
      /* Only for WSH Cover Viewer of Window Info
      window.NotifyOthers("InfoCoverCursorFollowMode", AFollowMode);
      */
      CursorFollow(AFollowMode);
   }
}
function CursorFollow(AFollowMode){
   KillCursorFollowAutoTimer();
   if (AFollowMode == CursorFollowMode.Auto){
         if (fb.IsPlaying | fb.IsPaused)
            on_playback_new_track()
         else
            on_item_focus_change();
      }
   else if (AFollowMode == CursorFollowMode.OnlyStoped) {
         if (fb.IsPlaying | fb.IsPaused)
            on_playback_new_track()
         else
            on_item_focus_change();
      }
   else if (AFollowMode == CursorFollowMode.Always) {
         on_item_focus_change();
      }
   else {                              //CursorFollowMode.Never
         if (fb.IsPlaying | fb.IsPaused)
            on_playback_new_track()
         else
            on_playback_stop();
   }
   SkipFocus = null;
}

/* ━━━━━━━━━━━━━━━━ Callback Functions ━━━━━━━━━━━━━━━━*/
function on_notify_data(name, info) {
   if (name == "StrArray") {
      info = info.split(",");
      for (var i = 0; i < info.length; i++){
         if (info == "[WSH Cover Viewer]"){            
            Properties.Panel.StrArray = info.splice(i+1, 53);            
            window.SetProperty("A02.Panel.StrArray", Properties.Panel.StrArray.toString());
            for(var j = 0; j < Properties.Panel.StrArray.length; j++)
               Properties.Panel.StrArray[j] = Properties.Panel.StrArray[j].replace(/\^/g,",");   
            
            if(FSO.FolderExists(Properties.Panel.WorkDirectory) && Properties.Image.Buttons.Visible){   
               Image.Buttons.Prevbtn.Tooltip.text = Properties.Panel.StrArray[14];
               Image.Buttons.Nextbtn.Tooltip.text = Properties.Panel.StrArray[15];
               Image.Buttons.Playbtn.Tooltip.text = Properties.Panel.StrArray[13] + " / " + Properties.Panel.StrArray[12];
               Image.Buttons.Playbtn.playtip = null;   
            }
            CollectGarbage();         // Release memory.   
            break;
         }
      }
   }      
   /* Only for WSH Cover Viewer of Window Info
   else if (name == "InfoPanelCursorFollowMode"){
      SetCursorFollowMode(info);
   }   */
}

var OnIni;   //Initialization, for skipfocus only at initialization in on_playback_new_track(metadb);
function on_size() {   
   if (window.Width < 10 || window.Height < 13) return;
   OnIni = true;
   
   Image.x = 0;
   Image.y = 0;
   Image.width = window.Width;
   Image.height = window.Height;   
   if (Image.Items.length == 0)
      CursorFollow(Properties.Panel.CursorFollowMode);
   GetImg(Image.Items[Image.Index], Properties, window.Width, window.Height);
   Image.RawBitmap = Image.Items[Image.Index].RawBitmap;
   Image.ResizeAll();
   
   if (Image.Caption) Image.Caption.x = 0;
   if (Image.Caption) Image.Caption.y = Image.height - Properties.Image.Caption.Font.Height - 2;
   if (Image.Caption) Image.Caption.width = window.Width;
   if (Image.Caption) Image.Caption.TxtWidth = 0;
   
   if (Image.Buttons) Image.Buttons.x = Math.floor((Image.width - 68)/ 2);
   if (Image.Buttons) {
      if (Image.Caption)
         Image.Buttons.y = Image.Caption.y -15;
      else
         Image.Buttons.y = Image.height - 13;
   }
   if (Image.Buttons) Image.Buttons.OnResize();
   
   CollectGarbage();         // Release memory.   
   OnIni = null;
}

function on_paint(gr) {
   if (window.Width < 10 || window.Height < 13) return;
   gr.SetSmoothingMode(Properties.Image.SmoothingMode);
   gr.SetInterpolationMode(Properties.Image.InterpolationMode);
   
   Image && Image.OnPaint(gr);
}

function on_timer_stoptimer(id) {
   KillStopTimer();
   on_playback_stop();      
   Image && Image.OnTimer(id);
}

function on_timer(id) {
   Image && Image.OnTimer(id);
}

var SkipFocus;
function on_playback_new_track(metadb){
   if (Properties.Panel.CursorFollowMode == CursorFollowMode.Auto && CursorFollowAutoTimerID){
      return;
   }
   if (Properties.Panel.CursorFollowMode == CursorFollowMode.Auto ||
      Properties.Panel.CursorFollowMode == CursorFollowMode.OnlyStoped ||
      Properties.Panel.CursorFollowMode == CursorFollowMode.Never){      
      var metadb = fb.GetNowPlaying();
      on_metadb_changed(metadb);
      if(!OnIni && fb.CursorFollowPlayback)
         SkipFocus = true;
   }
}
function on_item_focus_change(){      
   if (SkipFocus){
      SkipFocus = null;
      return;
   }
   if (Properties.Panel.CursorFollowMode == CursorFollowMode.Auto ||
         ((Properties.Panel.CursorFollowMode == CursorFollowMode.OnlyStoped) && (!fb.IsPlaying && !fb.IsPaused)) ||
         Properties.Panel.CursorFollowMode== CursorFollowMode.Always
      )
   {
      if (Properties.Panel.CursorFollowMode == CursorFollowMode.Auto && (fb.IsPlaying | fb.IsPaused)){
         KillCursorFollowAutoTimer();
         NeedCursorFollowAutoTimer = true;
      }
      var metadb = fb.GetFocusItem();
      on_metadb_changed(metadb);
      
   }
}
// cmd: (integer, begin with 0): default, play, next, prev, settrack, rand, resume:0123456
function on_playback_starting(cmd, is_paused) {
   // Skip all on_stop procedure playing immediately after stoped.
   if (StopTimerID){
      KillStopTimer();
      return;
   }
}
var StopTimerID = 0;
function KillStopTimer(){
   if (StopTimerID){
      window.ClearInterval(StopTimerID);
      StopTimerID = 0;
      CollectGarbage();      
   }
}
// reason: (integer, begin with 0): user, eof, starting_another, shutting_down:0123
function on_playback_stop(reason) {
   // If a command will playing immediately after stoped(like prev, next etc.), on_stop procedure will be skiped in on_starting().
   if (reason || reason == 0){
      StopTimerID = window.SetInterval(on_timer_stoptimer, 10);
      return;
   }
   
   // The real Stop, will come back from on_timer(), but reason will be null.
   if (Properties.Panel.CursorFollowMode == CursorFollowMode.Auto && CursorFollowAutoTimerID){
      KillCursorFollowAutoTimer();
      return;
   }
   if (Properties.Panel.CursorFollowMode == CursorFollowMode.Always)
      return;
   if (Properties.Panel.CursorFollowMode == CursorFollowMode.Auto || Properties.Panel.CursorFollowMode == CursorFollowMode.OnlyStoped)
      var metadb = fb.GetFocusItem()
   else if (Properties.Panel.CursorFollowMode == CursorFollowMode.Never)
      var metadb = null;
   on_metadb_changed(metadb);
   
}
function on_metadb_changed(metadb, fromhook) {
   Image && Image.OnNewTrack(metadb);   
   if (metadb)
      window.WatchMetadb(metadb)
   else
      window.UnWatchMetadb();
}

function on_mouse_wheel(step) {   
   if (step > 0)
      Image.Prev()
   else
      Image.Next();
}
function on_mouse_move(x, y) {
   if (Image.Buttons) Image.Buttons.OnMouseMove(x,y);
}
function on_mouse_lbtn_down(x, y, mask) {
   if (Image.Buttons) Image.Buttons.OnMouseLbtnDown(x,y,mask);
}
function on_mouse_lbtn_up(x, y, mask) {
   if (Image.Buttons) Image.Buttons.OnMouseLbtnUp(x,y,mask);
}
function on_mouse_leave() {
   if (Image.Buttons) Image.Buttons.OnMouseLeave();
}

MK_SHIFT    = 0x0004;                // The SHIFT key is pressed.
function on_mouse_rbtn_up(x, y, vkey){   
   if (vkey == MK_SHIFT)
         return false;
   else
      PopupMenu.Show(x,y);         // Show customize menu.
      return true;      
   /*
   return false: popup system menu;
   return true : don't popup system menu;
   */
}

/*                                  ********* User Functions *********                                                      */

/* ━━━━━━━━━━━━━━━━ Properties ━━━━━━━━━━━━━━━━
   All Settings stored here
*/
function GetProperties(){
   // 1---------------------------------------------------------------------
   this.Panel = {
      // The buttons's images / Help File / Blacklist File
      WorkDirectory : window.GetProperty("A01.Panel.WorkDirectory", "Images\\EIKO\\WSH Cover"),
      HelpFile : "WSH Cover Viewer Help.txt",
      // All needed strings.
      StrArray : window.GetProperty("A02.Panel.StrArray", ""),
      // Cursor Follow Mode
      CursorFollowMode : window.GetProperty("A03.Panel.CursorFollowMode", CursorFollowMode.Auto),
      // If CursorFollowMode == Auto, Cover will come back to playing automaticlly with this timer.
      CursorFollowMode_AutoBackInterval : window.GetProperty("A04.Panel.CursorFollowMode.AutoBackInterval", 10000)
   }
      
   if (this.Panel.StrArray.length < 100){
      this.Panel.StrArray = "WSH Cover Viewer,path does not exist.,file does not exist.,Cursor Follow Mode,Auto,Only Stoped,Always,Never,Sort,Default,Random,Shuffle,Pause Cycle,Resume Cycle,Previous Image,Next Image,First Image,Last Image,View With External Viewer,This image is embed image^ it can't be displayed in external viewer.,Open Containing Folder,Image Resize,Stretch Image,Keep Aspect Ratio,Change Animation,Disable,Enable,Fade,Go Up,Go Down,Go Left,Go Right,Others,Reflection,Buttons,Caption,Refresh Image,Clear Cache,Blacklist,Join,Remove,View,Open Blacklist File,Refresh Blacklist,Delete File,The default cover can't be joined to the Blacklist. ,Can not be deleted.,Yes^ delete from hard drive. Can't be restored.,No^  join the Blacklist;,Cancel^ do nothing.,Continue?,Properties,Help...";
      //this.Panel.StrArray ="WSH封面查看器,路径不存在。,文件不存在。,光标跟随模式,自动,仅在非播放时,总是,从不,排序,预设,随机,乱序,暂停循环,开始循环,上一张图片,下一张图片,第一张图片,最后一张图片,使用外部查看器打开,内嵌图片,无法用外部查看器打开。,打开图片所在文件夹,缩放效果,拉伸图像,保持比例,切换效果,禁用,启用,淡入淡出,向上退出,向下退出,向左退出,向右退出,其他,倒影,按钮,标题,刷新图片,清除缓存,黑名单,添加,删除,查看,查看全部,刷新名单,删除文件,默认封面不能加入黑名单。,不能删除。,是,删除,不可恢复;,否,加入黑名单;,取消,什么也不做。,继续吗?,属性设置,帮助...";
      window.SetProperty("A02.Panel.StrArray", this.Panel.StrArray);
   }
   this.Panel.StrArray = this.Panel.StrArray.split(",");
   for(var i = 0; i < this.Panel.StrArray.length; i++)
      this.Panel.StrArray = this.Panel.StrArray.replace(/\^/g,",");
      
   //fb.trace("abs =  "+   FSO.GetAbsolutePathName(this.Panel.WorkDirectory))   
   if (this.Panel.WorkDirectory && !FSO.GetDriveName(this.Panel.WorkDirectory))
      this.Panel.WorkDirectory = fb.FoobarPath + this.Panel.WorkDirectory;
   var path = this.Panel.WorkDirectory;
   if (path && path.substring(path.length-1, path.length) != "\\")         // must be end with "\\"
      this.Panel.WorkDirectory = path + "\\";
      
   if (!FSO.FolderExists(this.Panel.WorkDirectory))
      WshShell.Popup("Properties\rA01.Panel.WorkDirectory\r\r\" " + this.Panel.WorkDirectory + " \"      \r" +  this.Panel.StrArray[1], 7, this.Panel.StrArray[0], 0 + 16);
   else{
      // Help file
      this.Panel.HelpFile = this.Panel.WorkDirectory + this.Panel.HelpFile;
   }
         
   this.Panel.CursorFollowMode = Math.max(this.Panel.CursorFollowMode, CursorFollowMode.Auto);
   this.Panel.CursorFollowMode = Math.min(this.Panel.CursorFollowMode, CursorFollowMode.Never);
   window.SetProperty("A03.Panel.CursorFollowMode", this.Panel.CursorFollowMode);
      
   if (this.Panel.CursorFollowMode_AutoBackInterval < 2000){
      this.Panel.CursorFollowMode_AutoBackInterval = 2000;
      window.SetProperty("A04.Panel.CursorFollowMode.AutoBackInterval", 2000);
   }
         
   // 2 ---------------------------------------------------------------------
   this.Image = {   
      // In the same group, if SourceFormat is not changed, panel will not check any new files, and the images's cycle will not be reset.   
      GroupFormat : window.GetProperty("B01.Image.GroupFormat", "%artist% | %album%"),
      // Separate paths by "||"; "<embed>" means embed cover, must be a individual path in sourceformat.
      SourceFormat : window.GetProperty("B02.Image.SourceFormat", "<embed>||$directory_path(%path%)\\cover.*||$directory_path(%path%)\\front.*||$directory_path(%path%)\\back.*||$directory_path(%path%)\\%artist%.*||$directory_path(%path%)\\folder.*"),
      Sort : window.GetProperty("B03.Image.Sort", ImageSort.Shuffle),
      DefaultCoverPath : window.GetProperty("B04.Image.DefaultCoverPath", "Images\\EIKO\\WSH Cover\\Default.jpg"),
      
      // Only these types of files can be displayed. Not necessary to modify this at most times.
      SupportTypes: new Array("jpg", "jpeg", "png", "gif", "bmp"),
      // File larger than this value will not be loaded. <=0 means no limit.
      MaxFileSize: window.GetProperty("B05.Image.MaxFileSize(MB)", 2.5),
      // This panel also stores path search result, only stores the strings.
      PathCacheCapacity: window.GetProperty("B06.Image.PathCacheCapacity", 100),
      // Images is stored after resize, so you can set this value larger if your panel size is not very large.
      ImageCacheCapacity: window.GetProperty("B07.Image.ImageCacheCapacity", 50),
      // Image on Blacklist will not display, if Enable is true.
      Blacklist : {
         Enable: window.GetProperty("B08.Image.Blacklist.Enable", true),
         File : "Blacklist.txt",
         Items : null
      },   
      
      SmoothingMode : window.GetProperty("C01.Image.SmoothingMode", SmoothingMode.HighQuality),   
      InterpolationMode : window.GetProperty("C02.Image.InterpolationMode", InterpolationMode.HighQuality),
      // Stretch images to fit panel.
      Stretch : window.GetProperty("C03.Image.Stretch", true),
      // Keep image's aspect ratio.
      KeepAspectRatio : window.GetProperty("C04.Image.KeepAspectRatio", true)   
   }
   
   if(!FSO.GetDriveName(this.Image.DefaultCoverPath))
      this.Image.DefaultCoverPath = fb.FoobarPath + this.Image.DefaultCoverPath;
   if (!FSO.FileExists(this.Image.DefaultCoverPath))
      WshShell.Popup("Properties:\rB04.Image.DefaultCoverPath\r\r\" " + this.Image.DefaultCoverPath + " \"      \r" + this.Panel.StrArray[2], 7, this.Panel.StrArray[0], 0 + 16);   
      
   if (this.Image.MaxFileSize < 0){
      this.Image.MaxFileSize = 0;
      window.SetProperty("B05.Image.MaxFileSize(MB)", this.Image.MaxFileSize);
   }
   else
      this.Image.MaxFileSize = this.Image.MaxFileSize * 1024 * 1024;

   if (this.Image.PathCacheCapacity < 0)   {
      this.Image.PathCacheCapacity = 0;   
      window.SetProperty("B06.Image.PathCacheCapacity", this.Image.PathCacheCapacity);
   }
   
   if (this.Image.ImageCacheCapacity < 0)   {
      this.Image.ImageCacheCapacity = 0;   
      window.SetProperty("B07.Image.ImageCacheCapacity", this.Image.ImageCacheCapacity);
   }
   
   this.Image.Blacklist.File = this.Panel.WorkDirectory + this.Image.Blacklist.File;
   
   if (this.Image.Blacklist.Enable)      
      this.Image.Blacklist.Items = Blacklist_Load(this.Image.Blacklist.File);
   
   this.Image.SmoothingMode = Math.max(this.Image.SmoothingMode, SmoothingMode.Invalid);
   this.Image.SmoothingMode = Math.min(this.Image.SmoothingMode, SmoothingMode.AntiAlias);
   window.SetProperty("C01.Image.SmoothingMode", this.Image.SmoothingMode);
         
   this.Image.InterpolationMode = Math.max(this.Image.InterpolationMode, InterpolationMode.Invalid);
   this.Image.InterpolationMode = Math.min(this.Image.InterpolationMode, InterpolationMode.HighQualityBicubic);
   window.SetProperty("C02.Image.InterpolationMode", this.Image.InterpolationMode);
      
      // 2 F --------------------------------------------------------------------
      this.Image.Frame = {
         Visible : window.GetProperty("F01.Image.Frame.Visible", true),
         Offset : window.GetProperty("F02.Image.Frame.Offset", 2),
         BrushColor : window.GetProperty("F03.Image.Frame.Brush.Color", "255,255,255,255"),
         PenColor : window.GetProperty("F04.Image.Frame.Pen.Color", "140,160,170,200")
      }
   
   if (this.Image.Frame.Offset < 0){
      this.Image.Frame.Offset = 0;
      window.SetProperty("F02.Image.Frame.Offset", this.Image.Frame.Offset);
   }
   
   this.Image.Frame.BrushColor = CheckColor(this.Image.Frame.BrushColor);
   this.Image.Frame.PenColor = CheckColor(this.Image.Frame.PenColor);      
   
      // 2 G -------------------------------------------------------------------------
      this.Image.Reflection = {
         Visible : window.GetProperty("G01.Image.Reflection.Visible", true),
         MaskPath : window.GetProperty("G02.Image.Reflection.MaskPath", "Images\\EIKO\\WSH Cover\\mask.png"),
         Mask : null,
         Offset : window.GetProperty("G03.Image.Reflection.Offset", -1),
         Opacity : window.GetProperty("G04.Image.Reflection.Opacity", 200)
      }
            
   if (!FSO.GetDriveName(this.Image.Reflection.MaskPath))
      this.Image.Reflection.MaskPath = fb.FoobarPath + this.Image.Reflection.MaskPath;
   if (FSO.FileExists(this.Image.Reflection.MaskPath)) {
      this.Image.Reflection.Mask = gdi.Image(this.Image.Reflection.MaskPath)
        // r0lZ: workaround for ApplyMask() bug in v1.5.0
        this.Image.Reflection.Mask.RotateFlip(RotateFlipType.RotateNoneFlipY);
   } else
      WshShell.Popup("Properties:\rG02.Image.Reflection.MaskPath\r\r\" " + this.Image.Reflection.MaskPath + " \"      \r" + this.Panel.StrArray[2], 7, this.Panel.StrArray[0], 0 + 16);   
         
   if (this.Image.Reflection.Offset < -this.Image.Frame.Offset*2){
      this.Image.Reflection.Offset = -this.Image.Frame.Offset*2;
      window.SetProperty("G03.Image.Reflection.Offset", this.Image.Reflection.Offset);
   }
   
   this.Image.Reflection.Opacity = Math.max(this.Image.Reflection.Opacity, 0);
   this.Image.Reflection.Opacity = Math.min(this.Image.Reflection.Opacity, 255);
   window.SetProperty("G04.Image.Reflection.Opacity", this.Image.Reflection.Opacity);
   
   if (this.Image.Frame.BrushColor){
      var tc = this.Image.Frame.BrushColor;
      this.Image.Frame.BrushColor_Deep = RGBA(getRed(tc), getGreen(tc), getBlue(tc), Math.floor(getAlpha(tc)*this.Image.Reflection.Opacity/255));
      this.Image.Frame.BrushColor_Light = RGBA(getRed(tc), getGreen(tc), getBlue(tc), 0);
   }
   if (this.Image.Frame.PenColor){
      var tc = this.Image.Frame.PenColor;
      this.Image.Frame.PenColor_Deep = RGBA(getRed(tc), getGreen(tc), getBlue(tc), Math.floor(getAlpha(tc)*this.Image.Reflection.Opacity/255));
      this.Image.Frame.PenColor_Light = RGBA(getRed(tc), getGreen(tc), getBlue(tc), 0);
   }
   
      // 2 H -------------------------------------------------------------------
      this.Image.Caption = {
         Visible: window.GetProperty("H01.Image.Caption.Visible", true),
            // Changed by r0lZ:
            // alt caption format string showing only the image filename without the path and extension:
            // $substr(%cover_path%,$add($strrchr(%cover_path%,\),1),$sub($len(%cover_path%),$add($len($ext(%cover_path%)),1)))  '('%cover_index%/%cover_total%')'
         Format : window.GetProperty("H02.Image.Caption.Format", "$substr(%cover_path%,$add($strrchr(%cover_path%,\),1),$sub($len(%cover_path%),$add($len($ext(%cover_path%)),1)))  '('%cover_index%/%cover_total%')'"),
         //Format : window.GetProperty("H02.Image.Caption.Format", "$upper(%cover_path%)  '('%cover_index%/%cover_total%')'"),

         Font: window.GetProperty("H03.Image.Caption.Font", "Tahoma, 12, 0|4"),
         Color: window.GetProperty("H04.Image.Caption.Color", "255, 255, 255, 255"),
         Glow: window.GetProperty("H05.Image.Caption.Glow", "0, 32, 128, 255"),
         PlayingGlow: window.GetProperty("H06.Image.Caption.PlayingGlow", "64, 128, 255, 255")
      }
      
   this.Image.Caption.Font = SetFont(this.Image.Caption.Font);
   if (!this.Image.Caption.Font){
      this.Image.Caption.Font = gdi.Font("Tahoma", 12, 0|4);
   }
   this.Image.Caption.Font2 = gdi.Font("Tahoma", 11, 2);

   if (this.Image.Caption.Color)
      this.Image.Caption.Color = CheckColor(this.Image.Caption.Color)
   else
      this.Image.Caption.Color = RGBA(0,0,0);
   
   if (this.Image.Caption.Glow)
      this.Image.Caption.Glow = CheckColor(this.Image.Caption.Glow)
   else
      this.Image.Caption.Glow = RGBA(0,0,0);
   
   if (this.Image.Caption.PlayingGlow)
      this.Image.Caption.PlayingGlow = CheckColor(this.Image.Caption.PlayingGlow)
   else
      this.Image.Caption.PlayingGlow = RGBA(0,0,0);
      
      // 2 J -------------------------------------------------------------------
      this.Image.Buttons = {
         Visible: window.GetProperty("J01.Image.Buttons.Visible", true)
      }   
      
   // 3 ---------------------------------------------------------------------
   this.ChangeAnimation = {
      Enable : window.GetProperty("M01.Change.Animation.Enable", true),
      Mode : window.GetProperty("M02.Change.Animation.Mode", ChangeMode.Fade),
      RefreshInterval : window.GetProperty("M03.Change.Animation.RefreshInterval", 50),
      Duration : window.GetProperty("M04.Change.Animation.Duration", 500)
   }
   
   this.ChangeAnimation.Mode = Math.max(this.ChangeAnimation.Mode ,ChangeMode.Fade);   
   this.ChangeAnimation.Mode = Math.min(this.ChangeAnimation.Mode ,ChangeMode.Right);
   window.SetProperty("M02.Change.Animation.Mode", this.ChangeAnimation.Mode);      
   
   if (this.ChangeAnimation.RefreshInterval < 10){
      this.ChangeAnimation.RefreshInterval = 10;
      window.SetProperty("M03.Change.Animation.RefreshInterval", this.ChangeAnimation.RefreshInterval);
   }
   
   if (this.ChangeAnimation.Duration < this.ChangeAnimation.RefreshInterval * 2){
      this.ChangeAnimation.Duration = this.ChangeAnimation.RefreshInterval * 2;
      window.SetProperty("M04.Change.Animation.Duration", this.ChangeAnimation.Duration);      
   }
   
   // 4 ---------------------------------------------------------------------
   // AutoCycle all images of a track.
   this.Cycle = {
      Enable : window.GetProperty("N01.Cycle.Enable", true),
      Period : window.GetProperty("N02.Cycle.Period", 10000)   
   }

   if (this.Cycle.Period < this.ChangeAnimation.Duration * 2){
      this.Cycle.Period = this.ChangeAnimation.Duration * 2;
      window.SetProperty("N02.Cycle.Period", this.Cycle.Period);
   }
}

/* ━━━━━━━━━━━━━━━━ CheckColor Function ━━━━━━━━━━━━━━━━*/
function CheckColor(TColor){
   TColor = TColor.replace(/ |   /g, "");
   TColor = TColor.split(",");
   if (TColor.length < 3) return;
   for (var i = 0; i < 4 ; i++) {
      if (!TColor) TColor = 255;
      if (TColor < 0)
         TColor = 0
      else if (TColor > 255)
         TColor = 255
   }
   return RGBA(TColor[0], TColor[1], TColor[2], TColor[3]);         
}

/* ━━━━━━━━━━━━━━━━ SetFont Function ━━━━━━━━━━━━━━━━*/
function SetFont(AFontStr){
   if (!AFontStr) return;
   AFontStr = AFontStr.split(",");
   if (AFontStr.length != 3) return;      // It is not a font.
   if (AFontStr[1] < 6)
      AFontStr[1] = 6;
   var s = AFontStr[2].replace(/ |   /g, "");
   s = s.split("|");
   var st;
   for (var i = 0; i < s.length; i++){
      st = st | s;
   }
   return gdi.Font(AFontStr[0], AFontStr[1], st);
}

/* ━━━━━━━━━━━━━━━━ Kill timer Functions ━━━━━━━━━━━━━━━━*/
function KillCursorFollowAutoTimer (){
   if (CursorFollowAutoTimerID){
      window.ClearTimeout(CursorFollowAutoTimerID);
      CursorFollowAutoTimerID = 0;
      CollectGarbage();
   }
}

function CreateCursorFollowAutoTimer(){
   if (NeedCursorFollowAutoTimer && !PopupMenu.Showing){
      NeedCursorFollowAutoTimer = null;
      CursorFollowAutoTimerID = window.SetTimeout(on_timer, Properties.Panel.CursorFollowMode_AutoBackInterval);
   }
}

function KillChangeTimer (){
   if (Image.ChangeTimerID){
      window.ClearInterval(Image.ChangeTimerID);
      Image.ChangeTimerID = 0;
      CollectGarbage();
   }
}

function KillAutoCycleTimer (){
   if (Image.AutoCycleTimerID){
      window.ClearTimeout(Image.AutoCycleTimerID);
      Image.AutoCycleTimerID = 0;
      CollectGarbage();
   }
}
      
function KillGetImageTimer (){
   if (Image.GetImageTimerID){
      window.ClearInterval(Image.GetImageTimerID);
      Image.GetImageTimerID = 0;
      CollectGarbage();
   }
}

function KillResizeCacheTimer (){
   if (Image.ResizeCacheTimerID){
      window.ClearInterval(Image.ResizeCacheTimerID);
      Image.ResizeCacheTimerID = 0;
      CollectGarbage();
   }
}

// ━━━━━━━━━━━━━━━━ Calculator Random Index Function ━━━━━━━━━━━━━━━━
function RandomIndex (){
   var x = Math.random();
   return Math.floor(1000*x)%Image.Items.length;
}

// ━━━━━━━━━━━━━━━━ Re Sort Image.Items by Default Order Function ━━━━━━━━━━━━━━━━
function ReSortByDefault (){
   var DItems = GetImageItems(Image.PathFormat);
   for (var i = 0; i < DItems.length; i++){
      DItems.RawBitmap = RNow(DItems.path);
   }
   return DItems;
   
   function RNow (path){
      for (var j = 0; j < Image.Items.length; j++){
         if (path == Image.Items[j].path)
            return Image.Items[j].RawBitmap;
      }
   }
}

// ━━━━━━━━━━━━━━━━ Check metadb is or not playing ━━━━━━━━━━━━━━━━
function IsFocusPlaying(g_metadb_Focus, g_metadb_Playing){
      var bool = false;
      if(g_metadb_Focus && g_metadb_Playing){      
         var TitleFocus = fb.TitleFormat("%title%").EvalWithMetadb(g_metadb_Focus);   
         var TitlePlaying = fb.TitleFormat("%title%").EvalWithMetadb(g_metadb_Playing);
         if ((g_metadb_Focus.Path == g_metadb_Playing.Path) &&
            (g_metadb_Focus.Length == g_metadb_Playing.Length) &&
            (TitleFocus == TitlePlaying))
            bool = true;
      }
      return bool;      
}

// ━━━━━━━━━━━━━━━━ Define "ScrollString" Function ━━━━━━━━━━━━━━━━
function ScrollString(Atxt, Afont, Acolor, x, y, width, height){   
   this.txt = Atxt; this.font = Afont; this.color = Acolor; this.x = x; this.y = y; this.width = this.height = height;
   this.TxtWidth = 0; this.NR = 0;
   this.NeedRefresh = function(gr){
         this.TxtWidth = gr.CalcTextWidth(this.txt, this.font);
         this.NR = window.width > this.TxtWidth ? 0 : 1;
         if (this.NR == 1) {
            this.width = this.TxtWidth;
            this.direction = "L";                  
            }
         else {
            this.width = window.width;
         }
            if (!this.timerID) {this.timerID = window.SetInterval(on_timer, 120);}
         return this.NR;
   };
   this.RefreshPos = function(){
         if (this.x + this.width + 10> window.width && this.direction == "L") --this.x 
         else this.direction = "R";
         if (this.x < 10 && this.direction == "R") ++this.x
         else this.direction = "L";
   }
   
   this.draw = function(gr){
      if (this.TxtWidth == 0)  this.NeedRefresh(gr);
      
      //======= for boxblur
      var img_to_blur = gdi.CreateImage(this.width, this.height);
      var g = img_to_blur.GetGraphics();
      g.SetTextRenderingHint(TextRenderingHint.AntiAlias);
      
      if (IsFocusPlaying(fb.GetFocusItem(), fb.GetNowPlaying()))
         this.color = Properties.Image.Caption.PlayingGlow//RGBA(64,128,255,255)
      else
         this.color = Properties.Image.Caption.Glow;//RGBA(0, 32, 128,255);
      
      this.font = (this.txt != "No Track") ? Properties.Image.Caption.Font : Properties.Image.Caption.Font2;      
      g.GdiDrawText(this.txt, this.font, this.color, 0, 0, this.width, this.height, DT_CENTER | DT_TOP | DT_SINGLELINE);
      //g.DrawString(this.txt,  this.font, this.color, this.x, this.y, this.width, this.height, StringFormat(StringAlignment.Center, StringAlignment.Center));
      img_to_blur.ReleaseGraphics(g);
      img_to_blur.BoxBlur(2, 2);
      img_to_blur && gr.DrawImage(img_to_blur, this.x, this.y, this.width, this.height, 0, 0, this.width, this.height);
      
      gr.SetTextRenderingHint(TextRenderingHint.ClearTypeGridFit);
      gr.GdiDrawText(this.txt, this.font, Properties.Image.Caption.Color, this.x, this.y, this.width, this.height, DT_CENTER | DT_TOP | DT_SINGLELINE);
   };   
   
   this.OnTimer = function (id){
      if (this.timerID && id == this.timerID) {
         if (this.NR == 1){
               this.RefreshPos();
               window.RepaintRect(0, this.y, window.width, this.height);   
         }
         else {
               window.ClearInterval(this.timerID);
               this.timerID = 0;
               CollectGarbage();         // Release memory.      
               window.RepaintRect(0, this.y, window.width, this.height);   
         }
      }
   }
}

// ━━━━━━━━━━━━━━━━ Set Image Caption Function ━━━━━━━━━━━━━━━━
function SetCaptionTxt (){
      var Caption = Image.Caption;
      if (!Caption) return false;
      var ret = false;
      if (Image.metadb){
            /*      Extended Fields
               %cover_path%             Full path of the current cover file;
               %cover_filename%           Name of current cover file, including extension but excluding directory path;
               %cover_name%               Name of current cover file, excluding extension;
               %cover_index%             Index of the item on the cover list of current track;
               %cover_total%             Number of current track's covers;
            */               
            var path = Image.Items[Image.Index].path;                              // get path
            path = path.replace(/'/g, "''");                                                // replace ' to '' for TF
            path = path.replace(/file:\/\//g," '\('embed'\)' ");                           // for RawPath
            var fn = path.substring(path.lastIndexOf("\\") + 1, path.length);      // get filename
            if (path.indexOf("embed") >0) fn = "'\('embed'\)'" + fn;                 // for filename of RawPath
            var n = fn.substring(0, fn.lastIndexOf("."));                              // get name of file, excluding extension;
            
            var tf = Properties.Image.Caption.Format;
            tf = tf.replace(/\%cover_path\%/g, path);            
            tf = tf.replace(/\%cover_filename\%/g, fn);
            tf = tf.replace(/\%cover_name\%/g, n);
            tf = tf.replace(/\%cover_index\%/g, Image.Index + 1);      
            tf = tf.replace(/\%cover_total\%/g, Image.Items.length);      
            var txt =    fb.TitleFormat(tf).EvalWithMetadb(Image.metadb);   
         }
      else
            var txt = "No Track";
      if (txt != Caption.txt){
         Caption.txt = txt;
         Caption.x = Image.x;
         Caption.width = Image.width;
         Caption.TxtWidth = 0;   
         ret = true;
      }
      return ret;
}

// ━━━━━━━━━━━━━━━━ Create Buttons Object ━━━━━━━━━━━━━━━━
function Buttons (x, y){
   this.x = x;
   this.y = y;
   
   var img_Prev = gdi.Image(Properties.Panel.WorkDirectory+"\\buttons\\Prev.png");
   var img_Play = gdi.Image(Properties.Panel.WorkDirectory+"\\buttons\\Play.png");
   var img_Pause = gdi.Image(Properties.Panel.WorkDirectory+"\\buttons\\Pause.png");
   var img_Next = gdi.Image(Properties.Panel.WorkDirectory+"\\buttons\\Next.png");
   
   this.Prevbtn = new AniButton(this.x, this.y, 22, 12, img_Prev, null, Properties.Panel.StrArray[14]);
   this.Playbtn = new AniButton(this.x + 22, this.y, 24, 12, img_Play, null, Properties.Panel.StrArray[13] + " / " + Properties.Panel.StrArray[12]);
   this.Nextbtn = new AniButton(this.x + 22 + 24, this.y, 22, 12, img_Next, null, Properties.Panel.StrArray[15]);
   
      this.Playbtn.OnPaint = function(gr){         
         if (!ButtonVisible)
            return;
         
         if (!this.playtip){
            var sep = this.Tooltip.text.indexOf("/");
            this.playtip = this.Tooltip.text.substring(0, sep - 1);
            this.pausetip = this.Tooltip.text.substring(sep + 2, this.Tooltip.text.length);
         }
         
         this.img = (Properties.Cycle.Enable) ? img_Pause : img_Play;
         if (this.opacity < ButtonOpacity)
            gr.DrawImage(this.img, this.x, this.y, this.width, this.height, this.oldstate*this.width, 0, this.width, this.height,0,ButtonOpacity-this.opacity);
         gr.DrawImage(this.img, this.x, this.y, this.width, this.height, this.state*this.width,0, this.width, this.height,0,Math.min(this.opacity, ButtonOpacity));
         this.Tooltip.text = (Properties.Cycle.Enable)  ? this.pausetip : this.playtip;
      }      
      this.Playbtn.OnClick = function(){
         Image.AutoCycle();
         window.RepaintRect(this.x, this.y, this.width, this.height);
      }
      
      this.Prevbtn.OnClick = function(){
         Image.Prev();
      }
   
      this.Nextbtn.OnClick = function(){
         Image.Next();
      }
      
   this.OnPaint = function(gr){
      this.Prevbtn.OnPaint(gr);<