Guest User

WSH Simple Playlist Viewer v0.7.0 by Br3tt

a guest
Nov 10th, 2011
566
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==PREPROCESSOR==
  2. // @name "WSH Simple Playlist Viewer"
  3. // @version "0.7.0"
  4. // @author "Br3tt"
  5. // @feature "v1.4"
  6. // @feature "watch-metadb"
  7. // @feature "dragdrop"
  8. // ==/PREPROCESSOR==
  9.  
  10. // [Requirements]
  11. // * foobar2000 v1.1 or better ( >> http://foobar2000.org)
  12. // * WSH panel Mod v1.5.0 or better ( >> http://http://code.google.com/p/foo-wsh-panel-mod/downloads/list)
  13. // * Fonts :
  14. //   - guifx v2 transports ( >> http://http://blog.guifx.com/2009/04/02/guifx-v2-transport-font)
  15. //   - lucida console (bundled with Windows)
  16. //   - tahoma (bundled with Windows)
  17. // [/Requirements]
  18.  
  19. // [Informations]
  20. // * change colors and fonts in foobar2000 Preferences > DefaultUI or ColumsUI
  21. // * Some Settings can be changed in window Properties (right click columns header > Properties)
  22. // * double click on columns header > Show Now Playing item
  23. // * use keyboard to search artist in the playlist (incremental search feature, search string displayd on bottom-left)
  24. // * to sort hte playlist, use menu Edit>Sort
  25. // [/Informations]
  26.  
  27. //=================================================// General declarations
  28. // Used in window.SetCursor()
  29. // {{
  30. IDC_ARROW = 32512;
  31. IDC_IBEAM = 32513;
  32. IDC_WAIT = 32514;
  33. IDC_CROSS = 32515;
  34. IDC_UPARROW = 32516;
  35. IDC_SIZE = 32640;
  36. IDC_ICON = 32641;
  37. IDC_SIZENWSE = 32642;
  38. IDC_SIZENESW = 32643;
  39. IDC_SIZEWE = 32644;
  40. IDC_SIZENS = 32645;
  41. IDC_SIZEALL = 32646;
  42. IDC_NO = 32648;
  43. IDC_APPSTARTING = 32650;
  44. IDC_HAND = 32649;
  45. IDC_HELP = 32651;
  46. // }}
  47. // Use with GdiDrawText()
  48. // {{
  49. var DT_LEFT = 0x00000000;
  50. var DT_RIGHT = 0x00000002;
  51. var DT_TOP = 0x00000000;
  52. var DT_CENTER = 0x00000001;
  53. var DT_VCENTER = 0x00000004;
  54. var DT_WORDBREAK = 0x00000010;
  55. var DT_SINGLELINE = 0x00000020;
  56. var DT_CALCRECT = 0x00000400;
  57. var DT_NOPREFIX = 0x00000800;
  58. var DT_EDITCONTROL = 0x00002000;
  59. var DT_END_ELLIPSIS = 0x00008000;
  60. // }}
  61. // Keyboard Flags & Tools
  62. // {{
  63. var VK_BACK = 0x08;
  64. var VK_RETURN = 0x0D;
  65. var VK_SHIFT = 0x10;
  66. var VK_CONTROL = 0x11;
  67. var VK_ALT = 0x12;
  68. var VK_ESCAPE = 0x1B;
  69. var VK_PGUP = 0x21;
  70. var VK_PGDN = 0x22;
  71. var VK_END = 0x23;
  72. var VK_HOME = 0x24;
  73. var VK_LEFT = 0x25;
  74. var VK_UP = 0x26;
  75. var VK_RIGHT = 0x27;
  76. var VK_DOWN = 0x28;
  77. var VK_INSERT = 0x2D;
  78. var VK_DELETE = 0x2E;
  79. var KMask = {
  80.     none: 0,
  81.     ctrl: 1,
  82.     shift: 2,
  83.     ctrlshift: 3,
  84.     ctrlalt: 4,
  85.     ctrlaltshift: 5,
  86.     alt: 6
  87. };
  88. function GetKeyboardMask() {
  89.     var c = utils.IsKeyPressed(VK_CONTROL) ? true : false;
  90.     var a = utils.IsKeyPressed(VK_ALT) ? true : false;
  91.     var s = utils.IsKeyPressed(VK_SHIFT) ? true : false;
  92.     var ret = KMask.none;
  93.     if (c && !a && !s) ret = KMask.ctrl;
  94.     if (!c && !a && s) ret = KMask.shift;
  95.     if (c && !a && s) ret = KMask.ctrlshift;
  96.     if (c && a && !s) ret = KMask.ctrlalt;
  97.     if (c && a && s) ret = KMask.ctrlaltshift;
  98.     if (!c && a && !s) ret = KMask.alt;
  99.     return ret;
  100. };
  101. // }}
  102. // {{
  103. // Used in window.GetColorCUI()
  104. ColorTypeCUI = {
  105.     text: 0,
  106.     selection_text: 1,
  107.     inactive_selection_text: 2,
  108.     background: 3,
  109.     selection_background: 4,
  110.     inactive_selection_background: 5,
  111.     active_item_frame: 6
  112. };
  113. // Used in window.GetFontCUI()
  114. FontTypeCUI = {
  115.     items: 0,
  116.     labels: 1
  117. };
  118. // Used in window.GetColorDUI()
  119. ColorTypeDUI = {
  120.     text: 0,
  121.     background: 1,
  122.     highlight: 2,
  123.     selection: 3
  124. };
  125. // Used in window.GetFontDUI()
  126. FontTypeDUI = {
  127.     defaults: 0,
  128.     tabs: 1,
  129.     lists: 2,
  130.     playlists: 3,
  131.     statusbar: 4,
  132.     console: 5
  133. };
  134. //}}
  135. // {{
  136. // Used in gr.DrawString()
  137. function StringFormat() {
  138.     var h_align = 0,
  139.     v_align = 0,
  140.     trimming = 0,
  141.     flags = 0;
  142.     switch (arguments.length) {
  143.         case 3:
  144.         trimming = arguments[2];
  145.         case 2:
  146.         v_align = arguments[1];
  147.         case 1:
  148.         h_align = arguments[0];
  149.         break;
  150.         default:
  151.         return 0;
  152.     };
  153.     return ((h_align << 28) | (v_align << 24) | (trimming << 20) | flags);
  154. };
  155. var StringAlignment = {
  156.     Near: 0,
  157.     Centre: 1,
  158.     Far: 2
  159. };
  160. var lt_stringformat = StringFormat(StringAlignment.Near, StringAlignment.Near);
  161. var ct_stringformat = StringFormat(StringAlignment.Centre, StringAlignment.Near);
  162. var rt_stringformat = StringFormat(StringAlignment.Far, StringAlignment.Near);
  163. var lc_stringformat = StringFormat(StringAlignment.Near, StringAlignment.Centre);
  164. var cc_stringformat = StringFormat(StringAlignment.Centre, StringAlignment.Centre);
  165. var rc_stringformat = StringFormat(StringAlignment.Far, StringAlignment.Centre);
  166. var lb_stringformat = StringFormat(StringAlignment.Near, StringAlignment.Far);
  167. var cb_stringformat = StringFormat(StringAlignment.Centre, StringAlignment.Far);
  168. var rb_stringformat = StringFormat(StringAlignment.Far, StringAlignment.Far);
  169. //}}
  170. // {{
  171. // Used everywhere!
  172. function RGB(r, g, b) {
  173.     return (0xff000000 | (r << 16) | (g << 8) | (b));
  174. };
  175. function RGBA(r, g, b, a) {
  176.     return ((a << 24) | (r << 16) | (g << 8) | (b));
  177. };
  178. function num(strg, nb) {
  179.     var i;
  180.     var str = strg.toString();
  181.     var k = nb - str.length;
  182.     if (k > 0) {
  183.         for (i=0;i<k;i++) {
  184.             str = "0" + str;
  185.         };
  186.     };
  187.     return str.toString();
  188. };
  189. function TrackType(trkpath) {
  190.     var taggable;
  191.     var type;
  192.     switch (trkpath) {
  193.         case "file":
  194.         taggable = 1;
  195.         type = 0;
  196.         break;
  197.         case "cdda":
  198.         taggable = 1;
  199.         type = 1;
  200.         break;
  201.         case "FOO_":
  202.         taggable = 0;
  203.         type = 2;
  204.         break;
  205.         case "http":
  206.         taggable = 0;
  207.         type = 3;
  208.         break;
  209.         case "mms:":
  210.         taggable = 0;
  211.         type = 3;
  212.         break;
  213.         case "unpa":
  214.         taggable = 0;
  215.         type = 4;
  216.         break;
  217.         default:
  218.         taggable = 0;
  219.         type = 5;
  220.     };
  221.     return type;
  222. };
  223. //}}
  224.  
  225. //=================================================// Image declarations
  226. var playicon_off;
  227. var playicon_light;
  228. var playicon_shadow;
  229. var nocover;
  230. var streamcover;
  231. var star_img_off;
  232. var star_img_on;
  233. var star_img_hov;
  234. var star_img_kill;
  235.  
  236. //=================================================// Cover object
  237. cover2load = function () {
  238.     this.create = function (item, timer_value) {
  239.         this.item = item;
  240.         this.timer = window.SetInterval(function() {
  241.             window.ClearInterval(mycover2load[0].timer);
  242.             var cx = mycover2load[0].item.x + cover.margin;
  243.             var cy = mycover2load[0].item.y - ((cover.nbrows-1)*row.h);
  244.             mycover2load[0].item.cover_img = g_image_cache.getit(mycover2load[0].item);
  245.             mycover2load.shift();
  246.             window.RepaintRect(cx-2, cy-2, cover.w+4, cover.h+4);
  247.         }, timer_value);
  248.     };
  249. };
  250. var mycover2load = Array();
  251.  
  252. //=================================================// Cover Tools
  253. image_cache = function () {
  254.     this._cachelist = {};
  255.     this.hit = function (item) {
  256.         var img = this._cachelist[item.metadb.path];
  257.         if (typeof img == "undefined") {
  258.             mycover2load.push(new cover2load);
  259.             mycover2load[mycover2load.length-1].create(item, (250-mycover2load.length*5)*mycover2load.length);
  260.         };
  261.         return img;
  262.     };
  263.     this.getit = function (item) {
  264.         var img = refresh_cover(item);
  265.         this._cachelist[item.metadb.path] = img;
  266.         return img;
  267.     };
  268. };
  269. var g_image_cache = new image_cache;
  270.  
  271. function FormatCover(image, w, h) {
  272.     if(!image || w<=0 || h<=0) return image;
  273.     return image.Resize(w, h, 7);  
  274. };
  275.  
  276. function refresh_cover(item) {
  277.     var type = 0;
  278.     var cover_img;
  279.     var pw = cover.w-cover.margin*2;
  280.     var ph = cover.h-cover.margin*2;
  281.     // item.cover_type : 0 = nocover, 1 = external cover, 2 = embedded cover, 3 = stream
  282.     if(item.track_type!=3) {
  283.         if(item.metadb) {
  284.             cover_img = FormatCover(utils.GetAlbumArtEmbedded(item.metadb.rawpath, type), pw, ph);
  285.             if(!cover_img) {
  286.                 cover_img = FormatCover(utils.GetAlbumArtV2(item.metadb, type), pw, ph);
  287.                 if(!cover_img) {
  288.                     cover_img = FormatCover(nocover, pw, ph);
  289.                     item.cover_type = 0;
  290.                 }; else {
  291.                     item.cover_type = 1;
  292.                 };
  293.             }; else {
  294.                 item.cover_type = 2;
  295.             };
  296.         };
  297.     }; else if (fb.IsPlaying && fb.PlaybackLength) {
  298.         cover_img = FormatCover(streamcover, pw, ph);
  299.         item.cover_type = 3;
  300.     }; else {
  301.         cover_img = FormatCover(nocover, pw, ph);
  302.         item.cover_type = 0;
  303.     };
  304.     return cover_img;
  305. };
  306.  
  307. function reset_cover_timers() {
  308.     for(var i in mycover2load) {
  309.         mycover2load[i].timer && window.ClearInterval(mycover2load[i].timer);
  310.     };
  311.     mycover2load.splice(0, mycover2load.length);
  312. };
  313.  
  314. //=================================================// Button object
  315. ButtonStates = {normal: 0, hover: 1, down: 2};
  316. button = function (normal, hover, down) {
  317.     this.img = Array(normal, hover, down);
  318.     this.w = this.img[0].Width;
  319.     this.h = this.img[0].Height;
  320.     this.state = ButtonStates.normal;
  321.     this.draw = function (gr, x, y, alpha) {
  322.         this.x = x;
  323.         this.y = y;
  324.         this.img[this.state] && gr.DrawImage(this.img[this.state], this.x, this.y, this.w, this.h, 0, 0, this.w, this.h, 0, alpha);
  325.     };
  326.     this.display_context_menu = function (x, y, id) {};
  327.     this.repaint = function () {
  328.         window.RepaintRect(this.x, this.y, this.w, this.h);
  329.     };
  330.     this.checkstate = function (event, x, y) {
  331.         this.ishover = (x > this.x && x < this.x + this.w - 1 && y > this.y && y < this.y + this.h - 1);
  332.         this.old = this.state;
  333.         switch (event) {
  334.          case "down":
  335.             switch(this.state) {
  336.              case ButtonStates.normal:
  337.              case ButtonStates.hover:
  338.                 this.state = this.ishover ? ButtonStates.down : ButtonStates.normal;
  339.                 break;
  340.             };
  341.             break;
  342.          case "up":
  343.             this.state = this.ishover ? ButtonStates.hover : ButtonStates.normal;
  344.             break;
  345.          case "right":
  346.              if(this.ishover) this.display_context_menu(x, y, id);
  347.              break;
  348.          case "move":
  349.             switch(this.state) {
  350.              case ButtonStates.normal:
  351.              case ButtonStates.hover:
  352.                 this.state = this.ishover ? ButtonStates.hover : ButtonStates.normal;
  353.                 break;
  354.             };
  355.             break;
  356.          case "leave":
  357.             this.state = this.isdown ? ButtonStates.down : ButtonStates.normal;
  358.             break;
  359.         };
  360.         if(this.state!=this.old) this.repaint();
  361.         return this.state;
  362.     };
  363. };
  364.  
  365. //=================================================// Titleformat field
  366. var tf_path = fb.TitleFormat("$left(%_path_raw%,4)");
  367. var tf_cover_path = fb.TitleFormat("$replace(%path%,%filename_ext%,)");
  368. var tf_tracknumber = fb.TitleFormat("%tracknumber%");
  369. var tf_artist = fb.TitleFormat("$if(%length%,%artist%,'Stream')");
  370. var tf_title = fb.TitleFormat("%title%");
  371. var tf_albumartist = fb.TitleFormat("$if(%length%,%album artist%,'Stream')");
  372. var tf_album = fb.TitleFormat("$if2(%album%,$if(%length%,'Single','web radios'))");
  373. var tf_disc = fb.TitleFormat("$if2(%discnumber%,0)");
  374. var tf_disc_info = fb.TitleFormat("$if(%discnumber%,$ifgreater(%totaldiscs%,1,' - [disc '%discnumber%$if(%totaldiscs%,'/'%totaldiscs%']',']'),),)");
  375. var tf_rating = fb.TitleFormat("$if2(%rating%,0)");
  376. var tf_playcount = fb.TitleFormat("$if2(%play_counter%,$if2(%play_count%,0))");
  377. var tf_length = fb.TitleFormat("$if2(%length%,' 0:00')");
  378. var tf_date = fb.TitleFormat("$if2($year(%date%),)");
  379. var tf_playback_time = fb.TitleFormat("%playback_time%");
  380. var tf_playback_time_remaining = fb.TitleFormat("$if(%length%,-%playback_time_remaining%,'0:00')");
  381.  
  382. //=================================================// Globals
  383. wsh = {
  384.     transparency: window.GetProperty("wsh.transparency.enabled", false),
  385.     first_on_size: true,
  386.     context_menu_called: false,
  387.     action_shownowplaying: false,
  388.     drag_drop_enabled: window.GetProperty("wsh.drag_drop.enabled", false),
  389.     drag: false,
  390.     is_hover: false,
  391.     mousewheel_timerID: false,
  392.     repaint_rating_timer_ID: false
  393. };
  394. cover = {
  395.     visible: true,
  396.     margin: 6,
  397.     w: window.GetProperty("list.cover.size", 75),
  398.     nbrows: 0,
  399.     h: 0,
  400.     top_offset: 0,
  401.     show_total_tracks : window.GetProperty("cover.show_total_tracks.enabled", true)
  402. };
  403. column = {
  404.     visible: false,
  405.     h: 20,    
  406.     label: Array("Cover", "Track", "Artist / Title", "Rating", "Time"),
  407.     w: Array(0, 52, 0, 74, 50),
  408.     total: 5
  409. };
  410. list = {
  411.     total: 0,
  412.     nbvis: 0,
  413.     stock: 0,
  414.     offset: 0,
  415.     tocut: 0,
  416.     item: Array(),
  417.     handlelist: null,
  418.     metadblist_selection: plman.GetPlaylistSelectedItems(fb.ActivePlaylist),
  419.     singleton_select_id: 0,
  420.     nowplaying: plman.GetPlayingItemLocation(),
  421.     nowplaying_y: 0
  422. };
  423. row = {
  424.     h: window.GetProperty("list.row.height", 26),
  425.     w: 0
  426. };
  427. toolbar = {
  428.     visible: window.GetProperty("list.toolbar.visible", true),
  429.     default_h: window.GetProperty("list.toolbar.height", 20),
  430.     h: 20,
  431.     arr_buttons: Array()
  432. };
  433. vscrollbar = {
  434.     theme: false,
  435.     themed: window.GetProperty("list.vscrollbar.themed", true),
  436.     show: window.GetProperty("list.vscrollbar.visible", true),
  437.     visible: true,
  438.     hover: false,
  439.     x: 0,
  440.     y: 0,
  441.     default_w: get_system_scrollbar_width(),
  442.     w: get_system_scrollbar_width(),
  443.     h: 0,
  444.     button_total: 2,
  445.     default_step: window.GetProperty("list.vscrollbar.step", 3),
  446.     step: 3,
  447.     arr_buttons: Array(),
  448.     letter : null,
  449.     timerID: false
  450. };
  451. button_up = {
  452.     img_normal: null,
  453.     img_hover: null,
  454.     img_down: null,
  455.     x: 0,
  456.     y: 0,
  457.     w: vscrollbar.default_w,
  458.     h: vscrollbar.default_w,
  459.     timerID: false
  460. };
  461. button_down = {
  462.     img_normal: null,
  463.     img_hover: null,
  464.     img_down: null,
  465.     x: 0,
  466.     y: 0,
  467.     w: vscrollbar.default_w,
  468.     h: vscrollbar.default_w,
  469.     timerID: false
  470. };
  471. cursor = {
  472.     bt: null,
  473.     img_normal: null,
  474.     img_hover: null,
  475.     img_down: null,
  476.     popup: null,
  477.     x: 0,
  478.     y: 0,
  479.     w: vscrollbar.default_w,
  480.     h: vscrollbar.default_w+3,
  481.     default_h: vscrollbar.default_w+3,
  482.     hover: false,
  483.     drag: false,
  484.     grap_y: 0,
  485.     timerID: false
  486. };
  487. ItemStates = {
  488.     normal: 0,
  489.     hover: 1,
  490.     selected: 2
  491. };
  492.  
  493. //=================================================// Item object
  494. function repaint_rating(item) {
  495.     if(!wsh.repaint_rating_timer_ID) {
  496.         wsh.repaint_rating_timer_ID = window.SetTimeout(function() {
  497.             window.RepaintRect(item.rating_x, item.rating_y, rating_w, rating_h);
  498.             wsh.repaint_rating_timer_ID = false;
  499.         },40);
  500.     };
  501. };
  502. item = function () {
  503.     var i;
  504.     this.create = function(id, metadb, idx, gridx) {
  505.         if (typeof this.id == "undefined") {
  506.             this.idx = idx;
  507.             this.id = id;
  508.             this.gridx = gridx;
  509.             this.state = ItemStates.normal;
  510.             this.metadb = metadb;
  511.             if(metadb) {
  512.                 this.track_type = TrackType(this.metadb.rawpath.substring(0,4));
  513.                 this.tracknumber = tf_tracknumber.EvalWithMetadb(this.metadb);
  514.                 this.artist = tf_artist.EvalWithMetadb(this.metadb);
  515.                 this.title = tf_title.EvalWithMetadb(this.metadb);
  516.                 this.albumartist = tf_albumartist.EvalWithMetadb(this.metadb);
  517.                 this.album = tf_album.EvalWithMetadb(this.metadb);
  518.                 this.disc = tf_disc.EvalWithMetadb(this.metadb);
  519.                 this.rating = tf_rating.EvalWithMetadb(this.metadb);
  520.                 this.playcount = tf_playcount.EvalWithMetadb(this.metadb);
  521.                 this.length = tf_length.EvalWithMetadb(this.metadb);
  522.                 this.date = tf_date.EvalWithMetadb(this.metadb);
  523.                 this.group_key = this.albumartist+this.album+this.disc;
  524.             };
  525.             this.x = 0;
  526.             this.y = toolbar.h + (idx-list.tocut) * row.h;
  527.             this.w = ww - (vscrollbar.visible && vscrollbar.show?vscrollbar.w:0);
  528.             this.h = row.h;
  529.             this.tooltip = false;
  530.         };
  531.     };
  532.     this.template_it = function(id, metadb) {
  533.         if (typeof this.id == "undefined") {
  534.             this.id = id;
  535.             this.metadb = metadb;
  536.             if(metadb) {
  537.                 this.albumartist = tf_albumartist.EvalWithMetadb(this.metadb);
  538.                 this.album = tf_album.EvalWithMetadb(this.metadb);
  539.                 this.disc = tf_disc.EvalWithMetadb(this.metadb);
  540.                 this.group_key = this.albumartist+this.album+this.disc;
  541.             };
  542.         };
  543.     };
  544.     this.draw = function(gr, id) {
  545.         var parity = (Math.round(id/2)==(id/2));
  546.         if(this.metadb.Compare(fb.GetFocusItem())) {
  547.             plman.SetPlaylistSelectionSingle(fb.ActivePlaylist, plman.GetPlaylistFocusItemIndex(fb.ActivePlaylist), true);
  548.             if(this.state!=ItemStates.selected) {
  549.                 this.state = ItemStates.selected;
  550.                 list.singleton_select_id = this.id;
  551.             };
  552.         };
  553.         this.state = plman.IsPlaylistItemSelected(fb.ActivePlaylist, this.id) ? ItemStates.selected : ItemStates.normal;
  554.  
  555.         switch(this.state) {
  556.          case ItemStates.normal:
  557.             var text_colour = g_textcolor;
  558.             var tracknumber_backcolor = g_textcolor;
  559.             var tracknumber_textcolor = g_backcolor;
  560.             break;
  561.          case ItemStates.hover:
  562.             var text_colour = g_textcolor;
  563.             var tracknumber_backcolor = g_textcolor;
  564.             var tracknumber_textcolor = g_backcolor;
  565.             break;
  566.          case ItemStates.selected:
  567.             var text_colour = g_textcolor_sel;
  568.             var tracknumber_backcolor = g_textcolor_sel;
  569.             var tracknumber_textcolor = g_backcolor;
  570.             break;
  571.         };
  572.         // Item background
  573.         var bg_colour = parity?RGBA(255,255,255,6):RGBA(0,0,0,6);
  574.         if(this.state==ItemStates.selected) {
  575.             gr.FillSolidRect(this.x+column.w[0], this.y+1, this.w-column.w[0]-1, this.h-1, g_backcolor_sel&0x44ffffff);
  576.         }; else {
  577.             gr.FillSolidRect(this.x+column.w[0], this.y, this.w-column.w[0]-1, this.h, bg_colour);
  578.             gr.FillGradRect(this.x+column.w[0], this.y, this.w-column.w[0]-1, 1, 0, 0, g_backcolor&0x20000000, 0.5);
  579.             gr.FillGradRect(this.x+column.w[0], this.y+1, this.w-column.w[0]-1, 1, 0, 0, RGBA(255,255,255,35), 0.5);
  580.         };
  581.         gr.FillSolidRect(column.w[0]-0, this.y, 1, row.h, RGBA(0,0,0,100));
  582.         gr.FillSolidRect(column.w[0]-1, this.y, 1, row.h, RGBA(255,255,255,25));
  583.         gr.FillSolidRect(column.w[0]+1, this.y, 1, row.h, RGBA(0,0,0,60));
  584.         gr.FillSolidRect(column.w[0]+2, this.y, 1, row.h, RGBA(0,0,0,30));
  585.         gr.FillSolidRect(column.w[0]+3, this.y, 1, row.h, RGBA(0,0,0,10));
  586.        
  587.         // if last item, draw bottom shadow!
  588.         if(this.id==list.total-1) {
  589.             gr.FillGradRect(this.x+column.w[0], this.y+this.h, this.w-column.w[0], 4, 90, RGBA(0,0,0,70), 0, 1.0);
  590.             gr.FillSolidRect(this.x+column.w[0], this.y+this.h, this.w-column.w[0], 1, RGBA(0,0,0,25));
  591.             if(this.y+this.h<toolbar.h+wh) {
  592.                 gr.FillSolidRect(column.w[0]-0, this.y+this.h, 1, toolbar.h+wh-(this.y+this.h), RGBA(0,0,0,100));
  593.                 gr.FillSolidRect(column.w[0]-1, this.y+this.h, 1, toolbar.h+wh-(this.y+this.h), RGBA(255,255,255,25));
  594.                 gr.FillSolidRect(column.w[0]+1, this.y+this.h, 1, toolbar.h+wh-(this.y+this.h), RGBA(0,0,0,60));
  595.                 gr.FillSolidRect(column.w[0]+2, this.y+this.h, 1, toolbar.h+wh-(this.y+this.h), RGBA(0,0,0,30));
  596.                 gr.FillSolidRect(column.w[0]+3, this.y+this.h, 1, toolbar.h+wh-(this.y+this.h), RGBA(0,0,0,10));  
  597.             };
  598.         };
  599.        
  600.         // Draw fields
  601.         if(this.artist!=this.albumartist || this.grp_total < cover.nbrows) {
  602.             var artist_title_field = " "+this.artist+" / "+this.title;
  603.         }; else {
  604.             var artist_title_field = " "+this.title;
  605.         };
  606.        
  607.         var artist_title_width = gr.CalcTextWidth(artist_title_field, g_font);
  608.         if(this.playcount!=0) {
  609.             var measureStrTmp = gr.MeasureString("("+this.playcount+")", gdi.Font("tahoma", 8), 0, 0, 100, 7, 0);
  610.             var playcount_width = measureStrTmp.Width;
  611.         }; else {
  612.             var playcount_width = 0;
  613.         };
  614.         if(this.tracknumber=="?") this.tracknumber = num(this.grp_idx+1,2);
  615.         var tracknumber_width = gr.CalcTextWidth(this.tracknumber, g_font);
  616.  
  617.         // tracknumber
  618.         gr.SetSmoothingMode(2);
  619.         gr.FillRoundRect(this.x+column.w[0]+column.w[1]-(tracknumber_width+8)-4, this.y+5, (tracknumber_width+8), this.h-11, 1, 1, tracknumber_backcolor&0x66ffffff);
  620.         gr.DrawRoundRect(this.x+column.w[0]+column.w[1]-(tracknumber_width+8)-4, this.y+5, (tracknumber_width+8), this.h-11, 1, 1, 1.0, tracknumber_backcolor&0x72ffffff);
  621.         gr.SetSmoothingMode(0);
  622.         gr.GdiDrawText(this.tracknumber, g_font, tracknumber_textcolor, this.x+column.w[0]+column.w[1]-(tracknumber_width+8)-4, this.y, (tracknumber_width+5), this.h, DT_RIGHT | DT_CALCRECT | DT_VCENTER | DT_END_ELLIPSIS | DT_NOPREFIX);
  623.         // artist + title + playcount
  624.         if(artist_title_width+playcount_width+2>column.w[2]) this.tooltip = true;
  625.         gr.GdiDrawText(artist_title_field, g_font, text_colour, this.x+column.w[0]+column.w[1], this.y+1, (this.tooltip?column.w[2]-playcount_width-2:artist_title_width+2), this.h, DT_LEFT | DT_CALCRECT | DT_VCENTER | DT_END_ELLIPSIS | DT_NOPREFIX);
  626.         if(this.playcount!=0) {
  627.             gr.SetTextRenderingHint(5);
  628.             gr.DrawString("("+this.playcount+")", gdi.Font("tahoma", 8), text_colour&0x77ffffff, this.x+column.w[0]+column.w[1]+(this.tooltip?column.w[2]-playcount_width-2:artist_title_width+2), this.y-1, playcount_width, this.h, lc_stringformat);
  629.         };
  630.        
  631.         // Draw Cover Art & Group Breaks
  632.         if(this.grp_idx==0) {
  633.             if(cover.visible && this.idx!=list.tocut && this.grp_total>=cover.nbrows) {
  634.                 gr.FillGradRect(this.x+10, this.y, column.w[0]-20, 1, 0, 0, RGBA(0,0,0,100), 0.5);
  635.                 gr.FillGradRect(this.x+5, this.y-1, column.w[0]-10, 1, 0, 0, RGBA(255,255,255,50), 0.5);
  636.             };
  637.             gr.FillGradRect(this.x+column.w[0], this.y, this.w-column.w[0], 1, 0, RGBA(0,0,0,100), RGBA(0,0,0,50), 0.5);
  638.             gr.FillGradRect(this.x+column.w[0], this.y-1, this.w-column.w[0], 1, 0, RGBA(255,255,255,40), RGBA(255,255,255,10), 0.5);
  639.             gr.FillGradRect(this.x+column.w[0], this.y+1, this.w-column.w[0], 1, 0, RGBA(0,0,0,40), 0, 0.5);
  640.         };
  641.         if(cover.visible) {
  642.             // affect cover y to the items containing the cover art (part of the cover)
  643.             if(this.grp_idx<cover.nbrows) {
  644.                 list.item[this.idx].cover_top_y = this.y-(this.grp_idx*row.h)+cover.margin;
  645.             };
  646.         };
  647.         if(cover.visible && this.grp_idx==cover.nbrows-1) {
  648.             this.cover_img = g_image_cache.hit(this);
  649.             if(this.cover_img) {
  650.                 // Draw Cover Art
  651.                 gr.FillSolidRect(this.x+cover.margin-1, this.y-((cover.nbrows-1)*row.h)+cover.margin-(cover.top_offset*row.h)-1, cover.w-cover.margin*2+2, cover.h-cover.margin*2+2, RGBA(0,0,0,30));
  652.                 gr.FillSolidRect(this.x+cover.margin+1, this.y-((cover.nbrows-1)*row.h)+cover.margin-(cover.top_offset*row.h)+1, cover.w-cover.margin*2, cover.h-cover.margin*2, RGBA(0,0,0,75));
  653.                 gr.FillSolidRect(this.x+cover.margin+1, this.y-((cover.nbrows-1)*row.h)+cover.margin-(cover.top_offset*row.h)+1, cover.w-cover.margin*2+1, cover.h-cover.margin*2+1, RGBA(0,0,0,30));
  654.                 gr.DrawImage(this.cover_img, this.x+cover.margin, this.y-((cover.nbrows-1)*row.h)+cover.margin-(cover.top_offset*row.h), cover.w, cover.h, 0, 0, cover.w, cover.h, 0, 255);
  655.                 if(cover.show_total_tracks) {
  656.                     gr.FillSolidRect(this.x+cover.margin, this.cover_top_y+cover.h-cover.margin*2-10, cover.w-cover.margin*2, 10, RGBA(0,0,0,125));
  657.                     gr.FillSolidRect(this.x+cover.margin, this.cover_top_y+cover.h-cover.margin*2-11, cover.w-cover.margin*2, 1, RGBA(0,0,0,200));
  658.                 };
  659.                 gr.DrawRect(this.x+cover.margin, this.y-((cover.nbrows-1)*row.h)+cover.margin-(cover.top_offset*row.h), cover.w-cover.margin*2-1, cover.h-cover.margin*2-1, 1.0, RGB(255,255,255));
  660.                 gr.DrawRect(this.x+cover.margin+1, this.y-((cover.nbrows-1)*row.h)+cover.margin-(cover.top_offset*row.h)+1, cover.w-cover.margin*2-3, cover.h-cover.margin*2-3, 1.0, RGBA(0,0,0,50));
  661.                 if(cover.show_total_tracks) {
  662.                     gr.SetTextRenderingHint(5);
  663.                     gr.DrawString(this.grp_total+" TRACKS", gdi.Font("lucida console", 9), RGB(0,0,0), this.x+cover.margin, this.cover_top_y+cover.h-cover.margin*2-13+2, cover.w-cover.margin*2, 12, cc_stringformat);
  664.                     gr.DrawString(this.grp_total+" TRACKS", gdi.Font("lucida console", 9), RGB(255,255,255), this.x+cover.margin, this.cover_top_y+cover.h-cover.margin*2-13+1, cover.w-cover.margin*2, 12, cc_stringformat);
  665.                 };
  666.             };
  667.         };
  668.  
  669.         // Play icon + Duration
  670.         if(fb.PlayingPlaylist==fb.ActivePlaylist) {
  671.             if(fb.IsPlaying) {
  672.                 list.nowplaying = plman.GetPlayingItemLocation();
  673.                 if(this.id==list.nowplaying.PlaylistItemIndex) {
  674.                     list.nowplaying_y = this.y+this.h/2-playicon_off.Height/2;
  675.                     gr.DrawImage(playicon_shadow, this.x+column.w[0]+2, list.nowplaying_y, playicon_off.Width, playicon_off.Height, 0, 0, playicon_off.Width, playicon_off.Height, 0, 20);
  676.                     gr.DrawImage(playicon_shadow, this.x+column.w[0]+1, list.nowplaying_y, playicon_off.Width, playicon_off.Height, 0, 0, playicon_off.Width, playicon_off.Height, 0, 40);
  677.                     gr.DrawImage(playicon_shadow, this.x+column.w[0], list.nowplaying_y, playicon_off.Width, playicon_off.Height, 0, 0, playicon_off.Width, playicon_off.Height, 0, 60);
  678.                     gr.DrawImage(playicon_light, this.x+column.w[0], list.nowplaying_y, playicon_off.Width, playicon_off.Height, 0, 0, playicon_off.Width, playicon_off.Height, 0, 255);
  679.                     gr.DrawImage(playicon_off, this.x+column.w[0]-2, list.nowplaying_y, playicon_off.Width, playicon_off.Height, 0, 0, playicon_off.Width, playicon_off.Height, 0, 255);
  680.                     if(this.length==" 0:00") {
  681.                         this.playback_time_remaining = tf_playback_time.Eval(true);
  682.                     }; else {
  683.                         this.playback_time_remaining = tf_playback_time_remaining.Eval(true);
  684.                     };
  685.                     gr.GdiDrawText(this.playback_time_remaining+"  ", g_font, text_colour, this.x+column.w[0]+column.w[1]+column.w[2]+column.w[3], this.y+1, column.w[4], this.h, DT_RIGHT | DT_CALCRECT | DT_VCENTER | DT_END_ELLIPSIS);
  686.                 }; else {
  687.                     gr.GdiDrawText(this.length+"  ", g_font, text_colour, this.x+column.w[0]+column.w[1]+column.w[2]+column.w[3], this.y+1, column.w[4], this.h, DT_RIGHT | DT_CALCRECT | DT_VCENTER | DT_END_ELLIPSIS);
  688.                 };
  689.             }; else {
  690.                 gr.GdiDrawText(this.length+"  ", g_font, text_colour, this.x+column.w[0]+column.w[1]+column.w[2]+column.w[3], this.y+1, column.w[4], this.h, DT_RIGHT | DT_CALCRECT | DT_VCENTER | DT_END_ELLIPSIS);
  691.             };
  692.         }; else {
  693.             gr.GdiDrawText(this.length+"  ", g_font, text_colour, this.x+column.w[0]+column.w[1]+column.w[2]+column.w[3], this.y+1, column.w[4], this.h, DT_RIGHT | DT_CALCRECT | DT_VCENTER | DT_END_ELLIPSIS);
  694.         };
  695.  
  696.         // Rating engine
  697.         this.rating_x = this.x+column.w[0]+column.w[1]+column.w[2]+5;
  698.         this.rating_y = this.y + Math.round(row.h/2 - star_img_off.Height/2)+2;
  699.         if(this.rating_hov) {
  700.             for (i = 1; i < 6; i++){
  701.                 if(this.track_type<2){
  702.                     var img = (i > (this.rating_hov ? this.l_rating : this.rating)) ? star_img_off : (this.rating_hov ? (i==this.rating ? (i==this.l_rating ? star_img_kill : star_img_hov) : star_img_hov) : star_img_on);
  703.                     if(this.rating_hov && this.l_rating==this.rating) {
  704.                         img = i<=this.l_rating ? star_img_kill : star_img_off;
  705.                     };
  706.                 }; else {
  707.                     var img = star_img_off;
  708.                 };
  709.                 gr.DrawImage(img, this.rating_x+img.Width*(i-1), this.rating_y, img.Width, img.Height, 0, 0, img.Width, img.Height, 0, 255);
  710.             };
  711.         }; else {
  712.             gr.SetTextRenderingHint(4);
  713.             for(i=0;i<5;i++) {
  714.                 gr.DrawString("b", rating_font, (i+1<=this.rating)?text_colour&0xaaffffff:g_textcolor&0x20ffffff, this.rating_x+i*13, this.rating_y, 13, 18, lt_stringformat);
  715.             };
  716.         };
  717.     };
  718.    
  719.     this.checkstate = function (event, x, y, id) {
  720.         this.ishover = (x > this.x && x < this.x + this.w && y >= this.y && y < this.y + this.h);
  721.         if(this.ishover) {
  722.             this.cover_ishover = (this.grp_idx<cover.nbrows && x>cover.margin && x<column.w[0]-cover.margin && y>this.cover_top_y && y<this.cover_top_y+cover.h-cover.margin*2);
  723.         }; else {
  724.             this.cover_ishover = false;
  725.         };
  726.         var tmp = this.rating_hov;
  727.         var tmp_l_rating = this.l_rating;
  728.         this.rating_hov = (x > this.x+column.w[0]+column.w[1]+column.w[2] && x < this.x+column.w[0]+column.w[1]+column.w[2]+column.w[3] && y > this.rating_y+0 && y < this.rating_y + 16);
  729.         switch (event) {
  730.         case "down":
  731.             if(x>column.w[0]) {
  732.                 switch(this.state) {
  733.                 case ItemStates.normal:
  734.                 case ItemStates.hover:
  735.                     if(this.ishover && !this.rating_hov) {
  736.                         if(utils.IsKeyPressed(VK_CONTROL)) { // if Ctrl key pressed, add item to selection
  737.                             plman.SetPlaylistFocusItem(fb.ActivePlaylist, this.id);
  738.                             plman.SetPlaylistSelectionSingle(fb.ActivePlaylist, this.id, true);
  739.                             this.state = ItemStates.selected;
  740.                             list.singleton_select_id = this.id;
  741.                         }; else if(utils.IsKeyPressed(VK_SHIFT)) {
  742.                             if(list.singleton_select_id != this.id) {
  743.                                 SelectAtoB(list.singleton_select_id, this.id);
  744.                             };
  745.                         }; else {
  746.                             plman.SetPlaylistFocusItem(fb.ActivePlaylist, this.id);
  747.                             plman.ClearPlaylistSelection(fb.ActivePlaylist);
  748.                             plman.SetPlaylistSelectionSingle(fb.ActivePlaylist, this.id, true);
  749.                             this.state = ItemStates.selected;
  750.                             list.singleton_select_id = this.id;
  751.                         };
  752.                     };
  753.                     break;
  754.                 case ItemStates.selected:
  755.                     if(this.ishover && !this.rating_hov && list.metadblist_selection.Count>1) {
  756.                         if(utils.IsKeyPressed(VK_CONTROL)) { // if Ctrl key pressed, remove item from selection
  757.                             plman.SetPlaylistSelectionSingle(fb.ActivePlaylist, this.id, false);
  758.                             this.state = ItemStates.hover;
  759.                         }; else if(utils.IsKeyPressed(VK_SHIFT)) {
  760.                             if(list.singleton_select_id != this.id) {
  761.                                 SelectAtoB(list.singleton_select_id, this.id);
  762.                             };
  763.                         }; else {
  764.                             plman.SetPlaylistFocusItem(fb.ActivePlaylist, this.id);
  765.                             plman.ClearPlaylistSelection(fb.ActivePlaylist);
  766.                             plman.SetPlaylistSelectionSingle(fb.ActivePlaylist, this.id, true);
  767.                             this.state = ItemStates.selected;
  768.                             list.singleton_select_id = this.id;
  769.                         };
  770.                     };
  771.                     break;
  772.                 };
  773.                 // set Drag item status to true (for my WSH drag'n drop feature)
  774.                 if (this.ishover && !this.rating_hov && list.metadblist_selection.Count>0) wsh.drag = true;
  775.             }; else {
  776.                 // Cover Click done, select all items in the group
  777.                 if(this.grp_total>=cover.nbrows && this.cover_ishover) {
  778.                     SelectGroupItems(this.id - this.grp_idx, this.group_key);
  779.                 };
  780.             };
  781.             break;
  782.            
  783.         case "dblclk":
  784.             switch(this.state) {
  785.             case ItemStates.selected:
  786.                 if(this.ishover && !this.rating_hov) {
  787.                     fb.RunContextCommandWithMetadb("Play", this.metadb);
  788.                 };
  789.                 break;
  790.             };
  791.             break;
  792.            
  793.         case "right":
  794.             if(x>column.w[0]) {
  795.                 if(this.ishover && !this.rating_hov) {
  796.                     switch(this.state) {
  797.                         case ItemStates.normal:
  798.                         case ItemStates.hover:
  799.                             plman.SetPlaylistFocusItem(fb.ActivePlaylist, this.id);
  800.                             plman.ClearPlaylistSelection(fb.ActivePlaylist);
  801.                             plman.SetPlaylistSelectionSingle(fb.ActivePlaylist, this.id, true);
  802.                             this.state = ItemStates.selected;
  803.                             break;
  804.                     };
  805.                     new_context_menu(x, y, this.id, this.idx);
  806.                 };
  807.             }; else {
  808.                 if(this.grp_total>=cover.nbrows && this.cover_ishover) {
  809.                     // Cover Click done, select all items in the group
  810.                     SelectGroupItems(this.id - this.grp_idx, this.group_key);
  811.                     new_context_menu(x, y, this.id, this.idx);
  812.                 };
  813.             };
  814.             break;
  815.         case "up":
  816.             // Send handles to extra WSH panel that can support playlist add items from this panel selection
  817.             if(wsh.drag) {
  818.                 if(!wsh.is_hover && wsh.drag_drop_enabled) {
  819.                     window.NotifyOthers("WSH_playlist_drag_drop", list.metadblist_selection);
  820.                 };
  821.                 wsh.drag = false;
  822.                 window.SetCursor(IDC_ARROW);
  823.             };
  824.             // Rating
  825.             if(this.track_type<2 && this.rating_hov) {
  826.                 if(foo_playcount) {
  827.                     // Rate to database statistics brought by foo_playcount.dll
  828.                     if (this.l_rating != this.rating) {
  829.                         if(this.metadb) {
  830.                             var bool = fb.RunContextCommandWithMetadb("Rating/"+((this.l_rating==0) ? "<not set>" : this.l_rating), this.metadb);
  831.                             this.rating = this.l_rating;
  832.                         };
  833.                     }; else {
  834.                         var bool = fb.RunContextCommandWithMetadb("Rating/<not set>", this.metadb);
  835.                         this.rating = 0;
  836.                     };
  837.                 }; else {
  838.                     // Rate to file
  839.                     if (this.l_rating != this.rating) {
  840.                         if(this.metadb) {
  841.                             var bool = this.metadb.UpdateFileInfoSimple("RATING", this.l_rating);
  842.                             this.rating = this.l_rating;
  843.                         };
  844.                     }; else {
  845.                         var bool = this.metadb.UpdateFileInfoSimple("RATING","");
  846.                         this.rating = 0;
  847.                     };
  848.                 };
  849.             };
  850.             break;
  851.            
  852.         case "move":
  853.             if(y<=toolbar.h) {
  854.                 this.l_rating = 0;
  855.                 repaint_rating();
  856.             }; else {
  857.                 if(!wsh.drag) {
  858.                     // Rating
  859.                     if(this.rating_hov) {
  860.                         this.l_rating = Math.floor((x - this.rating_x) / star_img_off.Width) + 1;
  861.                     }; else {
  862.                         this.l_rating = 0;
  863.                     };
  864.                     switch(this.state) {
  865.                     case ItemStates.normal:
  866.                         if(this.ishover) {
  867.                             this.state = ItemStates.hover;
  868.                             if(this.rating_hov!=tmp) {
  869.                                 repaint_rating(this);
  870.                             };
  871.                         }; else {
  872.                             if(this.rating_hov!=tmp) {
  873.                                 repaint_rating(this);
  874.                             };  
  875.                         };
  876.                         break;
  877.                     case ItemStates.selected:
  878.                         if(this.rating_hov!=tmp || (this.rating_hov && this.l_rating!=tmp_l_rating) || (this.rating_hov && this.l_rating==0)) {
  879.                             repaint_rating(this);
  880.                         };
  881.                         break;
  882.                     case ItemStates.hover:
  883.                         if(!this.ishover) {
  884.                             this.state = ItemStates.normal;
  885.                             if(this.rating_hov!=tmp) {
  886.                                 repaint_rating(this);
  887.                             };  
  888.                         }; else {
  889.                             if(this.rating_hov!=tmp || (this.rating_hov && this.l_rating!=tmp_l_rating) || (this.rating_hov && this.l_rating==0)) {
  890.                                 repaint_rating(this);
  891.                             };
  892.                         };
  893.                         break;
  894.                     };
  895.                 };
  896.             };
  897.             break;
  898.            
  899.         case "leave":
  900.             this.l_rating = 0;
  901.             repaint_rating(this);
  902.             break;
  903.         };
  904.         return this.state;
  905.     };
  906. };
  907.  
  908. //=================================================// Globals (general)
  909. var g_instancetype = window.InstanceType;
  910. var g_font = null;
  911. var g_font_headers = null;
  912. var rating_font = gdi.Font("guifx v2 transports", 17, 0);
  913. var incsearch_font = gdi.Font("lucida console", 9, 0);
  914. var ww = 0, wh = 0;
  915. var mouse_x = 0, mouse_y = 0;
  916. var rating_x = 0, rating_y = 0;
  917. var rating_w = 0, rating_h = 0;
  918. var g_textcolor = 0, g_textcolor_sel = 0, g_textcolor_hl = 0, g_backcolor = 0, g_backcolor_sel = 0;
  919. var foo_playcount = utils.CheckComponent("foo_playcount", true);
  920. var g_start_new_track;
  921. var g_playtime_parity;
  922. var g_is_focused = false;
  923. var g_search_string = "";
  924. var clear_incsearch_timer = false;
  925. var incsearch_timer = false;
  926. var g_tooltip = window.CreateTooltip();
  927. var show_tooltip = false;
  928.  
  929. //=================================================// Tools (general)
  930. function get_system_scrollbar_width() {
  931.     var Shell = new ActiveXObject("WScript.Shell");
  932.     var tmp = Shell.RegRead("HKEY_CURRENT_USER\\Control Panel\\Desktop\\WindowMetrics\\ScrollWidth");
  933.     return Math.abs(tmp) / 15;
  934. };
  935.  
  936. function get_font() {
  937.     if (g_instancetype == 0) { // CUI
  938.         g_font = window.GetFontCUI(FontTypeCUI.items);
  939.         g_font_headers = window.GetFontCUI(FontTypeCUI.labels);
  940.     }; else if (g_instancetype == 1) { // DUI
  941.         g_font = window.GetFontDUI(FontTypeDUI.playlists);
  942.         g_font_headers = window.GetFontDUI(FontTypeDUI.tabs);
  943.     };
  944. };
  945.  
  946. function get_colors() {
  947.     if(g_instancetype == 0) { // CUI
  948.         g_textcolor = window.GetColorCUI(ColorTypeCUI.text);
  949.         g_textcolor_sel = window.GetColorCUI(ColorTypeCUI.selection_text);
  950.         g_textcolor_hl = window.GetColorCUI(ColorTypeCUI.active_item_frame);
  951.         g_backcolor = window.GetColorCUI(ColorTypeCUI.background);
  952.         g_backcolor_sel = window.GetColorCUI(ColorTypeCUI.selection_background);
  953.     }; else if(g_instancetype == 1) { // DUI
  954.         g_textcolor = window.GetColorDUI(ColorTypeDUI.text);
  955.         g_textcolor_sel = window.GetColorDUI(ColorTypeDUI.selection);
  956.         g_textcolor_hl = window.GetColorDUI(ColorTypeDUI.highlight);
  957.         g_backcolor = window.GetColorDUI(ColorTypeDUI.background);
  958.         g_backcolor_sel = g_textcolor_sel;
  959.     };
  960. };
  961.  
  962. //=================================================// Images (general)
  963. function set_scroller() {
  964.     var gb;
  965.    
  966.     try {
  967.         cursor.img_normal = gdi.CreateImage(cursor.w, cursor.h);
  968.     }; catch(e) {
  969.         cursor.h = cursor.default_h;
  970.         cursor.img_normal = gdi.CreateImage(cursor.w, cursor.h);
  971.     };
  972.    
  973.     gb = cursor.img_normal.GetGraphics();
  974.     // Draw Themed Scrollbar (lg/col)
  975.     try {
  976.         vscrollbar.theme.SetPartAndStateId(3, 1);
  977.         vscrollbar.theme.DrawThemeBackground(gb, 0, 0, cursor.w, cursor.h);
  978.     }; catch(e) {
  979.         gb.FillSolidRect(0, 1, cursor.w-0, cursor.h-2, g_backcolor);
  980.         gb.FillSolidRect(0, 1, cursor.w-0, cursor.h-2, RGBA(0,0,0,100));
  981.         gb.FillSolidRect(0, 1, cursor.w-1, cursor.h-3, g_textcolor&0x77ffffff);
  982.         gb.FillSolidRect(1, 2, cursor.w-2, cursor.h-4, g_backcolor);
  983.         gb.FillSolidRect(1, 2, cursor.w-2, cursor.h-4, RGBA(255,255,255,25));
  984.     };
  985.     cursor.img_normal.ReleaseGraphics(gb);
  986.  
  987.     cursor.img_hover = gdi.CreateImage(cursor.w, cursor.h);
  988.     gb = cursor.img_hover.GetGraphics();
  989.     // Draw Themed Scrollbar (lg/col)
  990.     try {
  991.         vscrollbar.theme.SetPartAndStateId(3, 2);
  992.         vscrollbar.theme.DrawThemeBackground(gb, 0, 0, cursor.w, cursor.h);
  993.     }; catch(e) {
  994.         gb.FillSolidRect(0, 1, cursor.w-0, cursor.h-2, g_backcolor);
  995.         gb.FillSolidRect(0, 1, cursor.w-0, cursor.h-2, RGBA(0,0,0,70));
  996.         gb.FillSolidRect(0, 1, cursor.w-1, cursor.h-3, g_textcolor&0x99ffffff);
  997.         gb.FillSolidRect(1, 2, cursor.w-2, cursor.h-4, g_backcolor);
  998.         gb.FillSolidRect(1, 2, cursor.w-2, cursor.h-4, RGBA(255,255,255,50));
  999.     };
  1000.     cursor.img_hover.ReleaseGraphics(gb);
  1001.     cursor.bt = new button(cursor.img_normal, cursor.img_hover, cursor.img_hover);
  1002. };
  1003.  
  1004. function init_vscrollbar_buttons() {
  1005.     var i, gb;
  1006.  
  1007.     cursor.popup = gdi.CreateImage(27, 22);
  1008.     gb = cursor.popup.GetGraphics();
  1009.     gb.SetSmoothingMode(2);
  1010.     gb.FillRoundRect(0,0,22-1,22-1,3,3,g_textcolor);
  1011.     gb.DrawRoundRect(0,0,22-1,22-1,3,3,1.0,RGBA(0,0,0,150));
  1012.     var points = Array(22-2,7, 22-2+6,11, 22-2,22-7);
  1013.     gb.FillPolygon(g_textcolor, 0, points);
  1014.     gb.DrawPolygon(RGBA(0,0,0,150), 1.0, points);
  1015.     gb.SetSmoothingMode(0);
  1016.     gb.FillSolidRect(22-4,6,3,22-10,g_textcolor);
  1017.     cursor.popup.ReleaseGraphics(gb);
  1018.    
  1019.     button_up.img_normal = gdi.CreateImage(button_up.w, button_up.h);
  1020.     gb = button_up.img_normal.GetGraphics();
  1021.     // Draw Themed Scrollbar (lg/col)
  1022.     try {
  1023.         vscrollbar.theme.SetPartAndStateId(1, 1);
  1024.         vscrollbar.theme.DrawThemeBackground(gb, 0, 0, button_up.w, button_up.h);
  1025.     }; catch(e) {
  1026.         gb.FillSolidRect(0, 0, button_up.w-0, button_up.h-0, g_backcolor);
  1027.         gb.FillSolidRect(0, 0, button_up.w-0, button_up.h-0, RGBA(0,0,0,100));
  1028.         gb.FillSolidRect(0, 0, button_up.w-1, button_up.h-1, g_textcolor&0x77ffffff);
  1029.         gb.FillSolidRect(1, 1, button_up.w-2, button_up.h-2, g_backcolor);
  1030.         gb.FillSolidRect(1, 1, button_up.w-2, button_up.h-2, RGBA(255,255,255,25));
  1031.         gb.SetTextRenderingHint(4);
  1032.         gui_font = gdi.Font("guifx v2 transports", 12, 0);
  1033.         gb.DrawString(".", gui_font, RGBA(255,255,255,25), 0, 1, button_up.w-0, button_up.h, cc_stringformat);
  1034.         gb.DrawString(".", gui_font, g_textcolor, 0, 0, button_up.w-0, button_up.h, cc_stringformat);
  1035.     };
  1036.     button_up.img_normal.ReleaseGraphics(gb);
  1037.  
  1038.     button_up.img_hover = gdi.CreateImage(button_up.w, button_up.h);
  1039.     gb = button_up.img_hover.GetGraphics();
  1040.     // Draw Themed Scrollbar (lg/col)
  1041.     try {
  1042.         vscrollbar.theme.SetPartAndStateId(1, 2);
  1043.         vscrollbar.theme.DrawThemeBackground(gb, 0, 0, button_up.w, button_up.h);
  1044.     }; catch(e) {
  1045.         gb.FillSolidRect(0, 0, button_up.w-0, button_up.h-0, g_backcolor);
  1046.         gb.FillSolidRect(0, 0, button_up.w-0, button_up.h-0, RGBA(0,0,0,70));
  1047.         gb.FillSolidRect(0, 0, button_up.w-1, button_up.h-1, g_textcolor&0x99ffffff);
  1048.         gb.FillSolidRect(1, 1, button_up.w-2, button_up.h-2, g_backcolor);
  1049.         gb.FillSolidRect(1, 1, button_up.w-2, button_up.h-2, RGBA(255,255,255,50));
  1050.         gb.SetTextRenderingHint(4);
  1051.         gui_font = gdi.Font("guifx v2 transports", 12, 0);
  1052.         gb.DrawString(".", gui_font, RGBA(255,255,255,50), 0, 1, button_up.w-0, button_up.h, cc_stringformat);
  1053.         gb.DrawString(".", gui_font, g_textcolor, 0, 0, button_up.w-0, button_up.h, cc_stringformat);
  1054.     };
  1055.     button_up.img_hover.ReleaseGraphics(gb);
  1056.  
  1057.     button_up.img_down = gdi.CreateImage(button_up.w, button_up.h);
  1058.     gb = button_up.img_down.GetGraphics();
  1059.     // Draw Themed Scrollbar (lg/col)
  1060.     try {
  1061.         vscrollbar.theme.SetPartAndStateId(1, 3);
  1062.         vscrollbar.theme.DrawThemeBackground(gb, 0, 0, button_up.w, button_up.h);
  1063.     }; catch(e) {
  1064.         gb.FillSolidRect(0, 0, button_up.w-0, button_up.h-0, g_textcolor&0x77ffffff);
  1065.         gb.FillSolidRect(0, 0, button_up.w-0, button_up.h-0, g_backcolor);
  1066.         gb.FillSolidRect(0, 0, button_up.w-1, button_up.h-1, RGBA(0,0,0,100));
  1067.         gb.FillSolidRect(1, 1, button_up.w-2, button_up.h-2, g_backcolor);
  1068.         gb.FillSolidRect(1, 1, button_up.w-2, button_up.h-2, RGBA(255,255,255,25));
  1069.         gb.SetTextRenderingHint(4);
  1070.         gui_font = gdi.Font("guifx v2 transports", 12, 0);
  1071.         gb.DrawString(".", gui_font, RGBA(255,255,255,25), 0, 1, button_up.w-0, button_up.h, cc_stringformat);
  1072.         gb.DrawString(".", gui_font, g_textcolor, 0, 0, button_up.w-0, button_up.h, cc_stringformat);
  1073.     };
  1074.     button_up.img_down.ReleaseGraphics(gb);
  1075.  
  1076.     button_down.img_normal = gdi.CreateImage(button_down.w, button_down.h);
  1077.     gb = button_down.img_normal.GetGraphics();
  1078.     // Draw Themed Scrollbar (lg/col)
  1079.     try {
  1080.         vscrollbar.theme.SetPartAndStateId(1, 5);
  1081.         vscrollbar.theme.DrawThemeBackground(gb, 0, 0, button_down.w, button_down.h);
  1082.     }; catch(e) {
  1083.         gb.FillSolidRect(0, 0, button_down.w-0, button_down.h-0, g_backcolor);
  1084.         gb.FillSolidRect(0, 0, button_down.w-0, button_down.h-0, RGBA(0,0,0,100));
  1085.         gb.FillSolidRect(0, 0, button_down.w-1, button_down.h-1, g_textcolor&0x77ffffff);
  1086.         gb.FillSolidRect(1, 1, button_down.w-2, button_down.h-2, g_backcolor);
  1087.         gb.FillSolidRect(1, 1, button_down.w-2, button_down.h-2, RGBA(255,255,255,25));
  1088.         gb.SetTextRenderingHint(4);
  1089.         gui_font = gdi.Font("guifx v2 transports", 12, 0);
  1090.         gb.DrawString(",", gui_font, RGBA(255,255,255,25), 0, 1, button_down.w-0, button_down.h, cc_stringformat);
  1091.         gb.DrawString(",", gui_font, g_textcolor, 0, 0, button_down.w-0, button_down.h, cc_stringformat);
  1092.     };
  1093.     button_down.img_normal.ReleaseGraphics(gb);
  1094.  
  1095.     button_down.img_hover = gdi.CreateImage(button_down.w, button_down.h);
  1096.     gb = button_down.img_hover.GetGraphics();
  1097.     // Draw Themed Scrollbar (lg/col)
  1098.     try {
  1099.         vscrollbar.theme.SetPartAndStateId(1, 6);
  1100.         vscrollbar.theme.DrawThemeBackground(gb, 0, 0, button_down.w, button_down.h);
  1101.     }; catch(e) {
  1102.         gb.FillSolidRect(0, 0, button_down.w-0, button_down.h-0, g_backcolor);
  1103.         gb.FillSolidRect(0, 0, button_down.w-0, button_down.h-0, RGBA(0,0,0,70));
  1104.         gb.FillSolidRect(0, 0, button_down.w-1, button_down.h-1, g_textcolor&0x99ffffff);
  1105.         gb.FillSolidRect(1, 1, button_down.w-2, button_down.h-2, g_backcolor);
  1106.         gb.FillSolidRect(1, 1, button_down.w-2, button_down.h-2, RGBA(255,255,255,50));
  1107.         gb.SetTextRenderingHint(4);
  1108.         gui_font = gdi.Font("guifx v2 transports", 12, 0);
  1109.         gb.DrawString(",", gui_font, RGBA(255,255,255,50), 0, 1, button_down.w-0, button_down.h, cc_stringformat);
  1110.         gb.DrawString(",", gui_font, g_textcolor, 0, 0, button_down.w-0, button_down.h, cc_stringformat);
  1111.     };
  1112.     button_down.img_hover.ReleaseGraphics(gb);
  1113.  
  1114.     button_down.img_down = gdi.CreateImage(button_down.w, button_down.h);
  1115.     gb = button_down.img_down.GetGraphics();
  1116.     // Draw Themed Scrollbar (lg/col)
  1117.     try {
  1118.         vscrollbar.theme.SetPartAndStateId(1, 7);
  1119.         vscrollbar.theme.DrawThemeBackground(gb, 0, 0, button_down.w, button_down.h);
  1120.     }; catch(e) {
  1121.         gb.FillSolidRect(0, 0, button_down.w-0, button_down.h-0, g_textcolor&0x77ffffff);
  1122.         gb.FillSolidRect(0, 0, button_down.w-0, button_down.h-0, g_backcolor);
  1123.         gb.FillSolidRect(0, 0, button_down.w-1, button_down.h-1, RGBA(0,0,0,100));
  1124.         gb.FillSolidRect(1, 1, button_down.w-2, button_down.h-1, g_backcolor);
  1125.         gb.FillSolidRect(1, 1, button_down.w-2, button_down.h-1, RGBA(255,255,255,25));
  1126.         gb.SetTextRenderingHint(4);
  1127.         gui_font = gdi.Font("guifx v2 transports", 12, 0);
  1128.         gb.DrawString(",", gui_font, RGBA(255,255,255,25), 0, 1, button_down.w-0, button_down.h, cc_stringformat);
  1129.         gb.DrawString(",", gui_font, g_textcolor, 0, 0, button_down.w-0, button_down.h, cc_stringformat);
  1130.     };
  1131.     button_down.img_down.ReleaseGraphics(gb);
  1132.  
  1133.     vscrollbar.arr_buttons.splice(0, vscrollbar.arr_buttons.length);
  1134.     for(i=0;i<vscrollbar.button_total;i++) {
  1135.         switch(i) {
  1136.          case 0:
  1137.             vscrollbar.arr_buttons.push(new button(button_up.img_normal, button_up.img_hover, button_up.img_down));
  1138.             break;
  1139.          case 1:
  1140.             vscrollbar.arr_buttons.push(new button(button_down.img_normal, button_down.img_hover, button_down.img_down));
  1141.             break;            
  1142.         };
  1143.     };
  1144. };
  1145.  
  1146. function init_icons() {
  1147.     var i;
  1148.     var gb;
  1149.     var gui_font = gdi.Font("guifx v2 transports", 15, 0);
  1150.        
  1151.     playicon_off = gdi.CreateImage(row.h+0, row.h+0);
  1152.     gb = playicon_off.GetGraphics();
  1153.     gb.SetSmoothingMode(2);
  1154.     var points = Array(0,0, (Math.floor(playicon_off.Width/2)>14?14:Math.floor(playicon_off.Width/2)),Math.floor(playicon_off.Width/2), 0,playicon_off.Width);
  1155.     gb.FillPolygon(g_backcolor, 0, points);
  1156.     gb.FillPolygon(RGBA(255,255,255,24), 0, points);
  1157.     gb.SetSmoothingMode(0);
  1158.     playicon_off.ReleaseGraphics(gb);
  1159.  
  1160.     playicon_light = gdi.CreateImage(row.h+0, row.h+0);
  1161.     gb = playicon_light.GetGraphics();
  1162.     gb.SetSmoothingMode(2);
  1163.     var points = Array(-1,0, (Math.floor(playicon_off.Width/2)>14?14:Math.floor(playicon_off.Width/2))-1,Math.floor(playicon_off.Width/2), -1,playicon_off.Width);
  1164.     gb.FillPolygon(g_backcolor, 0, points);
  1165.     gb.FillPolygon(RGBA(255,255,255,24), 0, points);
  1166.     gb.FillPolygon(RGBA(255,255,255,30), 0, points);
  1167.     gb.SetSmoothingMode(0);
  1168.     playicon_light.ReleaseGraphics(gb);
  1169.  
  1170.     playicon_shadow = gdi.CreateImage(row.h+0, row.h+0);
  1171.     gb = playicon_shadow.GetGraphics();
  1172.     gb.SetSmoothingMode(2);
  1173.     var points = Array(0,0, (Math.floor(playicon_off.Width/2)>14?14:Math.floor(playicon_off.Width/2)),Math.floor(playicon_off.Width/2), 0,playicon_off.Width);
  1174.     gb.FillPolygon(RGB(0,0,0), 0, points);
  1175.     gb.SetSmoothingMode(0);
  1176.     playicon_shadow.ReleaseGraphics(gb);
  1177.  
  1178.     star_img_off = gdi.CreateImage(13, 18);
  1179.     gb = star_img_off.GetGraphics();
  1180.     gui_font = gdi.Font("guifx v2 transports", 17, 0);
  1181.     gb.SetTextRenderingHint(4);
  1182.     gb.DrawString("b", gui_font, g_textcolor&0x20ffffff, 0, 0, 13, 18, lt_stringformat);
  1183.     star_img_off.ReleaseGraphics(gb);
  1184.  
  1185.     star_img_on = gdi.CreateImage(13, 18);
  1186.     gb = star_img_on.GetGraphics();
  1187.     gui_font = gdi.Font("guifx v2 transports", 17, 0);
  1188.     gb.SetTextRenderingHint(4);
  1189.     gb.DrawString("b", gui_font, g_textcolor_sel, 0, 0, 13, 18, lt_stringformat);
  1190.     star_img_on.ReleaseGraphics(gb);
  1191.  
  1192.     star_img_hov = gdi.CreateImage(13, 18);
  1193.     gb = star_img_hov.GetGraphics();
  1194.     gui_font = gdi.Font("guifx v2 transports", 17, 0);
  1195.     gb.SetTextRenderingHint(4);
  1196.     gb.DrawString("b", gui_font, g_textcolor_sel, 0, 0, 13, 18, lt_stringformat);
  1197.     star_img_hov.ReleaseGraphics(gb);
  1198.  
  1199.     star_img_kill = gdi.CreateImage(13, 18);
  1200.     gb = star_img_kill.GetGraphics();
  1201.     gui_font = gdi.Font("guifx v2 transports", 12, 0);
  1202.     gb.SetTextRenderingHint(4);
  1203.     gb.DrawString("x", gui_font, RGB(220,50,50), 2, 2, 13, 18, lt_stringformat);
  1204.     star_img_kill.ReleaseGraphics(gb);  
  1205.    
  1206.     // Set rating area width
  1207.     rating_w = Math.floor(star_img_off.Width*5);
  1208.     rating_h = Math.floor(star_img_off.Height);
  1209.    
  1210.     nocover = gdi.CreateImage(200, 200);
  1211.     gb = nocover.GetGraphics();
  1212.     gb.SetSmoothingMode(2);
  1213.     gb.FillGradRect(0,0,200,200,90,RGB(200,200,200),RGB(240,240,240),1.0);
  1214.     gb.SetTextRenderingHint(3);
  1215.     gui_font = gdi.Font("guifx v2 transports", 132, 0);
  1216.     gb.DrawString("x", gui_font, RGB(255,255,255), 1, 3, 200, 200, cc_stringformat);
  1217.     gb.DrawString("x", gui_font, RGB(120,120,120), 1, 0, 200, 200, cc_stringformat);
  1218.     nocover.ReleaseGraphics(gb);
  1219.  
  1220.     streamcover = gdi.CreateImage(200, 200);
  1221.     gb = streamcover.GetGraphics();
  1222.     gb.SetSmoothingMode(2);
  1223.     gb.FillGradRect(0,0,nocover.Width,nocover.Height,90,RGB(200,200,200),RGB(240,240,240),1.0);
  1224.     gb.SetTextRenderingHint(3);
  1225.     gui_font = gdi.Font("guifx v2 transports", 126, 0);
  1226.     gb.DrawString("$", gui_font, RGB(255,255,255), 1, 3, 200, 200, cc_stringformat);
  1227.     gb.DrawString("$", gui_font, RGB(120,120,120), 1, 0, 200, 200, cc_stringformat);
  1228.     streamcover.ReleaseGraphics(gb);
  1229.  
  1230. };
  1231.  
  1232. //=================================================// Playlist view (load)
  1233. function refresh_spv() {
  1234.     var group_item_index = 0;
  1235.     var group_items_total = 0;
  1236.     var prev_cursor_h = cursor.h;
  1237.    
  1238.     g_tooltip.Text="";
  1239.    
  1240.     reset_cover_timers();
  1241.    
  1242.     if(vscrollbar.show) {
  1243.         vscrollbar.visible = (list.total>list.nbvis);
  1244.     }; else {
  1245.         vscrollbar.visible = false;
  1246.     };
  1247.    
  1248.     list.item.splice(0, list.item.length);
  1249.    
  1250.     list.tocut = (list.offset >= cover.nbrows)?cover.nbrows:list.offset;
  1251.     var gdeb = list.offset - list.tocut;
  1252.     var i = gdeb;
  1253.     var k = 0;
  1254.     var previous_group_key = null;
  1255.     var loop = (list.total>0);
  1256.     var group_item_index_search = false;
  1257.        
  1258.     while(loop) {
  1259.         list.item.push(new item);
  1260.         list.item[k].create(i, list.handlelist.Item(i), k, 0);
  1261.         if(i==0 || previous_group_key!=list.item[k].group_key) {
  1262.             // count the number of tracks in the current new group
  1263.             group_items_total = GetGroupTotal(i, list.item[k].group_key);
  1264.             list.item[k].grp_total = group_items_total;
  1265.             // initialize the item index for the group
  1266.             if(i>0 && !group_item_index_search) {
  1267.                 group_item_index_search = true;
  1268.                 group_item_index = GetGroupItemIndex(i-1, list.item[k].group_key);
  1269.             }; else {
  1270.                 group_item_index = 0;
  1271.             };
  1272.             list.item[k].grp_idx = group_item_index;
  1273.             list.item[k].showcover = true;
  1274.         }; else {
  1275.             group_item_index++;
  1276.             list.item[k].grp_idx = group_item_index;
  1277.             list.item[k].grp_total = group_items_total;
  1278.         };
  1279.         list.item[k].visible = (k>=list.tocut && k<list.tocut+list.nbvis);
  1280.         if(k<list.nbvis+list.tocut) {
  1281.             list.stock = list.total - i - 1;
  1282.         };
  1283.         if(i==list.total-1 || k>list.nbvis+list.tocut+cover.nbrows) {
  1284.             loop = false;
  1285.         }; else {
  1286.             previous_group_key = list.item[k].group_key;
  1287.             k++;
  1288.             i++;
  1289.         };
  1290.     };
  1291.    
  1292.     var ratio = list.nbvis / list.total;
  1293.     if(ratio>1) ratio = 1;
  1294.     cursor.h = Math.round(ratio * vscrollbar.h);
  1295.     // boundaries for cursor height
  1296.     if(cursor.h>vscrollbar.h) cursor.h = vscrollbar.h;
  1297.     if(cursor.h<cursor.default_h) cursor.h = cursor.default_h;
  1298.     // redraw cursor image
  1299.     if(!cursor.bt || cursor.h!=prev_cursor_h) {
  1300.         set_scroller();
  1301.     };
  1302.     if(!cursor.drag) setcursory();
  1303. };
  1304.  
  1305. //=================================================// Offset calculations
  1306. function setcursory() {
  1307.     if(list.total>list.nbvis) {
  1308.         var ratio = list.offset / (list.total-list.nbvis);
  1309.         cursor.y = vscrollbar.y + Math.round((vscrollbar.h-cursor.h) * ratio);
  1310.     }; else {
  1311.         cursor.y = vscrollbar.y;
  1312.     };
  1313. };
  1314.  
  1315. function setoffset() {
  1316.     var ratio = (cursor.y-vscrollbar.y) / (vscrollbar.h-cursor.h);
  1317.     list.offset = Math.round((list.total-list.nbvis) * ratio);
  1318. };
  1319.  
  1320. //=================================================// Colour & Font Callbacks
  1321. function on_font_changed() {
  1322.     get_font();
  1323.     window.Repaint();
  1324. };
  1325.  
  1326. function on_colors_changed() {
  1327.     get_colors();
  1328.     init_icons();
  1329.     init_vscrollbar_buttons();
  1330.     set_scroller();
  1331.     window.Repaint();
  1332. };
  1333.  
  1334. //=================================================// START
  1335. function on_size() {
  1336.     if (!window.Width || !window.Height) return;
  1337.     if(g_instancetype == 0) { // CUI
  1338.         window.MinWidth = 200;
  1339.         window.MinHeight = 100;  
  1340.     }; else if(g_instancetype == 1) { // DUI
  1341.         window.MinWidth = 200;
  1342.         window.MinHeight = 100;
  1343.         window.MaxWidth = 700;
  1344.         window.MaxHeight = 700;
  1345.     };
  1346.  
  1347.     ww = window.Width;
  1348.     wh = window.Height;
  1349.     toolbar.h = toolbar.visible ? toolbar.default_h : 0;
  1350.     wh = wh - toolbar.h;
  1351.    
  1352.     get_colors();
  1353.     get_font();
  1354.     init_icons();
  1355.    
  1356.     list.nbvis = ((wh/row.h) == Math.ceil(wh/row.h)) ? Math.ceil(wh/row.h) : Math.ceil((wh/row.h)-1);
  1357.  
  1358.     if(vscrollbar.themed) {
  1359.         vscrollbar.theme = window.CreateThemeManager("scrollbar");
  1360.     }; else {
  1361.         vscrollbar.theme = false;
  1362.     };
  1363.     init_vscrollbar_buttons();
  1364.  
  1365.     button_up.y = 0;
  1366.     button_down.y = toolbar.h + wh - button_down.h;
  1367.     vscrollbar.y = button_up.h;
  1368.     vscrollbar.h = (wh+toolbar.h) - button_up.h - button_down.h;
  1369.     cursor.x = ww-vscrollbar.w;
  1370.     cursor.y = vscrollbar.y;
  1371.  
  1372.     if(cover.w>0) {
  1373.         cover.visible = true;
  1374.         column.w[0] = cover.w;
  1375.         cover.h = cover.w;
  1376.         cover.nbrows = Math.ceil(cover.w/row.h);
  1377.     }; else {
  1378.         cover.visible = false;
  1379.         column.w[0] = 0;
  1380.         cover.h = cover.w;
  1381.         cover.nbrows = Math.ceil(cover.w/row.h);
  1382.     };
  1383.  
  1384.     if(wsh.first_on_size) {
  1385.         // on foobar2000 launch (just before first repaint)
  1386.         wsh.first_on_size = false;
  1387.         on_playlist_switch();
  1388.     }; else {
  1389.         // on foobar2000 window resize
  1390.         on_resizing();
  1391.     };
  1392. };
  1393.  
  1394. function on_paint(gr) {
  1395.     var i;
  1396.        
  1397.     if(wsh.transparency) {
  1398.         gr.FillSolidRect(0, toolbar.h, ww, wh, g_backcolor&0xe0ffffff);
  1399.         // column[0] (cover)
  1400.         gr.FillGradRect(0, toolbar.h, column.w[0], wh, 0, RGBA(0,0,0,25), g_backcolor, 1.0);
  1401.         gr.FillGradRect(0, toolbar.h, column.w[0], wh, 0, RGBA(0,0,0,25), RGBA(255,255,255,25), 1.0);
  1402.     }; else {
  1403.         gr.FillSolidRect(0, toolbar.h, ww, wh, g_backcolor);
  1404.         // column[0] (cover)
  1405.         gr.FillSolidRect(0, toolbar.h, column.w[0], wh, g_backcolor);
  1406.         gr.FillGradRect(0, toolbar.h, column.w[0], wh, 0, RGBA(0,0,0,25), RGBA(255,255,255,25), 1.0);
  1407.     };
  1408.  
  1409.     if(list.total>0) {
  1410.         for(i=0;i<list.item.length;i++) {
  1411.             list.item[i].draw(gr, list.item[i].id);
  1412.         };
  1413.     }; else {
  1414.         gr.FillSolidRect(column.w[0]-0, toolbar.h, 1, wh, RGBA(0,0,0,100));
  1415.         gr.FillSolidRect(column.w[0]-1, toolbar.h, 1, wh, RGBA(255,255,255,25));
  1416.         gr.FillSolidRect(column.w[0]+1, toolbar.h, 1, wh, RGBA(0,0,0,60));
  1417.         gr.FillSolidRect(column.w[0]+2, toolbar.h, 1, wh, RGBA(0,0,0,30));
  1418.         gr.FillSolidRect(column.w[0]+3, toolbar.h, 1, wh, RGBA(0,0,0,10));
  1419.         if(fb.PlaylistCount>0) {
  1420.             var text_top = fb.GetPlaylistName(fb.ActivePlaylist);
  1421.             var text_bot = "This playlist is empty";
  1422.         }; else {
  1423.             var text_top = "WSH Simple Playlist Viewer";
  1424.             var text_bot = "Create a playlist to start!";
  1425.         };
  1426.         // empty playlist text info
  1427.         gr.GdiDrawText(text_top, gdi.Font("Tahoma", 17, 0), g_textcolor, column.w[0], toolbar.h-20, ww-column.w[0], wh, DT_CENTER | DT_CALCRECT | DT_VCENTER | DT_END_ELLIPSIS | DT_NOPREFIX);
  1428.         gr.GdiDrawText(text_bot, gdi.Font("Tahoma", 13, 0), g_textcolor, column.w[0], toolbar.h+20, ww-column.w[0], wh, DT_CENTER | DT_CALCRECT | DT_VCENTER | DT_END_ELLIPSIS | DT_NOPREFIX);
  1429.         gr.FillSolidRect(column.w[0], toolbar.h, ww-column.w[0], wh, g_backcolor&0xaaffffff);
  1430.         gr.FillGradRect(column.w[0]+40, toolbar.h+Math.floor(wh/2), ww-column.w[0]-80, 1, 0, 0, g_textcolor&0x66ffffff, 0.5);
  1431.     };
  1432.    
  1433.     // draw toolbar
  1434.     if(toolbar.visible) {
  1435.         gr.FillSolidRect(0, 0, ww, toolbar.h, g_backcolor);
  1436.         gr.FillSolidRect(0, toolbar.h-1, ww, 1, RGBA(0,0,0,100));
  1437.         gr.FillSolidRect(0, toolbar.h-2, ww, 1, RGBA(255,255,255,25));
  1438.         var start_point = 0;
  1439.         for(i=0;i<column.total;i++) {
  1440.             gr.GdiDrawText(column.label[i], g_font_headers, g_backcolor, start_point+5, 2, column.w[i]-10, toolbar.h-2, DT_LEFT | DT_CALCRECT | DT_VCENTER | DT_END_ELLIPSIS | DT_NOPREFIX);
  1441.             gr.GdiDrawText(column.label[i], g_font_headers, g_textcolor, start_point+5, 1, column.w[i]-10, toolbar.h-2, DT_LEFT | DT_CALCRECT | DT_VCENTER | DT_END_ELLIPSIS | DT_NOPREFIX);
  1442.             gr.FillSolidRect(start_point+5, 2, column.w[i]-10, toolbar.h-4, g_backcolor&0x33ffffff);
  1443.             start_point += column.w[i];
  1444.         };
  1445.         gr.FillGradRect(0, 0, ww, toolbar.h, 90, 0, g_textcolor&0x15ffffff, 1.0);
  1446.         start_point = 0;
  1447.         for(i=0;i<column.total-1;i++) {
  1448.             start_point += column.w[i];
  1449.             gr.FillSolidRect(start_point-1, 0, 1, toolbar.h-2, RGBA(255,255,255,25));
  1450.             gr.FillSolidRect(start_point, 0, 1, toolbar.h-1, RGBA(0,0,0,100));
  1451.         };
  1452.         gr.FillGradRect(0, 0, ww, Math.floor(toolbar.h/2), 90, RGBA(255,255,255,30), RGBA(255,255,255,15), 1.0);
  1453.         gr.FillGradRect(0, Math.ceil(toolbar.h/2), ww, Math.floor(toolbar.h/2), 90, g_textcolor&0x15ffffff, RGBA(0,0,0,50), 1.0);
  1454.         gr.FillSolidRect(0, 0, ww, 1, RGBA(255,255,255,50));
  1455.     };
  1456.    
  1457.     // draw vscrollbar
  1458.     if(vscrollbar.visible && vscrollbar.show) {
  1459.         // draw scrollbar background
  1460.         try {
  1461.             vscrollbar.theme.SetPartAndStateId(6, 1);
  1462.             vscrollbar.theme.DrawThemeBackground(gr, ww-vscrollbar.w, 0, vscrollbar.w, wh+toolbar.h);
  1463.             gr.FillSolidRect(ww-vscrollbar.w-1, 0, 1, wh+toolbar.h, RGBA(0,0,0,100));
  1464.         }; catch(e) {
  1465.             gr.FillSolidRect(ww-vscrollbar.w, 0, vscrollbar.w, wh+toolbar.h, g_backcolor);
  1466.             gr.FillGradRect(ww-vscrollbar.w, 0, vscrollbar.w, wh+toolbar.h, 0, RGBA(0,0,0,100), g_backcolor, 0.5);
  1467.             gr.FillGradRect(ww-vscrollbar.w, 0, vscrollbar.w, wh+toolbar.h, 0, 0, RGBA(255,255,255,25), 0.5);
  1468.             gr.FillSolidRect(ww-vscrollbar.w-1, 0, 1, wh+toolbar.h, RGBA(0,0,0,100));
  1469.         };
  1470.         // draw cursor
  1471.         cursor.bt.draw(gr, ww-vscrollbar.w, cursor.y, 255);
  1472.         try {
  1473.             vscrollbar.theme.SetPartAndStateId(9, 1);
  1474.             vscrollbar.theme.DrawThemeBackground(gr, ww-vscrollbar.w, cursor.y, cursor.w, cursor.h);
  1475.         }; catch(e) {};
  1476.         // draw scrollbar buttons (up/down)
  1477.         for(i=0;i<vscrollbar.arr_buttons.length;i++) {
  1478.             switch (i) {
  1479.              case 0:
  1480.                 vscrollbar.arr_buttons[i].draw(gr, ww-vscrollbar.w, button_up.y, 255);
  1481.                 break;
  1482.              case 1:
  1483.                 vscrollbar.arr_buttons[i].draw(gr, ww-vscrollbar.w, button_down.y, 255);
  1484.                 break;
  1485.             };
  1486.         };
  1487.         if(cursor.drag) {
  1488.             vscrollbar.letter = list.item[list.tocut].albumartist.substring(0,1).toUpperCase();
  1489.             cursor.popup && gr.DrawImage(cursor.popup, ww-vscrollbar.w-cursor.popup.Width-00, cursor.y+Math.floor(cursor.h/2)-Math.floor(cursor.popup.Height/2), cursor.popup.Width, cursor.popup.Height, 0, 0, cursor.popup.Width, cursor.popup.Height, 0, 255);
  1490.             cursor.popup && gr.GdiDrawText(vscrollbar.letter, gdi.Font("segoe ui", 14, 0), g_backcolor, ww-vscrollbar.w-cursor.popup.Width-00, cursor.y+Math.floor(cursor.h/2)-Math.floor(cursor.popup.Height/2), cursor.popup.Width-5, cursor.popup.Height, DT_CENTER | DT_CALCRECT | DT_VCENTER | DT_END_ELLIPSIS | DT_NOPREFIX);
  1491.         };
  1492.     };
  1493.    
  1494.     // Incremental Search Tooltip (bot/left corner)
  1495.     if(g_search_string.length>0) {
  1496.         gr.SetSmoothingMode(2);
  1497.         gr.FillRoundRect(-10, window.Height-14, g_search_string.length*5+16, 30, 5, 5, g_textcolor);
  1498.         gr.DrawRoundRect(-10, window.Height-14, g_search_string.length*5+16, 30, 5, 5, 1.0, RGBA(0,0,0,200));
  1499.         gr.GdiDrawText(g_search_string, incsearch_font, g_backcolor, 3, window.Height-11 , g_search_string.length*5+3 , 12, DT_LEFT | DT_NOPREFIX | DT_CALCRECT | DT_VCENTER | DT_END_ELLIPSIS);
  1500.     };
  1501. };
  1502.  
  1503. //=================================================// Mouse Callbacks
  1504. function on_mouse_lbtn_down(x, y) {
  1505.     var i;
  1506.  
  1507.     if(y>toolbar.h) {
  1508.         for(i=0;i<list.item.length;i++) {
  1509.             list.item[i].checkstate("down", x, y, i);
  1510.         };
  1511.     };
  1512.    
  1513.     if(vscrollbar.visible && vscrollbar.show) {
  1514.         if(cursor.bt.checkstate("down", x, y)==ButtonStates.down) {
  1515.             cursor.drag = true;
  1516.             cursor.grap_y = y - cursor.y;
  1517.         };
  1518.         if(vscrollbar.hover && !cursor.drag) {
  1519.             vscrollbar.step = list.nbvis;
  1520.             if(y<cursor.y) {
  1521.                 on_mouse_wheel(1);
  1522.                 button_up.timerID = window.SetInterval(function () { on_mouse_wheel(1); }, 100);
  1523.             }; else {
  1524.                 on_mouse_wheel(-1);
  1525.                 button_down.timerID = window.SetInterval(function () { on_mouse_wheel(-1); }, 100);
  1526.             };
  1527.         };
  1528.         // check other vscrollbar buttons
  1529.         for(i=0;i<vscrollbar.arr_buttons.length;i++) {
  1530.             switch(i) {
  1531.              case 0:
  1532.                 if(vscrollbar.arr_buttons[i].checkstate("down", x, y)==ButtonStates.down) {
  1533.                     vscrollbar.step = 1;
  1534.                     on_mouse_wheel(1);
  1535.                     button_up.timerID = window.SetInterval(function () { on_mouse_wheel(1); }, 100);
  1536.                 };
  1537.                 break;
  1538.              case 1:
  1539.                 if(vscrollbar.arr_buttons[i].checkstate("down", x, y)==ButtonStates.down) {
  1540.                     vscrollbar.step = 1;
  1541.                     on_mouse_wheel(-1);
  1542.                     button_down.timerID = window.SetInterval(function () { on_mouse_wheel(-1); }, 100);
  1543.                 };
  1544.                 break;
  1545.             };
  1546.         };
  1547.     };
  1548. };
  1549.  
  1550. function on_mouse_lbtn_dblclk(x, y, mask) {
  1551.     if(y>0 && y<toolbar.h && x<ww-vscrollbar.w) {
  1552.         wsh.action_shownowplaying = true;
  1553.         if(fb.ActivePlaylist != fb.PlayingPlaylist) {
  1554.             list.handlelist = plman.GetPlaylistItems(fb.PlayingPlaylist);
  1555.             list.total = list.handlelist.Count;
  1556.             fb.ActivePlaylist = fb.PlayingPlaylist;
  1557.         }; else {
  1558.             ShowNowPlaying();
  1559.             window.Repaint();
  1560.         };
  1561.     }; else {
  1562.         if(x>ww-vscrollbar.w) {
  1563.             on_mouse_lbtn_down(x, y);
  1564.         }; else if(x>column.w[0]){
  1565.             for(var i in list.item) {
  1566.                 list.item[i].checkstate("dblclk", x, y, i);
  1567.             };
  1568.         };
  1569.     };
  1570. };
  1571.  
  1572. function on_mouse_lbtn_up(x, y) {
  1573.     var i;
  1574.        
  1575.     // scrollbar button up and down RESET
  1576.     vscrollbar.step = vscrollbar.default_step;
  1577.     button_up.timerID && window.ClearInterval(button_up.timerID);
  1578.     button_up.timerID = false;
  1579.     button_down.timerID && window.ClearInterval(button_down.timerID);
  1580.     button_down.timerID = false;
  1581.  
  1582.     // check scrollbar buttons
  1583.     cursor.bt.checkstate("up", x, y);
  1584.     for(i=0;i<vscrollbar.arr_buttons.length;i++) {
  1585.         vscrollbar.arr_buttons[i].checkstate("up", x, y);
  1586.     };
  1587.    
  1588.     if(cursor.drag) {
  1589.         setcursory();
  1590.         window.RepaintRect(ww-vscrollbar.w-cursor.popup.Width-5, 0, cursor.popup.Width+vscrollbar.w+5, toolbar.h+wh);
  1591.         cursor.drag = false;
  1592.     }; else {
  1593.         // check items
  1594.         for(i=list.tocut;i<list.item.length;i++) {
  1595.             list.item[i].checkstate("up", x, y, i);
  1596.         };
  1597.     };
  1598. };
  1599.  
  1600. function on_mouse_rbtn_down(x, y) {
  1601.     var i;
  1602.     for(i=list.tocut;i<list.item.length;i++) {
  1603.         list.item[i].checkstate("right", x, y, i);
  1604.     };
  1605. };
  1606.  
  1607. function on_mouse_move(x, y) {
  1608.     var i, txt;
  1609.    
  1610.     if(x==mouse_x && y==mouse_y) return true;
  1611.    
  1612.     wsh.is_hover = (x>=0 && x<=ww && y>=0 && y<=toolbar.h+wh);
  1613.     if(wsh.drag_drop_enabled && wsh.drag) window.SetCursor(IDC_HELP); else window.SetCursor(IDC_ARROW);
  1614.  
  1615.     if(x<ww-vscrollbar.w && y>toolbar.h) {
  1616.         show_tooltip = false;
  1617.         for(i=list.tocut;i<list.item.length;i++) {
  1618.             list.item[i].checkstate("move", x, y, i);
  1619.             if(list.item[i].ishover) {
  1620.                 if(x>column.w[0] && x<column.w[0]+column.w[1]) {
  1621.                     txt = list.item[i].artist+" / "+list.item[i].title;
  1622.                     if(txt.length>0 && list.item[i].tooltip) show_tooltip = true;
  1623.                     if(g_tooltip.Text != txt) {
  1624.                         g_tooltip.Deactivate();
  1625.                         g_tooltip.TrackActivate = true;
  1626.                         g_tooltip.Text = txt;
  1627.                         g_tooltip.TrackPosition(list.item[i].x+column.w[0]+column.w[1]+2, list.item[i].y+Math.floor(row.h/2)-(18/2));
  1628.                     };
  1629.                 }; else if(list.item[i].grp_total>=cover.nbrows && x>cover.margin && x<column.w[0]-cover.margin) {
  1630.                     list.item[i].disc_info = tf_disc_info.EvalWithMetadb(list.item[i].metadb);
  1631.                     if(list.item[i].album.length>0) {
  1632.                         if(list.item[i].date.length>0) {
  1633.                             txt = list.item[i].albumartist+": "+list.item[i].album+list.item[i].disc_info+" ("+list.item[i].date+")";
  1634.                         }; else {
  1635.                             txt = list.item[i].albumartist+": "+list.item[i].album+list.item[i].disc_info;
  1636.                         };
  1637.                     }; else {
  1638.                         txt = list.item[i].artist+": Singles";
  1639.                     };
  1640.                     if(txt.length>0 && list.item[i].grp_idx<cover.nbrows && y>list.item[i].cover_top_y && y<list.item[i].cover_top_y+cover.h-cover.margin*2) {
  1641.                         show_tooltip = true;
  1642.                         if(g_tooltip.Text != txt) {
  1643.                             g_tooltip.Deactivate();
  1644.                             g_tooltip.TrackActivate = true;
  1645.                             g_tooltip.Text = txt;
  1646.                             g_tooltip.TrackPosition(list.item[i].x+cover.margin, list.item[i].cover_top_y+cover.h-cover.margin*2+2);
  1647.                         };
  1648.                     };
  1649.                 };
  1650.             };
  1651.         };
  1652.         if(show_tooltip) {
  1653.             g_tooltip.Activate();
  1654.             g_tooltip.TrackActivate = true;
  1655.         }; else {
  1656.             g_tooltip.Deactivate();
  1657.             g_tooltip.TrackActivate = false;
  1658.             g_tooltip.Text="";
  1659.         };
  1660.     }; else {
  1661.         g_tooltip.Deactivate();
  1662.         g_tooltip.TrackActivate = false;
  1663.         g_tooltip.Text="";
  1664.     };
  1665.    
  1666.     if(vscrollbar.visible && vscrollbar.show) {
  1667.         vscrollbar.hover = (x>=ww-vscrollbar.w && x<=ww && y>=vscrollbar.y && y<=vscrollbar.y+vscrollbar.h);
  1668.         cursor.hover = (x>=cursor.x && x<=cursor.x+cursor.w && y>=cursor.y && y<=cursor.y+cursor.h);
  1669.         // check buttons
  1670.         cursor.bt.checkstate("move", x, y);
  1671.         for(i=0;i<vscrollbar.arr_buttons.length;i++) {
  1672.             vscrollbar.arr_buttons[i].checkstate("move", x, y);
  1673.         };
  1674.         if(cursor.drag && mouse_y!=y) {
  1675.             reset_cover_timers();
  1676.             cursor.y = y - cursor.grap_y;
  1677.             // check boundaries
  1678.             if(cursor.y<vscrollbar.y) cursor.y = vscrollbar.y;
  1679.             if(cursor.y>vscrollbar.y+vscrollbar.h-cursor.h) cursor.y = vscrollbar.y+vscrollbar.h-cursor.h;
  1680.  
  1681.             if(!vscrollbar.timerID) {
  1682.                 vscrollbar.timerID = window.SetTimeout(function() {
  1683.                     window.RepaintRect(ww-vscrollbar.w,toolbar.h,vscrollbar.w,wh);
  1684.                 }, 20);
  1685.             };
  1686.            
  1687.             if(!cursor.timerID) {
  1688.                 cursor.timerID = window.SetTimeout(function() {
  1689.                     setoffset();
  1690.                     refresh_spv();
  1691.                     window.Repaint();
  1692.                     cursor.timerID = false;
  1693.                 }, 50);
  1694.             };
  1695.         };
  1696.     };
  1697.    
  1698.     mouse_x = x;
  1699.     mouse_y = y;
  1700. };
  1701.  
  1702. function on_mouse_wheel(delta) {
  1703.     var i;
  1704.     if(delta>0) {
  1705.         for(i=0;i<vscrollbar.step;i++) {
  1706.             if(list.offset>0) list.offset--;
  1707.         };
  1708.     }; else {
  1709.         var step_max = list.stock<vscrollbar.step ? list.stock : vscrollbar.step;
  1710.         for(i=0;i<step_max;i++) {
  1711.             if(list.stock>0) list.offset++;
  1712.         };
  1713.     };
  1714.  
  1715.     reset_cover_timers();
  1716.    
  1717.     if(!wsh.mousewheel_timerID) {
  1718.         wsh.mousewheel_timerID = window.SetTimeout(function() {
  1719.             refresh_spv();
  1720.             window.Repaint();
  1721.             wsh.mousewheel_timerID = false;
  1722.         }, 25);
  1723.     };
  1724. };
  1725.  
  1726. function on_mouse_leave() {    
  1727.     // check buttons
  1728.     cursor.bt.checkstate("leave", 0, 0);
  1729.     for(var i in vscrollbar.arr_buttons) {
  1730.         vscrollbar.arr_buttons[i].checkstate("leave", 0, 0);
  1731.     };
  1732.     for(i=list.tocut;i<list.item.length;i++) {
  1733.         list.item[i].checkstate("leave", 0, 0, i);
  1734.     };
  1735.     window.Repaint();
  1736.    
  1737.     g_tooltip.Text="";
  1738. };
  1739.  
  1740. //=================================================// Playlist Callbacks
  1741. function on_playlists_changed() {
  1742.     window.Repaint();
  1743. };
  1744.  
  1745. function on_resizing() { // function called manually in on_size Callback
  1746.     refresh_spv();
  1747.     if(vscrollbar.show) {
  1748.         vscrollbar.visible = (list.total>list.nbvis);
  1749.     }; else {
  1750.         vscrollbar.visible = false;
  1751.     };
  1752.     vscrollbar.w = vscrollbar.visible?vscrollbar.default_w:0;
  1753.     column.w[2] = ww - column.w[0] - column.w[1] - column.w[3] - column.w[4] - vscrollbar.w;
  1754.     list.metadblist_selection = plman.GetPlaylistSelectedItems(fb.ActivePlaylist);
  1755.     window.Repaint();
  1756. };
  1757.  
  1758. function on_playlist_switch() {
  1759.     list.offset = 0;
  1760.     list.handlelist = plman.GetPlaylistItems(fb.ActivePlaylist);
  1761.     list.total = list.handlelist.Count;
  1762.     refresh_spv();
  1763.     if(vscrollbar.show) {
  1764.         vscrollbar.visible = (list.total>list.nbvis);
  1765.     }; else {
  1766.         vscrollbar.visible = false;
  1767.     };
  1768.     vscrollbar.w = vscrollbar.visible?vscrollbar.default_w:0;
  1769.     column.w[2] = ww - column.w[0] - column.w[1] - column.w[3] - column.w[4] - vscrollbar.w;
  1770.     if(wsh.action_shownowplaying) {
  1771.         ShowNowPlaying();
  1772.     }; else {
  1773.         ShowSelectedItem(-1);
  1774.     };
  1775.     list.metadblist_selection = plman.GetPlaylistSelectedItems(fb.ActivePlaylist);
  1776.     window.Repaint();
  1777. };
  1778.  
  1779. function on_playlist_items_added(playlist_idx) {
  1780.     if(playlist_idx==fb.ActivePlaylist) {
  1781.         on_playlist_switch();
  1782.     };
  1783. };
  1784.  
  1785. function on_playlist_items_reordered(playlist_idx) {
  1786.     if(playlist_idx==fb.ActivePlaylist) {
  1787.         on_playlist_switch();
  1788.     };
  1789. };
  1790.  
  1791. function on_selection_changed(metadb) {
  1792.     if(!g_start_new_track) {
  1793.         if(!wsh.context_menu_called) {
  1794.             ShowSelectedItem(-1);
  1795.         }; else {
  1796.             wsh.context_menu_called = false;
  1797.             //ShowSelectedItem(-1);
  1798.             window.Repaint();
  1799.         };
  1800.     };
  1801.     g_start_new_track = false;
  1802. };
  1803.  
  1804. function on_playlist_items_selection_change() {
  1805.     list.metadblist_selection = plman.GetPlaylistSelectedItems(fb.ActivePlaylist);
  1806.     window.Repaint();
  1807. };
  1808.  
  1809. //=================================================// Other Callbacks
  1810. function on_item_focus_change() {
  1811.     list.handlelist = plman.GetPlaylistItems(fb.ActivePlaylist);
  1812.     list.total = list.handlelist.Count;
  1813.    
  1814.     if(list.offset>list.total) list.offset = 0;
  1815.    
  1816.     if(list.metadblist_selection) {
  1817.         on_metadb_changed();
  1818.     };
  1819. };
  1820.  
  1821. function on_metadb_changed() {
  1822.     plman.SetActivePlaylistContext();
  1823.     refresh_spv();
  1824.     window.Repaint();
  1825. };
  1826.  
  1827. function on_focus(is_focused) {
  1828.     g_is_focused = is_focused;
  1829. };
  1830.  
  1831. //=================================================// Keyboard Callbacks
  1832. function on_key_up(vkey) {
  1833.     // scroll keys up and down RESET (step and timers)
  1834.     vscrollbar.step = vscrollbar.default_step;
  1835.     button_up.timerID && window.ClearInterval(button_up.timerID);
  1836.     button_up.timerID = false;
  1837.     button_down.timerID && window.ClearInterval(button_down.timerID);
  1838.     button_down.timerID = false;
  1839. };
  1840.  
  1841. function on_key_down(vkey) {
  1842.     var mask = GetKeyboardMask();
  1843.     var focus_id;
  1844.    
  1845.     if (mask == KMask.none) {
  1846.         switch (vkey) {
  1847.         case VK_BACK:
  1848.             if(g_search_string.length>0) {
  1849.                 g_search_string = g_search_string.substring(0, g_search_string.length-1);
  1850.                 window.RepaintRect(0, window.Height-15, ww, 16);
  1851.                 clear_incsearch_timer && window.ClearInterval(clear_incsearch_timer);
  1852.                 incsearch_timer && window.ClearTimeout(incsearch_timer);
  1853.                 incsearch_timer = window.SetTimeout(function () {
  1854.                     IncrementalSearch();
  1855.                     incsearch_timer = false;
  1856.                 }, 500);
  1857.             };
  1858.             break;
  1859.         case VK_ESCAPE:
  1860.         case 222:
  1861.             // reset incremental search String
  1862.             g_search_string = "";
  1863.             if(!fb.IsPlaying) window.RepaintRect(0, window.Height-15, ww, 16);
  1864.             break;
  1865.         case VK_UP:
  1866.             if(!button_up.timerID) {
  1867.                 vscrollbar.step = 1;
  1868.                 on_mouse_wheel(1);
  1869.                 button_up.timerID = window.SetInterval(function () { on_mouse_wheel(1); }, 100);
  1870.             };
  1871.             break;
  1872.         case VK_DOWN:
  1873.             if(!button_down.timerID) {
  1874.                 vscrollbar.step = 1;
  1875.                 on_mouse_wheel(-1);
  1876.                 button_down.timerID = window.SetInterval(function () { on_mouse_wheel(-1); }, 100);
  1877.             };
  1878.             break;
  1879.         case VK_PGUP:
  1880.             if(!button_up.timerID) {
  1881.                 vscrollbar.step = list.nbvis;
  1882.                 on_mouse_wheel(1);
  1883.                 button_up.timerID = window.SetInterval(function () { on_mouse_wheel(1); }, 100);
  1884.             };
  1885.             break;
  1886.         case VK_PGDN:
  1887.             if(!button_down.timerID) {
  1888.                 vscrollbar.step = list.nbvis;
  1889.                 on_mouse_wheel(-1);
  1890.                 button_down.timerID = window.SetInterval(function () { on_mouse_wheel(-1); }, 100);
  1891.             };
  1892.             break;
  1893.         case VK_RETURN:
  1894.             // play focus item
  1895.             fb.RunContextCommandWithMetadb("Play", fb.GetFocusItem());
  1896.             break;
  1897.         case VK_END:
  1898.             list.offset = list.total-list.nbvis;
  1899.             on_metadb_changed();
  1900.             break;
  1901.         case VK_HOME:
  1902.             list.offset = 0;
  1903.             on_metadb_changed();
  1904.             break;
  1905.         case VK_DELETE:
  1906.             plman.RemovePlaylistSelection(fb.ActivePlaylist, false);
  1907.             on_playlist_switch();
  1908.             break;
  1909.         default:
  1910.             if(g_search_string.length<=20) {
  1911.                 if((vkey>=65 && vkey<=90) || vkey==32) {
  1912.                     var ch = String.fromCharCode(vkey);
  1913.                     g_search_string = g_search_string + ch;
  1914.                     window.RepaintRect(0, window.Height-15, ww, 16);
  1915.                     clear_incsearch_timer && window.ClearInterval(clear_incsearch_timer);
  1916.                     clear_incsearch_timer = false;
  1917.                     incsearch_timer && window.ClearTimeout(incsearch_timer);
  1918.                     incsearch_timer = window.SetTimeout(function () {
  1919.                         IncrementalSearch();
  1920.                         incsearch_timer = false;
  1921.                     }, 500);
  1922.                 };
  1923.                 if(vkey>=96 && vkey<=105) {
  1924.                     var ch = (vkey-96);
  1925.                     g_search_string = g_search_string + ch;
  1926.                     window.RepaintRect(0, window.Height-15, ww, 16);
  1927.                     clear_incsearch_timer && window.ClearInterval(clear_incsearch_timer);
  1928.                     clear_incsearch_timer = false;
  1929.                     incsearch_timer && window.ClearInterval(incsearch_timer);
  1930.                     incsearch_timer = window.SetInterval(function () {
  1931.                         IncrementalSearch();
  1932.                         incsearch_timer && window.ClearInterval(incsearch_timer);
  1933.                         incsearch_timer = false;
  1934.                     }, 500);
  1935.                 };
  1936.             };
  1937.             break;
  1938.         };
  1939.     }; else {
  1940.         switch(mask) {
  1941.             case KMask.ctrl:
  1942.                 if(vkey==65) { // CTRL+A
  1943.                     fb.RunMainMenuCommand("Edit/Select all");
  1944.                     window.Repaint();
  1945.                 };
  1946.                 if(vkey==70) { // CTRL+F
  1947.                     fb.RunMainMenuCommand("Edit/Search");
  1948.                 };
  1949.                 if(vkey==78) { // CTRL+N
  1950.                     fb.RunMainMenuCommand("File/New playlist");
  1951.                 };
  1952.                 if(vkey==79) { // CTRL+O
  1953.                     fb.RunMainMenuCommand("File/Open...");
  1954.                 };
  1955.                 if(vkey==80) { // CTRL+P
  1956.                     fb.RunMainMenuCommand("File/Preferences");
  1957.                 };
  1958.                 if(vkey==83) { // CTRL+S
  1959.                     fb.RunMainMenuCommand("File/Save playlist...");
  1960.                 };
  1961.                 break;
  1962.             case KMask.alt:
  1963.                 if(vkey==65) { // ALT+A
  1964.                     fb.RunMainMenuCommand("View/Always on Top");
  1965.                 };
  1966.                 break;
  1967.         };
  1968.     };
  1969. };
  1970.  
  1971. //=================================================// Item Context Menu
  1972. function new_context_menu(x, y, id, array_id) {
  1973.     var i;
  1974.     var MF_STRING = 0x00000000;
  1975.     var MF_SEPARATOR = 0x00000800;
  1976.     var MF_GRAYED = 0x00000001;
  1977.     var MF_DISABLED = 0x00000002;
  1978.     var MF_POPUP = 0x00000010;
  1979.        
  1980.     wsh.context_menu_called = true;
  1981.    
  1982.     var _menu = window.CreatePopupMenu();
  1983.     var Context = fb.CreateContextMenuManager();
  1984.    
  1985.     var _child01 = window.CreatePopupMenu();
  1986.     var _child02 = window.CreatePopupMenu();
  1987.    
  1988.     list.metadblist_selection = plman.GetPlaylistSelectedItems(fb.ActivePlaylist);
  1989.     Context.InitContext(list.metadblist_selection);
  1990.     Context.BuildMenu(_menu, 1, -1);
  1991.  
  1992.     _menu.AppendMenuItem(MF_SEPARATOR, 0, "");
  1993.     _menu.AppendMenuItem(MF_STRING | MF_POPUP, _child01.ID, "Selection...");      
  1994.     _menu.AppendMenuItem(MF_SEPARATOR, 0, "");
  1995.     _menu.AppendMenuItem(fb.IsPlaying?MF_STRING:MF_DISABLED|MF_GRAYED, 880, "Show Now Playing");
  1996.     _menu.AppendMenuItem((list.total>0)?MF_STRING:MF_DISABLED|MF_GRAYED, 890, "Refresh playlist");
  1997.  
  1998.     _child01.AppendMenuItem((fb.IsAutoPlaylist(fb.ActivePlaylist))?MF_DISABLED|MF_GRAYED:MF_STRING, 1000, "Remove");
  1999.     _child01.AppendMenuItem(MF_STRING | MF_POPUP, _child02.ID, "Send to...");
  2000.  
  2001.     _child02.AppendMenuItem(MF_STRING, 2000, "a New playlist...");
  2002.     _child02.AppendMenuItem(MF_SEPARATOR, 0, "");
  2003.     for(i=0;i<fb.PlaylistCount;i++) {
  2004.         if(i!=fb.ActivePlaylist && !fb.IsAutoPlaylist(i)) {
  2005.             _child02.AppendMenuItem(MF_STRING, 2001+i, plman.GetPlaylistName(i));
  2006.         };
  2007.     };
  2008.  
  2009.     var ret = _menu.TrackPopupMenu(x, y);
  2010.     if(ret<800) {
  2011.         Context.ExecuteByID(ret - 1);
  2012.     }; else if(ret<1000) {
  2013.         switch (ret) {
  2014.         case 880:
  2015.             wsh.action_shownowplaying = true;
  2016.             if(fb.ActivePlaylist != fb.PlayingPlaylist) {
  2017.                 list.handlelist = plman.GetPlaylistItems(fb.PlayingPlaylist);
  2018.                 list.total = list.handlelist.Count;
  2019.                 fb.ActivePlaylist = fb.PlayingPlaylist;
  2020.             }; else {
  2021.                 ShowNowPlaying();
  2022.                 window.Repaint();
  2023.             };
  2024.             break;
  2025.         case 890:
  2026.             g_image_cache = new image_cache;
  2027.             CollectGarbage();
  2028.             window.Repaint();
  2029.             break;
  2030.         };
  2031.     }; else {
  2032.         switch (ret) {
  2033.         case 1000:
  2034.             plman.RemovePlaylistSelection(fb.ActivePlaylist, false);
  2035.             on_playlist_switch();
  2036.             break;
  2037.         case 2000:
  2038.             fb.RunMainMenuCommand("File/New playlist");
  2039.             plman.InsertPlaylistItems(fb.PlaylistCount-1, 0, list.metadblist_selection, false);
  2040.             break;
  2041.         default:
  2042.             var insert_index = fb.PlaylistItemCount(ret-2001);
  2043.             plman.InsertPlaylistItems((ret-2001), insert_index, list.metadblist_selection, false);
  2044.         };
  2045.     };
  2046. };
  2047.  
  2048. //=================================================// Playback Callbacks
  2049. function on_playback_new_track(info) {
  2050.     g_start_new_track = true;
  2051.     if(!cursor.drag && fb.CursorFollowPlayback) {
  2052.         CursorFollowsPlayback();
  2053.         refresh_spv();
  2054.     };
  2055.     window.Repaint();
  2056. };  
  2057.  
  2058. function on_playback_stop(reason) {
  2059.     if(reason==0) { // on user Stop
  2060.         on_metadb_changed();
  2061.     };
  2062. };
  2063.  
  2064. function on_playback_pause(state){
  2065. };
  2066.  
  2067. function on_playback_time(time) {
  2068.     g_playtime_parity = (time/2-Math.floor(time/2)==0)?0:1;
  2069.     // only repaint the now playing line, if we aren't dragging vscrollbar cursor!
  2070.     if(!cursor.timerID) window.RepaintRect(column.w[0], list.nowplaying_y+1, ww-vscrollbar.w, row.h-2);
  2071. };
  2072.  
  2073. //=================================================// Playlist Tools
  2074. function CursorFollowsPlayback() {
  2075.    
  2076.     if(!fb.IsPlaying || list.total<=list.nbvis) return true;
  2077.    
  2078.     if(fb.ActivePlaylist != fb.PlayingPlaylist) {
  2079.         fb.ActivePlaylist = fb.PlayingPlaylist;
  2080.         list.handlelist = plman.GetPlaylistItems(fb.ActivePlaylist);
  2081.         list.total = list.handlelist.Count;
  2082.     };
  2083.     list.nowplaying = plman.GetPlayingItemLocation();
  2084.     var pid = list.nowplaying.PlaylistItemIndex;
  2085.     var already_shown = false;
  2086.     for(var i in list.item) {
  2087.         if(list.item[i].id == pid) {
  2088.             if(list.item[i].visible) {
  2089.                 already_shown = true;
  2090.             };
  2091.             break;
  2092.         };
  2093.     };
  2094.     if(!already_shown) {
  2095.         list.offset = pid - (pid<Math.floor(list.nbvis/2) ? pid : Math.floor(list.nbvis/2) );
  2096.         if(pid>list.total-1-Math.floor(list.nbvis/2)) {
  2097.             list.offset = list.total-list.nbvis;
  2098.         };
  2099.     };
  2100.     list.singleton_select_id = pid;
  2101. };
  2102.  
  2103. function ShowNowPlaying() {
  2104.    
  2105.     if(!fb.IsPlaying || list.total<=list.nbvis) return true;
  2106.    
  2107.     plman.ClearPlaylistSelection(fb.ActivePlaylist);
  2108.    
  2109.     wsh.action_shownowplaying = false;
  2110.     list.nowplaying = plman.GetPlayingItemLocation();
  2111.     var pid = list.nowplaying.PlaylistItemIndex;
  2112.     if(pid>=0 && pid<list.total) {
  2113.         list.offset = pid - (pid<Math.floor(list.nbvis/2) ? pid : Math.floor(list.nbvis/2) );
  2114.         if(pid>list.total-1-Math.floor(list.nbvis/2)) {
  2115.             list.offset = list.total-list.nbvis;
  2116.         };
  2117.         refresh_spv();
  2118.     };
  2119.     plman.SetPlaylistFocusItem(fb.ActivePlaylist, pid);
  2120.     plman.SetPlaylistSelectionSingle(fb.ActivePlaylist, pid, true);
  2121. };
  2122.  
  2123. function ShowSelectedItem(pid) {
  2124.     if(list.total==0 || !fb.GetFocusItem(false)) return true;
  2125.    
  2126.     if(pid<0) {
  2127.         pid = plman.GetPlaylistFocusItemIndex(fb.ActivePlaylist);
  2128.     };
  2129.     var already_shown = false;
  2130.     for(var i in list.item) {
  2131.         if(list.item[i].id == pid) {
  2132.             if(list.item[i].visible) {
  2133.                 already_shown = true;
  2134.             };
  2135.             break;
  2136.         };
  2137.     };
  2138.     if(!already_shown) {
  2139.         list.offset = pid - (pid<Math.floor(list.nbvis/2) ? pid : Math.floor(list.nbvis/2) );
  2140.         if(pid>list.total-1-Math.floor(list.nbvis/2)) {
  2141.             list.offset = list.total-0-list.nbvis;
  2142.         };
  2143.         refresh_spv();
  2144.     };
  2145.     plman.SetPlaylistFocusItem(fb.ActivePlaylist, pid);
  2146.     plman.SetPlaylistSelectionSingle(fb.ActivePlaylist, pid, true);
  2147. };
  2148.  
  2149. function SelectAtoB(start_id, end_id) {
  2150.     var i;
  2151.     var affectedItems = Array();
  2152.    
  2153.     plman.ClearPlaylistSelection(fb.ActivePlaylist);
  2154.    
  2155.     if(start_id<end_id) {
  2156.         var deb = start_id;
  2157.         var fin = end_id;
  2158.     }; else {
  2159.         var deb = end_id;
  2160.         var fin = start_id;        
  2161.     };
  2162.  
  2163.     for(i=deb;i<=fin;i++) {
  2164.         affectedItems.push(i);
  2165.     };
  2166.     plman.SetPlaylistSelection(fb.ActivePlaylist, affectedItems, true);
  2167.     window.Repaint();
  2168. };
  2169.  
  2170. function SelectGroupItems(start_id, group_key) {
  2171.     var i;
  2172.     var count=0;
  2173.     var gitem;
  2174.     var affectedItems = Array();
  2175.    
  2176.     if(!utils.IsKeyPressed(VK_CONTROL)) {
  2177.         plman.ClearPlaylistSelection(fb.ActivePlaylist);
  2178.     };
  2179.  
  2180.     for(i=start_id;i<list.total;i++) {
  2181.         gitem = new item;
  2182.         gitem.template_it(i, list.handlelist.Item(i));
  2183.         if(gitem.group_key!=group_key) {
  2184.             break;
  2185.         }; else {
  2186.             affectedItems.push(i);
  2187.         };
  2188.         count++;
  2189.         if(count>9999) break;
  2190.     };
  2191.     plman.SetPlaylistSelection(fb.ActivePlaylist, affectedItems, true);
  2192.     list.singleton_select_id = start_id;
  2193.     plman.SetPlaylistFocusItem(fb.ActivePlaylist, start_id);
  2194.     on_item_focus_change();
  2195. };
  2196.  
  2197. function GetGroupItemIndex(start_id, group_key) {
  2198.     var i;
  2199.     var count=0;
  2200.     var gitem;
  2201.  
  2202.     for(i=start_id;i>=0;i--) {
  2203.         gitem = new item;
  2204.         gitem.template_it(i, list.handlelist.Item(i));
  2205.         if(gitem.group_key==group_key) {
  2206.             count++;
  2207.             if(count>9999) break;
  2208.         }; else {
  2209.             break;
  2210.         };
  2211.     };
  2212.     return count;
  2213. };
  2214.  
  2215. function GetGroupTotal(start_id, group_key) {
  2216.     var i;
  2217.     var count=1;
  2218.     var gitem;
  2219.    
  2220.     for(i=start_id+1;i<list.total;i++) {
  2221.         gitem = new item;
  2222.         gitem.template_it(i, list.handlelist.Item(i));
  2223.         if(gitem.group_key==group_key) {
  2224.             count++;
  2225.             if(count>9999) break;
  2226.         }; else {
  2227.             break;
  2228.         };
  2229.     };
  2230.     return count;
  2231. };
  2232.  
  2233. function IncrementalSearch() {
  2234.     var i;
  2235.     var count=0;
  2236.     var albumartist;
  2237.     var chr;
  2238.     var gstart;
  2239.     var pid = -1;
  2240.    
  2241.     // exit if no search string in cache
  2242.     if(g_search_string.length<=0) return true;
  2243.    
  2244.     // 1st char of the search string
  2245.     var first_chr = g_search_string.substring(0,1);  
  2246.     var len = g_search_string.length;
  2247.    
  2248.     // which start point for the search
  2249.     if(list.total>500) {
  2250.         albumartist = tf_albumartist.EvalWithMetadb(list.handlelist.Item(Math.floor(list.total/2)));
  2251.         chr = albumartist.substring(0,1);
  2252.         if(chr.charCodeAt(chr) <= first_chr.charCodeAt(first_chr)) {
  2253.             gstart = Math.floor(list.total/2);
  2254.         }; else {
  2255.             gstart = 0;
  2256.         };
  2257.     }; else {
  2258.         gstart = 0;
  2259.     };
  2260.  
  2261.     for(i=gstart;i<list.total;i++) {
  2262.         albumartist = tf_albumartist.EvalWithMetadb(list.handlelist.Item(i));
  2263.         if(albumartist.substring(0,len).toUpperCase()==g_search_string) {
  2264.             pid = i;
  2265.             break;
  2266.         };
  2267.     };
  2268.    
  2269.     if(pid>0) { // found!
  2270.         plman.ClearPlaylistSelection(fb.ActivePlaylist);
  2271.         ShowSelectedItem(pid);
  2272.     };
  2273.    
  2274.     clear_incsearch_timer && window.ClearInterval(clear_incsearch_timer);
  2275.     clear_incsearch_timer = window.SetInterval(function () {
  2276.         // reset incremental search string after 3 seconds without any key pressed
  2277.         g_search_string = "";
  2278.         window.RepaintRect(0, window.Height-15, ww, 16);
  2279.         clear_incsearch_timer && window.ClearInterval(clear_incsearch_timer);
  2280.         clear_incsearch_timer = false;
  2281.     }, 2000);
  2282. };
  2283.  
  2284. //=================================================// Drag'n'Drop Callbacks
  2285. var wsh_dragging = false;
  2286.  
  2287. function on_drag_enter() {
  2288.     wsh_dragging = true;
  2289. };
  2290.  
  2291. function on_drag_leave() {
  2292.     wsh_dragging = false;
  2293. };
  2294.  
  2295. function on_drag_over(action, x, y, mask) {
  2296.     on_mouse_move(x, y);
  2297. };
  2298.  
  2299. function on_drag_drop(action, x, y, mask) {
  2300.     wsh_dragging = false;
  2301.     // We are going to process the dropped items to a playlist
  2302.     action.ToPlaylist();
  2303.     action.Playlist = fb.ActivePlaylist;
  2304.     action.ToSelect = false;
  2305. };
  2306.  
Add Comment
Please, Sign In to add comment