home *** CD-ROM | disk | FTP | other *** search
/ InterCD 2001 November / november_2001.iso / Browsers / Netscape 6.1 / browser.xpi / bin / chrome / toolkit.jar / content / global / nsFileView.js < prev    next >
Encoding:
Text File  |  2001-06-04  |  16.2 KB  |  492 lines

  1. /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  2.  *
  3.  * The contents of this file are subject to the Mozilla Public
  4.  * License Version 1.1 (the "License"); you may not use this file
  5.  * except in compliance with the License. You may obtain a copy of
  6.  * the License at http://www.mozilla.org/MPL/
  7.  *
  8.  * Software distributed under the License is distributed on an "AS
  9.  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  10.  * implied. See the License for the specific language governing
  11.  * rights and limitations under the License.
  12.  *
  13.  * The Original Code is mozilla.org code.
  14.  *
  15.  * The Initial Developer of the Original Code is Netscape
  16.  * Communications Corporation.  Portions created by Netscape are
  17.  * Copyright (C) 2000 Netscape Communications Corporation.  All
  18.  * Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *  Brian Ryner <bryner@netscape.com>
  22.  *
  23.  */
  24.  
  25. /* This file implements an nsIOutlinerView for the filepicker */
  26.  
  27. const nsILocalFile = Components.interfaces.nsILocalFile;
  28. const nsLocalFile_CONTRACTID = "@mozilla.org/file/local;1";
  29. const nsIFile = Components.interfaces.nsIFile;
  30. const nsIScriptableDateFormat = Components.interfaces.nsIScriptableDateFormat;
  31. const nsScriptableDateFormat_CONTRACTID = "@mozilla.org/intl/scriptabledateformat;1";
  32. const nsIAtomService = Components.interfaces.nsIAtomService;
  33. const nsAtomService_CONTRACTID = "@mozilla.org/atom-service;1";
  34.  
  35. var gDateService = null;
  36.  
  37. function numMatchingChars(str1, str2) {
  38.   var minLength = Math.min(str1.length, str2.length);
  39.   for (var i = 0; ((i < minLength) && (str1[i] == str2[i])); i++);
  40.   return i;
  41. }
  42.  
  43. function sortFilename(a, b) {
  44.   if (a.cachedName < b.cachedName) {
  45.     return -1;
  46.   } else {
  47.     return 1;
  48.   }
  49. }
  50.   
  51. function sortSize(a, b) {
  52.   if (a.cachedSize < b.cachedSize) {
  53.     return -1;
  54.   } else if (a.cachedSize > b.cachedSize) {
  55.     return 1;
  56.   } else {
  57.     return 0;
  58.   }
  59. }
  60.  
  61. function sortDate(a, b) {
  62.   if (a.cachedDate < b.cachedDate) {
  63.     return -1;
  64.   } else if (a.cachedDate > b.cachedDate) {
  65.     return 1;
  66.   } else {
  67.     return 0;
  68.   }
  69. }
  70.  
  71. function formatDate(date) {
  72.   var modDate = new Date(date);
  73.   return gDateService.FormatDateTime("", gDateService.dateFormatShort,
  74.                                      gDateService.timeFormatSeconds,
  75.                                      modDate.getFullYear(), modDate.getMonth()+1,
  76.                                      modDate.getDate(), modDate.getHours(),
  77.                                      modDate.getMinutes(), modDate.getSeconds());
  78. }
  79.  
  80. function nsFileView() {
  81.   this.mShowHiddenFiles = false;
  82.   this.mDirectoryFilter = false;
  83.   this.mFileList = [];
  84.   this.mDirList = [];
  85.   this.mFilteredFiles = [];
  86.   this.mCurrentFilter = ".*";
  87.   this.mSelectionCallback = null;
  88.   this.mOutliner = null;
  89.   this.mReverseSort = false;
  90.   this.mSortType = 0;
  91.   this.mTotalRows = 0;
  92.  
  93.   if (!gDateService) {
  94.     gDateService = Components.classes[nsScriptableDateFormat_CONTRACTID]
  95.       .getService(nsIScriptableDateFormat);
  96.   }
  97.  
  98.   var atomService = Components.classes[nsAtomService_CONTRACTID]
  99.                       .getService(nsIAtomService);
  100.   this.mDirectoryAtom = atomService.getAtom("directory");
  101.   this.mFileAtom = atomService.getAtom("file");
  102. }
  103.  
  104. /* class constants */
  105.  
  106. nsFileView.SORTTYPE_NAME = 1;
  107. nsFileView.SORTTYPE_SIZE = 2;
  108. nsFileView.SORTTYPE_DATE = 3;
  109.  
  110. nsFileView.prototype = {
  111.  
  112.   /* readonly attribute long rowCount; */
  113.   set rowCount(c) { throw "readonly property"; },
  114.   get rowCount() { return this.mTotalRows; },
  115.  
  116.   /* attribute nsIOutlinerSelection selection; */
  117.   set selection(s) { this.mSelection = s; },
  118.   get selection() { return this.mSelection; },
  119.  
  120.   set selectionCallback(f) { this.mSelectionCallback = f; },
  121.   get selectionCallback() { return this.mSelectionCallback; },
  122.  
  123.   /* nsISupports methods */
  124.  
  125.   /* void QueryInterface(in nsIIDRef uuid, 
  126.      [iid_is(uuid),retval] out nsQIResult result); */
  127.   QueryInterface: function(iid) {
  128.     if (!iid.equals(nsIOutlinerView) &&
  129.         !iid.equals(nsISupports)) {
  130.           throw Components.results.NS_ERROR_NO_INTERFACE;
  131.         }
  132.     return this;
  133.   },
  134.  
  135.   /* nsIOutlinerView methods */
  136.  
  137.   /* void getRowProperties(in long index, in nsISupportsArray properties); */
  138.   getRowProperties: function(index, properties) { },
  139.  
  140.   /* void getCellProperties(in long row, in wstring colID, in nsISupportsArray properties); */
  141.   getCellProperties: function(row, colID, properties) {
  142.     if (row < this.mDirList.length)
  143.       properties.AppendElement(this.mDirectoryAtom);
  144.     else if ((row - this.mDirList.length) < this.mFilteredFiles.length)
  145.       properties.AppendElement(this.mFileAtom);
  146.   },
  147.  
  148.   /* void getColumnProperties(in wstring colID, in nsIDOMElement colElt,
  149.      in nsISupportsArray properties); */
  150.   getColumnProperties: function(colID, colElt, properties) { },
  151.  
  152.   /* boolean isContainer(in long index); */
  153.   isContainer: function(index) { return false; },
  154.  
  155.   /* boolean isContainerOpen(in long index); */
  156.   isContainerOpen: function(index) { return false;},
  157.  
  158.   /* boolean isContainerEmpty(in long index); */
  159.   isContainerEmpty: function(index) { return false; },
  160.  
  161.   /* boolean isSorted (); */
  162.   isSorted: function() { return (this.mSortType > 0); },
  163.  
  164.   /* boolean canDropOn (in long index); */
  165.   canDropOn: function(index) { return false; },
  166.  
  167.   /* boolean canDropBeforeAfter (in long index, in boolean before); */
  168.   canDropBeforeAfter: function(index, before) { return false; },
  169.  
  170.   /* void drop (in long row, in long orientation); */
  171.   drop: function(row, orientation) { },
  172.  
  173.   /* long getParentIndex(in long rowIndex); */
  174.   getParentIndex: function(rowIndex) { return -1; },
  175.  
  176.   /* boolean hasNextSibling(in long rowIndex, in long afterIndex); */
  177.   hasNextSibling: function(rowIndex, afterIndex) {
  178.     return (afterIndex < (this.mTotalRows - 1));
  179.   },
  180.  
  181.   /* long getLevel(in long index); */
  182.   getLevel: function(index) { return 0; },
  183.   
  184.   /* wstring getCellText(in long row, in wstring colID); */
  185.   getCellText: function(row, colID) {
  186.     /* we cache the file size and last modified dates -
  187.        this function must be very fast since it's called
  188.        whenever the cell needs repainted */
  189.     var file, isdir = false;
  190.     if (row < this.mDirList.length) {
  191.       isdir = true;
  192.       file = this.mDirList[row];
  193.     } else if ((row - this.mDirList.length) < this.mFilteredFiles.length) {
  194.       file = this.mFilteredFiles[row - this.mDirList.length];
  195.     } else {
  196.       return "";
  197.     }
  198.  
  199.     if (colID == "FilenameColumn") {
  200.       if (!("cachedName" in file)) {
  201.         file.cachedName = file.file.unicodeLeafName;
  202.       }
  203.       return file.cachedName;
  204.     } else if (colID == "LastModifiedColumn") {
  205.       if (!("cachedDate" in file)) {
  206.         // perhaps overkill, but lets get the right locale handling
  207.         file.cachedDate = file.file.lastModificationDate;
  208.         file.cachedDateText = formatDate(file.cachedDate);
  209.       }
  210.       return file.cachedDateText;
  211.     } else if (colID == "FileSizeColumn") {
  212.       if (isdir) {
  213.         return "";
  214.       } else {
  215.         if (!("cachedSize" in file)) {
  216.           file.cachedSize = String(file.file.fileSize);
  217.         }
  218.       }
  219.       return file.cachedSize;
  220.     }
  221.     return "";
  222.   },
  223.  
  224.   /* void setOutliner(in nsIOutlinerBoxObject outliner); */
  225.   setOutliner: function(outliner) { this.mOutliner = outliner; },
  226.  
  227.   /* void toggleOpenState(in long index); */
  228.   toggleOpenState: function(index) { },
  229.  
  230.   /* void cycleHeader(in wstring colID, in nsIDOMElement elt); */
  231.   cycleHeader: function(colID, elt) { },
  232.  
  233.   /* void selectionChanged(); */
  234.   selectionChanged: function() {
  235.     if (this.mSelectionCallback && this.mSelection.currentIndex != -1) {
  236.       var file;
  237.       if (this.mSelection.currentIndex < this.mDirList.length) {
  238.         file = this.mDirList[this.mSelection.currentIndex].file;
  239.       } else if ((this.mSelection.currentIndex - this.mDirList.length) < this.mFilteredFiles.length) {
  240.         file = this.mFilteredFiles[this.mSelection.currentIndex - this.mDirList.length].file;
  241.       }
  242.  
  243.       if (file) {
  244.         this.mSelectionCallback(file);
  245.       }
  246.     }
  247.   },
  248.  
  249.   /* void cycleCell(in long row, in wstring colID); */
  250.   cycleCell: function(row, colID) { },
  251.  
  252.   /* boolean isEditable(in long row, in wstring colID); */
  253.   isEditable: function(row, colID) { return false; },
  254.  
  255.   /* void setCellText(in long row, in wstring colID, in wstring value); */
  256.   setCellText: function(row, colID, value) { },
  257.  
  258.   /* void performAction(in wstring action); */
  259.   performAction: function(action) { },
  260.  
  261.   /* void performActionOnRow(in wstring action, in long row); */
  262.   performActionOnRow: function(action, row) { },
  263.  
  264.   /* void performActionOnCell(in wstring action, in long row, in wstring colID); */
  265.   performActionOnCell: function(action, row, colID) { },
  266.  
  267.   /* private attributes */
  268.  
  269.   /* attribute boolean showHiddenFiles */
  270.   set showHiddenFiles(s) {
  271.     this.mShowHiddenFiles = s;
  272.     this.setDirectory(this.mDirectoryPath);
  273.   },
  274.  
  275.   get showHiddenFiles() { return this.mShowHiddenFiles; },
  276.  
  277.   /* attribute boolean showOnlyDirectories */
  278.   set showOnlyDirectories(s) {
  279.     this.mDirectoryFilter = s;
  280.     this.filterFiles();
  281.   },
  282.  
  283.   get showOnlyDirectories() { return this.mDirectoryFilter; },
  284.  
  285.   /* readonly attribute short sortType */
  286.   set sortType(s) { throw "readonly property"; },
  287.   get sortType() { return this.mSortType; },
  288.  
  289.   /* readonly attribute boolean reverseSort */
  290.   set reverseSort(s) { throw "readonly property"; },
  291.   get reverseSort() { return this.mReverseSort; },
  292.  
  293.   /* private methods */
  294.   sort: function(sortType, reverseSort, forceSort) {
  295.     if (sortType == this.mSortType && reverseSort != this.mReverseSort && !forceSort) {
  296.       this.mDirList.reverse();
  297.       this.mFilteredFiles.reverse();
  298.     } else {
  299.       var compareFunc, i;
  300.       
  301.       /* We pre-fetch all the data we are going to sort on, to avoid
  302.          calling into C++ on every comparison */
  303.  
  304.       switch (sortType) {
  305.       case 0:
  306.         /* no sort has been set yet */
  307.         return;
  308.       case nsFileView.SORTTYPE_NAME:
  309.         for (i = 0; i < this.mDirList.length; i++) {
  310.           this.mDirList[i].cachedName = this.mDirList[i].file.unicodeLeafName;
  311.         }
  312.         for (i = 0; i < this.mFilteredFiles.length; i++) {
  313.           this.mFilteredFiles[i].cachedName = this.mFilteredFiles[i].file.unicodeLeafName;
  314.         }
  315.         compareFunc = sortFilename;
  316.         break;
  317.       case nsFileView.SORTTYPE_SIZE:
  318.         for (i = 0; i < this.mDirList.length; i++) {
  319.           this.mDirList[i].cachedSize = this.mDirList[i].file.fileSize;
  320.         }
  321.         for (i = 0; i < this.mFilteredFiles.length; i++) {
  322.           this.mFilteredFiles[i].cachedSize = this.mFilteredFiles[i].file.fileSize;
  323.         }
  324.         compareFunc = sortSize;
  325.         break;
  326.       case nsFileView.SORTTYPE_DATE:
  327.         for (i = 0; i < this.mDirList.length; i++) {
  328.           this.mDirList[i].cachedDate = this.mDirList[i].file.lastModificationDate;
  329.           this.mDirList[i].cachedDateText = formatDate(this.mDirList[i].cachedDate);
  330.         }
  331.         for (i = 0; i < this.mFilteredFiles.length; i++) {
  332.           this.mFilteredFiles[i].cachedDate = this.mFilteredFiles[i].file.lastModificationDate;
  333.           this.mFilteredFiles[i].cachedDateText = formatDate(this.mFilteredFiles[i].cachedDate);
  334.         }
  335.         compareFunc = sortDate;
  336.         break;
  337.       default:
  338.         throw("Unsupported sort type " + sortType);
  339.         break;
  340.       }
  341.       this.mDirList.sort(compareFunc);
  342.       this.mFilteredFiles.sort(compareFunc);
  343.     }
  344.  
  345.     this.mSortType = sortType;
  346.     this.mReverseSort = reverseSort;
  347.     if (this.mOutliner) {
  348.       this.mOutliner.invalidate();
  349.     }
  350.   },
  351.  
  352.   setDirectory: function(directory) {
  353.     this.mDirectoryPath = directory;
  354.     this.mFileList = [];
  355.     this.mDirList = [];
  356.  
  357.     var dir = Components.classes[nsLocalFile_CONTRACTID].createInstance(nsILocalFile);
  358.     dir.followLinks = false;
  359.     dir.initWithUnicodePath(directory);
  360.     var dirEntries = dir.QueryInterface(nsIFile).directoryEntries;
  361.     var nextFile;
  362.     var fileobj;
  363.     //var time = new Date();
  364.  
  365.     while (dirEntries.hasMoreElements()) {
  366.       nextFile = dirEntries.getNext().QueryInterface(nsIFile);
  367.       // XXXjag hack for bug 82355 till symlink handling is fixed (bug 57995)
  368.       var isDir = false;
  369.       try {
  370.         isDir = nextFile.isDirectory();
  371.       } catch (e) {
  372.         // this here to fool the rest of the code into thinking
  373.         // this is a nsIFile object
  374.         nextFile = { unicodeLeafName : nextFile.unicodeLeafName,
  375.                      fileSize : 0,
  376.                      lastModificationDate : 0,
  377.                      isHidden: function() { return false; } };
  378.       }
  379.       // end of hack
  380.       if (isDir) {
  381.         if (!nextFile.isHidden() || this.mShowHiddenFiles) {
  382.           fileobj = new Object();
  383.           fileobj.file = nextFile;
  384.           this.mDirList[this.mDirList.length] = fileobj;
  385.         }
  386.       } else if (!this.mDirectoryFilter) {
  387.         this.mFileList[this.mFileList.length] = nextFile;
  388.       }
  389.     }
  390.  
  391.     //time = new Date() - time;
  392.     //dump("load time: " + time/1000 + " seconds\n");
  393.  
  394.     this.mFilteredFiles = [];
  395.  
  396.     if (this.mOutliner) {
  397.       var oldRows = this.mTotalRows;
  398.       this.mTotalRows = this.mDirList.length;
  399.       if (this.mDirList.length != oldRows) {
  400.         this.mOutliner.rowCountChanged(0, this.mDirList.length - oldRows);
  401.       }
  402.       this.mOutliner.invalidate();
  403.     }
  404.  
  405.     //time = new Date();
  406.  
  407.     this.filterFiles();
  408.  
  409.     //time = new Date() - time;
  410.     //dump("filter time: " + time/1000 + " seconds\n");
  411.     //time = new Date();
  412.  
  413.     this.sort(this.mSortType, this.mReverseSort);
  414.  
  415.     //time = new Date() - time;
  416.     //dump("sort time: " + time/1000 + " seconds\n");
  417.   },
  418.  
  419.   setFilter: function(filter) {
  420.     // The filter may contain several components, i.e.:
  421.     // *.html; *.htm
  422.     // First separate it into its components
  423.     var filterList = filter.split(/;[ ]*/);
  424.  
  425.     if (filterList.length == 0) {
  426.       // this shouldn't happen
  427.       return;
  428.     }
  429.  
  430.     // Transform everything in the array to a regexp
  431.     var tmp = filterList[0].replace(/\./g, "\\.");
  432.     filterList[0] = tmp.replace(/\*/g, ".*");
  433.     var shortestPrefix = filterList[0];
  434.     
  435.     for (var i = 1; i < filterList.length; i++) {
  436.       // * becomes .*, and we escape all .'s with \
  437.       tmp = filterList[i].replace(/\./g, "\\.");
  438.       filterList[i] = tmp.replace(/\*/g, ".*");
  439.       shortestPrefix = shortestPrefix.substr(0, numMatchingChars(shortestPrefix, filterList[i]));
  440.     }
  441.     
  442.     var filterStr = shortestPrefix+"(";
  443.     var startpos = shortestPrefix.length;
  444.     for (i = 0; i < filterList.length; i++) {
  445.       filterStr += filterList[i].substr(shortestPrefix.length) + "|";
  446.     }
  447.  
  448.     this.mCurrentFilter = new RegExp(filterStr.substr(0, (filterStr.length) - 1) + ")");
  449.     this.mFilteredFiles = [];
  450.  
  451.     if (this.mOutliner) {
  452.       var rowDiff = -(this.mTotalRows - this.mDirList.length);
  453.       this.mTotalRows = this.mDirList.length;
  454.       this.mOutliner.rowCountChanged(this.mDirList.length, rowDiff);
  455.       this.mOutliner.invalidate();
  456.     }
  457.     this.filterFiles();
  458.     this.sort(this.mSortType, this.mReverseSort, true);
  459.   },
  460.  
  461.   filterFiles: function() {
  462.     for(var i = 0; i < this.mFileList.length; i++) {
  463.       var file = this.mFileList[i];
  464.  
  465.       if ((this.mShowHiddenFiles || !file.isHidden()) &&
  466.           file.unicodeLeafName.search(this.mCurrentFilter) == 0) {
  467.         this.mFilteredFiles[this.mFilteredFiles.length] = { file : file };
  468.       }
  469.     }
  470.  
  471.     this.mTotalRows = this.mDirList.length + this.mFilteredFiles.length;
  472.  
  473.     // Tell the outliner how many rows we just added
  474.     if (this.mOutliner) {
  475.       this.mOutliner.rowCountChanged(this.mDirList.length, this.mFilteredFiles.length);
  476.     }
  477.   },
  478.  
  479.   getSelectedFile: function() {
  480.     if (0 <= this.mSelection.currentIndex) {
  481.       if (this.mSelection.currentIndex < this.mDirList.length) {
  482.         return this.mDirList[this.mSelection.currentIndex].file;
  483.       } else if ((this.mSelection.currentIndex - this.mDirList.length) < this.mFilteredFiles.length) {
  484.         return this.mFilteredFiles[this.mSelection.currentIndex - this.mDirList.length].file;
  485.       }
  486.     }
  487.  
  488.     return null;
  489.   }
  490. }
  491.  
  492.