﻿//====================================================
//--------------------------------- WSH Cover Panel -------------------------------------
//-------------- v1.0 , code for foo_uie_wsh_mod v1.3.4 or higher ---------------
//--------------------- Code by Jensen (jensen-yg@163.com) ----------------------
//--------------------- Mod by karevos -----------------------
//====================================================

function RGBA(r,g,b,a){
	var res = 0xff000000|(r<<16)|(g<<8)|(b);
	if (a!=undefined) res = (res & 0x00ffffff) | (a << 24)
	return res;
}
//====================================================
// Properties Object =======================================
/* All settings store here */
var Properties = new function (){
	this.Panel = {
		// 0: Never, 1: When not playing, 2: Always.
		FollowCursor: window.GetProperty("Panel.FollowCursor", 1),
		// Where the images folders in.
		WorkDirectory: window.GetProperty("Panel.WorkDirectory", fb.FoobarPath + "skins\\dreamix\\wsh-cover\\"),
		// "cn": Chinese, "en": English, "auto": Auto choose, base on foobar2000 core language.
		lang: window.GetProperty("Panel.Language", "auto").toLowerCase(),			// Language
		// Separate with comma, like "255,255,255"
		BackGroundColor: window.GetProperty("Panel.BackGroundColor", ""),
		Tooltip: window.CreateTooltip()			// Create button's tooltip
	};
	
	if (typeof(this.Panel.FollowCursor)!="number")
		this.Panel.FollowCursor = 1;
	else if (this.Panel.FollowCursor<0)
		this.Panel.FollowCursor = 0;
	else if (this.Panel.FollowCursor>2)
		this.Panel.FollowCursor = 2;
	
	if (this.Panel.lang != "cn" && this.Panel.lang != "en")
		this.Panel.lang = (fb.TitleFormat("$meta()").Eval(true)=="[未知函数]") ? "cn" : "en";
	
	try {
		this.Panel.FSO = new ActiveXObject("Scripting.FileSystemObject");
	} catch(e) {
		// Impossible to work without FSO.
		fb.ShowPopupMessage(this.Panel.lang=="cn" ? "无法创建FSO对象, WSH Cover面板无法工作." : "Can not create File System Object (FSO), WSH Cover Panel can't work.", "WSH Cover Panel", 1);
		new ActiveXObject("Scripting.FileSystemObject");		// End scripts running.
	}
	
	if (!this.Panel.FSO.FolderExists(this.Panel.WorkDirectory)) {
        window.SetProperty("Panel.WorkDirectory", fb.FoobarPath + "skins\\dreamix\\wsh-cover\\");
        this.Panel.WorkDirectory = window.GetProperty("Panel.WorkDirectory");
    }
	
	if (this.Panel.BackGroundColor) {
		this.Panel.BackGroundColor = this.Panel.BackGroundColor.replace(/ |	/g, "");
		this.Panel.BackGroundColor = this.Panel.BackGroundColor.split(",");
		for (var i in this.Panel.BackGroundColor) {
			if (this.Panel.BackGroundColor[i]<0)
				this.Panel.BackGroundColor[i] = 0;
			else if (this.Panel.BackGroundColor[i]>255)
				this.Panel.BackGroundColor[i] = 255;
		}
		if (this.Panel.BackGroundColor.length<4)
			this.Panel.BackGroundColor.length = 4;
	}
	
	//---------------------------------------------------------------------
	this.Cycle = {
		// Enable image cycle.
		Enable: window.GetProperty("Cycle.Enable", true),
		// Interval period.
		Period: window.GetProperty("Cycle.Period", 10000),
		// Cycle all the wildcard match files in image SourceFormat.
		CycleInWildCard: window.GetProperty("Cycle.CycleInWildCard", true),
		// Pause images cycle automatic when following cursor.
		AutoPauseWhenFollowCursor: window.GetProperty("Cycle.AutoPauseWhenFollowCursor", true),
		// Animations on image's changing. Not only in image cycle, but also in track switching.
		Animation: {
			Enable: window.GetProperty("Cycle.Animation.Enable", true),
			RefreshInterval: window.GetProperty("Cycle.Animation.RefreshInterval", 50),
			Duration: window.GetProperty("Cycle.Animation.Duration", 400)
		}
	};
	
	if (typeof(this.Cycle.Period)!="number")
		this.Cycle.Period = 7000;
	else if (this.Cycle.Period<100)
		this.Cycle.Period = 100;
	
	if (typeof(this.Cycle.Animation.RefreshInterval)!="number")
		this.Cycle.Animation.RefreshInterval = 50;
	else if (this.Cycle.Animation.RefreshInterval<10)
		this.Cycle.Animation.RefreshInterval = 10;
	
	if (typeof(this.Cycle.Animation.Duration)!="number")
		this.Cycle.Animation.Duration = 300;
	else if (this.Cycle.Animation.Duration<10)
		this.Cycle.Animation.Duration = 10;
	
	//---------------------------------------------------------------------
	this.Image = {
		// Separate paths by "||"; use "|||" to separate default images and other images, only can be used once; "<embed>" means embed cover, must be a individual path in sourceformat.
		SourceFormat: window.GetProperty("Image.SourceFormat", "<embed>||$directory_path(%path%)\\cover.*||$directory_path(%path%)\\folder.*||$directory_path(%path%)\\front.*||$if(%album%,$directory_path(%path%)\\%album%*.jpg,)||$if(%artist%,$directory_path(%path%)\\%artist%*.jpg,)"),
		// In same group, if SourceFormat not changed, panel will not check any new files, and the images cycle will not be reset.
		GroupFormat: window.GetProperty("Image.GroupFormat", "%album%"),
		// Default image path.
		DefaultImagePath: window.GetProperty("Image.DefaultImagePath", this.Panel.WorkDirectory + "nocover.jpg"),
		// File larger than this value will not be loaded. <=0 means no limit.
		MaxFileSize: window.GetProperty("Image.MaxFileSize", 2097152),
		// Keep images aspect ratio.
		KeepAspectRatio: window.GetProperty("Image.KeepAspectRatio", true),
		// Stretch images to fit panel.
		Stretch: window.GetProperty("Image.Stretch", false),
		// Images is stored after resize, so you can set this value larger if your panel size is not very large.
		ImageCacheCapacity: window.GetProperty("Image.ImageCacheCapacity", 10),
		// This panel also stores path search result, only stores the strings.
		PathCacheCapacity: window.GetProperty("Image.PathCacheCapacity", 20),
		// Only these types of files can be displayed. Not necessary to modify this at most times.
		SupportTypes: new Array("jpg", "jpeg", "png", "gif", "bmp"),
		// Image smoothing mode.
		SmoothingMode: window.GetProperty("Image.SmoothingMode", 0),
		// Image interpolation mode in resizing.
		InterpolationMode: window.GetProperty("Image.InterpolationMode", 0)
	};
	
	if (!this.Panel.FSO.FileExists(this.Image.DefaultImagePath)) {
        window.SetProperty("Image.DefaultImagePath", this.Panel.WorkDirectory + "nocover.jpg");
        this.Image.DefaultImagePath = window.GetProperty("Image.DefaultImagePath");
    }

    if (typeof(this.Image.MaxFileSize)!="number")
		this.Image.MaxFileSize = 2097152;
	else if (this.Image.MaxFileSize<0)
		this.Image.MaxFileSize = 0;
	
	if (typeof(this.Image.ImageCacheCapacity)!="number")
		this.Image.ImageCacheCapacity = 10;
	else if (this.Image.ImageCacheCapacity<0)
		this.Image.ImageCacheCapacity = 0;
	
	if (typeof(this.Image.PathCacheCapacity)!="number")
		this.Image.PathCacheCapacity = 20;
	else if (this.Image.PathCacheCapacity<0)
		this.Image.PathCacheCapacity = 0;
	
	if (typeof(this.Image.SmoothingMode)!="number")
		this.Image.SmoothingMode = 0;
	else if (this.Image.SmoothingMode<-1)
		this.Image.SmoothingMode = -1;
	else if (this.Image.SmoothingMode>4)
		this.Image.SmoothingMode = 4;
	
	if (typeof(this.Image.InterpolationMode)!="number")
		this.Image.InterpolationMode = 0;
	else if (this.Image.InterpolationMode<-1)
		this.Image.InterpolationMode = -1;
	else if (this.Image.InterpolationMode>7)
		this.Image.InterpolationMode = 7;
		
	//---------------------------------------------------------------------
	this.Buttons = {
		// Whether display the control buttons.
		Display: window.GetProperty("Buttons.Display", true),
		// 0: topleft, 1:topright, 2:bottomleft, 3:bottomright
		Position: window.GetProperty("Buttons.Position", "4")
	};
	
	if (typeof(this.Buttons.Position)!="number")
		this.Buttons.Position = 4;
	else if (this.Buttons.Position<0)
		this.Buttons.Position = 0;
	else if (this.Buttons.Position>5)
		this.Buttons.Position = 5;
	
}();


