home *** CD-ROM | disk | FTP | other *** search
/ Chip 2002 January / 01_02.iso / software / netscape62win / browser.xpi / bin / chrome / toolkit.jar / content / global / nsFileView.js < prev    next >
Encoding:
Text File  |  2001-07-31  |  16.4 KB  |  496 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) {
  236.       var file = null;
  237.       var rangeCount = this.mSelection.getRangeCount();
  238.       if (rangeCount > 0) {
  239.         var rangeStart = new Object();
  240.         var rangeEnd = new Object();
  241.         this.mSelection.getRangeAt(0, rangeStart, rangeEnd);
  242.         if (rangeStart.value < this.mDirList.length) {
  243.           file = this.mDirList[rangeStart.value].file;
  244.         } else if ((rangeStart.value - this.mDirList.length) < this.mFilteredFiles.length) {
  245.           file = this.mFilteredFiles[rangeStart.value - this.mDirList.length].file;
  246.         }
  247.       }
  248.       
  249.       this.mSelectionCallback(file);
  250.     }
  251.   },
  252.  
  253.   /* void cycleCell(in long row, in wstring colID); */
  254.   cycleCell: function(row, colID) { },
  255.  
  256.   /* boolean isEditable(in long row, in wstring colID); */
  257.   isEditable: function(row, colID) { return false; },
  258.  
  259.   /* void setCellText(in long row, in wstring colID, in wstring value); */
  260.   setCellText: function(row, colID, value) { },
  261.  
  262.   /* void performAction(in wstring action); */
  263.   performAction: function(action) { },
  264.  
  265.   /* void performActionOnRow(in wstring action, in long row); */
  266.   performActionOnRow: function(action, row) { },
  267.  
  268.   /* void performActionOnCell(in wstring action, in long row, in wstring colID); */
  269.   performActionOnCell: function(action, row, colID) { },
  270.  
  271.   /* private attributes */
  272.  
  273.   /* attribute boolean showHiddenFiles */
  274.   set showHiddenFiles(s) {
  275.     this.mShowHiddenFiles = s;
  276.     this.setDirectory(this.mDirectoryPath);
  277.   },
  278.  
  279.   get showHiddenFiles() { return this.mShowHiddenFiles; },
  280.  
  281.   /* attribute boolean showOnlyDirectories */
  282.   set showOnlyDirectories(s) {
  283.     this.mDirectoryFilter = s;
  284.     this.filterFiles();
  285.   },
  286.  
  287.   get showOnlyDirectories() { return this.mDirectoryFilter; },
  288.  
  289.   /* readonly attribute short sortType */
  290.   set sortType(s) { throw "readonly property"; },
  291.   get sortType() { return this.mSortType; },
  292.  
  293.   /* readonly attribute boolean reverseSort */
  294.   set reverseSort(s) { throw "readonly property"; },
  295.   get reverseSort() { return this.mReverseSort; },
  296.  
  297.   /* private methods */
  298.   sort: function(sortType, reverseSort, forceSort) {
  299.     if (sortType == this.mSortType && reverseSort != this.mReverseSort && !forceSort) {
  300.       this.mDirList.reverse();
  301.       this.mFilteredFiles.reverse();
  302.     } else {
  303.       var compareFunc, i;
  304.       
  305.       /* We pre-fetch all the data we are going to sort on, to avoid
  306.          calling into C++ on every comparison */
  307.  
  308.       switch (sortType) {
  309.       case 0:
  310.         /* no sort has been set yet */
  311.         return;
  312.       case nsFileView.SORTTYPE_NAME:
  313.         for (i = 0; i < this.mDirList.length; i++) {
  314.           this.mDirList[i].cachedName = this.mDirList[i].file.unicodeLeafName;
  315.         }
  316.         for (i = 0; i < this.mFilteredFiles.length; i++) {
  317.           this.mFilteredFiles[i].cachedName = this.mFilteredFiles[i].file.unicodeLeafName;
  318.         }
  319.         compareFunc = sortFilename;
  320.         break;
  321.       case nsFileView.SORTTYPE_SIZE:
  322.         for (i = 0; i < this.mDirList.length; i++) {
  323.           this.mDirList[i].cachedSize = this.mDirList[i].file.fileSize;
  324.         }
  325.         for (i = 0; i < this.mFilteredFiles.length; i++) {
  326.           this.mFilteredFiles[i].cachedSize = this.mFilteredFiles[i].file.fileSize;
  327.         }
  328.         compareFunc = sortSize;
  329.         break;
  330.       case nsFileView.SORTTYPE_DATE:
  331.         for (i = 0; i < this.mDirList.length; i++) {
  332.           this.mDirList[i].cachedDate = this.mDirList[i].file.lastModificationDate;
  333.           this.mDirList[i].cachedDateText = formatDate(this.mDirList[i].cachedDate);
  334.         }
  335.         for (i = 0; i < this.mFilteredFiles.length; i++) {
  336.           this.mFilteredFiles[i].cachedDate = this.mFilteredFiles[i].file.lastModificationDate;
  337.           this.mFilteredFiles[i].cachedDateText = formatDate(this.mFilteredFiles[i].cachedDate);
  338.         }
  339.         compareFunc = sortDate;
  340.         break;
  341.       default:
  342.         throw("Unsupported sort type " + sortType);
  343.         break;
  344.       }
  345.       this.mDirList.sort(compareFunc);
  346.       this.mFilteredFiles.sort(compareFunc);
  347.     }
  348.  
  349.     this.mSortType = sortType;
  350.     this.mReverseSort = reverseSort;
  351.     if (this.mOutliner) {
  352.       this.mOutliner.invalidate();
  353.     }
  354.   },
  355.  
  356.   setDirectory: function(directory) {
  357.     this.mDirectoryPath = directory;
  358.     this.mFileList = [];
  359.     this.mDirList = [];
  360.  
  361.     var dir = Components.classes[nsLocalFile_CONTRACTID].createInstance(nsILocalFile);
  362.     dir.followLinks = false;
  363.     dir.initWithUnicodePath(directory);
  364.     var dirEntries = dir.QueryInterface(nsIFile).directoryEntries;
  365.     var nextFile;
  366.     var fileobj;
  367.     //var time = new Date();
  368.  
  369.     while (dirEntries.hasMoreElements()) {
  370.       nextFile = dirEntries.getNext().QueryInterface(nsIFile);
  371.       // XXXjag hack for bug 82355 till symlink handling is fixed (bug 57995)
  372.       var isDir = false;
  373.       try {
  374.         isDir = nextFile.isDirectory();
  375.       } catch (e) {
  376.         // this here to fool the rest of the code into thinking
  377.         // this is a nsIFile object
  378.         nextFile = { unicodeLeafName : nextFile.unicodeLeafName,
  379.                      fileSize : 0,
  380.                      lastModificationDate : 0,
  381.                      isHidden: function() { return false; } };
  382.       }
  383.       // end of hack
  384.       if (isDir) {
  385.         if (!nextFile.isHidden() || this.mShowHiddenFiles) {
  386.           fileobj = new Object();
  387.           fileobj.file = nextFile;
  388.           this.mDirList[this.mDirList.length] = fileobj;
  389.         }
  390.       } else if (!this.mDirectoryFilter) {
  391.         this.mFileList[this.mFileList.length] = nextFile;
  392.       }
  393.     }
  394.  
  395.     //time = new Date() - time;
  396.     //dump("load time: " + time/1000 + " seconds\n");
  397.  
  398.     this.mFilteredFiles = [];
  399.  
  400.     if (this.mOutliner) {
  401.       var oldRows = this.mTotalRows;
  402.       this.mTotalRows = this.mDirList.length;
  403.       if (this.mDirList.length != oldRows) {
  404.         this.mOutliner.rowCountChanged(0, this.mDirList.length - oldRows);
  405.       }
  406.       this.mOutliner.invalidate();
  407.     }
  408.  
  409.     //time = new Date();
  410.  
  411.     this.filterFiles();
  412.  
  413.     //time = new Date() - time;
  414.     //dump("filter time: " + time/1000 + " seconds\n");
  415.     //time = new Date();
  416.  
  417.     this.sort(this.mSortType, this.mReverseSort);
  418.  
  419.     //time = new Date() - time;
  420.     //dump("sort time: " + time/1000 + " seconds\n");
  421.   },
  422.  
  423.   setFilter: function(filter) {
  424.     // The filter may contain several components, i.e.:
  425.     // *.html; *.htm
  426.     // First separate it into its components
  427.     var filterList = filter.split(/;[ ]*/);
  428.  
  429.     if (filterList.length == 0) {
  430.       // this shouldn't happen
  431.       return;
  432.     }
  433.  
  434.     // Transform everything in the array to a regexp
  435.     var tmp = filterList[0].replace(/\./g, "\\.");
  436.     filterList[0] = tmp.replace(/\*/g, ".*");
  437.     var shortestPrefix = filterList[0];
  438.     
  439.     for (var i = 1; i < filterList.length; i++) {
  440.       // * becomes .*, and we escape all .'s with \
  441.       tmp = filterList[i].replace(/\./g, "\\.");
  442.       filterList[i] = tmp.replace(/\*/g, ".*");
  443.       shortestPrefix = shortestPrefix.substr(0, numMatchingChars(shortestPrefix, filterList[i]));
  444.     }
  445.     
  446.     var filterStr = shortestPrefix+"(";
  447.     var startpos = shortestPrefix.length;
  448.     for (i = 0; i < filterList.length; i++) {
  449.       filterStr += filterList[i].substr(shortestPrefix.length) + "|";
  450.     }
  451.  
  452.     this.mCurrentFilter = new RegExp(filterStr.substr(0, (filterStr.length) - 1) + ")", "i");
  453.     this.mFilteredFiles = [];
  454.  
  455.     if (this.mOutliner) {
  456.       var rowDiff = -(this.mTotalRows - this.mDirList.length);
  457.       this.mTotalRows = this.mDirList.length;
  458.       this.mOutliner.rowCountChanged(this.mDirList.length, rowDiff);
  459.       this.mOutliner.invalidate();
  460.     }
  461.     this.filterFiles();
  462.     this.sort(this.mSortType, this.mReverseSort, true);
  463.   },
  464.  
  465.   filterFiles: function() {
  466.     for(var i = 0; i < this.mFileList.length; i++) {
  467.       var file = this.mFileList[i];
  468.  
  469.       if ((this.mShowHiddenFiles || !file.isHidden()) &&
  470.           file.unicodeLeafName.search(this.mCurrentFilter) == 0) {
  471.         this.mFilteredFiles[this.mFilteredFiles.length] = { file : file };
  472.       }
  473.     }
  474.  
  475.     this.mTotalRows = this.mDirList.length + this.mFilteredFiles.length;
  476.  
  477.     // Tell the outliner how many rows we just added
  478.     if (this.mOutliner) {
  479.       this.mOutliner.rowCountChanged(this.mDirList.length, this.mFilteredFiles.length);
  480.     }
  481.   },
  482.  
  483.   getSelectedFile: function() {
  484.     if (0 <= this.mSelection.currentIndex) {
  485.       if (this.mSelection.currentIndex < this.mDirList.length) {
  486.         return this.mDirList[this.mSelection.currentIndex].file;
  487.       } else if ((this.mSelection.currentIndex - this.mDirList.length) < this.mFilteredFiles.length) {
  488.         return this.mFilteredFiles[this.mSelection.currentIndex - this.mDirList.length].file;
  489.       }
  490.     }
  491.  
  492.     return null;
  493.   }
  494. }
  495.  
  496.