home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 September / PCWorld_2008-09_cd.bin / komunikace / kmeleon / K-Meleon1.1.3en-US.exe / chrome / newsfox.jar / content / newsfox / ui.js < prev   
Text File  |  2007-10-15  |  45KB  |  1,579 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is NewsFox.
  15.  *
  16.  * The Initial Developer of the Original Code is
  17.  * Andy Frank <andy@andyfrank.com>.
  18.  * Portions created by the Initial Developer are Copyright (C) 2005-2007
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Andrey Gromyko <andrey@gromyko.name>
  23.  *   Ron Pruitt <wa84it@gmail.com>
  24.  *
  25.  * Alternatively, the contents of this file may be used under the terms of
  26.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  27.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28.  * in which case the provisions of the GPL or the LGPL are applicable instead
  29.  * of those above. If you wish to allow use of your version of this file only
  30.  * under the terms of either the GPL or the LGPL, and not to allow others to
  31.  * use your version of this file under the terms of the MPL, indicate your
  32.  * decision by deleting the provisions above and replace them with the notice
  33.  * and other provisions required by the LGPL or the GPL. If you do not delete
  34.  * the provisions above, a recipient may use your version of this file under
  35.  * the terms of any one of the MPL, the GPL or the LGPL.
  36.  *
  37.  * ***** END LICENSE BLOCK ***** */
  38.  
  39. ////////////////////////////////////////////////////////////////
  40. // Global
  41. ////////////////////////////////////////////////////////////////
  42.  
  43. // shared with rss.js
  44. var gAutoRefreshTimer = null;
  45. var gCheckInProgress = false;
  46. var gFeedsToCheck = new Array();
  47. var gNewItemsCount = 0;
  48. var gNumToCheck;
  49. var gDisplayArticle = true;
  50.  
  51. // ui.js only
  52. var defaultURL = "";
  53. var firstLoad = true;
  54. var loadIndex;
  55. var initNewsfox = false;
  56. var clean = false;
  57.  
  58. ////////////////////////////////////////////////////////////////
  59. // Lifecycle
  60. ////////////////////////////////////////////////////////////////
  61.  
  62. function startup()
  63. {
  64.   // This method was getting called twice, not sure why
  65.   // so short circuit if method already invoked.
  66.   if (initNewsfox) return;
  67.     initNewsfox = true;
  68.     if (navigator.userAgent.indexOf('K-Meleon') > -1) gKMeleon = true;
  69.     if (navigator.userAgent.indexOf('Firefox\/3') > -1 || navigator.userAgent.indexOf('GranParadiso') > -1) gFF3 = true;
  70.     document.getElementById("notBusyText").value = NEWSFOX + " " + VERSION;
  71.     gOptions.startup();
  72.     doHorizontal(gOptions.horiz);
  73.     makeCss();
  74.     getAccel();
  75.     gCollect = new EmptyCollection();
  76.   loadModels();
  77.     backupOpml();
  78.   setTimeout(loadAllFeeds,30);
  79.     doLivemarks();
  80.  
  81.     frames["hrefContent"].location.href = "chrome://newsfox/content/help/start.xhtml";
  82.     var newFeedUrl = gOptions.addUrl;
  83.     if (newFeedUrl != "") addFeedUrl();
  84.   if(gOptions.checkOnStartup)
  85.     {
  86.         if (newFeedUrl != "" && !gCheckInProgress)
  87.             gFeedsToCheck.push(newFeedUrl);   // check new feed first
  88.     checkFeeds();
  89.     }
  90.     if( gOptions.autoRefresh )
  91.         gAutoRefreshTimer = setTimeout(this.checkFeeds, gOptions.autoRefreshInterval * 60 * 1000);
  92. }
  93.  
  94. function saveModels()
  95. {
  96.   saveFeedModel();
  97.   saveGroupModel();
  98.   saveIndices();
  99. }
  100.  
  101. function refreshModel()
  102. {
  103.   loadFeedModel();
  104.   loadGroupModel();
  105.   loadIndices();
  106.   var tree = document.getElementById("newsfox.feedTree");
  107.   tree.view = new FeedTreeModel();
  108. }
  109.  
  110. function refreshModelSelect(index)
  111. {
  112.   var feedtree = document.getElementById("newsfox.feedTree");
  113.   var fRow = feedtree.treeBoxObject.getFirstVisibleRow();
  114.   feedtree.view = new FeedTreeModel();
  115.   feedtree.treeBoxObject.scrollToRow(fRow);
  116.   feedtree.view.selection.select(index);
  117.   feedtree.treeBoxObject.ensureRowIsVisible(index);
  118. }
  119.  
  120. function cleanup()
  121. {
  122.   if (!initNewsfox) return;
  123.   if (clean) return;
  124.     if( null != gAutoRefreshTimer )
  125.         clearTimeout(gAutoRefreshTimer);
  126.     gOptions.rmObserver();
  127.   saveFeedModel();
  128.   saveGroupModel();
  129.   saveIndices();
  130.   clean = true;
  131. }
  132.  
  133. function fixIndices()
  134. {
  135.   for (var i=0; i<gFdGroup.length; i++)
  136.   {
  137.     gIdx.fdgp[i] = i;
  138.     gIdx.feed[i] = -1;
  139.     gIdx.catg[i] = 0;
  140.     gIdx.open[i] = gFdGroup[i].expanded = false;
  141.   }
  142.   gIdx.fdgp.length = gIdx.feed.length = gIdx.catg.length = gIdx.open.length = gFdGroup.length;
  143.   var feedtree = document.getElementById("newsfox.feedTree");
  144.   feedtree.view = new FeedTreeModel();
  145.   saveIndices();
  146.  
  147. // check all feeds in group 0
  148.   var modelSize = gFmodel.size();
  149.   var isIn = new Array();
  150.   for (i=0; i<modelSize; i++) isIn[i] = false;
  151.   for (i=0; i<gFdGroup[0].list.length; i++)
  152.     isIn[gFdGroup[0].list[i]] = true;
  153.   for (i=0; i<modelSize; i++)
  154.     if (!isIn[i]) gFdGroup[0].list.push(i);
  155.  
  156. // check no higher numbered feeds
  157.   for (i=0; i<gFdGroup.length; i++)
  158.     for (var j=0; j<gFdGroup[i].list.length; j++)
  159.       if (gFdGroup[i].list[j] >= modelSize)
  160.         gFdGroup[i].list.splice(j,1);
  161. }
  162.  
  163. function loadModels()
  164. {
  165.   var file = getProfileDir();
  166.   file.append("master.xml");
  167.   var hasFeeds = file.exists();
  168.   var file = getProfileDir();
  169.   file.append("master_index.xml");
  170.   var hasGroups = file.exists();
  171.   if (!hasGroups)
  172.   {
  173.     gFdGroup[0] = new FeedGroup();
  174.     gFdGroup.length = 1;
  175.         const NF_SB = document.getElementById("newsfox-string-bundle");
  176.     gFdGroup[0].title = NF_SB.getString('FEEDS');
  177.     gFdGroup[0].expanded = false;
  178.     gIdx.fdgp[0] = 0;
  179.     gIdx.feed[0] = -1;
  180.     gIdx.catg[0] = 0;
  181.     gIdx.open[0] = false;
  182.     if (hasFeeds)
  183.     {
  184.       loadFeedModel();
  185.       for (var i=0; i<gFmodel.size(); i++)
  186.         gFdGroup[0].list[i] = i;
  187.       gFdGroup[0].list.length = gFmodel.size();
  188.     }
  189.     else
  190.     {
  191.       gFdGroup[0].list.length = 0;
  192.     }
  193.     saveGroupModel();
  194.     saveIndices();
  195.   }
  196.   if (hasFeeds) loadFeedModel();
  197.   loadGroupModel();
  198.   loadIndices();
  199.   var feedtree = document.getElementById("newsfox.feedTree");
  200.   feedtree.view = new FeedTreeModel();
  201.   if (!hasGroups && hasFeeds) feedtree.view.toggleOpenState(0);
  202. }
  203.  
  204. function loadAllFeeds()
  205. {
  206.   if (!firstLoad) return;
  207.   firstLoad = false;
  208.     loadIndex = gFmodel.size() - 1;
  209.     if (loadIndex < 0) return;
  210.     setTimeout(loadAFeed,30);
  211. }
  212.  
  213. function loadAFeed()
  214. {
  215.     if (loadIndex == 0) return;
  216.     loadFeed(gFmodel.get(loadIndex));
  217.     loadIndex--;
  218.     setTimeout(loadAFeed,30);
  219. }
  220.  
  221. ////////////////////////////////////////////////////////////////
  222. // Commands
  223. ////////////////////////////////////////////////////////////////
  224.  
  225. /**
  226.  * Check feeds for new items.
  227.  */
  228. function feedCheck(button)
  229. {
  230. // need to pass array of URLs in case feeds are deleted during check
  231.   if (gCheckInProgress) return;
  232.   gCheckInProgress = true;
  233.   var feedtree = document.getElementById("newsfox.feedTree");
  234.   var index = feedtree.currentIndex;
  235.   var level = feedtree.view.getLevel(index);
  236.   if (index == -1 || button) level = -1;
  237.  
  238.   var elem = document.getElementById("busyText");
  239.   elem.removeAttribute("hidden");
  240.   var elem = document.getElementById("notBusyText");
  241.   elem.hidden = "true";
  242.  
  243.   switch(level) 
  244.   {
  245.     case 0:  // check in group
  246.       var curGrp = gIdx.fdgp[index];
  247.       for (var i=0; i<gFdGroup[curGrp].list.length; i++)
  248.                 gFeedsToCheck.push(gFmodel.get(gFdGroup[curGrp].list[i]).url);
  249.       break;
  250.         case 2:  // in category, check its feed
  251.             while (feedtree.view.getLevel(index) != 1) index--;
  252.             feedtree.view.toggleOpenState(index);
  253.           feedtree.view.selection.select(index);
  254.     case 1:  // check feed
  255.       gFeedsToCheck.push(gFmodel.get(gIdx.feed[index]).url);
  256.       break;
  257.     case -1:  // none selected or button, do auto
  258.             var autoFeed = false;
  259.             if (gFeedsToCheck.length > 0) autoFeed = true; 
  260.       if( null != gAutoRefreshTimer )
  261.         clearTimeout(gAutoRefreshTimer);
  262.       for (var i=0; i<gFdGroup[0].list.length; i++)
  263.         if (gFmodel.get(gFdGroup[0].list[i]).autoCheck)
  264.           gFeedsToCheck.push(gFmodel.get(gFdGroup[0].list[i]).url);
  265.             var rootList = gFdGroup[0].list;
  266.             var checkLast = gFmodel.get(rootList[rootList.length-1]).autoCheck;
  267.             if (autoFeed)
  268.                 if (checkLast) gFeedsToCheck.length--;  // don't do new feed twice
  269.                 else gFeedsToCheck.shift();  // don't do new feed at all
  270.   }
  271.   gNumToCheck = gFeedsToCheck.length;
  272.     gNewItemsCount = 0;
  273.   precheckFeed(gFeedsToCheck);
  274. }
  275.  
  276. /**
  277.  * Auto check feeds.
  278.  */
  279. function checkFeeds()
  280. {
  281.     if (gCheckInProgress) setTimeout(checkFeeds,500);
  282.   feedCheck(true);
  283. }
  284.  
  285. /**
  286.  * Add a new group.
  287.  */
  288. function addGroup(isSearch)
  289. {
  290.   var num = gFdGroup.length;
  291.   var grp = new FeedGroup();
  292.     if (isSearch) grp.search = true;
  293.   gFdGroup.push(grp);
  294.   var index = gIdx.fdgp.length;
  295.   gIdx.fdgp.push(num);
  296.   gIdx.feed.push(-1);
  297.   gIdx.catg.push(0);
  298.   gIdx.open.push(false);
  299.   saveIndices();
  300.   saveGroupModel();
  301.   var feedtree = document.getElementById("newsfox.feedTree");
  302.   feedtree.treeBoxObject.rowCountChanged(index,1);
  303.   showGroupOptions(index,true,isSearch);
  304. }
  305.  
  306. /**
  307.  * Delete a group.
  308.  */
  309. function deleteGroup(confirm)
  310. {
  311.   var feedtree = document.getElementById("newsfox.feedTree");
  312.   var index = feedtree.currentIndex;
  313.   if (feedtree.view.getLevel(index) != 0) return;
  314.   var curGrp = gIdx.fdgp[index];
  315.   if (curGrp == 0) return;
  316.  
  317.     const NF_SB = document.getElementById("newsfox-string-bundle");
  318.   var confirmationMessage = NF_SB.getString('confirmation.deleteGroup');
  319.   confirmationMessage += "\n\n" + gFdGroup[curGrp].title;
  320.   if (!confirm || !gOptions.confirmDelete || window.confirm(confirmationMessage))
  321.   {
  322.     if (gFdGroup[curGrp].expanded) feedtree.view.toggleOpenState(index);
  323.     gFdGroup.splice(curGrp,1);
  324.     for (var i=index+1;i < gIdx.fdgp.length;i++)
  325.       gIdx.fdgp[i]--;
  326.     gIdx.fdgp.splice(index,1);
  327.     gIdx.feed.splice(index,1);
  328.     gIdx.catg.splice(index,1);
  329.     gIdx.open.splice(index,1);
  330.     feedtree.treeBoxObject.rowCountChanged(index,-1);
  331.     saveGroupModel();
  332.     saveIndices();
  333.         if (index >= gIdx.fdgp.length) index = gIdx.fdgp.length - 1;
  334.         feedtree.view.selection.select(index);
  335.   }
  336. }
  337.  
  338. /**
  339.  * Add a new feed.
  340.  */
  341. function addFeed(isStartup)
  342. {
  343.   var feedtree = document.getElementById("newsfox.feedTree");
  344.   var index = feedtree.currentIndex;
  345.   var pRow = feedtree.treeBoxObject.getFirstVisibleRow();
  346.     var pGrp = gIdx.fdgp[index];
  347.     var pFeed = gIdx.feed[index];
  348.     var exp0 = gFdGroup[0].expanded;
  349.   var url = defaultURL;
  350.   createNewFeed(gFmodel, url, false, true);
  351.   var nFeed = gFmodel.size() - 1;
  352.   index = 0;
  353.   while (nFeed != gIdx.feed[index]) index++;
  354.     if (!isStartup)
  355.     {
  356.       index = showFeedOptions(index,true);
  357.         if (gOptions.selectAddedFeed)
  358.         {
  359.             if (!exp0 && gIdx.fdgp[index] != 0)
  360.             {
  361.                 feedtree.view.toggleOpenState(0);
  362.               feedtree.treeBoxObject.scrollToRow(pRow);
  363.               feedtree.treeBoxObject.ensureRowIsVisible(feedtree.currentIndex);
  364.             }
  365.         }
  366.         else
  367.         {
  368.             if (!exp0) feedtree.view.toggleOpenState(0);
  369.             var i = gIdx.fdgp.length-1;
  370.             while ((gIdx.fdgp[i] != pGrp || gIdx.feed[i] != pFeed) && i > 0) i--;
  371.           feedtree.treeBoxObject.scrollToRow(pRow);
  372.           feedtree.view.selection.select(i);
  373.           feedtree.treeBoxObject.ensureRowIsVisible(i);
  374.         }
  375.     }
  376.     else
  377.     {
  378.         feedCheck(false);
  379.         feedtree.view.selection.select(index);
  380.     }
  381. }
  382.  
  383. function addFeedUrl()
  384. {
  385.     var isStartup = false;
  386.     if (gOptions.addUrl == NEWSFOX_RSS) isStartup = true;
  387.     defaultURL = gOptions.addUrl;
  388.     gOptions.addUrl = "";
  389.     gOptions.save();
  390.     if (!isStartup || gFmodel.size() == 0) addFeed(isStartup);
  391.     defaultURL = "";
  392. }
  393.  
  394. function deleteRow()
  395. {
  396.   var feedtree = document.getElementById("newsfox.feedTree");
  397.     var index = feedtree.currentIndex;
  398.     var level = feedtree.view.getLevel(index);
  399.   if (level == 1) deleteFeed(index,true);
  400.     else if (level == 0) deleteGroup(true);
  401. }
  402.  
  403. function deleteSingleFeedRow()
  404. {
  405.   var feedtree = document.getElementById("newsfox.feedTree");
  406.   var index = feedtree.currentIndex;
  407.   if (feedtree.view.getLevel(index) != 1) return;
  408.     var nGrp = gIdx.fdgp[index];
  409.     if (nGrp == 0) return;
  410.   var nFeed = gIdx.feed[index];
  411.     if (!(index + 1 < gIdx.feed.length && gIdx.feed[index+1] >= 0)) // next is not feed
  412.         index--;
  413.  
  414.     for (var j=gFdGroup[nGrp].list.length-1; j>=0; j--)
  415.         if (gFdGroup[nGrp].list[j] == nFeed)
  416.             gFdGroup[nGrp].list.splice(j,1);
  417.     if (gFdGroup[nGrp].list.length == 0) gIdx.open[getGroupRow(nGrp)] = gFdGroup[nGrp].expanded = false;
  418.  
  419.     for (var i=gIdx.feed.length-1; i>=0; i--)
  420.         if (gIdx.fdgp[i] == nGrp && gIdx.feed[i] == nFeed)
  421.         {
  422.             gIdx.fdgp.splice(i,1);
  423.             gIdx.feed.splice(i,1);
  424.             gIdx.catg.splice(i,1);
  425.             gIdx.open.splice(i,1);
  426.             feedtree.treeBoxObject.rowCountChanged(i,-1);
  427.         }
  428.     feedtree.view.selection.select(index);
  429. }
  430.  
  431. /**
  432.  * Permanently delete a feed.
  433.  */
  434. function deleteFeed(index,confirm)
  435. {
  436.   var feedtree = document.getElementById("newsfox.feedTree");
  437.   var fRow = feedtree.treeBoxObject.getFirstVisibleRow();
  438.   if (index == -1) index = feedtree.currentIndex;
  439.   if (feedtree.view.getLevel(index) != 1) return;
  440.     var nGrp = gIdx.fdgp[index];
  441.   var nFeed = gIdx.feed[index];
  442.     var newFeed;
  443.     if (index + 1 < gIdx.feed.length && gIdx.feed[index+1] >= 0)
  444.         newFeed = gIdx.feed[index+1];
  445.     else
  446.         newFeed = gIdx.feed[index-1];
  447.     if (newFeed > nFeed) newFeed--;
  448.   var feed = gFmodel.get(nFeed);
  449.     const NF_SB = document.getElementById("newsfox-string-bundle");
  450.   var confirmationMessage = NF_SB.getString('confirmation.deleteFeed');
  451.   confirmationMessage += "\n\n" + feed.getDisplayName();
  452.   if (!confirm || !gOptions.confirmDelete || window.confirm(confirmationMessage))
  453.   {
  454.     deleteFeedFromDisk(feed);
  455.     for (var i=0; i<gFdGroup.length; i++)
  456.       for (var j=0; j<gFdGroup[i].list.length; j++)
  457.         if (gFdGroup[i].list[j] >= nFeed)
  458.             (gFdGroup[i].list[j] == nFeed) ? gFdGroup[i].list[j]=-2 : gFdGroup[i].list[j]--;
  459.     for (i=0; i<gIdx.feed.length; i++)
  460.       if (gIdx.feed[i] >= nFeed)
  461.         (gIdx.feed[i] == nFeed) ? gIdx.feed[i]=-2 : gIdx.feed[i]--;
  462.     for (var i=0; i<gFdGroup.length; i++)
  463.     {
  464.       for (var j=gFdGroup[i].list.length-1; j>=0; j--)
  465.         if (gFdGroup[i].list[j] == -2)
  466.           gFdGroup[i].list.splice(j,1);
  467.       if (gFdGroup[i].list.length == 0) gIdx.open[getGroupRow(i)] = gFdGroup[i].expanded = false;
  468.     }
  469.     for (i=gIdx.feed.length-1; i>=0; i--)
  470.       if (gIdx.feed[i] == -2)
  471.       {
  472.         gIdx.fdgp.splice(i,1);
  473.         gIdx.feed.splice(i,1);
  474.         gIdx.catg.splice(i,1);
  475.         gIdx.open.splice(i,1);
  476.              feedtree.treeBoxObject.rowCountChanged(i,-1);
  477.       }
  478.     gFmodel.remove(feed);
  479.     saveModels();
  480.     feedtree.treeBoxObject.scrollToRow(fRow);
  481.         if (newFeed < 0)
  482.             feedtree.view.selection.select(getGroupRow(nGrp));
  483.         else
  484.             feedtree.view.selection.select(getFeedRow(nGrp,newFeed));
  485.         setTitle();
  486.   }
  487. }
  488.  
  489. /**
  490.  * Delete articles.
  491.  */
  492. function deleteArticle()
  493. {
  494.   try
  495.   {
  496.     var tree = document.getElementById("newsfox.feedTree");
  497.     var index = tree.currentIndex;
  498.     var feed = gFmodel.get(gIdx.feed[index]);
  499.  
  500.         const NF_SB = document.getElementById("newsfox-string-bundle");
  501.     var confirmationMessage = NF_SB.getString('confirmation.deleteArticles');
  502.     if (gOptions.confirmDelete && !confirm(confirmationMessage))
  503.       return;
  504.  
  505.     // Regular delete
  506.     var arttree = document.getElementById("newsfox.articleTree");
  507.         for (var i=0; i<gCollect.size(); i++)
  508.             if (arttree.view.selection.isSelected(i))
  509.                 gCollect.getFeed(i).deleteById(gCollect.get(i).id);
  510.  
  511.         if (gCollect.type == 0 || gCollect.type == 3)  // group or search
  512.         {
  513.             var curGrp = gFdGroup[gCollect.grpindex];
  514.             for (i=0; i<curGrp.list.length; i++)
  515.                 saveFeed(gFmodel.get(curGrp.list[i]));
  516.         }
  517.         else                    // feed or category
  518.         saveFeed(feed);
  519.  
  520.     saveFeedModel();
  521.  
  522.     // Update unread count
  523.     tree = document.getElementById("newsfox.feedTree");
  524.     tree.treeBoxObject.invalidate();
  525.     feedSelected();   // need new collection
  526.         setTitle();
  527.   }
  528.   catch (err)
  529.   {
  530.     var msg = "deleteArticle(): " + err;
  531.     alert(msg);
  532.   }
  533. }
  534.  
  535. /**
  536.  * Open articles.
  537.  */
  538. function openArticle()
  539. {
  540.   try
  541.   {
  542.     var arttree = document.getElementById("newsfox.articleTree");
  543.         for (var i=0; i<gCollect.size(); i++)
  544.             if (arttree.view.selection.isSelected(i))
  545.                 openNewTab(gCollect.get(i).link);
  546.   }
  547.   catch (err)
  548.   {
  549.     var msg = "openArticle(): " + err;
  550.     alert(msg);
  551.   }
  552. }
  553.  
  554. function catchUp(doflag)
  555. {
  556.   var feedtree = document.getElementById("newsfox.feedTree");
  557.   feedtree.view.selection.select(0);
  558.   markFlaggedUnread(doflag,true);
  559. }
  560.  
  561. function markFlaggedUnread(doflag,read)
  562. {
  563.   for(var i=0; i < gCollect.size(); i++)
  564.   {
  565.     var flagged = gCollect.isFlagged(i);
  566.     gCollect.setRead(i, doflag ? !flagged : (read ? true : false));
  567.     if (doflag) gCollect.setFlagged(i,0);
  568.     }
  569.  
  570.   var feedtree = document.getElementById("newsfox.feedTree");
  571.   feedtree.treeBoxObject.invalidate();
  572.   var articleTree = document.getElementById("newsfox.articleTree");
  573.   articleTree.treeBoxObject.invalidate();
  574.     setTitle();
  575. }
  576.  
  577. function openUnread()
  578. {
  579.   var feedTree = document.getElementById("newsfox.feedTree");
  580.   if( feedTree.currentIndex == -1) return;
  581.   var article, read;
  582.   for(var i=0; i < gCollect.size(); i++)
  583.   {
  584.     article = gCollect.get(i);
  585.     read = gCollect.isRead(i);
  586.     gCollect.setRead(i,true);
  587.     if (!read) openNewTab(article.link);
  588.   }
  589.   feedTree.treeBoxObject.invalidate();
  590.   var articleTree = document.getElementById("newsfox.articleTree");
  591.   articleTree.treeBoxObject.invalidate();
  592. }
  593.  
  594. /**
  595.  * Show help.
  596.  */
  597. function help()
  598. {
  599.   frames["hrefContent"].location.href = "chrome://newsfox/content/help/overview.xhtml";
  600.     var contentDeck = document.getElementById("contentDeck");
  601.     contentDeck.selectedIndex = 0;
  602. }
  603.  
  604. function showShortcuts()
  605. {
  606.     var art = new Article();
  607.     var shortcutMenu = document.getElementById("help.shortcut");
  608.     art.title = shortcutMenu.label;
  609.     art.date = new Date();
  610.     art.category = "";
  611.     art.link = "";
  612.     art.body = "";
  613.  
  614.     var keySet = document.getElementById("shortcut-keys");
  615.     var mods;
  616.     for (var i=keySet.firstChild; i != null; i=i.nextSibling)
  617.     {
  618.         mods = i.getAttribute("modifiers");
  619.         mods += (mods.length > 0) ? "-" : "";
  620.         art.body += i.getAttribute("label") + ":  " + mods + i.getAttribute("key") + "<br/>";
  621.     }
  622.  
  623.     const NF_SB = document.getElementById("newsfox-string-bundle");
  624.   var customShortcut = NF_SB.getString('customShortcut');
  625.     art.body += "<br/>" + customShortcut + "<br/>";
  626.  
  627.     var contentDeck = document.getElementById("contentDeck");
  628.     resetIframe();
  629.     getTextView(art);
  630.     contentDeck.selectedIndex = 1;
  631. }
  632.  
  633. /**
  634.  * Goto to homepage of selected feed.
  635.  */
  636. function home()
  637. {
  638.   var tree = document.getElementById("newsfox.feedTree");
  639.   var feed = gFmodel.get( gIdx.feed[tree.currentIndex] );
  640.  
  641.   openNewTab(feed.homepage);
  642. }
  643.  
  644. ////////////////////////////////////////////////////////////////
  645. // Options
  646. ////////////////////////////////////////////////////////////////
  647.  
  648. /**
  649.  * Show global options.
  650.  */
  651. function showOptions()
  652. {
  653.     var keyType = new Array();
  654.     var index = 0;
  655.     var keySet = document.getElementById("shortcut-keys");
  656.     for (var i=keySet.firstChild; i != null; i=i.nextSibling)
  657.     {
  658.         keyType[index] = 3;  // 3 is custom
  659.         for (var j=0; j<3; j++)
  660.         {
  661.             var modMatch = (i.getAttribute("modifiers") == i.getAttribute("mod"+j));
  662.             var keyMatch = (i.getAttribute("key") == i.getAttribute("key"+j));
  663.             if (modMatch && keyMatch)
  664.                 keyType[index] = j;
  665.         }
  666.         if (keyType[index] != keyType[0])
  667.             keyType[0] = 3;  // it is custom
  668.         index++;
  669.     }
  670.     var keyIndex = keyType[0];
  671.     if (keySet.firstChild == null) keyIndex = 0;
  672.     var newKeyIndex, mvContents;
  673.     var curDir = getPref("global.directory", "str", "");
  674.   var params = { ok:false, style:gOptions.globalStyle, checkOnStartup:gOptions.checkOnStartup, autoRefresh:gOptions.autoRefresh, autoRefreshInterval:gOptions.autoRefreshInterval, notifyUponNew:gOptions.notifyUponNew, confirmDelete:gOptions.confirmDelete, keyIndex:keyIndex, newKeyIndex:newKeyIndex, nfDir:curDir, mvContents:mvContents, dateStyle:gOptions.dateStyle };
  675.   var win = window.openDialog("chrome://newsfox/content/programOptions.xul",
  676.     "newsfox-dialog","chrome,centerscreen,modal", params);
  677.     var oldAutoRefreshInterval = gOptions.autoRefreshInterval;
  678.  
  679.   if (params.ok)
  680.   {
  681.     gOptions.globalStyle = params.style;
  682.     gOptions.checkOnStartup = params.checkOnStartup;
  683.         gOptions.autoRefresh = params.autoRefresh;
  684.         if( gOptions.autoRefresh )
  685.         {
  686.             if( gOptions.autoRefreshInterval != params.autoRefreshInterval )
  687.             {
  688.                 if( null != gAutoRefreshTimer )
  689.                     clearTimeout(gAutoRefreshTimer);
  690.                 gAutoRefreshTimer = setTimeout(this.checkFeeds, params.autoRefreshInterval * 60 * 1000);
  691.             }
  692.         }
  693.         else if( null != gAutoRefreshTimer )
  694.             clearTimeout(gAutoRefreshTimer);
  695.         gOptions.autoRefreshInterval = params.autoRefreshInterval;
  696.         gOptions.notifyUponNew = params.notifyUponNew;
  697.         gOptions.confirmDelete = params.confirmDelete;
  698.         gOptions.dateStyle = params.dateStyle;
  699.         newKeyIndex = params.newKeyIndex;
  700.         if (keyIndex != newKeyIndex) updateShortcuts(newKeyIndex);
  701.     gOptions.save(false);
  702.  
  703.         if (curDir != params.nfDir)
  704.         {
  705.             var nsIPH = Components.classes["@mozilla.org/network/protocol;1?name=file"].createInstance(Components.interfaces.nsIFileProtocolHandler);
  706.             var feedtree = document.getElementById("newsfox.feedTree");
  707.             cleanup();
  708.             feedtree.view = null;
  709.             gMsgDone = true;
  710.             if (params.mvContents < 2)  // copy or move
  711.             {
  712.                 var oldDirFile = nsIPH.getFileFromURLSpec(curDir);
  713.                 var newDirFile = nsIPH.getFileFromURLSpec(params.nfDir);
  714.                 var newDirName = newDirFile.leafName;
  715.                 var newDirParent = newDirFile.parent;
  716. // TODO need to put up a progress meter if long copy
  717. // doesn't work: 
  718. //var hithere = setTimeout(lttOn,500);
  719.                 try
  720.                 {
  721.                     newDirFile.remove(true);
  722.                     oldDirFile.copyTo(newDirParent,newDirName);
  723.                 }
  724.                 catch(err)
  725.                 {
  726.                     const NF_SB = document.getElementById("newsfox-string-bundle");
  727.                     var dirNotChanged = NF_SB.getString('dirNotChanged');
  728.                     alert(dirNotChanged + "\n\n" + err);
  729.                     return;
  730.                 }
  731. //if (hithere != null) clearTimeout(hithere);
  732. //loadingTooltip(false);
  733.                 setPref("global.directory", "str", params.nfDir);
  734.                 gNewsfoxDirURL = params.nfDir;
  735.                 refreshModel();
  736.                 if (params.mvContents == 1) oldDirFile.remove(true);
  737.             }
  738.             else
  739.             {
  740.                 setPref("global.directory", "str", params.nfDir);
  741.                 location.href = "chrome://newsfox/content/newsfox.xul";
  742.             }
  743.         }
  744.   }
  745.     return;
  746. }
  747.  
  748. //function lttOn()
  749. //{
  750. //    loadingTooltip(true);
  751. //}
  752. /**
  753.  * Show options dialog for current feed/group.
  754.  */
  755. function chooseOptions()
  756. {
  757.   var feedtree = document.getElementById("newsfox.feedTree");
  758.   var index = feedtree.currentIndex;
  759.   if (index == -1) return;
  760.   var level = feedtree.view.getLevel(index);
  761.     var isSearch = gFdGroup[gIdx.fdgp[index]].search;
  762.   if (level == 1) showFeedOptions(index,false);
  763.     else showGroupOptions(index,false,isSearch);
  764. }
  765.  
  766. /**
  767.  * Show options dialog for current group/search.
  768.  */
  769. function showGroupOptions(index,isNew,isSearch)
  770. {
  771.     // following two lines due to a bug in FF that sometimes doesn't
  772.     // hide context menu properly
  773.   var feedMenu = document.getElementById("feedMenu");
  774.     feedMenu.hidePopup();
  775.   var feedtree = document.getElementById("newsfox.feedTree");
  776.   var grp = gIdx.fdgp[index];
  777.  
  778.   var expgrp = false;
  779.   if (grp != 0 && gFdGroup[grp].expanded)
  780.   {
  781.     feedtree.view.toggleOpenState(index);
  782.     expgrp = true;
  783.   }
  784.   var exp0 = false;
  785.   if (gFdGroup[0].expanded)
  786.   {
  787.     feedtree.view.toggleOpenState(0);
  788.     exp0 = true;
  789.   }
  790.  
  791.   var feeds = new Array();
  792.   for (var i=0; i<gFmodel.size(); i++)
  793.     feeds.push(gFmodel.get(i));
  794.  
  795.   var titlelist = new Array();
  796.   for (i=0; i<gFdGroup.length; i++)
  797.     titlelist.push(gFdGroup[i].title);
  798.  
  799.   var lists = new Array();
  800.   lists[0] = new Array();
  801.   for (i=0; i<gFdGroup[0].list.length; i++)
  802.     lists[0].push(gFdGroup[0].list[i]);
  803.   lists[1] = new Array();
  804.   for (i=0; i<gFdGroup[grp].list.length; i++)
  805.     lists[1].push(gFdGroup[grp].list[i]);
  806.  
  807.     var flagged = null;
  808.     var unread = null;
  809.     var text = null;
  810.     var textflags = null;
  811.     var startTime = null;
  812.     var endTime = null;
  813.     if (isSearch)
  814.     {
  815.         var srchdat = gFdGroup[grp].srchdat;
  816.         flagged = srchdat.flagged;
  817.         unread = srchdat.unread;
  818.         text = srchdat.text;
  819.         textflags = srchdat.textflags;
  820.         startTime = srchdat.startTime;
  821.         endTime = srchdat.endTime;
  822.     }
  823.  
  824.   var params = { ok:false, grp:grp, newGrp:grp, feeds:feeds, titlelist:titlelist, lists:lists, flagged:flagged, unread:unread, text:text, textflags:textflags, startTime:startTime, endTime:endTime, isSearch:isSearch, fdGp:gFdGroup, isNew:isNew };
  825.   var win = window.openDialog("chrome://newsfox/content/groupOptions.xul",
  826.     "newsfox-dialog","chrome,centerscreen,modal", params);
  827.  
  828.   if (params.ok)
  829.   {
  830.         if (isSearch)
  831.         {
  832.             gFdGroup[grp].srchdat.flagged = params.flagged;
  833.             gFdGroup[grp].srchdat.unread = params.unread;
  834.             gFdGroup[grp].srchdat.text = params.text;
  835.             gFdGroup[grp].srchdat.textflags = params.textflags;
  836.             gFdGroup[grp].srchdat.startTime = params.startTime;
  837.             gFdGroup[grp].srchdat.endTime = params.endTime;
  838.         }
  839.     gFdGroup[grp].title = params.titlelist[grp];
  840.     var feedslist = params.lists[0];
  841.     for (i=0; i<feedslist.length; i++)
  842.       gFdGroup[0].list[i] = feedslist[i];
  843.     gFdGroup[0].list.length = feedslist.length;
  844.     var grplist = params.lists[1];
  845.     for (i=0; i<grplist.length; i++)
  846.       gFdGroup[grp].list[i] = grplist[i];
  847.     gFdGroup[grp].list.length = grplist.length;
  848.     mvGrp(grp,params.newGrp);
  849.         grp = (params.newGrp >= grp) ? params.newGrp-1 : params.newGrp;
  850.     if (gFdGroup[grp].list.length == 0) expgrp = false;
  851.     saveIndices();
  852.     saveGroupModel();
  853.     loadGroupModel();
  854.   }
  855.   else    // !params.ok
  856.   {
  857.     if (isNew)
  858.     {
  859.             index = getGroupRow(grp);
  860.       refreshModelSelect(index);
  861.       deleteGroup(false);
  862.     }
  863.   }
  864.   if (exp0) feedtree.view.toggleOpenState(0);
  865.   index = getGroupRow(grp);
  866.   if (expgrp) feedtree.view.toggleOpenState(index);
  867.   refreshModelSelect(index);
  868. }
  869.  
  870. /**
  871.  * Show options dialog for current feed.
  872.  */
  873. function showFeedOptions(index,isNew)
  874. {
  875.   var checkFeed = false;
  876.   var curGrp = gIdx.fdgp[index];
  877.   var nFeed = gIdx.feed[index];
  878.   var feed = gFmodel.get(nFeed);
  879.   var namearray = new Array();
  880.   var membarray = new Array();
  881.   var membarray2 = new Array();
  882.   var tmp, j;
  883.   for (var i=0; i<gFdGroup.length; i++)
  884.   {
  885.     namearray.push(gFdGroup[i].title);
  886.     tmp = 0;
  887.     for (j=0; j<gFdGroup[i].list.length; j++)
  888.       if (gFdGroup[i].list[j] == nFeed) tmp = 1;
  889.         membarray2.push(tmp);
  890.         if (gFdGroup[i].search)
  891.             if (isNew)
  892.                 tmp = 3;
  893.             else
  894.                 tmp += 2;
  895.     membarray.push(tmp);
  896.   }
  897.   var groupstr = namearray.join();
  898.   var groupmemb = membarray.join();
  899.  
  900.   var params = { ok:false, name:feed.getDisplayName(), iconsrc:feed.icon.src, homepage:feed.homepage, url:feed.url, style:feed.style, deleteOld:feed.deleteOld, dontDeleteUnread:feed.dontDeleteUnread, autoCheck:feed.autoCheck, groupstr:groupstr, groupmemb:groupmemb, isNew:isNew, checkFeed:checkFeed, uid:feed.uid, model:gFmodel, daysToKeep:feed.daysToKeep };
  901.   var win = window.openDialog("chrome://newsfox/content/feedOptions.xul",
  902.     "newsfox-dialog","chrome,centerscreen,modal", params);
  903.  
  904.   if (params.ok)
  905.   {
  906.     var newmembarray = params.groupmemb.split(",");
  907.     for (i=0; i<membarray2.length; i++)
  908.     {
  909.       var diff = newmembarray[i] - membarray2[i];
  910.       if (diff == 1)  // adding
  911.       {
  912.         gFdGroup[i].list.push(nFeed);
  913.         if (gFdGroup[i].expanded)
  914.         {
  915.           var top = getGroupRow(i+1);
  916.           gIdx.fdgp.splice(top,0,i);
  917.           gIdx.feed.splice(top,0,nFeed);
  918.           gIdx.catg.splice(top,0,0);
  919.           gIdx.open.splice(top,0,false);
  920.         }
  921.       }
  922.       else if (diff == -1)  // deleting
  923.       {
  924.         for (var j=gFdGroup[i].list.length-1; j>=0; j--)
  925.           if (gFdGroup[i].list[j] == nFeed)
  926.             gFdGroup[i].list.splice(j,1);
  927.         if (gFdGroup[i].list.length == 0) gIdx.open[getGroupRow(i)] = gFdGroup[i].expanded = false;
  928.         var grprow = getGroupRow(i);
  929.         var top = getGroupRow(i+1);
  930.         for (j=top-1; j>=grprow; j--)
  931.           if (gIdx.feed[j] == nFeed)
  932.           {
  933.             gIdx.fdgp.splice(j,1);
  934.             gIdx.feed.splice(j,1);
  935.             gIdx.catg.splice(j,1);
  936.             gIdx.open.splice(j,1);
  937.           }
  938.       }
  939.     }
  940.  
  941.     feed.url = params.url.trim();
  942.     feed.style = params.style;
  943.     feed.deleteOld = params.deleteOld;
  944.     feed.dontDeleteUnread = params.dontDeleteUnread;
  945.         feed.daysToKeep = params.daysToKeep;
  946.     feed.autoCheck = params.autoCheck;
  947.     if (isNew)
  948.     {
  949.       var uid = gFmodel.makeUniqueUid(feed.url);
  950.       feed.uid = uid;
  951.             deleteFeedFromDisk(feed);
  952.       feed.defaultName = uid;
  953.             feed.homepage = null;   // TODO feed.homepage gets set via downloadicon?
  954.     }
  955.     else
  956.     {
  957.       var val = params.name.trim();
  958.       if (val == feed.defaultName) feed.customName = ""
  959.       else feed.customName = val;
  960.       if (params.iconsrc == "")
  961.         feed.icon.src = ICON_OK;
  962.       else
  963.         feed.icon.src = params.iconsrc.trim();
  964.       feed.homepage = params.homepage.trim();
  965.       downloadIcon(feed);
  966.     }
  967.     saveModels();
  968.         if (isNew) curGrp++;   // prefer not FEEDS group
  969.        index = getGroupRow(curGrp);    // index may have changed, recompute
  970.     while (gIdx.feed[index] != nFeed && index < gIdx.fdgp.length) index++;
  971.     if (gIdx.feed[index] != nFeed)
  972.     {
  973.       index=0;
  974.       while (gIdx.feed[index] != nFeed  && index < gIdx.fdgp.length) index++;
  975.       if (gIdx.feed[index] != nFeed)    // can't happen, feed still exists
  976.       { 
  977.         refreshModelSelect(0);
  978.         return 0;
  979.       }
  980.     }
  981.     refreshModelSelect(index); 
  982.     if (params.checkFeed) feedCheck(false);
  983.   }
  984.   else   //  !params.ok
  985.   {
  986.     if (isNew)
  987.     {
  988. //      refreshModelSelect(index);
  989.       deleteFeed(index,false);
  990.     }
  991.   }
  992.     return index;
  993. }
  994.  
  995. /**
  996.  * Troubleshoot feed errors.
  997.  */
  998. function troubleshoot()
  999. {
  1000.   var tree = document.getElementById("newsfox.feedTree");
  1001.   var nFeed = gIdx.feed[tree.currentIndex];
  1002.   if (nFeed < 0) return;
  1003.   var feed = gFmodel.get(nFeed);
  1004.  
  1005.     resetIframe();
  1006.   var iframe = document.getElementById("buildContent");
  1007.   var doc = iframe.contentDocument;
  1008.  
  1009.   var summary = getErrorSummary(feed.error);
  1010.   var remedies = getErrorRemedies(feed.error);
  1011.  
  1012.         var b = doc.createElement("b");
  1013.         b.appendChild(doc.createTextNode(summary));
  1014.   doc.body.appendChild(b);
  1015.   doc.body.appendChild(doc.createElement("hr"));
  1016.  
  1017.       var p = doc.createElement("p");
  1018.       p.innerHTML = remedies;
  1019.   doc.body.appendChild(p);
  1020.  
  1021.         if (feed.error.substring(0,1) == ERROR_INVALID_FEED_URL)
  1022.         {
  1023.             var p1 = doc.createElement("p");
  1024.             var a1 = doc.createElement("a");
  1025.             a1.setAttribute("href",FEED_VALIDATOR + escape(feed.url));
  1026.             const NF_SB = document.getElementById("newsfox-string-bundle");
  1027.             var feedValidator = NF_SB.getString('remedy_checkFeedValidator');
  1028.             a1.innerHTML = feedValidator;
  1029.             p1.appendChild(a1);
  1030.     doc.body.appendChild(p1);
  1031.         }
  1032.  
  1033.       var p2 = doc.createElement("p");
  1034.         var a2 = doc.createElement("a");
  1035.         a2.setAttribute("href",feed.url);
  1036.         a2.innerHTML = feed.url;
  1037.       p2.appendChild(a2);
  1038.   doc.body.appendChild(p2);
  1039.  
  1040.     var contentDeck = document.getElementById("contentDeck");
  1041.     contentDeck.selectedIndex = 1;
  1042.     frames["hrefContent"].location.href = "about:blank";
  1043. }
  1044.  
  1045. ////////////////////////////////////////////////////////////////
  1046. // Selection
  1047. ////////////////////////////////////////////////////////////////
  1048.  
  1049. /**
  1050.  * Feed selected.
  1051.  */
  1052. function feedSelected()
  1053. {
  1054.   var tree = document.getElementById("newsfox.feedTree");
  1055.   var index = tree.currentIndex;
  1056.   var arttree = document.getElementById("newsfox.articleTree");
  1057.   if (index == -1)
  1058.     {
  1059.         gCollect = new EmptyCollection();
  1060.         arttree.view = null;
  1061.         return;
  1062.     }
  1063.   var nFeed = gIdx.feed[index];
  1064.   if (nFeed < 0)  // group
  1065.     {
  1066.         loadingTooltip(true);
  1067.         var grpindex = gIdx.fdgp[index];
  1068.         var grp = gFdGroup[grpindex];
  1069.         for (var i=0; i<grp.list.length; i++)
  1070.         {
  1071.             loadFeed(gFmodel.get(grp.list[i]));
  1072.             setPmeter((100*i)/grp.list.length);
  1073.         }
  1074.         gCollect = new GroupCollection(grpindex, grp.search);
  1075.         setPmeter(0);
  1076.         loadingTooltip(false);
  1077.     }
  1078.     else if (nFeed >= 0)  // feed
  1079.     {
  1080.       var feed = gFmodel.get(nFeed);
  1081.         downloadIcon(feed);
  1082.         loadFeed(feed);
  1083.         feed.flags.length = feed.size();
  1084. //RPdebug if (feed.flags.length != feed.size()) 
  1085. //RPdebug  alert("feed flags length " + feed.flags.length + "     feed.size " + feed.size());
  1086.  
  1087.         gCollect = new NormalCollection(nFeed, gIdx.catg[index],true);
  1088.     }
  1089.   
  1090.   arttree.view = null;  
  1091.   arttree.view = new ArticleTreeModel();
  1092.  
  1093. //    troubleshoot();
  1094. }
  1095.  
  1096. /**
  1097.  * Article selected.
  1098.  */
  1099. function articleSelected()
  1100. {
  1101.     if (!gDisplayArticle) return;
  1102.   var tree = document.getElementById("newsfox.articleTree");
  1103.   var index = tree.currentIndex;
  1104.     if (index == -1) return;
  1105.   var art = gCollect.get(index);
  1106.   var read = gCollect.isRead(index);
  1107.  
  1108.   if (!read)
  1109.   {
  1110.     gCollect.setRead(index, true);
  1111.     tree.treeBoxObject.invalidateRow(index);
  1112.     var tree = document.getElementById("newsfox.feedTree");
  1113.     tree.treeBoxObject.invalidate();
  1114.         setTitle();
  1115.   }
  1116.  
  1117.   // Stop any previous page loading
  1118.     var nsIWebNav = Components.interfaces.nsIWebNavigation;
  1119.     var iframe = document.getElementById("hrefContent");
  1120.     var webnav = iframe.docShell.QueryInterface(nsIWebNav);
  1121.     webnav.stop(nsIWebNav.STOP_NETWORK);
  1122.     var iframe = document.getElementById("buildContent");
  1123.     var webnav = iframe.docShell.QueryInterface(nsIWebNav);
  1124.     webnav.stop(nsIWebNav.STOP_NETWORK);
  1125.  
  1126.   // Display body in content
  1127.     var contentDeck = document.getElementById("contentDeck");
  1128.   var style = gCollect.getFeed(index).getStyle();
  1129.   if (style == 2 && art.link)
  1130.   {
  1131.     // Display as webpage
  1132.     frames["hrefContent"].location.href = art.link;
  1133.         contentDeck.selectedIndex = 0;
  1134.   }
  1135.   else
  1136.   {
  1137.         // display using innerHTML to resolve security issues pointed out by Wladimir Palant
  1138.         resetIframe();
  1139.         getTextView(art);
  1140.         contentDeck.selectedIndex = 1;
  1141.   }
  1142. }
  1143.  
  1144. function articleTreeMClicked()
  1145. {
  1146.     var tree = document.getElementById("newsfox.articleTree");
  1147.     var index = tree.currentIndex;
  1148.     if (index == -1) return;  // in header
  1149.     var article = gCollect.get(index);
  1150.     openNewTab(article.link);
  1151. }
  1152.  
  1153. function articleTreeDblClicked()
  1154. {
  1155.     this.articleTreeMClicked();
  1156. }
  1157.  
  1158. /**
  1159.  * Select the next unread article.
  1160.  */
  1161. function selectNextUnreadArticle()
  1162. {
  1163.   var feedtree = document.getElementById("newsfox.feedTree");
  1164.   var arttree = document.getElementById("newsfox.articleTree");
  1165.   var row = feedtree.currentIndex;
  1166.   if (row < 0) return;
  1167.   var curGrp = gIdx.fdgp[row];
  1168.     var artIndex = arttree.currentIndex;
  1169.     if (artIndex < 0) artIndex = 0;
  1170.   if (gCollect.type != 1)  // group or category
  1171.     {
  1172.         for (var j = artIndex; j < gCollect.size(); j++)
  1173.             if (!gCollect.isRead(j))
  1174.             {
  1175.                 arttree.view.selection.select(j);
  1176.           arttree.treeBoxObject.ensureRowIsVisible(j);
  1177.           return;
  1178.             }
  1179.     }
  1180.     else   // feed
  1181.     {
  1182.     // current row
  1183.       var feed = gFmodel.get(gIdx.feed[row]);
  1184.       feedtree.view.selection.select(row);
  1185.       feedtree.treeBoxObject.ensureRowIsVisible(row);
  1186.       for (var j = artIndex; j < feed.size(); j++)
  1187.         if (!feed.isRead(j))
  1188.         {
  1189.           arttree.view.selection.select(j);
  1190.           arttree.treeBoxObject.ensureRowIsVisible(j);
  1191.           return;
  1192.         }
  1193.     // rest of folder
  1194.       while (++row < gIdx.fdgp.length && gIdx.fdgp[row] == curGrp)
  1195.       {
  1196.         feed = gFmodel.get(gIdx.feed[row]);
  1197.         feedtree.view.selection.select(row);
  1198.         feedtree.treeBoxObject.ensureRowIsVisible(row);
  1199.         for (var j = 0; j < feed.size(); j++)
  1200.           if (!feed.isRead(j))
  1201.           {
  1202.             arttree.view.selection.select(j);
  1203.             arttree.treeBoxObject.ensureRowIsVisible(j);
  1204.             return;
  1205.           }
  1206.       }
  1207.     }
  1208.     const NF_SB = document.getElementById("newsfox-string-bundle");
  1209.   alert(NF_SB.getString('noNextUnread'));
  1210. }
  1211.  
  1212. /**
  1213.  * Select the previous unread article.
  1214.  */
  1215. function selectPrevUnreadArticle()
  1216. {
  1217.   var feedtree = document.getElementById("newsfox.feedTree");
  1218.   var arttree = document.getElementById("newsfox.articleTree");
  1219.   var row = feedtree.currentIndex;
  1220.   if (row < 0) return;
  1221.   var curGrp = gIdx.fdgp[row];
  1222.   var artIndex = arttree.currentIndex;
  1223.   if (artIndex < 0)
  1224.   {
  1225.     artIndex = gCollect.size()-1;
  1226.     if (artIndex < 0) artIndex = 0;
  1227.   }
  1228.   if (gCollect.type != 1)  // group or category
  1229.     {
  1230.         for (var j = artIndex; j >= 0; j--)
  1231.             if (!gCollect.isRead(j))
  1232.             {
  1233.                 arttree.view.selection.select(j);
  1234.           arttree.treeBoxObject.ensureRowIsVisible(j);
  1235.           return;
  1236.             }
  1237.     }
  1238.     else   // feed
  1239.     {
  1240.     // current row
  1241.       var feed = gFmodel.get(gIdx.feed[row]);
  1242.       feedtree.view.selection.select(row);
  1243.       feedtree.treeBoxObject.ensureRowIsVisible(row);
  1244.       for (var j = artIndex; j >= 0; j--)
  1245.         if (!feed.isRead(j))
  1246.         {
  1247.           arttree.view.selection.select(j);
  1248.           arttree.treeBoxObject.ensureRowIsVisible(j);
  1249.           return;
  1250.         }
  1251.     // rest of folder
  1252.       while (gIdx.fdgp[--row] == curGrp && gIdx.feed[row] > -1)
  1253.       {
  1254.         var feed = gFmodel.get(gIdx.feed[row]);
  1255.         feedtree.view.selection.select(row);
  1256.         feedtree.treeBoxObject.ensureRowIsVisible(row);
  1257.         for (var j = feed.size()-1; j >= 0; j--)
  1258.           if (!feed.isRead(j))
  1259.           {
  1260.             arttree.view.selection.select(j);
  1261.             arttree.treeBoxObject.ensureRowIsVisible(j);
  1262.             return;
  1263.           }
  1264.       }
  1265.     }
  1266.     const NF_SB = document.getElementById("newsfox-string-bundle");
  1267.   alert(NF_SB.getString('noPrevUnread'));
  1268. }
  1269.  
  1270. ////////////////////////////////////////////////////////////////
  1271. // Event Handlers
  1272. ////////////////////////////////////////////////////////////////
  1273.  
  1274. /**
  1275.  * Route events.
  1276.  */
  1277. function handleEvent(e) 
  1278. {
  1279.   if (e.keyCode == 0) handleMouseEvent(e);
  1280.   handleKeyEvent(e);
  1281. }
  1282.  
  1283. /**
  1284.  * Handle mouse events.
  1285.  */
  1286. function handleMouseEvent(e) 
  1287. {
  1288. }
  1289.  
  1290. /**
  1291.  * Handle keyboard events.
  1292.  */
  1293. function handleKeyEvent(e)
  1294. {
  1295.     var focus = 0;
  1296.     var focusId = document.commandDispatcher.focusedElement.id;
  1297.     if (focusId == "newsfox.feedTree") focus = 1;
  1298.     else if (focusId == "newsfox.articleTree") focus = 2;
  1299.     else return;
  1300.  
  1301.   switch(e.keyCode) 
  1302.   {
  1303.     case 39:  // right-arrow
  1304.             if (e.ctrlKey)
  1305.                 if (focus == 2)
  1306.                 {
  1307.                   var arttree = document.getElementById("newsfox.articleTree");
  1308.                     arttree.view.selection.select(arttree.currentIndex);
  1309.                 }
  1310.             break;
  1311.     case 40:  // down-arrow
  1312.       if (e.altKey) 
  1313.       {
  1314.         e.stopPropagation();
  1315.         e.preventDefault();
  1316.         selectNextUnreadArticle(); 
  1317.       }
  1318.       else if (e.ctrlKey)
  1319.                 if (focus == 1) moveIt(false);
  1320.       break;
  1321.  
  1322.     case 38:   // up-arrow
  1323.       if (e.altKey) 
  1324.       {
  1325.         e.stopPropagation();
  1326.         e.preventDefault();
  1327.         selectPrevUnreadArticle(); 
  1328.       }
  1329.       else if (e.ctrlKey)
  1330.                 if (focus == 1) moveIt(true);
  1331.       break;
  1332.  
  1333.     case 46: // delete
  1334.       if (focus == 2) deleteArticle();
  1335.       else if (focus == 1) deleteRow();
  1336.       break;
  1337.   }
  1338. }
  1339.  
  1340. ////////////////////////////////////////////////////////////////
  1341. // Util
  1342. ////////////////////////////////////////////////////////////////
  1343.  
  1344. function openNewTab(url)
  1345. {
  1346.     if (url == NO_LINK) return;
  1347.     if(!gKMeleon)
  1348.     {
  1349.         const kWindowMediatorContractID = "@mozilla.org/appshell/window-mediator;1";
  1350.         const kWindowMediatorIID = Components.interfaces.nsIWindowMediator;
  1351.         const kWindowMediator = Components.classes[kWindowMediatorContractID].getService(kWindowMediatorIID);
  1352.         var browserWindow = kWindowMediator.getMostRecentWindow("navigator:browser");
  1353.         var browser = browserWindow.getBrowser();
  1354.         var tab = browser.addTab(url);
  1355.     }
  1356.     else
  1357.     {
  1358.         window.open(url);
  1359.         window.focus();
  1360.     }
  1361. //  ctrl-alt-tab to open in background on K-M
  1362. }
  1363.  
  1364. function onOptMenuShowing(menu)
  1365. {
  1366.   var feedtree = document.getElementById("newsfox.feedTree");
  1367.   var index = feedtree.currentIndex;
  1368.   var level = feedtree.view.getLevel(index);
  1369.   if (index == -1) level = -1;
  1370.  
  1371.   var children = menu.childNodes;
  1372.   for (var i = 0; i < children.length; i++)
  1373.   {
  1374.     var id = children[i].getAttribute("id");
  1375.     switch (id)
  1376.     {
  1377.       case "tool.group":
  1378.       case "tool.groupExport":
  1379.     if (level == 0) children[i].setAttribute("disabled",false)
  1380.     else children[i].setAttribute("disabled",true);
  1381.     break;
  1382.       case "tool.feed":
  1383.     if (level == 1) children[i].setAttribute("disabled",false)
  1384.     else children[i].setAttribute("disabled",true);
  1385.     break;
  1386.     }
  1387.   }
  1388.   return true;
  1389. }
  1390.  
  1391. function onFeedMenuShowing(menu)
  1392. {
  1393.   var feedtree = document.getElementById("newsfox.feedTree");
  1394.   var index = feedtree.currentIndex;
  1395.     if (index == -1) return false;
  1396.   var level = feedtree.view.getLevel(index);
  1397.  
  1398.   var children = menu.childNodes;
  1399.   for (var i = 0; i < children.length; i++)
  1400.   {
  1401.     var id = children[i].getAttribute("id");
  1402.     switch (id)
  1403.     {
  1404.       case "home":
  1405.     if (level == 0 || gFmodel.get(gIdx.feed[index]).homepage == "") children[i].setAttribute("disabled",true)
  1406.     else children[i].setAttribute("disabled",false);
  1407.     break;
  1408.       case "checkFeed":
  1409.       case "props":
  1410.     if (level <= 1) children[i].setAttribute("disabled",false)
  1411.     else children[i].setAttribute("disabled",true);
  1412.     break;
  1413.     }
  1414.   }
  1415.   return true;
  1416. }
  1417.  
  1418. function mvGrp(oldgrp, newgrp)
  1419. {
  1420.   if (oldgrp == newgrp || oldgrp+1 == newgrp || oldgrp == 0 || newgrp == 0) return;
  1421.   var up = 1*(newgrp > oldgrp);
  1422.   var down = 1 - up;
  1423.   var newrow = getGroupRow(newgrp);
  1424.   var oldrow = getGroupRow(oldgrp);
  1425.   var i = oldrow;
  1426.   while (i < gIdx.fdgp.length && gIdx.fdgp[i] == oldgrp) i++;
  1427.   var num = i - oldrow;
  1428.   for (i=0; i<num; i++)
  1429.   {
  1430.     gIdx.fdgp.splice(newrow+i*down,0,gIdx.fdgp[oldrow+i*down]);
  1431.     gIdx.fdgp.splice(oldrow+(i+1)*down,1);
  1432.     gIdx.feed.splice(newrow+i*down,0,gIdx.feed[oldrow+i*down]);
  1433.     gIdx.feed.splice(oldrow+(i+1)*down,1);
  1434.     gIdx.catg.splice(newrow+i*down,0,gIdx.catg[oldrow+i*down]);
  1435.     gIdx.catg.splice(oldrow+(i+1)*down,1);
  1436.     gIdx.open.splice(newrow+i*down,0,gIdx.open[oldrow+i*down]);
  1437.     gIdx.open.splice(oldrow+(i+1)*down,1);
  1438.   }
  1439.   var tmpGrp = gFdGroup[oldgrp];
  1440.   gFdGroup.splice(oldgrp,1);
  1441.   gFdGroup.splice(newgrp-up,0,tmpGrp);
  1442.   grpChg(oldgrp,-2);
  1443.   if (newgrp > oldgrp)
  1444.   {
  1445.     newgrp--;
  1446.     for (var i=0; i<(newgrp-oldgrp); i++)
  1447.       grpChg(oldgrp+i+1,oldgrp+i);
  1448.   }
  1449.   else
  1450.     for (var i=0; i<(oldgrp-newgrp); i++)
  1451.       grpChg(oldgrp-i-1,oldgrp-i);
  1452.   grpChg(-2,newgrp);
  1453.   saveGroupModel();
  1454.   saveIndices();
  1455.   refreshModelSelect(newrow - up*num);
  1456. }
  1457.  
  1458. function grpChg(oldgrp,newgrp)
  1459. {
  1460.   for (var i=0; i<gIdx.fdgp.length; i++)
  1461.     if (gIdx.fdgp[i] == oldgrp)
  1462.       gIdx.fdgp[i] = newgrp;
  1463. }
  1464.  
  1465. function getGroupRow(grp)
  1466. {
  1467.   var i = gIdx.fdgp.length - 1;
  1468.   while (i >= 0 && gIdx.fdgp[i] >= grp) i--;
  1469.   return ++i;
  1470. }
  1471.  
  1472. function getFeedRow(grp,nFeed)
  1473. {
  1474.   var i = getGroupRow(grp);
  1475.   while (i < gIdx.fdgp.length && gIdx.fdgp[i] == grp && gIdx.feed[i] != nFeed) i++;
  1476.   return i;  // returns row after last feed if not in group
  1477. }
  1478.  
  1479. function moveIt(movingUp)
  1480. {
  1481.   var feedtree = document.getElementById("newsfox.feedTree");
  1482.   var index = feedtree.currentIndex;
  1483.   var curGrp = gIdx.fdgp[index];
  1484.   var curFeed = gIdx.feed[index];
  1485.   if (gIdx.catg[index] != 0) return;  // on category
  1486.   if (curFeed == -1)                // on group
  1487.   {
  1488.     if (movingUp && curGrp <= 1) return;
  1489.     if (!movingUp && curGrp == gFdGroup.length-1) return;
  1490.     var newGrp = curGrp + 2;
  1491.     if (movingUp) newGrp -= 3;
  1492.     mvGrp(curGrp,newGrp);
  1493.   }
  1494.   else                             // on feed
  1495.   {
  1496.     var curPos = -1;
  1497.     for (var i=0; i<gFdGroup[curGrp].list.length; i++)
  1498.       if (gFdGroup[curGrp].list[i] == curFeed) curPos = i;
  1499.     if (movingUp && curPos == 0) return;
  1500.     if (!movingUp && curPos == gFdGroup[curGrp].list.length-1) return;
  1501.     var newFeed;
  1502.     if (movingUp) newFeed = gFdGroup[curGrp].list[curPos-1]
  1503.     else if (curPos+2 == gFdGroup[curGrp].list.length) newFeed = -2
  1504.     else newFeed = gFdGroup[curGrp].list[curPos+2];
  1505.     mvFeed(curGrp,curFeed,newFeed);
  1506.   }
  1507. }
  1508.  
  1509. function mvFeed(curGrp,curFeed,newFeed)
  1510. {
  1511.   var feedtree = document.getElementById("newsfox.feedTree");
  1512.   var curRow = getFeedRow(curGrp,curFeed);
  1513.   var newRow = getFeedRow(curGrp,newFeed);
  1514.   var curPos;
  1515.   var newPos = gFdGroup[curGrp].list.length;
  1516.   for (var i=0; i<gFdGroup[curGrp].list.length; i++)
  1517.   {
  1518.     if (gFdGroup[curGrp].list[i] == curFeed) curPos = i
  1519.     else if (gFdGroup[curGrp].list[i] == newFeed) newPos = i;
  1520.   }
  1521.   var up = (newRow > curRow);
  1522.   if (curPos == newPos || curPos+1 == newPos) return;
  1523.   var curExpand = gIdx.open[curRow];
  1524.   if (curExpand) feedtree.view.toggleOpenState(curRow);
  1525.   gFdGroup[curGrp].list.splice(curPos,1);
  1526.   gFdGroup[curGrp].list.splice(newPos-up,0,curFeed);
  1527.   newRow = getFeedRow(curGrp,newFeed);
  1528.   gIdx.feed.splice(curRow,1);
  1529.   gIdx.feed.splice(newRow-up,0,curFeed);
  1530.   gIdx.fdgp.splice(curRow,1);
  1531.   gIdx.fdgp.splice(newRow-up,0,curGrp);
  1532.   gIdx.catg.splice(curRow,1);
  1533.   gIdx.catg.splice(newRow-up,0,0);
  1534.   gIdx.open.splice(curRow,1);
  1535.   gIdx.open.splice(newRow-up,0,false);
  1536.   if (curExpand) feedtree.view.toggleOpenState(newRow-up);
  1537.  
  1538.   saveGroupModel();
  1539.   saveIndices();
  1540.   refreshModelSelect(newRow-up);
  1541. }
  1542.  
  1543. function loadingTooltip(show)
  1544. {
  1545.     var tooltip = document.getElementById("loadingTooltip");
  1546.     if (show)
  1547.         tooltip.showPopup();
  1548.     else
  1549.         tooltip.hidePopup();
  1550. }
  1551.  
  1552. function setPmeter(value)
  1553. {
  1554.     var pmeter = document.getElementById("pmeter");
  1555.     if (value == 0)
  1556.         pmeter.hidden = true;
  1557.     else
  1558.     {
  1559.         pmeter.hidden = false;
  1560.         pmeter.setAttribute("value", value + "%");
  1561.     }
  1562. }
  1563.  
  1564. function doHorizontal(horiz)
  1565. {
  1566.     var hartBox = document.getElementById("hbox3pane");
  1567.     var rightPane = document.getElementById("rightpane");
  1568.     if(horiz)
  1569.     {
  1570.         var vartBox = document.getElementById("vbox3pane");
  1571.         vartBox.setAttribute("orient","horizontal");
  1572.         rightPane.appendChild(vartBox);
  1573.         var vartsplitter = document.getElementById("vartsplitter");
  1574.         var hartsplitter = document.getElementById("hartsplitter");
  1575.         vartBox.replaceChild(hartsplitter,vartsplitter);
  1576.     }
  1577.     rightPane.removeChild(hartBox);
  1578. }
  1579.