//====================================================
// Three funtions ==========================================

//---------------------------------------------------------------------------------------------
/* Calculate image's new size and offsets in new width and height range. 
Use panel's default settings of stretch and ratio if they are not specified */
function CalcNewImgSize (img, dstW, dstH, srcW,srcH, strch, kar) {
	if (!img) return;
	if (!srcW) srcW = img.width;
	if (!srcH) srcH = img.height;
	if (strch==undefined) strch = Properties.Image.Stretch;
	if (kar==undefined) kar = Properties.Image.KeepAspectRatio;
	
	var size;
	if (strch) {
		size = {x:0, y:0, width:dstW, height:dstH};
		if (kar) {
			size.width = Math.ceil(srcW*dstH/srcH);
			if (size.width>dstW) {
				size.width = dstW;
				size.height = Math.ceil(srcH*dstW/srcW);
			}
		}
	} else {
		size = {x:0, y:0, width:srcW, height:srcH};
		if (kar) {
			if (srcH>dstH) {
				size.height = dstH;
				size.width = Math.ceil(srcW*dstH/srcH);
			}
			if (size.width>dstW) {
				size.width = dstW;
				size.height = Math.ceil(srcH*dstW/srcW);
			}
		} else {
			size.width = Math.min(srcW, dstW);
			size.height = Math.min(srcH, dstH);
		}
	}
	size.x = Math.floor((dstW-size.width)/2);
	size.y = Math.floor((dstH-size.height)/2);
	return size;
}

//---------------------------------------------------------------------------------------------
/* Reisze image to new width and height */
function ResizeImg (img, dstW, dstH) {
	if (!img || !dstW || !dstH) return img;
	if (img.width==dstW && img.height==dstH) return img;
	
	var newimg = gdi.CreateImage(dstW, dstH);
	var g = newimg.GetGraphics();
	g.SetSmoothingMode(Properties.Image.SmoothingMode);
	g.SetInterpolationMode(Properties.Image.InterpolationMode);
	g.DrawImage(img, 0, 0, dstW, dstH, 0, 0, img.width, img.height);
	newimg.ReleaseGraphics(g);
	CollectGarbage();			// Release memory.
	return newimg;
}

//---------------------------------------------------------------------------------------------
/* Remove an element from "this" array, return the removed element */
function RemoveFromArray (index) {
	if (index==0)
		return this.shift();
	else if (index==this.length-1)
		return this.pop();
	var c = this[index];
	var rest = this.slice(index+1);
	this.length = index;
	this.push.apply(this, rest);
	return c;
}


//====================================================
// Define Image Loader ====================================
/* Loader image file from specified path. It contains a image cache.
Primarily provides the "GetImg()" method */
var ImageLoader = new function (Prop) {
	var ImgCacheCapacity = Prop.Image.ImageCacheCapacity;

	if (ImgCacheCapacity) {
		// Cache array is always sorted, sort by last accessed.
		// Array's length never change.
		// No empty element, only empty "cacheitem".
		// No item will really be deleted, only be deassigned.
		var ImgsCache = new Array;
		// Admissible size error in cache reading.
		ImgsCache.ImgSizeError1 = 10;		// For enlarge.
		ImgsCache.ImgSizeError2 = 50;		// For shrink.
		
		// Class of the items in cache.
		ImgsCache.cacheItem = function (path, imgobj) {
			this.Path = path;			// String.
			this.ImgObj = imgobj;			// Object, gdi.Image() object, or empty.
			this.srcW = 0;			// Source width.
			this.srcH = 0;			// Source height
		};
		
		// Fill the cache array with empty cacheitems.
		for (var i=0; i<ImgCacheCapacity-1; i++)
			ImgsCache.push(new ImgsCache.cacheItem(null, null));
		
		// Remove element from cache array, return the removed element.
		ImgsCache.remove = RemoveFromArray;
		
		// Store item in the beginning of the cache array. Duplicate item's "ImgObj" will be overwritten.
		ImgsCache.Store = function (path, imgobj, srcW, srcH) {
			var c;
			if (this.SearchFor(path)) {
				c = this[0];			// This is the one just found.
				c.ImgObj = imgobj;
				if (srcW) c.srcW = srcW;
				if (srcH) c.srcH = srcH;
			} else if (c = this.pop()) {
				c.Path = path;
				c.ImgObj = imgobj;
				c.srcW = srcW ? srcW : (imgobj ? imgobj.width : 0);			// Store source width.
				c.srcH = srcH ? srcH : (imgobj ? imgobj.height : 0);			// Store source height.
				this.unshift(c);
			}
		};

		// Search in cache, find the image object.
		// Resize "ImgObj" to "dst" size, if current size is not enough, and the source size is closer, remove this item and return nothing.
		// Or, move it to the beginning of the cache array, then resize image, then return this cache item.
		// If no result found, return nothing.
		ImgsCache.SearchFor = function (path, dstW, dstH) {
			var i = 0;
			for (i; i<this.length; i++)			// Find it.
				if (this[i].Path==path)
					break;
			
			if (i<this.length) {
				var c = this.remove(i);
				var img = c.ImgObj;
				// For resizing------------------------
				if (img && dstW && dstH) {
					var size = CalcNewImgSize(img, dstW, dstH, c.srcW, c.srcH);
					// If image should be enlarged and it still can be enlarged...
					if ((size.width>img.width && img.width<c.srcW) || (size.height>img.height && img.height<c.srcH)) {
						// If the dst size is not too large...
						if (size.width-img.width<this.ImgSizeError1 && size.width<c.srcW && size.height-img.height<this.ImgSizeError1 && size.height<c.srcH)
							img = ResizeImg(img, size.width, size.height);		// Only resize, no cache.
						else {
							// Return to reload image file.
							this.push(c);
							return;
						}
					// If it's shrinking...
					} else if (size.width<img.width || size.height<img.height) {
						// If the dst size is too small...
						if (img.width-size.width>this.ImgSizeError2 || img.height-size.height>this.ImgSizeError2) {
							img = ResizeImg(img, size.width, size.height);		// Resize and cache.
							c.ImgObj = img;
						} else
							img = ResizeImg(img, size.width, size.height);		// Resize and don't cache.
					} else {
						size = CalcNewImgSize(img, dstW, dstH);
						img = ResizeImg(img, size.width, size.height);
						c.ImgObj = img;
					}
				}
				//------------------------------------
				this.unshift(c);
				return (img || true);		// If path was found, return something "true" at all times.
			}
		};
		
		// Clear cache, only reset all cache items indeed.
		ImgsCache.Clear = function () {
			for (var i=0; i<this.length-1; i++) {
				this[i].Path = null;
				this[i].ImgObj = null;
				this[i].srcW = 0;
				this[i].srcH = 0;
			}
		};
	}
	
	// Load image file from specified path, generate gdi.Image() object, resize it, cache it, then return it.
	// If path invalid or file corrupt, return nothing.
	this.GetImg = function (path, dstW, dstH, NoCache) {
		if (!path) return;
		var imgobj;
		if (!NoCache && ImgsCache)
			imgobj = ImgsCache.SearchFor(path, dstW, dstH);
		
		if (imgobj) {
			if (typeof(imgobj)!="object") imgobj = null;		// Maybe it's a boolean value.
		} else {
			if (path.charAt(0)=="<")		// Embed image.
				imgobj = utils.GetAlbumArtEmbedded(path.substring(1, path.length-1), 0);
			else
				imgobj = gdi.Image(path);
			
			var srcW, srcH;
			if (imgobj) {
				srcW = imgobj.width;
				srcH = imgobj.height;
				if (dstW && dstH) {
					var size = CalcNewImgSize(imgobj, dstW, dstH);
					imgobj = ResizeImg(imgobj, size.width, size.height);
				}
			}
			// Store every path, even no valid image exists.
			ImgsCache && ImgsCache.Store(path, imgobj, srcW, srcH);
		}
		
		if (imgobj)
			return imgobj;
	};
	
	this.ClearCache = function () {
		ImgsCache && ImgsCache.Clear();
	};
	
} (Properties);

