# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is the Download Actions Manager.
#
# The Initial Developer of the Original Code is
# Ben Goodger.
# Portions created by the Initial Developer are Copyright (C) 2000-2005
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#   Ben Goodger <ben@mozilla.org>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****

const kRootTypePrefix = "urn:mimetype:";

///////////////////////////////////////////////////////////////////////////////
// MIME Types Datasource RDF Utils
function NC_URI(aProperty)
{
  return "http://home.netscape.com/NC-rdf#" + aProperty;
}

function MIME_URI(aType)
{
  return "urn:mimetype:" + aType;
}

function HANDLER_URI(aHandler)
{
  return "urn:mimetype:handler:" + aHandler;
}

function APP_URI(aType)
{
  return "urn:mimetype:externalApplication:" + aType;
}

var gDownloadActionsWindow = {  
  _tree         : null,
  _editButton   : null,
  _removeButton : null,
  _actions      : [],
  _plugins      : {},
  _bundle       : null,
  _pref         : Components.classes["@mozilla.org/preferences-service;1"]
                            .getService(Components.interfaces.nsIPrefBranch),
  _mimeSvc      : Components.classes["@mozilla.org/uriloader/external-helper-app-service;1"]
                            .getService(Components.interfaces.nsIMIMEService),
  _excludingPlugins           : false,
  _excludingMissingExtensions : false,
  
  init: function ()
  {
    (this._editButton = document.getElementById("editFileHandler")).disabled = true;
    (this._removeButton = document.getElementById("removeFileHandler")).disabled = true;    
   
    // Initialize the File Type list
    this._bundle = document.getElementById("bundlePreferences");
    this._tree = document.getElementById("fileHandlersList");
    this._loadView();
    // Determine any exclusions being applied - e.g. don't show types for which
    // only a plugin handler exists, don't show types lacking extensions, etc. 
    this._view._rowCount = this._updateExclusions();    
    this._tree.treeBoxObject.view = this._view;  

    var indexToSelect = parseInt(this._tree.getAttribute("lastSelected"));
    if (indexToSelect < this._tree.view.rowCount)
      this._tree.view.selection.select(indexToSelect);
    this._tree.focus();    
  },
  
  _loadView: function ()
  {
    // Reset ALL the collections and state flags, because we can call this after
    // the window has initially displayed by resetting the filter. 
    this._actions = [];
    this._plugins = {};
    this._view._filtered = false;
    this._view._filterSet = [];
    this._view._usingExclusionSet = false;
    this._view._exclusionSet = [];
    this._view._filterValue = "";

    this._loadPluginData();
    this._loadMIMERegistryData();
  },
  
  _updateRowCount: function (aNewRowCount)
  {
    var oldCount = this._view._rowCount;
    this._view._rowCount = 0;
    this._tree.treeBoxObject.rowCountChanged(0, -oldCount);
    this._view._rowCount = aNewRowCount;
    this._tree.treeBoxObject.rowCountChanged(0, aNewRowCount);
  },
  
  uninit: function ()
  {
  },
  
  _updateExclusions: function ()
  {
    this._excludingPlugins = true;
    this._excludingMissingExtensions = true;    
    this._view._exclusionSet = [].concat(this._actions);
    if (this._excludingMissingExtensions) {
      this._view._usingExclusionSet = true;
      for (var i = 0; i < this._view._exclusionSet.length;) {
        if (!this._view._exclusionSet[i].hasExtension)
          this._view._exclusionSet.splice(i, 1);
        else
          ++i;
      }
    }
    if (this._excludingPlugins) {
      this._view._usingExclusionSet = true;
      for (i = 0; i < this._view._exclusionSet.length;) {
        if (this._view._exclusionSet[i].handledOnlyByPlugin)
          this._view._exclusionSet.splice(i, 1);
        else
          ++i        
      }      
    }

    return this._view._usingExclusionSet ? this._view._exclusionSet.length 
                                         : this._view._filtered ? this._view._filterSet.length 
                                                                : this._actions.length;
  },   
  
  _loadPluginData: function ()
  {
  },

  _createAction: function (aMIMEType, aActionName, 
                           aIsEditable, aHandleMode, aCustomHandler,
                           aPluginAvailable, aPluginEnabled, 
                           aHandledOnlyByPlugin)
  {
    var newAction = !(aMIMEType in this._plugins);
    var action = newAction ? new FileAction() : this._plugins[aMIMEType];
    action.type = aMIMEType;
    var info = this._mimeSvc.getFromTypeAndExtension(action.type, null);
    
    // File Extension
    try {
      action.extension = info.primaryExtension;
    }
    catch (e) {
      action.extension = this._bundle.getString("extensionNone");
      action.hasExtension = false;
    }
    
    // Large and Small Icon
    try {
      action.smallIcon = "moz-icon://goat." + info.primaryExtension + "?size=16";
      action.bigIcon = "moz-icon://goat." + info.primaryExtension + "?size=32";
    }
    catch (e) {
      action.smallIcon = "moz-icon://goat?size=16&contentType=" + info.MIMEType;
      action.bigIcon = "moz-icon://goat?contentType=" + info.MIMEType + "&size=32";
    }

    // Pretty Type Name
    if (info.description == "") {
      try {
        action.typeName = this._bundle.getFormattedString("fileEnding", [info.primaryExtension.toUpperCase()]);
      }
      catch (e) { 
        // Wow, this sucks, just show the MIME type as a last ditch effort to display
        // the type of file that this is. 
        action.typeName = info.MIMEType;
      }
    }
    else
      action.typeName = info.description;

    // Pretty Action Name
    if (aActionName)
      action.action         = aActionName;
    action.pluginAvailable  = aPluginAvailable;
    action.pluginEnabled    = aPluginEnabled;
    action.editable         = aIsEditable;
    action.handleMode       = aHandleMode;
    action.customHandler    = aCustomHandler;
    action.mimeInfo         = info;
    action.handledOnlyByPlugin  = aHandledOnlyByPlugin
    
    if (newAction && !(action.handledOnlyByPlugin && !action.pluginEnabled)) {
      this._actions.push(action);
      this._plugins[action.type] = action;
    }      
    return action;
  },
  
  _loadMIMEDS: function ()
  {
    var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"]
                                .getService(Components.interfaces.nsIProperties);
    
    var file = fileLocator.get("UMimTyp", Components.interfaces.nsIFile);

    var ioService = Components.classes["@mozilla.org/network/io-service;1"]
                              .getService(Components.interfaces.nsIIOService);
    var fileHandler = ioService.getProtocolHandler("file")
                               .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
    this._mimeDS = this._rdf.GetDataSourceBlocking(fileHandler.getURLSpecFromFile(file));
  },
  
  _getLiteralValue: function (aResource, aProperty)
  {
    var property = this._rdf.GetResource(NC_URI(aProperty));
    var value = this._mimeDS.GetTarget(aResource, property, true);
    if (value)
      return value.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
    return "";
  },
  
  _getChildResource: function (aResource, aProperty)
  {
    var property = this._rdf.GetResource(NC_URI(aProperty));
    return this._mimeDS.GetTarget(aResource, property, true);
  },
  
  _getDisplayNameForFile: function (aFile)
  {
#ifdef XP_WIN
    if (aFile instanceof Components.interfaces.nsILocalFileWin) {
      try {
        return aFile.getVersionInfoField("FileDescription"); 
      }
      catch (e) {
        // fall through to the filename
      }
    }
#endif
    // XXXben - Read the bundle name on OS X.
    var ios = Components.classes["@mozilla.org/network/io-service;1"]
                        .getService(Components.interfaces.nsIIOService);
    var url = ios.newFileURI(aFile).QueryInterface(Components.interfaces.nsIURL);
    return url.fileName;
  },  
  
  _loadMIMERegistryData: function ()
  {
    this._rdf = Components.classes["@mozilla.org/rdf/rdf-service;1"]
                          .getService(Components.interfaces.nsIRDFService);
    this._loadMIMEDS();                          
                          
    var root = this._rdf.GetResource("urn:mimetypes:root");
    var container = Components.classes["@mozilla.org/rdf/container;1"]
                              .createInstance(Components.interfaces.nsIRDFContainer);
    container.Init(this._mimeDS, root);
    
    var elements = container.GetElements();
    while (elements.hasMoreElements()) {
      var type = elements.getNext();
      if (!(type instanceof Components.interfaces.nsIRDFResource))
        break;
      var editable = this._getLiteralValue(type, "editable") == "true";
      if (!editable)
        continue;
      
      var handler = this._getChildResource(type, "handlerProp");
      var alwaysAsk = this._getLiteralValue(handler, "alwaysAsk") == "true";
      if (alwaysAsk)
        continue;
      var saveToDisk        = this._getLiteralValue(handler, "saveToDisk") == "true";
      var useSystemDefault  = this._getLiteralValue(handler, "useSystemDefault") == "true";
      var editable          = this._getLiteralValue(type, "editable") == "true";
      var handledInternally = this._getLiteralValue(handler, "handleInternal") == "true";
      var externalApp       = this._getChildResource(handler, "externalApplication");
      var externalAppPath   = this._getLiteralValue(externalApp, "path");
      try {
        var customHandler = Components.classes["@mozilla.org/file/local;1"]
                                      .createInstance(Components.interfaces.nsILocalFile);
        customHandler.initWithPath(externalAppPath);
      }
      catch (e) {
        customHandler = null;
      }      
      if (customHandler && !customHandler.exists())
        customHandler = null;
      var mimeType = this._getLiteralValue(type, "value");
      var typeInfo = this._mimeSvc.getFromTypeAndExtension(mimeType, null);

      // Determine the pretty name of the associated action.
      var actionName = "";
      var handleMode = 0;
      if (saveToDisk) {
        // Save the file to disk
        actionName = this._bundle.getString("saveToDisk");
        handleMode = FILEACTION_SAVE_TO_DISK;
      }
      else if (useSystemDefault) {
        // Use the System Default handler
        actionName = this._bundle.getFormattedString("openWith", 
                                                     [typeInfo.defaultDescription]);
        handleMode = FILEACTION_OPEN_DEFAULT;
      }
      else {
        // Custom Handler
        if (customHandler) {
          actionName = this._bundle.getFormattedString("openWith", 
                                                       [this._getDisplayNameForFile(customHandler)]);
          handleMode = FILEACTION_OPEN_CUSTOM;
        }
        else {
          // Corrupt datasource, invalid custom handler path. Revert to default.
          actionName = this._bundle.getFormattedString("openWith", 
                                                       [typeInfo.defaultDescription]);
          handleMode = FILEACTION_OPEN_DEFAULT;
        }
      }

      if (handledInternally)
        handleMode = FILEACTION_OPEN_INTERNALLY;
      
      var pluginAvailable = mimeType in this._plugins && this._plugins[mimeType].pluginAvailable;
      var pluginEnabled = pluginAvailable && this._plugins[mimeType].pluginEnabled;
      if (pluginEnabled) {
        handleMode = FILEACTION_OPEN_PLUGIN;
        actionName = null;
      }
      var action = this._createAction(mimeType, actionName, editable, handleMode, 
                                      customHandler, pluginAvailable, pluginEnabled,
                                      false);
    }
  },
  
  _view: {
    _filtered           : false,
    _filterSet          : [],
    _usingExclusionSet  : false,
    _exclusionSet       : [],
    _filterValue        : "",

    _rowCount: 0,
    get rowCount() 
    { 
      return this._rowCount; 
    },
    
    get activeCollection ()
    {
      return this._filtered ? this._filterSet 
                            : this._usingExclusionSet ? this._exclusionSet 
                                                      : gDownloadActionsWindow._actions;
    },

    getItemAtIndex: function (aIndex)
    {
      return this.activeCollection[aIndex];
    },
    
    getCellText: function (aIndex, aColumn)
    {
      switch (aColumn.id) {
      case "fileExtension":
        return this.getItemAtIndex(aIndex).extension.toUpperCase();
      case "fileType":
        return this.getItemAtIndex(aIndex).typeName;
      case "fileMIMEType":
        return this.getItemAtIndex(aIndex).type;
      case "fileHandler":
        return this.getItemAtIndex(aIndex).action;
      }
      return "";
    },
    getImageSrc: function (aIndex, aColumn) 
    {
      if (aColumn.id == "fileExtension") 
        return this.getItemAtIndex(aIndex).smallIcon;
      return "";
    },
    _selection: null, 
    get selection () { return this._selection; },
    set selection (val) { this._selection = val; return val; },
    getRowProperties: function (aIndex, aProperties) {},
    getCellProperties: function (aIndex, aColumn, aProperties) {},
    getColumnProperties: function (aColumn, aProperties) {},
    isContainer: function (aIndex) { return false; },
    isContainerOpen: function (aIndex) { return false; },
    isContainerEmpty: function (aIndex) { return false; },
    isSeparator: function (aIndex) { return false; },
    isSorted: function (aIndex) { return false; },
    canDrop: function (aIndex, aOrientation) { return false; },
    drop: function (aIndex, aOrientation) {},
    getParentIndex: function (aIndex) { return -1; },
    hasNextSibling: function (aParentIndex, aIndex) { return false; },
    getLevel: function (aIndex) { return 0; },
    getProgressMode: function (aIndex, aColumn) {},    
    getCellValue: function (aIndex, aColumn) {},
    setTree: function (aTree) {},    
    toggleOpenState: function (aIndex) { },
    cycleHeader: function (aColumn) {},    
    selectionChanged: function () {},    
    cycleCell: function (aIndex, aColumn) {},    
    isEditable: function (aIndex, aColumn) { return false; },
    isSelectable: function (aIndex, aColumn) { return false; },
    setCellValue: function (aIndex, aColumn, aValue) {},    
    setCellText: function (aIndex, aColumn, aValue) {},    
    performAction: function (aAction) {},  
    performActionOnRow: function (aAction, aIndex) {},    
    performActionOnCell: function (aAction, aindex, aColumn) {}
  },

  removeFileHandler: function ()
  {
    var selection = this._tree.view.selection; 
    if (selection.count < 1)
      return;
      
    var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
                                  .getService(Components.interfaces.nsIPromptService);
    var flags = promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_0;
    flags += promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_1;

    var title = this._bundle.getString("removeTitle" + (selection.count > 1 ? "Multiple" : "Single"));
    var message = this._bundle.getString("removeMessage" + (selection.count > 1 ? "Multiple" : "Single"));
    var button = this._bundle.getString("removeButton" + (selection.count > 1 ? "Multiple" : "Single"));
    rv = promptService.confirmEx(window, title, message, flags, button, 
                                 null, null, null, { value: 0 });
    if (rv != 0)
      return;     

    var rangeCount = selection.getRangeCount();
    var lastSelected = 0;
    var mimeDSDirty = false;
    for (var i = 0; i < rangeCount; ++i) {
      var min = { }; var max = { };
      selection.getRangeAt(i, min, max);
      for (var j = min.value; j <= max.value; ++j) {
        var item = this._view.getItemAtIndex(j);
        if (!item.handledOnlyByPlugin) {
          // There is data for this type in the MIME registry, so make sure we
          // remove it from the MIME registry. We don't disable the plugin here because
          // if we do there's currently no way through the UI to re-enable it. We may
          // come up with some sort of solution for that at a later date. 
          var typeRes = this._rdf.GetResource(MIME_URI(item.type));
          var handlerRes = this._getChildResource(typeRes, "handlerProp");
          var extAppRes = this._getChildResource(handlerRes, "externalApplication");
          this._cleanResource(extAppRes);
          this._cleanResource(handlerRes);
          this._cleanResource(typeRes); 
          mimeDSDirty = true;         
        }
        lastSelected = (j + 1) >= this._view.rowCount ? j-1 : j;
      }
    }
    if (mimeDSDirty && 
        this._mimeDS instanceof Components.interfaces.nsIRDFRemoteDataSource)
      this._mimeDS.Flush();
    
    // Just reload the list to make sure deletions are respected
    this._loadView();
    this._updateRowCount(this._updateExclusions());

    selection.select(lastSelected);
  },
  
  _cleanResource: function (aResource)
  {
    var labels = this._mimeDS.ArcLabelsOut(aResource);
    while (labels.hasMoreElements()) {
      var arc = labels.getNext();
      if (!(arc instanceof Components.interfaces.nsIRDFResource))
        break;
      var target = this._mimeDS.GetTarget(aResource, arc, true);
      this._mimeDS.Unassert(aResource, arc, target);
    }
  },
  
  _disablePluginForItem: function (aItem)
  {
  },
  
  _enablePluginForItem: function (aItem)
  {
  },
  
  _ensureMIMERegistryEntry: function (aItem)
  {
    var root = this._rdf.GetResource("urn:mimetypes:root");
    var container = Components.classes["@mozilla.org/rdf/container;1"]
                              .createInstance(Components.interfaces.nsIRDFContainer);
    container.Init(this._mimeDS, root);
    
    var itemResource = this._rdf.GetResource(MIME_URI(aItem.type));
    var handlerResource = null;
    if (container.IndexOf(itemResource) == -1) {
      container.AppendElement(itemResource);
      this._setLiteralValue(itemResource, "editable", "true");
      this._setLiteralValue(itemResource, "value", aItem.type);
      
      handlerResource = this._rdf.GetResource(HANDLER_URI(aItem.type));
      this._setLiteralValue(handlerResource, "alwaysAsk", "false");
      var handlerProp = this._rdf.GetResource(NC_URI("handlerProp"));
      this._mimeDS.Assert(itemResource, handlerProp, handlerResource, true);
      
      var extAppResource = this._rdf.GetResource(APP_URI(aItem.type));
      this._setLiteralValue(extAppResource, "path", "");
      var extAppProp = this._rdf.GetResource(NC_URI("externalApplication"));
      this._mimeDS.Assert(handlerResource, extAppProp, extAppResource, true);
    }
    else
      handlerResource = this._getChildResource(itemResource, "handlerProp");
        
    return handlerResource;
  },
  
  _setLiteralValue: function (aResource, aProperty, aValue)
  {
    var property = this._rdf.GetResource(NC_URI(aProperty));
    var newValue = this._rdf.GetLiteral(aValue);
    var oldValue = this._mimeDS.GetTarget(aResource, property, true);
    if (oldValue)
      this._mimeDS.Change(aResource, property, oldValue, newValue);
    else
      this._mimeDS.Assert(aResource, property, newValue, true);
  },
  
  editFileHandler: function ()
  {
    var selection = this._tree.view.selection; 
    if (selection.count != 1)
      return;

    var item = this._view.getItemAtIndex(selection.currentIndex);
    openDialog("chrome://messenger/content/preferences/changeaction.xul", 
               "_blank", "modal,centerscreen", item);
    
    // Update the database
    switch (item.handleMode) {
    case FILEACTION_OPEN_PLUGIN:
      this._enablePluginForItem(item);
      // We don't need to adjust the database because plugin settings always
      // supercede whatever is in the db, leaving it untouched allows the last
      // user setting(s) to be preserved if they ever revert.
      break;
    case FILEACTION_OPEN_DEFAULT:
      this._disablePluginForItem(item);
      var handlerRes = this._ensureMIMERegistryEntry(item);
      this._setLiteralValue(handlerRes, "useSystemDefault", "true");
      this._setLiteralValue(handlerRes, "saveToDisk", "false");
      break;
    case FILEACTION_OPEN_CUSTOM:
      this._disablePluginForItem(item);
      var handlerRes = this._ensureMIMERegistryEntry(item);
      this._setLiteralValue(handlerRes, "useSystemDefault", "false");
      this._setLiteralValue(handlerRes, "saveToDisk", "false");
      var extAppRes = this._getChildResource(handlerRes, "externalApplication");
      this._setLiteralValue(extAppRes, "path", item.customHandler.path);
      break;
    case FILEACTION_SAVE_TO_DISK:
      this._disablePluginForItem(item);
      var handlerRes = this._ensureMIMERegistryEntry(item);
      this._setLiteralValue(handlerRes, "useSystemDefault", "false");
      this._setLiteralValue(handlerRes, "saveToDisk", "true");
      break;
    }
    
    if (this._mimeDS instanceof Components.interfaces.nsIRDFRemoteDataSource)
      this._mimeDS.Flush();
    
    // Update the view
    this._tree.treeBoxObject.invalidateRow(selection.currentIndex);    
  },
  
  onSelectionChanged: function ()
  {
    if (this._tree.view.rowCount == 0)
      return;
      
    var selection = this._tree.view.selection; 
    var selected = selection.count;
    this._removeButton.disabled = selected == 0;
    this._editButton.disabled = selected != 1;
    var stringKey = selected > 1 ? "removeButtonMultiple" : "removeButtonSingle";
    this._removeButton.label = this._bundle.getString(stringKey);
    
    var canRemove = true;
    var canEdit = true;
    
    var rangeCount = selection.getRangeCount();
    var min = { }, max = { };
    var setLastSelected = false;
    for (var i = 0; i < rangeCount; ++i) {
      selection.getRangeAt(i, min, max);
      
      for (var j = min.value; j <= max.value; ++j) {
        if (!setLastSelected) {
          // Set the last selected index to the first item in the selection
          this._tree.setAttribute("lastSelected", j);
          setLastSelected = true;
        }

        var item = this._view.getItemAtIndex(j);
        if (item && 
            (!item.editable || item.handleMode == FILEACTION_OPEN_INTERNALLY))
          canEdit = false;
        
        if (item && 
            (!item.editable || item.handleMode == FILEACTION_OPEN_INTERNALLY ||
             item.handledOnlyByPlugin))
          canRemove = false;
      }
    }
    
    if (!canRemove)
      this._removeButton.disabled = true;
    if (!canEdit)
      this._editButton.disabled = true;
  },
  
  _lastSortProperty : "",
  _lastSortAscending: false,
  sort: function (aProperty) 
  {
    var ascending = (aProperty == this._lastSortProperty) ? !this._lastSortAscending : true;
    function sortByProperty(a, b) 
    {
      return a[aProperty].toLowerCase().localeCompare(b[aProperty].toLowerCase());
    }
    function sortByExtension(a, b)
    {
      if (!a.hasExtension && b.hasExtension)
        return 1;
      if (!b.hasExtension && a.hasExtension)
        return -1;
      return a.extension.toLowerCase().localeCompare(b.extension.toLowerCase());
    }
    // Sort the Filtered List, if in Filtered mode
    if (!this._view._filtered) { 
      this._view.activeCollection.sort(aProperty == "extension" ? sortByExtension : sortByProperty);
      if (!ascending)
        this._view.activeCollection.reverse();
    }

    this._view.selection.clearSelection();
    this._view.selection.select(0);
    this._tree.treeBoxObject.invalidate();
    this._tree.treeBoxObject.ensureRowIsVisible(0);

    this._lastSortAscending = ascending;
    this._lastSortProperty = aProperty;
  },
  
  clearFilter: function ()
  {    
    // Clear the Filter and the Tree Display
    document.getElementById("filter").value = "";
    this._view._filtered = false;
    this._view._filterSet = [];

    // Just reload the list to make sure deletions are respected
    this._loadView();
    this._updateRowCount(this._updateExclusions());

    // Restore selection
    this._view.selection.clearSelection();
    for (i = 0; i < this._lastSelectedRanges.length; ++i) {
      var range = this._lastSelectedRanges[i];
      this._view.selection.rangedSelect(range.min, range.max, true);
    }
    this._lastSelectedRanges = [];

    document.getElementById("actionsIntro").value = this._bundle.getString("actionsAll");
    document.getElementById("clearFilter").disabled = true;
  },
  
  _actionMatchesFilter: function (aAction)
  {
    return aAction.extension.toLowerCase().indexOf(this._view._filterValue) != -1 ||
           aAction.typeName.toLowerCase().indexOf(this._view._filterValue) != -1 || 
           aAction.type.toLowerCase().indexOf(this._view._filterValue) != -1 ||
           aAction.action.toLowerCase().indexOf(this._view._filterValue) != -1;
  },
  
  _filterActions: function (aFilterValue)
  {
    this._view._filterValue = aFilterValue;
    var actions = [];
    var collection = this._view._usingExclusionSet ? this._view._exclusionSet : this._actions;
    for (var i = 0; i < collection.length; ++i) {
      var action = collection[i];
      if (this._actionMatchesFilter(action)) 
        actions.push(action);
    }
    return actions;
  },
  
  _lastSelectedRanges: [],
  _saveState: function ()
  {
    // Save selection
    var seln = this._view.selection;
    this._lastSelectedRanges = [];
    var rangeCount = seln.getRangeCount();
    for (var i = 0; i < rangeCount; ++i) {
      var min = {}; var max = {};
      seln.getRangeAt(i, min, max);
      this._lastSelectedRanges.push({ min: min.value, max: max.value });
    }
  },
  
  _filterTimeout: -1,
  onFilterInput: function ()
  {
    if (this._filterTimeout != -1)
      clearTimeout(this._filterTimeout);
   
    function filterActions()
    {
      var filter = document.getElementById("filter").value;
      if (filter == "") {
        gDownloadActionsWindow.clearFilter();
        return;
      }        
      var view = gDownloadActionsWindow._view;
      view._filterSet = gDownloadActionsWindow._filterActions(filter);
      if (!view._filtered) {
        // Save Display Info for the Non-Filtered mode when we first
        // enter Filtered mode. 
        gDownloadActionsWindow._saveState();
        view._filtered = true;
      }

      // Clear the display
      gDownloadActionsWindow._updateRowCount(view._filterSet.length);
      
      view.selection.select(0);
      document.getElementById("actionsIntro").value = gDownloadActionsWindow._bundle.getString("actionsFiltered");
      document.getElementById("clearFilter").disabled = false;
    }
    window.filterActions = filterActions;
    this._filterTimeout = setTimeout("filterActions();", 500);
  },
  
  onFilterKeyPress: function (aEvent)
  {
    if (aEvent.keyCode == 27) // ESC key
      this.clearFilter();
  },
  
  focusFilterBox: function ()
  { 
    var filter = document.getElementById("filter");
    filter.focus();
    filter.select();
  }  
};

