home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Freeware / Miro 1.0 / Miro_Installer.exe / components / jsbridge.js < prev    next >
Encoding:
Text File  |  2007-11-12  |  31.8 KB  |  836 lines

  1. /*
  2. # Miro - an RSS based video player application
  3. # Copyright (C) 2005-2007 Participatory Culture Foundation
  4. #
  5. # This program is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation; either version 2 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
  18. */
  19.  
  20. const JSBRIDGE_CONTRACTID = "@participatoryculture.org/dtv/jsbridge;1";
  21. const JSBRIDGE_CLASSID    = Components.ID("{421AA951-F53D-4499-B362-E432CAE920F4}");
  22.  
  23. var pybridge = Components.classes["@participatoryculture.org/dtv/pybridge;1"].
  24.         getService(Components.interfaces.pcfIDTVPyBridge);
  25. var minimizer = Components.classes["@participatoryculture.org/dtv/minimize;1"].
  26.         getService(Components.interfaces.pcfIDTVMinimize);
  27.  
  28. function writelog(str) {
  29.     Components.classes['@mozilla.org/consoleservice;1']
  30.     .getService(Components.interfaces.nsIConsoleService)    
  31.     .logStringMessage(str);
  32. }
  33.  
  34. function twoDigits(data) {
  35.     if (data < 10) return "0" + data;
  36.     else return ""+data;
  37. }
  38.  
  39.  
  40. function formatTime(milliseconds) {
  41.     if(milliseconds < 0) milliseconds = 0;
  42.     total_seconds = Math.floor(milliseconds / 1000);
  43.     var hours = Math.floor(total_seconds/3600);
  44.     var mins = Math.floor((total_seconds - hours*3600)/60);
  45.     var secs = total_seconds - hours*3600 - mins*60;
  46.     return twoDigits(hours)+":"+twoDigits(mins)+":"+twoDigits(secs);
  47. }
  48.  
  49. function makeLocalFile(path) {
  50.     var file = Components.classes["@mozilla.org/file/local;1"].
  51.             createInstance(Components.interfaces.nsILocalFile);
  52.     file.initWithPath(path);
  53.     return file;
  54. }
  55.  
  56. function pickSavePath(window, title, defaultDirectory, defaultFilename) {
  57.     var nsIFilePicker = Components.interfaces.nsIFilePicker;
  58.     var fp = Components.classes["@mozilla.org/filepicker;1"]
  59.                   .createInstance(nsIFilePicker);
  60.     fp.init(window, title, nsIFilePicker.modeSave);
  61.     if(defaultDirectory) {
  62.        fp.setDefaultDirectory(makeLocalFile(defaultDirectory));
  63.     }
  64.     if(defaultFilename) {
  65.       fp.defaultString = defaultFilename;
  66.     }
  67.     var returncode = fp.show();
  68.     if (returncode == nsIFilePicker.returnOK || 
  69.             returncode == nsIFilePicker.returnReplace) {
  70.        return fp.file.path;
  71.     }  else {
  72.        return null;
  73.     }
  74. }
  75.  
  76. function LoadFinishedListener(area)
  77. {
  78.     this.area = area;
  79. }
  80.  
  81. var actionGroupCommands = {
  82.   'ChannelSelected': Array('menuitem-copychannelurl', 'menuitem-mailchannel'),
  83.   'ChannelFolderSelected': Array(),
  84.   'VideoSelected': Array('menuitem-copyvideourl', 'menuitem-savevideo'),
  85.   'VideosSelected': Array('menuitem-removevideos'),
  86.   'PlaylistLikeSelected': Array('menuitem-renameplaylist'),
  87.   'PlaylistLikesSelected': Array('menuitem-removeplaylists'),
  88.   'ChannelLikesSelected': Array(),
  89.   'ChannelLikeSelected': Array('menuitem-renamechannel',
  90.                   'menuitem-removechannels', 'menuitem-updatechannels'),
  91.   'ChannelsSelected': Array(),
  92.   'VideoPlayable': Array(),
  93. }
  94.  
  95. LoadFinishedListener.prototype =
  96. {
  97.   QueryInterface : function(aIID)
  98.   {
  99.     if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
  100.         aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
  101.         aIID.equals(Components.interfaces.nsISupports))
  102.     {
  103.       return this;
  104.     }
  105.     throw Components.results.NS_NOINTERFACE;
  106.   },
  107.  
  108.   onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus)
  109.   {
  110.     var allFlags = (Components.interfaces.nsIWebProgressListener.STATE_STOP |
  111.         Components.interfaces.nsIWebProgressListener.STATE_IS_WINDOW);
  112.     if((aStateFlags & allFlags) == allFlags) {
  113.       pybridge.pageLoadFinished(this.area, aRequest.name);
  114.     }
  115.   },
  116.   onProgressChange : function(aWebProgress, aRequest, aCurSelfProgress,
  117. aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) { },
  118.   onLocationChange : function(aWebProgress, aRequest, aLocation) { },
  119.   onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage) { },
  120.   onSecurityChange : function(aWebProgress, aRequest, aState) { }
  121.  
  122. // Reference to the progress listeners we create, addProgressListener uses a
  123. // weak reference, so we need to keep a real reference around or else they
  124. // will magically disapear
  125. var progressListeners = {}
  126.  
  127. function jsBridge() { }
  128.  
  129. jsBridge.prototype = {
  130.   QueryInterface: function(iid) {
  131.     if (iid.equals(Components.interfaces.pcfIDTVJSBridge) ||
  132.       iid.equals(Components.interfaces.nsISupports))
  133.       return this;
  134.     throw Components.results.NS_ERROR_NO_INTERFACE;
  135.   },
  136.  
  137.   init: function(window) {
  138.     this.window = window;
  139.     this.document = window.document;
  140.     this.initBrowser("mainDisplay");
  141.     this.initBrowser("videoInfoDisplay");
  142.     this.initBrowser("channelsDisplay");
  143.     this.hideVideoControlsTimer = Components.classes["@mozilla.org/timer;1"].
  144.           createInstance(Components.interfaces.nsITimer);
  145.     this.videoFilename = null;
  146.     this.searchEngineTitles = this.searchEngineNames = null;
  147.  
  148.     var self = this;
  149.     self.lastMouseDownX = self.lastMouseDownY = 0;
  150.     var saveMousedownPosition = function(event) { 
  151.         self.lastMouseDownX = event.screenX;
  152.         self.lastMouseDownY = event.screenY;
  153.     }
  154.     this.document.addEventListener('mousedown', saveMousedownPosition, true);
  155.  
  156.  if (this.window.outerWidth < 800) {
  157.     this.window.outerWidth=800;
  158.   }
  159.  if (this.window.outerHeight < 500) {
  160.     this.window.outerHeight=500;
  161.   }
  162.   },
  163.  
  164.   closeWindow: function() {
  165.     this.window.close();
  166.   },
  167.  
  168.   maximizeWindow: function () {
  169.     this.window.maximize();
  170.     this.window.maximized = true;
  171.   },
  172.  
  173.   initBrowser: function(area) {
  174.     var browser = this.document.getElementById(area);
  175.     var listener = new LoadFinishedListener(area);
  176.     browser.addProgressListener(listener);
  177.     progressListeners[area] = listener;
  178.   },
  179.  
  180.   copyTextToClipboard: function(text) {
  181.     var gClipboardHelper = Components.classes["@mozilla.org/widget/clipboardhelper;1"].getService(Components.interfaces.nsIClipboardHelper);
  182.     gClipboardHelper.copyString(text);
  183.   },
  184.  
  185.   showContextMenu: function(menuItems) {
  186.     var popup = this.document.getElementById('contextPopup');
  187.     while (popup.firstChild) {
  188.       popup.removeChild(popup.firstChild);
  189.     }
  190.     menu = menuItems.split("\n");
  191.     for(var i = 0; i < menu.length; i++) {
  192.       if(menu[i]) {
  193.         var newItem = this.document.createElement('menuitem');
  194.         if(menu[i].charAt(0) != '_') {
  195.           newItem.setAttribute("label", menu[i]);
  196.         } else {
  197.           newItem.setAttribute("label", menu[i].substr(1));
  198.           newItem.setAttribute("disabled", "true");
  199.         }
  200.         newItem.setAttribute("oncommand", 
  201.            "pybridge.handleContextMenu(" + i + ");");
  202.       } else {
  203.         var newItem = this.document.createElement('menuseparator');
  204.       }
  205.       popup.appendChild(newItem);
  206.     }
  207.     popup.showPopup(this.document.documentElement, this.lastMouseDownX,
  208.         this.lastMouseDownY, "popup", null, null);
  209.   },
  210.   showSearchMenu: function() {
  211.     if(!this.searchEngineNames || !this.searchEngineTitles) return;
  212.  
  213.     var popup = this.document.getElementById('searchMenu');
  214.     while (popup.firstChild) {
  215.       popup.removeChild(popup.firstChild);
  216.     }
  217.     for (var i = 0; i < this.searchEngineNames.length; i++) {
  218.         var newItem = this.document.createElement('menuitem');
  219.         newItem.setAttribute("label", this.searchEngineTitles[i]);
  220.         newItem.setAttribute("image", "chrome://dtv/content/images/search_icon_" + this.searchEngineNames[i] + ".png");
  221.         newItem.setAttribute("class", "menuitem-iconic");
  222.         newItem.setAttribute("oncommand", 
  223.            "jsbridge.setSearchEngine('" + this.searchEngineNames[i] + "');");
  224.         popup.appendChild(newItem);
  225.     }
  226.     var textbox = this.document.getElementById('search-textbox');
  227.     popup.showPopup(textbox, -1, -1, "popup", "bottomleft", "topleft");
  228.   },
  229.  
  230.   showChoiceDialog: function(id, title, description, defaultLabel, otherLabel) {
  231.     var params = { "id": id, "title": title, "description" : description, 
  232.         "defaultLabel": defaultLabel, "otherLabel": otherLabel, "out" : -1};
  233.     this.window.openDialog("chrome://dtv/content/choice_dialog.xul",
  234.             "dialog", "chrome,dependent,centerscreen,modal", params);
  235.   },
  236.  
  237.   showCheckboxDialog: function(id, title, description, defaultLabel,
  238.               otherLabel, checkboxText, checkboxValue) {
  239.     var params = { "id": id, "title": title, "description" : description, 
  240.         "defaultLabel": defaultLabel, "otherLabel": otherLabel, 
  241.         "checkboxText": checkboxText, "checkboxValue": checkboxValue,
  242.         "out" : -1};
  243.     this.window.openDialog("chrome://dtv/content/checkbox_dialog.xul",
  244.             "dialog", "chrome,dependent,centerscreen,modal", params);
  245.   },
  246.   showCheckboxTextboxDialog: function(id, title, description, defaultLabel,
  247.               otherLabel, checkboxText, checkboxValue, textboxValue) {
  248.     var params = { "id": id, "title": title, "description" : description, 
  249.         "defaultLabel": defaultLabel, "otherLabel": otherLabel, 
  250.         "checkboxText": checkboxText, "checkboxValue": checkboxValue,
  251.         "out" : -1, "textboxValue": textboxValue};
  252.     this.window.openDialog("chrome://dtv/content/checkboxtextbox_dialog.xul",
  253.             "dialog", "chrome,dependent,centerscreen,modal", params);
  254.   },
  255.  
  256.  
  257.   showThreeChoiceDialog: function(id, title, description, defaultLabel,
  258.                         secondLabel, thirdLabel) {
  259.     var params = { "id": id, "title": title, "description" : description, 
  260.         "defaultLabel": defaultLabel, "secondLabel": secondLabel, 
  261.         "thirdLabel": thirdLabel, "out" : -1};
  262.     this.window.openDialog("chrome://dtv/content/three_choice_dialog.xul",
  263.             "dialog", "chrome,dependent,centerscreen,modal", params);
  264.   },
  265.  
  266.   showMessageBoxDialog: function(id, title, description) {
  267.     var params = { "id": id, "title": title, "description" : description,
  268.             "out" : -1};
  269.     this.window.openDialog("chrome://dtv/content/message_box_dialog.xul",
  270.             "dialog", "chrome,dependent,centerscreen,modal", params);
  271.   },
  272.  
  273.   showHTTPAuthDialog: function(id, description, prefillUser, prefillPassword) {
  274.     var params = {"id": id, "text" : description, "prefillUser": prefillUser,
  275.         "prefillPassword": prefillPassword, "out" : null};
  276.     this.window.openDialog("chrome://dtv/content/password.xul",
  277.             "dialog", "chrome,dependent,centerscreen,modal", params);
  278.   },
  279.  
  280.   showBugReportDialog: function(when, report) {
  281.     var params = {"when" : when, "report": report};
  282.     this.window.openDialog("chrome://dtv/content/bugreport.xul",
  283.             "choice", "chrome,dependent,centerscreen,modal", params);
  284.   },
  285.  
  286.   showTextEntryDialog: function(id, title, description, defaultLabel, 
  287.                                 otherLabel, prefillText) {
  288.     var params = { "id": id, "title": title, "description" : description, 
  289.         "defaultLabel": defaultLabel, "otherLabel": otherLabel, 
  290.         "prefillText": prefillText, "out" : -1};
  291.     this.window.openDialog("chrome://dtv/content/text_entry_dialog.xul",
  292.             "dialog", "chrome,dependent,centerscreen,modal", params);
  293.   },
  294.  
  295.   showSearchChannelDialog: function(id, channels, engines, defaultTerm, defaultStyle, defaultChannel, defaultEngine, defaultURL) {
  296.     var params = { "id": id, "channels" : channels, "engines" : engines,
  297.            "defaultTerm": defaultTerm, "defaultStyle": defaultStyle,
  298.            "defaultChannel": defaultChannel, "defaultEngine": defaultEngine,
  299.            "defaultURL": defaultURL, "Out" : -1};
  300.     this.window.openDialog("chrome://dtv/content/searchchannel.xul",
  301.             "dialog", "chrome,dependent,centerscreen,modal", params);
  302.   },
  303.  
  304.   showUpdateAvailableDialog: function(id, title, description, defaultLabel,
  305.   otherLabel, releaseNotes) {
  306.     var params = { "id": id, "title": title, "description": description,
  307.         "defaultLabel": defaultLabel, "otherLabel": otherLabel, 
  308.         "releaseNotes": releaseNotes }
  309.     this.window.openDialog("chrome://dtv/content/update_available_dialog.xul",
  310.             "dialog", "chrome,dependent,centerscreen,modal,resizable", params);
  311.   },
  312.  
  313.   setCollapsed: function(id, value) {
  314.     var elt = this.document.getElementById(id);
  315.     elt.setAttribute("collapsed", value);
  316.   },
  317.  
  318.   setActive: function(id, active) {
  319.     var elt = this.document.getElementById(id);
  320.     if(active) elt.className = id;
  321.     else elt.className = id + "-inactive";
  322.   },
  323.  
  324.   showVideoDisplay: function() {
  325.     this.setCollapsed("video-box", "false");
  326.     this.setCollapsed("mainDisplay", "true");
  327.     this.setActive("bottom-buttons-previous", true);
  328.     this.setActive("bottom-buttons-stop", true);
  329.     this.setActive("bottom-buttons-play", true);
  330.     this.setActive("bottom-buttons-fullscreen", true);
  331.     this.setActive("bottom-buttons-next", true);
  332.     this.setActive("progress-slider", true);
  333.   },
  334.  
  335.   hideVideoDisplay: function() {
  336.     this.setCollapsed("video-box", "true");
  337.     this.setCollapsed("mainDisplay", "false");
  338.     this.setActive("bottom-buttons-previous", false);
  339.     this.setActive("bottom-buttons-stop", false);
  340.     this.setActive("bottom-buttons-play", false);
  341.     this.setActive("bottom-buttons-fullscreen", false);
  342.     this.setActive("bottom-buttons-next", false);
  343.     this.setActive("progress-slider", false);
  344.   },
  345.  
  346.   setExternalVideoDisplay: function() {
  347.     this.setActive("bottom-buttons-stop", true);
  348.   },
  349.  
  350.   positionVolumeSlider: function(volume) {
  351.     var left = 25;
  352.     var right= 98;
  353.     var position = left + (right-left) * volume;
  354.     position = Math.min(right, Math.max(left, position));
  355.     this.document.getElementById("knob").left = position;
  356.   },
  357.  
  358.   hideForFullscreen: Array('channelsDisplay', 'mainSplitter',
  359.         'resizer-left', 'bottom-left', 'resizer-bottom-right','titlebar'),
  360.   showForFullscreen: Array('bottom-left-blank', 'bottom-right-blank'),
  361.  
  362.   toggleFullscreen: function() {
  363.     if(this.window.fullScreen) this.leaveFullscreen();
  364.     else this.enterFullscreen();
  365.   },
  366.  
  367.   enterFullscreen: function() {
  368.     if(this.window.fullScreen) return;
  369.     this.window.fullScreen = true;
  370.     for(var i = 0; i < this.hideForFullscreen.length; i++) {
  371.           var elt = this.document.getElementById(this.hideForFullscreen[i]);
  372.           elt.collapsed = true;
  373.     }
  374.     for(var i = 0; i < this.showForFullscreen.length; i++) {
  375.           var elt = this.document.getElementById(this.showForFullscreen[i]);
  376.           elt.collapsed = false;
  377.     }
  378.  
  379.  
  380.     var self = this;
  381.     this.mousedown = false;
  382.     this.justResized = false;
  383.     this.mousemoveListener = function(event) {
  384.         if((!self.mousedown) && (!self.justResized)) self.onMouseMoveFullscreen(); 
  385.     }
  386.     this.mousedownListener = function(event) { 
  387.         self.mousedown = true;
  388.         self.hideVideoControlsTimer.cancel();
  389.     }
  390.     this.mouseupListener = function(event) { 
  391.         self.mousedown = false;
  392.         self.startHideVideoControlsTimer();
  393.     }
  394.     this.document.addEventListener('mousemove', this.mousemoveListener, true);
  395.     this.document.addEventListener('mousedown', this.mousedownListener, true);
  396.     this.document.addEventListener('mouseup', this.mouseupListener, true);
  397.     this.startHideVideoControlsTimer();
  398.   },
  399.  
  400.   leaveTotallyFullscreen: function() {
  401.     this.document.getElementById('bottom').collapsed = false;
  402.     this.document.getElementById('videoInfoDisplay').collapsed = false;
  403.     this.hideVideoControlsTimer.cancel();
  404.     pybridge.showCursor(true);
  405.   },
  406.  
  407.   onMouseMoveFullscreen: function() {
  408.       this.leaveTotallyFullscreen();
  409.       this.startHideVideoControlsTimer();
  410.   },
  411.  
  412.   startHideVideoControlsTimer: function() {
  413.     var bottom = this.document.getElementById('bottom')
  414.     var videoInfoDisplay = this.document.getElementById('videoInfoDisplay')
  415.     var self = this;
  416.     // If we don't have this second callback, we ALWAYs immediately
  417.     // get a mouse move event in Vista and go out of "totally
  418.     // fullscreen" mode as soon as we go into it
  419.     var callback2 = {notify: function() {
  420.         self.justResized = false;
  421.     }};
  422.     var callback = {notify: function() {
  423.         self.justResized = true;
  424.         videoInfoDisplay.collapsed = bottom.collapsed = true;
  425.         pybridge.showCursor(false);
  426.         self.hideVideoControlsTimer.initWithCallback(callback2, 100,
  427.                           Components.interfaces.nsITimer.TYPE_ONE_SHOT);
  428.  
  429.     }};
  430.     this.hideVideoControlsTimer.initWithCallback(callback, 3000,
  431.             Components.interfaces.nsITimer.TYPE_ONE_SHOT);
  432.   },
  433.  
  434.  
  435.   leaveFullscreen: function() {
  436.     if(!this.window.fullScreen) return;
  437.     this.window.fullScreen = false;
  438.     for(var i = 0; i < this.hideForFullscreen.length; i++) {
  439.           var elt = this.document.getElementById(this.hideForFullscreen[i]);
  440.           elt.collapsed = false;
  441.     }
  442.     for(var i = 0; i < this.showForFullscreen.length; i++) {
  443.           var elt = this.document.getElementById(this.showForFullscreen[i]);
  444.           elt.collapsed = true;
  445.     }
  446.     this.leaveTotallyFullscreen();
  447.     this.document.removeEventListener('mousemove', this.mousemoveListener, true);
  448.     this.document.removeEventListener('mouseup', this.mouseupListener, true);
  449.     this.document.removeEventListener('mousedown', this.mousedownListener, true);
  450.   },
  451.  
  452.   xulNavigateDisplay: function(area, uri) {
  453.     var browser = this.document.getElementById(area);
  454.     browser.loadURI(uri);
  455.   },
  456.  
  457.   getDocument: function(area) {
  458.     var browser = this.document.getElementById(area);
  459.     return browser.contentDocument;
  460.   },
  461.  
  462.   createNode: function(document, xml) {
  463.     var r = document.createRange();
  464.     r.selectNode(document.documentElement);
  465.     return r.createContextualFragment(xml);
  466.   },
  467.  
  468.   xulAddElementAtEnd: function(area, xml, id) {
  469.     var document = this.getDocument(area);
  470.     var elt = document.getElementById(id);
  471.     elt.insertBefore(this.createNode(document, xml), null);
  472.   },
  473.   xulAddElementBefore: function(area, xml, id) {
  474.     var document = this.getDocument(area);
  475.     var elt = document.getElementById(id);
  476.     elt.parentNode.insertBefore(this.createNode(document, xml), elt);
  477.   },
  478.   xulRemoveElement: function(area, id) {
  479.     var document = this.getDocument(area);
  480.     var elt = document.getElementById(id);
  481.     elt.parentNode.removeChild(elt);
  482.   },
  483.   xulChangeElement: function(area, id, xml) {
  484.     var document = this.getDocument(area);
  485.     var elt = document.getElementById(id);
  486.     var next = elt.nextSibling;
  487.     elt.parentNode.removeChild(elt);
  488.     next.parentNode.insertBefore(this.createNode(document, xml), next);
  489.   },
  490.   xulChangeAttribute: function(area, id, name, value) {
  491.     var document = this.getDocument(area);
  492.     var elt = document.getElementById(id);
  493.     elt.setAttribute(name, value);
  494.   },
  495.   xulRemoveAttribute: function(area, id, name) {
  496.     var document = this.getDocument(area);
  497.     var elt = document.getElementById(id);
  498.     elt.removeAttribute(name);
  499.   },
  500.   xulHideElement: function(area, id) {
  501.     var document = this.getDocument(area);
  502.     var elt = document.getElementById(id);
  503.     elt.style.display = 'none';
  504.   },
  505.   xulShowElement: function(area, id) {
  506.     var document = this.getDocument(area);
  507.     var elt = document.getElementById(id);
  508.     elt.style.display = '';
  509.   },
  510.  
  511.   setActionGroupEnabled: function(group, enabled) {
  512.      var elements = actionGroupCommands[group];
  513.      if(group == 'VideoPlayable') {
  514.        this.setActive("bottom-buttons-play", enabled);
  515.      }
  516.      for(var i = 0; i < elements.length; i++) {
  517.        var elt = this.document.getElementById(elements[i]);
  518.        if(!elt) continue;
  519.        var commandID = elt.getAttribute('command');
  520.        if(!commandID) continue;
  521.        var command = this.document.getElementById(commandID);
  522.        if(!command) continue;
  523.        if(!enabled) {
  524.          command.setAttribute('disabled', true);
  525.        } else {
  526.          command.removeAttribute('disabled');
  527.        }
  528.      }
  529.   },
  530.   updateVideoFilename: function(newFilename) {
  531.     if(newFilename) this.videoFilename = newFilename;
  532.     else this.videoFilename = null;
  533.   },
  534.  
  535.   saveVideo: function() {
  536.     if(this.videoFilename == null) return;
  537.     var saveMenuItem = this.document.getElementById('menuitem-savevideo');
  538.     var picked = pickSavePath(this.window, saveMenuItem.getAttribute('label'),
  539.             null, this.videoFilename);
  540.     if (picked) pybridge.saveVideoFile(picked);
  541.   },
  542.  
  543.   performStartupTasks: function() {
  544.     var wwatch = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  545.                 .getService(Components.interfaces.nsIWindowWatcher);
  546.     var startupTasksURL = "chrome://dtv/content/startup.xul";
  547.     this.startup = wwatch.openWindow(null, startupTasksURL, 
  548.             "DemocracyPlayerStartup", "chrome,dialog=yes,all", null);
  549.   },
  550.  
  551.   updateSearchProgress: function (message) {
  552.     this.startup.updateSearchProgress (message);
  553.   },
  554.  
  555.   searchFinished: function (message) {
  556.     writelog(message);
  557.     this.startup.searchFinished (message);
  558.   },
  559.  
  560.   searchCancelled: function (message) {
  561.     this.startup.searchCancelled (message);
  562.   },
  563.  
  564.   setSliderText: function(elapsed) {
  565.     var sliderText = this.document.getElementById("progress-text");
  566.     sliderText.childNodes[0].nodeValue = formatTime(elapsed);
  567.   },
  568.  
  569.   setDuration: function(duration) {
  570.     var sliderText = this.document.getElementById("duration-text");
  571.     sliderText.childNodes[0].nodeValue = formatTime(duration);
  572.   },
  573.  
  574.   moveSlider: function(fractionDone) {
  575.     var left = 61;
  576.     var right = 204;
  577.     var newSliderPos = Math.floor(left + fractionDone*(right-left));
  578.     var progressSlider = this.document.getElementById("progress-slider");
  579.     progressSlider.left = newSliderPos;
  580.   },
  581.  
  582.   setSearchEngineInfo: function(names, titles) {
  583.     this.searchEngineNames = names;
  584.     this.searchEngineTitles = titles;
  585.   },
  586.  
  587.   setSearchEngine: function(engine) {
  588.     var searchIcon = this.document.getElementById("search-icon");
  589.     searchIcon.setAttribute("src",'images/search_icon_' + engine + '.png');
  590.   },
  591.   updateMenus: function (states) {
  592.  
  593.      // Strings with new labels
  594.      var removeChannels = new Object();
  595.      var updateChannels = new Object();
  596.      var removePlaylists = new Object();
  597.      var removeVideos = new Object();
  598.      pybridge.getLabel("RemoveChannels","",0,0,0,removeChannels);
  599.      pybridge.getLabel("UpdateChannels","",0,0,0,updateChannels);
  600.      pybridge.getLabel("RemovePlaylists","",0,0,0,removePlaylists);
  601.      pybridge.getLabel("RemoveVideos","",0,0,0,removeVideos);
  602.  
  603.      states = states.QueryInterface(Components.interfaces.nsICollection);
  604.  
  605.      for (var i=0;i<states.Count();i++) {
  606.          var state = states.GetElementAt(i);
  607.          state = state.QueryInterface(Components.interfaces.nsICollection);
  608.          var stateName = state.GetElementAt(0).QueryInterface(Components.interfaces.nsIVariant);
  609.  
  610.          var actions = state.GetElementAt(1);
  611.          actions.QueryInterface(Components.interfaces.nsICollection);
  612.          for (var j=0;j<actions.Count();j++) {
  613.              var action = actions.GetElementAt(j).QueryInterface(Components.interfaces.nsIVariant)
  614.  
  615.              if (action == "RemoveChannels")
  616.                  pybridge.getLabel("RemoveChannels",stateName,0,0,0,
  617.                                    removeChannels);
  618.              if (action == "UpdateChannels")
  619.                  pybridge.getLabel("UpdateChannels",stateName,0,0,0,
  620.                                    updateChannels);
  621.              if (action == "RemovePlaylists")
  622.                  pybridge.getLabel("RemovePlaylists",stateName,0,0,0,
  623.                                    removePlaylists);
  624.              if (action == "RemoveVideos")
  625.                  pybridge.getLabel("RemoveVideos",stateName,0,0,0,
  626.                                    removeVideos);
  627.          }
  628.          
  629.      }
  630.  
  631.      var ele = this.document.getElementById("menuitem-removechannels");
  632.      ele.setAttribute("label", removeChannels.value);
  633.      ele = this.document.getElementById("menuitem-updatechannels");
  634.      ele.setAttribute("label", updateChannels.value);
  635.      ele = this.document.getElementById("menuitem-removeplaylists");
  636.      ele.setAttribute("label", removePlaylists.value);
  637.      ele = this.document.getElementById("menuitem-removevideos");
  638.      ele.setAttribute("label", removeVideos.value);
  639.  
  640.   },
  641.   updateTrayMenus: function (unwatched, downloading, paused) {
  642.       var playUnwatched = new Object();
  643.       var pauseDownloads = new Object();
  644.       var restoreDownloads = new Object();
  645.       var restoreWindow = new Object();
  646.       var minstate = "";
  647.  
  648.       if (minimizer.isMinimized()){
  649.           minstate = "restore";
  650.       }
  651.       
  652.  
  653.      // Tray menu strings that get updated periodically
  654.      pybridge.getLabel("PlayUnwatched","",unwatched, downloading, paused, playUnwatched);
  655.      pybridge.getLabel("PauseDownloads","",unwatched, downloading, paused,pauseDownloads);
  656.      pybridge.getLabel("ResumeDownloads","",unwatched, downloading, paused, restoreDownloads);
  657.      pybridge.getLabel("RestoreWindow",minstate,unwatched, downloading, paused, restoreWindow);
  658.  
  659.      var ele = this.document.getElementById("traymenu-playunwatched");
  660.      ele.setAttribute("label", playUnwatched.value);
  661.      ele = this.document.getElementById("traymenu-pausedownloads");
  662.      ele.setAttribute("label", pauseDownloads.value);
  663.      ele = this.document.getElementById("traymenu-resumedownloads");
  664.      ele.setAttribute("label", restoreDownloads.value);
  665.      ele = this.document.getElementById("traymenu-restorewindow");
  666.      ele.setAttribute("label", restoreWindow.value);
  667.  
  668.   },
  669.   setPrefDocument: function (document) {
  670.     this.prefDocument = document;
  671.   },
  672.  
  673.   directoryWatchAdded: function (id, dirname, shown) {
  674.     if (this.prefDocument == null) {
  675.       return;
  676.     }
  677.  
  678.     var setVals = function (xulDirectory) {
  679.       xulDirectory.getElementsByAttribute('role', 'directory')[0]
  680.     .setAttribute('value', dirname);
  681.       xulDirectory.getElementsByAttribute('role', 'shown')[0]
  682.     .setAttribute('checked', shown);
  683.       xulDirectory.getElementsByAttribute('role', 'shown')[0]
  684.     .setAttribute('folder_id', id);
  685.     }
  686.  
  687.     var xulListBox = this.prefDocument.getElementById('movies-collection-listbox');
  688.     var oldChildList = xulListBox.getElementsByAttribute('folder_id', id);
  689.     if (oldChildList.length > 0) {
  690.     setVals(oldChildList[0]);
  691.     } else {
  692.       var xulDirectory = this.prefDocument.getElementById('blueprints')
  693.     .getElementsByAttribute('role', 'movies-collection-directory')[0].cloneNode(true);
  694.       setVals(xulDirectory);
  695.       xulDirectory.setAttribute('folder_id', id);
  696.       xulListBox.appendChild(xulDirectory);
  697.     }
  698.   },
  699.  
  700.   directoryWatchRemoved: function (id) {
  701.     if (this.prefDocument == null) {
  702.       return;
  703.     }
  704.     var xulListBox = this.prefDocument.getElementById('movies-collection-listbox');
  705.     var oldChildList = xulListBox.getElementsByAttribute('folder_id', id);
  706.     if (oldChildList.length > 0) {
  707.       xulListBox.removeChild(oldChildList[0]);
  708.     }
  709.     this.prefDocument.selectDirectoryWatch(true);
  710.   },
  711.   showPopup: function(x, y) {
  712.       // show popup and adjust position once we know width / height
  713.       // Based on core.js from Firefox minimizetotray extension
  714.       var screenwidth = this.window.screen.width;
  715.       var screenheight = this.window.screen.height;
  716.       var document = this.document;
  717.       var window = this.window;
  718.  
  719.       this.popup = document.getElementById('traypopup');
  720.       var self = this;
  721.  
  722.       function minimize_onshown(event, x, y, screenWidth, screenHeight, document) {
  723.  
  724.           var popup = event.target;
  725.           popup.removeEventListener("popupshown",
  726.                                     popup._minimizetotray_onshown,
  727.                                     true);
  728.         
  729.           var box = popup.popupBoxObject;
  730.           if (x + box.width > screenWidth)
  731.           x = x - box.width;
  732.           if (y + box.height > screenHeight)
  733.           y = y - box.height;
  734.         
  735.           // re-show the popup in the right position
  736.           popup.hidePopup();
  737.           document.popupNode = null;
  738.           minimizer.contextMenuHack();
  739.           popup.showPopup(
  740.                           document.documentElement,
  741.                           x, y,
  742.                           "context",
  743.                           "", "");
  744.           minimizer.contextMenuHack2();
  745.       }
  746.       this.popup._minimizetotray_onshown = function(event){ return minimize_onshown(event, x, y, screenwidth, screenheight, document); };
  747.       this.popup.addEventListener("popupshown",
  748.                              this.popup._minimizetotray_onshown,
  749.                              true);
  750.  
  751.       this.popup.showPopup(document.documentElement,   // anchoring element
  752.                       -10000,                    // x
  753.                       -10000,                    // y
  754.                       "context",                  // type
  755.                       "",                         // popupanchor (ignored)
  756.                       "");
  757.  
  758.   },
  759.  
  760.   showOpenDialog: function (id, title, defaultDirectory, typeString, types) {
  761.       var nsIFilePicker = Components.interfaces.nsIFilePicker;
  762.       var fp = Components.classes["@mozilla.org/filepicker;1"]
  763.                       .createInstance(nsIFilePicker);
  764.       fp.init(this.window, title, nsIFilePicker.modeOpen);
  765.       if(defaultDirectory) {
  766.           fp.setDefaultDirectory(makeLocalFile(defaultDirectory));
  767.       }
  768.       if(types) {
  769.         var filters = new Array();
  770.         for(var i = 0; i < types.length; i++) {
  771.           filters[i] = "*." + types[i];
  772.         }
  773.         fp.appendFilter(typeString, filters.join(";"));
  774.       }
  775.       fp.appendFilters(nsIFilePicker.filterAll);
  776.       if (fp.show() == nsIFilePicker.returnOK){
  777.         pybridge.handleFileDialog(id, fp.file.path);
  778.     }
  779.   },
  780.  
  781.   showSaveDialog: function (id, title, defaultDirectory, defaultFilename) {
  782.       var picked = pickSavePath(this.window, title, defaultDirectory, defaultFilename);
  783.       if (picked) pybridge.handleFileDialog(id, picked);
  784.   },
  785. };
  786.  
  787. var Module = {
  788.   _classes: {
  789.       jsBridge: {
  790.           classID: JSBRIDGE_CLASSID,
  791.           contractID: JSBRIDGE_CONTRACTID,
  792.           className: "DTV Javascript helpers",
  793.           factory: {
  794.               createInstance: function(delegate, iid) {
  795.                   if (delegate)
  796.                       throw Components.results.NS_ERROR_NO_AGGREGATION;
  797.                   return new jsBridge().QueryInterface(iid);
  798.               }
  799.           }
  800.       }
  801.   },
  802.  
  803.   registerSelf: function(compMgr, fileSpec, location, type) {
  804.       var reg = compMgr.QueryInterface(
  805.           Components.interfaces.nsIComponentRegistrar);
  806.  
  807.       for (var key in this._classes) {
  808.           var c = this._classes[key];
  809.           reg.registerFactoryLocation(c.classID, c.className, c.contractID,
  810.                                       fileSpec, location, type);
  811.       }
  812.   },
  813.  
  814.   getClassObject: function(compMgr, cid, iid) {
  815.       if (!iid.equals(Components.interfaces.nsIFactory))
  816.           throw Components.results.NS_ERROR_NO_INTERFACE;
  817.  
  818.       for (var key in this._classes) {
  819.           var c = this._classes[key];
  820.           if (cid.equals(c.classID))
  821.               return c.factory;
  822.       }
  823.  
  824.       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  825.   },
  826.  
  827.   canUnload: function (aComponentManager) {
  828.       return true;
  829.   },
  830. };
  831.  
  832. function NSGetModule(compMgr, fileSpec) {
  833.   return Module;
  834. }
  835.