var GetImg = ImageLoader.GetImg;


//====================================================
// Define Path Checker =====================================
/* Find matches image files in directorys. It contains a paths cache.
Primarily provides the "GetImgPaths()" method */
var PathChecker = new function (Prop, ImgLoader) {
	var FSO = Prop.Panel.FSO;
	var ImgSrcFmt = Prop.Image.SourceFormat;
	var ImgSrcStr = "";
	var SupportTypes = Prop.Image.SupportTypes;
	var MaxFileSize = Prop.Image.MaxFileSize;
	var CycleInWildCard = Prop.Cycle.CycleInWildCard;
	var GetEmbedImg = ImgLoader;
	var FoundFiles = new Array;
	var PathsArray;
	var PathCacheCapacity = Prop.Image.PathCacheCapacity;
	
	if (PathCacheCapacity) {
		// This cache array is similar to the images cache array.
		var PathsCache = new Array;
		PathsCache.FSO = FSO;
		
		PathsCache.cacheItem = function (path, files) {
			this.Path = path;			// String.
			this.MatchFiles = files;			// Must an array, path array.
		};
		
		for (var i=0; i<PathCacheCapacity-1; i++)
			PathsCache.push(new PathsCache.cacheItem(null, null))
		
		PathsCache.remove = RemoveFromArray;
		
		// Duplicate item will be overwritten.
		PathsCache.Store = function (path, files) {
			var c;
			if (this.SearchFor(path))
				this[0].MatchFiles = files;
			else if (c = this.pop()) {
				c.Path = path;
				c.MatchFiles = files;
				this.unshift(c);
			}
		};
		
		// Search in cache, returns the search result, and move it to the beginning of the cache array.
		// All files in result array will be checked before return, if one or more files doesn't exist, remove this item and return nothing.
		// If no result found, return nothing.
		PathsCache.SearchFor = function (path) {
			var i = 0;
			for (i; i<this.length; i++)
				if (this[i].Path==path)
					break;
			
			if (i<this.length) {
				var c = this.remove(i);
				var rslt = c.MatchFiles;
				
				for (var j=0; j<rslt.length; j++)		// Check whether all the files are exist.
					if (!this.FSO.FileExists(rslt[j])) {
						c.Path = null;
						this.push(c);
						return;
					}
				
				this.unshift(c);
				return rslt;
			}
		};
		
		PathsCache.Clear = function () {
			for (var i=0; i<this.length-1; i++) {
				this[i].Path = null;
				this[i].MatchFiles = null;
			}
		};
	}
	
	// Union Array2 into Array1.
	var unionArray = function (Array1, Array2 ) {
		var seen = {};
		for (var i=0; i<Array1.length; i++)
			seen[Array1[i]] = true;
		for (var i=0; i<Array2.length; i++)
			if (!seen[Array2[i]])
				Array1.push(Array2[i]);
	};
	
	// Calculate ImageSourceFormat, and replace <embed> with <rawpath>.
	var CalcPathFmt = function (pathfmt, metadb) {
		if (fb.GetFocusItem()){
			if (metadb)
				var paths =  fb.TitleFormat(ImgSrcFmt).EvalWithMetadb(metadb)
			else
				var paths =  fb.TitleFormat(ImgSrcFmt).Eval();
			paths = paths.replace(/<embed>/gi, "<"+metadb.RawPath+">");
		} else
			var paths = Prop.Image.DefaultImagePath;
//		fb.trace("paths=" + paths);
		return paths;
	};
	
	// Check file type and file size.
	var IsFilePropOK = function (file) {
		var ext = FSO.GetExtensionName(file).toLowerCase();
		for (var i=0; i<SupportTypes.length; i++) {
			if (ext==SupportTypes[i] && (MaxFileSize<=0 || file.Size<=MaxFileSize))
				return true;
		}
		return false;
	};
	
	// Find matches image files in "ImgSrc", cache relational paths, return the valid paths array.
	// If "ImgSrc" has nothing changed, return the previous array directly.
	// If no valid path found, return an empty array.
	this.GetImgPaths = function (metadb) {
		var newsrc = CalcPathFmt(ImgSrcFmt, metadb);
		if (newsrc==ImgSrcStr)
			return FoundFiles;		// If the source format has nothing changed, return the previous results directly.
		else
			ImgSrcStr = newsrc;
		
		PathsArray = ImgSrcStr.split("||");
		var NewFoundFiles = new Array;
		
		for (var i=0; i<PathsArray.length; i++) {
			var Path = PathsArray[i];
			var SearchResults = PathsCache ? PathsCache.SearchFor(Path) : null;
			if (!SearchResults) {
				SearchResults = new Array;
				var EmbedPath = Path.match(/<.*>/);
				if (EmbedPath) {			// Check embed cover.
					EmbedPath = EmbedPath.toString();
					if (GetEmbedImg(EmbedPath))
						SearchResults.push(EmbedPath);
				} else if (Path.indexOf("*")==-1 && Path.indexOf("?")==-1) {		// If not wildcard exist.
						if (!FSO.FileExists(Path)) continue;
						SearchResults.push(Path);
				} else {		// Search in wildcard.
					var foldername = FSO.GetParentFolderName(Path);
					if (!FSO.FolderExists(foldername)) continue;
					
					if (CycleInWildCard) {
						// Check file type and size first -----------------
						var ValidFiles = PathsCache ? PathsCache.SearchFor(foldername+"\\*") : null;		// Search in cache first.
						if (!ValidFiles) {
							ValidFiles = new Array;
							var e = new Enumerator(FSO.GetFolder(foldername).Files);
							for (; !e.atEnd(); e.moveNext()) {
								var file = e.item();
								if (IsFilePropOK(file))
									ValidFiles.push(file.Path);
							}
							PathsCache && PathsCache.Store(foldername+"\\*", ValidFiles);		// Store this step's result in cache.
						}
						// Then match wildcard ------------------
						var exp = FSO.GetFileName(Path);
						if (exp!="*" && exp!="*.*") {
							for (var j=0; j<ValidFiles.length; j++) {
								if (utils.PathWildcardMatch(exp, FSO.GetFileName(ValidFiles[j])))
									SearchResults.push(ValidFiles[j]);
							}
						} else
							SearchResults = ValidFiles;

					} else {
						var exp = FSO.GetFileName(Path);
						var e = new Enumerator(FSO.GetFolder(foldername).Files);
						for (; !e.atEnd(); e.moveNext()) {
							var file = e.item();
							if (IsFilePropOK(file) && utils.PathWildcardMatch(exp, file.Name)) {
								SearchResults.push(file.Path);
								break;			// One file per path is enough.
							}
						}
					}
				}
				PathsCache && PathsCache.Store(Path, SearchResults);		// Store search results for this path.
			}
			// Merge these files of this path into the final results, duplicate files will only keep one.
			unionArray(NewFoundFiles, SearchResults);
		}
		
		if (NewFoundFiles.join()!=FoundFiles.join())		// If the result has nothing changed, return the previous results directly.
			FoundFiles = NewFoundFiles;
		CollectGarbage();			// Release memory.
		return FoundFiles;
	};
	
	this.ClearCache = function () {
		PathsCache && PathsCache.Clear();
		ImgSrcStr = "";
	};
	
} (Properties, GetImg);

var GetImgPaths = PathChecker.GetImgPaths;


//====================================================
// Define Display Style =====================================
/* Define style, display image and animation.
Primarily provides the "ChangeImage()" method */
var Display = new function (Prop, ImgLoader) {
	this.margin = {top: 0, left: 0, bottom: 0, right: 0};
	this.x = this.margin.left;
	this.y = this.margin.top;
	this.width;
	this.height;
	var FSO = Prop.Panel.FSO;
	var EnableFading = Prop.Cycle.Animation.Enable;
	var RefreshInterval = Prop.Cycle.Animation.RefreshInterval;
	var step = Math.min(Math.ceil(255*RefreshInterval/Prop.Cycle.Animation.Duration),255);
	var DefaultImg = gdi.Image(Prop.Image.DefaultImagePath);
	var ext = FSO.GetExtensionName(Prop.Image.DefaultImagePath).toLowerCase();
	var DefaultRaw = null;
	if (DefaultImg && ext!="png" && ext!="gif")
		DefaultRaw = DefaultImg.CreateRawBitmap();
	var ImgPath = null;
	var CurImage = DefaultImg;
	var CurRaw = DefaultRaw;
	var CurSize = null;
	var NewImage = null;
	var NewSize = null;
	var CanBeCreateRaw = true;
	var opacity = 255;
	var timer = null;
	
	if (ImgLoader)
		var GetImg = ImgLoader;
	else
		var GetImg = function () {
			return gdi.Image(path);
		};
	
	// Change now displaying image to the new one.
	// "path" is a string object, or empty.
	// When "path" is empty, means change to style default image (DefaultImg).
	// "GroupChanged" means "GroupFormat" calculate result changes.
	this.ChangeImage = function (path, GroupChanged) {
		if ((ImgPath!=null && path!=undefined) && path==ImgPath) return;
		ImgPath = path;
		var newimg;
		if (ImgPath) {
			newimg = GetImg(ImgPath, this.width, this.height);
			var ext = FSO.GetExtensionName(ImgPath).toLowerCase();
			CanBeCreateRaw = ext!="png" && ext!="gif";
		} else
			newimg = DefaultImg;
        if (EnableFading) {
			if (NewImage) {
				CurImage = NewImage;
				//CurRaw = NewImage==DefaultImg ? DefaultRaw : (CanBeCreateRaw ? CurImage.CreateRawBitmap() : null);
				CurSize = NewSize;
				opacity = 255;
			}
			NewImage = newimg;
			NewSize = CalcNewImgSize(NewImage, this.width, this.height);
			if (!timer) timer = window.CreateTimerInterval(RefreshInterval);
		} else {
			CurImage = newimg;
			CurRaw = ImgPath ? (CanBeCreateRaw ? CurImage.CreateRawBitmap() : null) : DefaultRaw;
			CurSize = CalcNewImgSize(CurImage, this.width, this.height);
			window.RepaintRect(this.x, this.y, this.width, this.height);
		}
	};
	
	this.Refresh = function () {
		if (ImgPath) {
			CurImage = GetImg(ImgPath, this.width, this.height, true);			// Get image bypass cache.
			if (CurRaw) CurRaw = CurImage.CreateRawBitmap();
		} else {
			DefaultImg = gdi.Image(Prop.Image.DefaultImagePath);
			DefaultRaw = DefaultImg.CreateRawBitmap();
			CurImage = DefaultImg;
			CurRaw = DefaultRaw;
		}
		if (CurImage)
			CurSize = CalcNewImgSize(CurImage, this.width, this.height);
		if (NewImage)
			NewSize = CalcNewImgSize(NewImage, this.width, this.height);
		window.Repaint();
	};
	
	this.OnPaint = function (gr) {
		var Img, size;
		if (Img = CurImage) {
			size = CurSize;
			if (opacity==255 && CurRaw)
				// This funtion is much more faster.
				gr.GdiDrawBitmap(CurRaw, this.x+size.x, this.y+size.y, size.width, size.height, 0, 0, Img.width, Img.height);
			else
				gr.DrawImage(Img, this.x+size.x, this.y+size.y, size.width, size.height, 0, 0, Img.width, Img.height, 0, opacity);
		}
		if (Img = NewImage) {
			size = NewSize;
			gr.DrawImage(Img, this.x+size.x, this.y+size.y, size.width, size.height, 0, 0, Img.width, Img.height, 0, 255-opacity);
		}
	};
	
	this.OnTimer = function (id) {
		if (timer && id==timer.ID) {
			if (opacity>0) {
				opacity = Math.max(opacity-step,0);
				window.RepaintRect(this.x, this.y, this.width, this.height);
			} else {
				CurImage = NewImage;
				CurRaw = ImgPath ? (CanBeCreateRaw ? CurImage.CreateRawBitmap() : null) : DefaultRaw;
				CurSize = NewSize;
				NewImage = null;
				NewSize = null;
				opacity = 255;
				timer && window.KillTimer(timer);
				timer = null;
				CollectGarbage();			// Release memory.
				//window.RepaintRect(this.x, this.y, this.width, this.height);
			}
		}
	};
	
	this.OnResize = function (ww, wh) {
		this.width = ww-this.margin.left-this.margin.right;
		this.height = wh-this.margin.top-this.margin.bottom;
		
		if (ImgPath) {
			CurImage = GetImg(ImgPath, this.width, this.height);
			if (CurRaw) CurRaw = CurImage.CreateRawBitmap();
		}
		if (CurImage)
			CurSize = CalcNewImgSize(CurImage, this.width, this.height);
		if (NewImage)
			NewSize = CalcNewImgSize(NewImage, this.width, this.height);
	};
	
} (Properties, GetImg);


//====================================================
// Define Main Controller ====================================
/* Main controller of the panel,
controls images loading, changing, cycle, and paths checking */
var Controller = new function (Prop, GetImgPaths, Dsp) {
	var CycleEnabled = Prop.Cycle.Enable;
	this.CycleActivated = false;
	var CyclePeriod = Prop.Cycle.Period;
	var GroupFmt = Prop.Image.GroupFormat;
	var GroupStr = null;
	var ImgPaths = null;
	this.CurImgPath = null;
	var CurImgIdx = 0;
	this.Paused = window.GetProperty("Cycle.Paused", !CycleEnabled);
	var timer = null;
	var _this = this;
	
	var ChangeImg = function (arg, GroupChanged) {
		switch (arg) {
			case 2:		// Last
				CurImgIdx = ImgPaths.length-1;
				break;
			case 1:		// Next
				CurImgIdx = CurImgIdx+1<ImgPaths.length ? CurImgIdx+1 : 0;
				break;
			case -1:		// Previous
				CurImgIdx = CurImgIdx-1>=0 ? CurImgIdx-1 : ImgPaths.length-1;
				break;
			case -2:		// First
				CurImgIdx = 0;
				break;
			default:		// Default
				arg = 0;
		}
		var path = arg ? ImgPaths[CurImgIdx] : null;
		toDefault(path ? false : true);
		
		if (!GroupChanged && path==_this.CurImgPath)
			return;
		else
			_this.CurImgPath = path;
		
		Dsp.ChangeImage(_this.CurImgPath, GroupChanged);
	};
	
	var ResetTimer = function () {
		if (!timer) return;
		window.KillTimer(timer);
		timer = window.CreateTimerInterval(CyclePeriod);
		CollectGarbage();			// Release memory.
	};
	
	this.Play = function () {
		this.Paused = false;
		window.SetProperty("Cycle.Paused", this.Paused);
		if (!this.CycleActivated) return;
		if (!timer) timer = window.CreateTimerInterval(CyclePeriod);
	};
	
	this.Pause = function (p) {
		if (!p) {
			this.Paused = true;
			window.SetProperty("Cycle.Paused", this.Paused);
		}
		if (!this.CycleActivated) return;
		if (timer) {
			window.KillTimer(timer);
			timer = null;
			CollectGarbage();			// Release memory.
		}
	};
	
	this.Next = function () {
		if (!this.CycleActivated) return;
		ResetTimer();
		ChangeImg(1);
	};
	
	this.Previous = function () {
		if (!this.CycleActivated) return;
		ResetTimer();
		ChangeImg(-1);
	};
	
	this.First = function () {
		if (!this.CycleActivated) return;
		ResetTimer();
		ChangeImg(-2);
	};
	
	this.Last = function () {
		if (!this.CycleActivated) return;
		ResetTimer();
		ChangeImg(2);
	};
	
	this.OnNewTrack = function (metadb, followcur) {
		var NewImgPaths = GetImgPaths(metadb);
		if (metadb)
			var groupstr = fb.TitleFormat(GroupFmt).EvalWithMetadb(metadb)
		else
			var groupstr = fb.TitleFormat(GroupFmt).Eval();
		if (GroupStr!=groupstr) {
			GroupStr = groupstr;
			var IsNewGroup = true;
		} else
			var IsNewGroup = false;
		if (ImgPaths!=NewImgPaths || IsNewGroup) {
			ImgPaths = NewImgPaths;
			if (ImgPaths.length<=1)
				SetCycleStatus(false);
			else
				SetCycleStatus(CycleEnabled);
			ResetTimer();
			ChangeImg(-2, IsNewGroup);
		}
		
		if (followcur && Prop.Cycle.AutoPauseWhenFollowCursor) {
			this.Pause(true);
			if (CurImgIdx!=0)
				ChangeImg(-2);
		} else
			!this.Paused && this.Play();
	};
	
	this.OnStop = function (reason) {
		timer && window.KillTimer(timer);
		if (reason<=1)
			ChangeImg();
		if (reason!=2) {
			this.Pause(true);
			SetCycleStatus(false);
			ImgPaths = null;
			this.CurImgPath = null;
			CurImgIdx = 0;
			GroupStr = null;
		}
	};
	
	this.OnTimer = function (id) {
		if (timer && id==timer.ID)
			this.Next();
	};
	
} (Properties, GetImgPaths, Display);


//====================================================
// Define Control Buttons ===================================
/* All button's funtions are calls of Controller's method */
if (Properties.Buttons.Display && Properties.Cycle.Enable) {
	var Buttons = new function (Prop, Ctrl) {
		var BtnDir = Prop.Panel.WorkDirectory;
		var lang = Prop.Panel.lang;
		var Position = Prop.Buttons.Position;
		this.x = 0;
		this.y = 0;
		this.width = 0;
		this.height = 0;
		var opacity = 0;
		var defaultOp = 150;
		var hbtn = null;
		var dbtn = null;
		var timer = null;
		var RefreshInterval = 50;
		var step = 40;
		var dstOp = 0;
		var _this = this;
		this.BtnsArray = new Array();
		
		// Define button class ------------------------------------------------
		var Button = function (x, y, img, OnClick, tiptext) {
			this.x = x;
			this.y = y;
			this.width = img.width/4;
			this.height = img.height;
			this.Img = img;
			this.tiptext = tiptext;
			this.state = 3;		// 0=normal, 1=hover, 2=down, 3=disabled
			this.enabled = false;
			this.OnClick = OnClick;
			var Tooltip = Prop.Panel.Tooltip;
			
			this.isXYinBtn = function (x, y) {
				if (!this.enabled) return false;
				return (x >= this.x && y >= this.y && x<= this.x + this.width && y <= this.y + this.height) ? true : false;
			};
			
			this.Draw = function (gr, op) {
				if (!opacity) return;
				gr.DrawImage(this.Img, this.x, this.y, this.width, this.height, this.state*this.Img.width/4, 0, this.width, this.height, 0, opacity);
			};
			
			this.ChangeState = function (s, enabled) {
				//if (!this.enabled && !enabled) return;
				if (enabled===undefined) {
					if (s==this.state)
						return;
					else
						this.state = s;
				} else {
					this.enabled = enabled;
					this.state = enabled ? 0 : 3;
				}
				if (s==1) {
					Tooltip.Text =  this.tiptext;
					Tooltip.Activate();
				} else
					Tooltip.Deactivate();
				if (opacity)
					window.RepaintRect(this.x, this.y, this.width, this.height);
			};
		};
		
		// Create buttons --------------------------------
		var img_play = gdi.Image(BtnDir+"Play.png");
		var img_pause = gdi.Image(BtnDir+"Pause.png");
		var img_next = gdi.Image(BtnDir+"Next.png");
		var img_prev = gdi.Image(BtnDir+"Prev.png");
		var xOffset = this.x;
		this.BtnsArray.push(new Button(xOffset, this.y, img_prev, function(){Ctrl.Previous();}, lang=="cn" ? "上一张图片" : "Previous Image"));
		xOffset += this.BtnsArray[this.BtnsArray.length-1].width;
		this.BtnsArray.push(PlayBtn = new Button(xOffset, this.y, img_play, function(){SetPauseStatus(Ctrl.Paused)}, ""));
		xOffset += this.BtnsArray[this.BtnsArray.length-1].width;
		this.BtnsArray.push(new Button(xOffset, this.y, img_next, function(){Ctrl.Next();}, lang=="cn" ? "下一张图片" : "Next Image"));
		xOffset += this.BtnsArray[this.BtnsArray.length-1].width;
		
		PlayBtn.tiptext_play = lang=="cn" ? "循环封面" : "Cycle Covers";
		PlayBtn.tiptext_pause = lang=="cn" ? "暂停循环" : "Pause Cycle";
		PlayBtn.tiptext = Ctrl.Paused ? PlayBtn.tiptext_play : PlayBtn.tiptext_pause;
		PlayBtn.img_pause = img_pause;
		PlayBtn.img_play = img_play;
		PlayBtn.Img = Ctrl.Paused ? PlayBtn.img_play : PlayBtn.img_pause;
		PlayBtn.ChangeState(null, true);
		this.PlayBtn = PlayBtn;
		
		this.width = xOffset-this.x;
		this.height = PlayBtn.height;
		//---------------------------------------
		this.SetCycleStatus = function (s) {
			this.BtnsArray[0].ChangeState(null, s);
			this.BtnsArray[2].ChangeState(null, s);
		};
		
		this.SetPauseStatus = function (s) {
			if (s) {
				PlayBtn.Img = PlayBtn.img_pause;
				PlayBtn.tiptext = PlayBtn.tiptext_pause;
			} else {
				PlayBtn.Img = PlayBtn.img_play;
				PlayBtn.tiptext = PlayBtn.tiptext_play;
			}
		};
		
		var isXYinBtns = function (x, y) {
			return (x >= _this.x && y >= _this.y && x<= _this.x + _this.width && y <= _this.y + _this.height) ? true : false;
		};
		
		var Fading = function (dstop) {
			if (dstOp==dstop) return;
			dstOp = dstop;
			if (!timer) timer = window.CreateTimerInterval(RefreshInterval);
		};
		
		this.OnPaint = function (gr) {
			if (!opacity) return;
			for (var i=0; i<this.BtnsArray.length; i++)
				this.BtnsArray[i].Draw(gr, opacity);
		};
		
		this.OnMouseMove = function (x, y) {
			if (isXYinBtns(x, y)) {
				if (opacity!=255) {
					dstOp = 255;
					opacity = 255;
					window.RepaintRect(this.x, this.y, this.width, this.height);
				}
			} else if (opacity!=defaultOp)
				Fading(defaultOp);
			
			if (dbtn) {
				if (dbtn.isXYinBtn(x, y))
					dbtn.ChangeState(2);
				else
					dbtn.ChangeState(1);
			} else {
				for (var i=0; i < this.BtnsArray.length ; i++)
					if (this.BtnsArray[i].isXYinBtn(x,y)) {
						if (hbtn!=this.BtnsArray[i]) {
							if(hbtn) hbtn.ChangeState(0);
							hbtn = this.BtnsArray[i];
							hbtn.ChangeState(1);
						}
						break;
					}
				if (i==this.BtnsArray.length) {
					if (hbtn) {
						hbtn.ChangeState(0);
						hbtn = null;
					}
				}
			}
		};
		
		this.OnLbtnDown = function (x, y) {
			if (hbtn) {
				dbtn = hbtn;
				dbtn.ChangeState(2);
			}
		};
		
		this.OnLbtnUp = function (x, y) {
			if (dbtn) {
				if (dbtn.state==2) {
					dbtn.OnClick();
					dbtn.ChangeState(1);
				}
				dbtn = null;
				this.OnMouseMove(x, y);
			}
		};
		
		this.OnMouseLeave = function () {
			Fading(0);
			if (hbtn) {
				hbtn.ChangeState(0);
				hbtn = null;
			}
		};
		
		this.OnTimer = function (id) {
			if (timer && id==timer.ID) {
				if (opacity==dstOp) {
					timer && window.KillTimer(timer);
					timer = null;
					CollectGarbage();			// Release memory.
					//window.RepaintRect(this.x, this.y, this.width, this.height);
				} else {
					if (opacity<dstOp)
						opacity = Math.min(opacity+step, dstOp);
					else
						opacity = Math.max(opacity-step, dstOp);
					window.RepaintRect(this.x, this.y, this.width, this.height);
				}
			}
		};
		
		this.OnResize = function (ww, wh) {
			if (Position==1 || Position==4)
				this.x = (ww-this.width)/2;
			else if (Position==2 || Position==5)
				this.x = ww-this.width;
			
			if (Position>2)
				this.y = wh-this.height;
				
			var x = this.x;
			for (var i=0; i<this.BtnsArray.length; i++) {
				this.BtnsArray[i].x = x;
				x += this.BtnsArray[i].width;
				this.BtnsArray[i].y = this.y;
			};
			this.width = x-this.x;
		};
		
	} (Properties, Controller);

} else
	var Buttons = {};


//====================================================
// Functions Menu ========================================
var FuncMenu = new function (Prop, Ctrl, Dsp, Btns, ImgLoader, ImgFinder) {
	// Flags ----------
	var MF_SEPARATOR = 0x00000800;
	var MF_ENABLED = 0x00000000;
	var MF_GRAYED = 0x00000001;
	var MF_DISABLED = 0x00000002;
	var MF_UNCHECKED = 0x00000000;
	var MF_CHECKED = 0x00000008;
	var MF_STRING = 0x00000000;
	var MF_POPUP = 0x00000010;
	var MF_RIGHTJUSTIFY = 0x00004000;
	
	var lang = Prop.Panel.lang;
	var ItemList = {};
	var ItemID = 1;
	
	var BuildMenu = function (items) {
		var menu = window.CreatePopupMenu();
		var mf, id, radio;
		for (var i=0; i<items.length; i++) {
			mf =  items[i].Flag || MF_STRING;
			id = items[i].ID || ItemID++;
			menu.AppendMenuItem(mf, id, items[i].Caption);
			if (i==items.Radio)
				radio = id;
			ItemList[id] = items[i];
		}
		radio && menu.CheckMenuRadioItem(1, items.length, radio);
		return menu;
	};
	
	// Submenu: Follow Cursor -------------------------
	var Menu_FC_Items = new Array(
		{
			Caption: lang=="cn" ? "仅在非播放时" : "Only when not playing",
			Func: function(){
				if (Prop.Panel.FollowCursor==1) return;
				Prop.Panel.FollowCursor = 1;
				window.SetProperty("Panel.FollowCursor", 1);
				Menu_FC_Items.Radio = 0;
				if (fb.IsPlaying)
					on_playback_new_track(fb.GetNowPlaying());
				else
					on_item_focus_change(fb.GetFocusItem());
				RebuildMenu();
			}
		},
		{
			Caption: lang=="cn" ? "总是" : "Always",
			Func: function(){
				if (Prop.Panel.FollowCursor==2) return;
				Prop.Panel.FollowCursor = 2;
				window.SetProperty("Panel.FollowCursor", 2);
				Menu_FC_Items.Radio = 1;
				on_item_focus_change(fb.GetFocusItem());
				RebuildMenu();
			}
		},
		{
			Caption: lang=="cn" ? "从不" : "Never",
			Func: function(){
				if (Prop.Panel.FollowCursor==0) return;
				Prop.Panel.FollowCursor = 0;
				window.SetProperty("Panel.FollowCursor", 0);
				Menu_FC_Items.Radio = 2;
				if (fb.IsPlaying)
					on_playback_new_track(fb.GetNowPlaying());
				else
					on_playback_stop(0);
				RebuildMenu();
			}
		}
	);
	var fc = Prop.Panel.FollowCursor;
	Menu_FC_Items.Radio = fc==1 ? 0 : fc==2 ? 1 : fc==0 ? 2 : null;
	var Menu_SubItem_FC = {
		Flag: MF_POPUP,
		Caption: lang=="cn" ? "光标跟随模式" : "Follow Cursor",
		ID: null
	}
	
	// Submenu: Image Stretching ----------------------
	var Menu_IS_Items = new Array(
		{
			Flag: Prop.Image.Stretch ? MF_CHECKED : MF_UNCHECKED,
			Caption: lang=="cn" ? "拉伸图像" : "Stretch Image",
			Func: function(){
				Prop.Image.Stretch = !Prop.Image.Stretch;
				window.SetProperty("Image.Stretch", Prop.Image.Stretch);
				this.Flag = Prop.Image.Stretch ? MF_CHECKED : MF_UNCHECKED;
				Dsp && Dsp.Refresh();
				RebuildMenu();
			}
		},
		{
			Flag: Prop.Image.KeepAspectRatio ? MF_CHECKED : MF_UNCHECKED,
			Caption: lang=="cn" ? "保持比例" : "Keep Aspect Ratio",
			Func: function(){
				Prop.Image.KeepAspectRatio = !Prop.Image.KeepAspectRatio;
				window.SetProperty("Image.KeepAspectRatio", Prop.Image.KeepAspectRatio);
				this.Flag = Prop.Image.KeepAspectRatio ? MF_CHECKED : MF_UNCHECKED;
				Dsp && Dsp.Refresh();
				RebuildMenu();
			}
		}
	);
	var Menu_SubItem_IS = {
		Flag: MF_POPUP,
		Caption: lang=="cn" ? "图像拉伸" : "Image Stretching",
		ID: null
	}
	
	// Main menu --------------------------------
	var Item_cycle = {
		Flag: MF_ENABLED,
		cap_play: lang=="cn" ? "继续循环" : "Resume Cycle",
		cap_pause: lang=="cn" ? "暂停循环" : "Pause Cycle",
		Caption: null,
		Func: function () {Ctrl && SetPauseStatus(Ctrl.Paused);}
	};
	Item_cycle.Caption = Ctrl.Paused ? Item_cycle.cap_play : Item_cycle.cap_pause;
	
	var Item_VWEV, Item_OIF;
	var Menu_Items = new Array (
		Item_cycle,
		{
			Flag: MF_GRAYED,
			Caption: lang=="cn" ? "上一张图片" : "Previous Image",
			Func: function(){Ctrl & Ctrl.Previous();}
		},
		{
			Flag: MF_GRAYED,
			Caption: lang=="cn" ? "下一张图片" : "Next Image",
			Func: function(){Ctrl && Ctrl.Next();}
		},
		{
			Flag: MF_GRAYED,
			Caption: lang=="cn" ? "第一张图片" : "First Image",
			Func: function(){Ctrl && Ctrl.First();}
		},
		{
			Flag: MF_GRAYED,
			Caption: lang=="cn" ? "最后一张图片" : "Last Image",
			Func: function(){Ctrl && Ctrl.Last();}
		},
		//--------------------------------------------------------------
		Item_VWEV = {
			Flag: MF_GRAYED,
			Caption: lang=="cn" ? "在外部查看器中查看" : "View With External Viewer",
			Func: function(){
				var path = Ctrl.CurImgPath;
				if (!path) return;
				if (path.charAt(0)=="<") {
					fb.ShowPopupMessage(lang=="cn" ? "当前图片为内嵌图片，无法用外部查看器显示" : "This image is embed image, can't be displayed in external viewer", "WSH Cover Panel", 1);
					return;
				}
				if (!Prop.Panel.ShellObj)
					Prop.Panel.ShellObj= new ActiveXObject("Shell.Application");
				Prop.Panel.ShellObj.ShellExecute('"' + path + '"', "", "", "open", 1);
			}
		},
		Item_OIF = {
			Flag: MF_GRAYED,
			Caption: lang=="cn" ? "打开图片所在目录" : "Open Containing Folder",
			Func: function(){
				var path = Ctrl.CurImgPath;
				if (!path) return;
				if (path.charAt(0)=="<")
					path = path.substring(1, path.length-1);
				if (!Prop.Panel.ShellObj)
					Prop.Panel.ShellObj= new ActiveXObject("Shell.Application");
				Prop.Panel.ShellObj.ShellExecute("explorer", '/select,\"' + path + '"', "", "open", 1);
			}
		},
		{
			Flag: MF_SEPARATOR		// Insert separator.
		},
		Menu_SubItem_IS			// Insert "Image Stretching" submenu.
		,
		Menu_SubItem_FC			// Insert "Follow Cursor" submenu.
		,
		{
			Flag: MF_SEPARATOR
		},
		{
			Caption: lang=="cn" ? "刷新图片" : "Refresh Image",
			Func: function(){Dsp && Dsp.Refresh();}
		},
		{
			Flag: (Prop.Image.ImageCacheCapacity || Prop.Image.PathCacheCapacity) ? MF_ENABLED : MF_GRAYED,
			Caption: lang=="cn" ? "清除缓存" : "Clear Cache",
			Func: function () {
				ImgLoader && ImgLoader.ClearCache();
				ImgFinder && ImgFinder.ClearCache();
				CollectGarbage();			// Release memory.
			}
		},
		{
			Flag: MF_SEPARATOR
		},
		{
			Caption: lang=="cn" ? "WSH Cover 参数设置..." : "WSH Cover Properties...",
			Func: function(){window.ShowProperties();}
		},
		{
			Caption: lang=="cn" ? "帮助..." : "Help...",
			Func: function(){
				var HelpFile = Prop.Panel.WorkDirectory + "WSH_Cover_Properties_Help.txt";
				if (!Prop.Panel.FSO.FileExists(HelpFile)) {
					fb.ShowPopupMessage(Prop.Panel.lang=="cn" ? "找不到 "+HelpFile+" 文件." : "Can not find file "+HelpFile+" .", "WSH Cover Panel", 1);
					return;
				}
				var file = Prop.Panel.FSO.OpenTextFile(HelpFile, 1);
				var txt = file.ReadAll();
				fb.ShowPopupMessage(txt, "WSH Cover Panel", 2);
				file.Close();
			}
		}
	);
	
	this.ViewWithExternalViewer = Item_VWEV.Func;
	this.Menu_Items = Menu_Items;
	var Menu, Menu_FC;
	
	var RebuildMenu = function(){
		ItemList = {};
		ItemID = 1;
		Menu_FC = BuildMenu(Menu_FC_Items);
		Menu_SubItem_FC.ID = Menu_FC.ID;
		Menu_IS = BuildMenu(Menu_IS_Items);
		Menu_SubItem_IS.ID = Menu_IS.ID;
		Menu = BuildMenu(Menu_Items);
	};
	
	this.RebuildMenu = RebuildMenu;
	RebuildMenu();
	
	this.Show = function (x, y) {
		var ret = Menu.TrackPopupMenu(x, y);
		if (ret!=0)
			ItemList[ret].Func();
	};
	
	this.SetCycleStatus = function (s) {
		for (var i=1; i<5; i++)
			Menu_Items[i].Flag = s ? MF_ENABLED : MF_GRAYED;
		RebuildMenu();
	};
	
	this.SetPauseStatus = function (s) {
		if (s)
			Item_cycle.Caption = Item_cycle.cap_pause;
		else
			Item_cycle.Caption = Item_cycle.cap_play;
		RebuildMenu();
	};
	
	var IsDefaultImg = true;
	this.toDefault = function (s) {
		if (s==IsDefaultImg) return;
		IsDefaultImg = s;
		Item_VWEV.Flag = s ? MF_GRAYED : MF_ENABLED;
		Item_OIF.Flag = s ? MF_GRAYED : MF_ENABLED;
		RebuildMenu();
	};
	
} (Properties, Controller, Display, Buttons, ImageLoader, PathChecker);


//====================================================
function SetCycleStatus (s) {
	Controller.CycleActivated = s;
	Buttons.SetCycleStatus && Buttons.SetCycleStatus(s);
	FuncMenu && FuncMenu.SetCycleStatus(s);
};

function SetPauseStatus (s) {
	if (s)
		Controller.Play();
	else
		Controller.Pause();
	Buttons.SetPauseStatus && Buttons.SetPauseStatus(s);
	FuncMenu && FuncMenu.SetPauseStatus(s);
};

function toDefault (s) {
	FuncMenu && FuncMenu.toDefault(s);
};


//====================================================
// BackGround ===========================================
var bgcolor = Properties.Panel.BackGroundColor;
if (bgcolor)
	bgcolor = RGBA(bgcolor[0], bgcolor[1], bgcolor[2], bgcolor[3]);


//====================================================
//====================================================
on_item_focus_change();

function on_paint(gr){
	if (bgcolor)
		gr.FillSolidRect(0, 0, ww, wh, bgcolor);
	gr.SetSmoothingMode(Properties.Image.SmoothingMode);
	gr.SetInterpolationMode(Properties.Image.InterpolationMode);
	Display.OnPaint && Display.OnPaint(gr);
	Buttons.OnPaint && Buttons.OnPaint(gr);
}
function on_size(){
	if (!window.Width || !window.Height) return;
	ww = window.Width;
	wh = window.Height;
	Display.OnResize && Display.OnResize(ww, wh);
	Buttons.OnResize && Buttons.OnResize(ww, wh);
	if (fb.IsPlaying){
		var metadb = fb.GetNowPlaying();
		on_playback_new_track(metadb);
	}
}
// Track events ---------------------------------------------
function on_item_focus_change(){
	if (fb.GetFocusItem() && (Properties.Panel.FollowCursor==2 || (Properties.Panel.FollowCursor==1 && !fb.IsPlaying)))
		Controller.OnNewTrack && Controller.OnNewTrack(fb.GetFocusItem(), true);
}
function on_playlist_switch(){
	if (Properties.Panel.FollowCursor==2 || (Properties.Panel.FollowCursor==1 && !fb.IsPlaying))
		Controller.OnNewTrack && Controller.OnNewTrack(fb.GetFocusItem(), true);
}
function on_playback_new_track(metadb){
	if (Properties.Panel.FollowCursor<=1) {
		Controller.OnNewTrack && Controller.OnNewTrack(metadb);
    }
}
function on_playback_stop(reason){
	var metadb = fb.GetFocusItem();
	if (Properties.Panel.FollowCursor==0 || !metadb)
		Controller.OnStop && Controller.OnStop(reason);
	else if (Properties.Panel.FollowCursor==1 && reason!=2)
		Controller.OnNewTrack && Controller.OnNewTrack(metadb, true);
}
// Mouse events --------------------------------------------
var rbtnDown, ShiftDown, mbtnDown;
function on_mouse_move(x,y){
	Buttons.OnMouseMove && Buttons.OnMouseMove(x, y);
}
function on_mouse_lbtn_down(x,y){
	Buttons.OnLbtnDown && Buttons.OnLbtnDown(x, y);
}
function on_mouse_lbtn_up(x,y){
	Buttons.OnLbtnUp && Buttons.OnLbtnUp(x, y);
}
function on_mouse_leave(){
	Buttons.OnMouseLeave && Buttons.OnMouseLeave();
}
function on_mouse_rbtn_down(x, y, vkey){
	rbtnDown = true;
	ShiftDown = vkey==6 ? true : false;
}
function on_mouse_rbtn_up(x, y, vkey){
	if (!rbtnDown) return true;
	rbtnDown = false;
	if (ShiftDown)
		return;			// If shift key was pressed down, show default right click menu.
	else {
		FuncMenu.Show(x,y);			// Show customize menu.
		return true;			// Disable default right click menu.
	}
}
function on_mouse_mbtn_down(x, y, mask) {
	mbtnDown = true;
}
function on_mouse_mbtn_up(x, y, mask) {
	if (!mbtnDown) return;
	FuncMenu.ViewWithExternalViewer();
	mbtnDown = false;
}
function on_mouse_wheel(delta){
	if (delta>0)
		Controller && Controller.Previous();
	else
		Controller && Controller.Next();
}
//---------------------------------------------------------
function on_timer(id){
	Controller.OnTimer && Controller.OnTimer(id);
	Display.OnTimer && Display.OnTimer(id);
	Buttons.OnTimer && Buttons.OnTimer(id);
}

//EOF