Beispiel #1
0
/**
 * This is a list of files in a certain directory. Each file is listed as one <code>JComponent
 * </code> in a vertical list.
 */
public abstract class FileList extends JPanel {
  private static final long serialVersionUID = 1L;

  /** A client property used to associated JComponents with a File. */
  protected static final String KEY_FILE = FileList.class.getName() + ".file";

  private static FileFilter ACCEPT_ALL_FILTER =
      new FileFilter() {
        @Override
        public boolean accept(File pathname) {
          return true;
        }
      };

  private File[] currentDirectories = null;
  private ObservableList<File> fileList = new ObservableList<File>();
  private EDTMirror<File> constrainedList =
      fileList.getListModelEDTMirror(
          new Filter<File>() {

            @Override
            public boolean accept(File t) {
              SearchConstraints<File> c = searchConstraints;
              return c == null ? true : c.accepts(t);
            }
          });
  private final FileFilter fileFilter;

  Runnable updateContentsRunnable =
      new Runnable() {
        public void run() {
          updateFileListComponents();
        }
      };

  private SearchConstraints<File> searchConstraints;
  private boolean finishedSearch = false;

  static ExecutorService executor = Executors.newFixedThreadPool(6);

  class SearchRunnable implements Runnable {

    long searchID;

    public SearchRunnable(long searchID) {
      this.searchID = searchID;
    }

    public void run() {
      try {
        fileList.clear();
        for (File dir : currentDirectories) {
          search(dir);
        }
      } finally {
        synchronized (FileList.this) {
          if (searchID == searchCounter.get()) {
            finishedSearch = true;
            SwingUtilities.invokeLater(updateContentsRunnable);
          }
        }
      }
    }

    private void search(File currentDirectory) {
      FileTreeIterator iter = new FileTreeIterator(currentDirectory);
      while (iter.hasNext()) {
        File file = iter.next();
        boolean accept = fileFilter.accept(file);
        synchronized (FileList.this) {
          if (searchID == searchCounter.get()) {
            if (accept) {

              int i =
                  searchConstraints == null
                      ? Collections.binarySearch(fileList, file)
                      : Collections.binarySearch(fileList, file, searchConstraints);
              if (i >= 0) {
                // already exists? great
              } else {
                fileList.add(-i - 1, file);
              }
            }
          } else {
            return;
          }
        }
      }
    }
  };

  /**
   * @param primaryFileFilter this optional filter is immutable and supersedes any possible
   *     SearchConstraints.
   * @param constraints this optional set of constraints is used to fine-tune what the primary
   *     FileFilter accepts.
   */
  public FileList(FileFilter primaryFileFilter, SearchConstraints<File> constraints) {
    this.fileFilter = primaryFileFilter == null ? ACCEPT_ALL_FILTER : primaryFileFilter;
    setSearchConstraints(constraints);

    constrainedList.addListDataListener(
        new ListDataListener() {

          @Override
          public void intervalAdded(ListDataEvent e) {
            contentsChanged(e);
          }

          @Override
          public void intervalRemoved(ListDataEvent e) {
            contentsChanged(e);
          }

          @Override
          public void contentsChanged(ListDataEvent e) {
            SwingUtilities.invokeLater(updateContentsRunnable);
          }
        });

    setLayout(new GridBagLayout());
  }

  /**
   * @param directories an initial set of directories this FileList refers to.
   * @param primaryFileFilter this optional filter is immutable and supersedes any possible
   *     SearchConstraints.
   * @param constraints this optional set of constraints is used to fine-tune what the primary
   *     FileFilter accepts.
   */
  public FileList(
      File[] directories, FileFilter primaryFileFilter, SearchConstraints<File> constraints) {
    this(primaryFileFilter, constraints);
    if (directories != null) setDirectories(directories);
  }

  public synchronized boolean setSearchConstraints(SearchConstraints<File> constraints) {
    if (this.searchConstraints == null && constraints == null) return false;
    if (this.searchConstraints != null
        && constraints != null
        && this.searchConstraints.equals(constraints)) return false;
    this.searchConstraints = constraints;
    constrainedList.refresh(false);
    return true;
  }

  protected JThrobber throbber = new JThrobber();
  protected JLabel emptyLabel = new JLabel("No results found.");
  protected JLabel searchingLabel = new JLabel("No results found.");
  private Map<File, JComponent> componentCache = new HashMap<File, JComponent>();
  private JLabel fluff = new JLabel();

  /**
   * Set the number of columns this list should use.
   *
   * @param columnCount
   */
  public void setColumnCount(int columnCount) {
    if (columnCount <= 0)
      throw new IllegalArgumentException("value (" + columnCount + ") must be 1 or greater");
    putClientProperty("columnCount", columnCount);
  }

  /** @return the number of columns this list should use. The default is 1. */
  public int getColumnCount() {
    Integer i = (Integer) getClientProperty("columnCount");
    if (i == null) return 1;
    return i;
  }

  protected synchronized void updateFileListComponents() {
    throbber.setVisible(!finishedSearch);
    removeAll();
    GridBagConstraints c = new GridBagConstraints();
    c.gridx = getColumnCount() - 1;
    c.gridy = 0;
    c.weightx = 0;
    c.weighty = 0;
    c.insets = new Insets(5, 5, 5, 5);
    c.anchor = GridBagConstraints.EAST;
    add(throbber, c);

    c.gridx = 0;
    c.weightx = 1;
    c.anchor = GridBagConstraints.WEST;
    c.fill = GridBagConstraints.BOTH;
    if (constrainedList.getSize() == 0) {
      if (finishedSearch) {
        add(emptyLabel, c);
      } else {
        add(searchingLabel, c);
      }
    }

    for (int a = 0; a < constrainedList.getSize(); a++) {
      File file = constrainedList.getElementAt(a);
      JComponent jc = componentCache.get(file);
      if (jc == null) {
        jc = createComponent(file);
        jc.putClientProperty(KEY_FILE, file);
        componentCache.put(file, jc);
      }

      add(jc, c);
      c.gridx = (c.gridx + 1) % getColumnCount();
      if (c.gridx == 0) c.gridy++;
    }
    c.gridy++;
    c.weighty = 1;
    add(fluff, c);

    revalidate();
    repaint();
  }

  /**
   * Returns the files currently displayed in this list.
   *
   * @return the files current displayed in this list. This will include files that pass the primary
   *     FileFilter and the current SearchConstraints.
   * @see #getAllFiles()
   */
  public File[] getVisibleFiles() {
    Vector<File> files = new Vector<File>();
    for (int a = 0; a < getComponentCount(); a++) {
      Component comp = getComponent(a);
      if (comp instanceof JComponent) {
        JComponent jc = (JComponent) comp;
        File file = (File) jc.getClientProperty(KEY_FILE);
        if (file != null) {
          files.add(file);
        }
      }
    }
    return files.toArray(new File[files.size()]);
  }

  /**
   * Returns all available files -- although some might not be visible.
   *
   * @return all known available files that pass the primary FileFilter. Note this includes files
   *     that do NOT pass the current SearchConstraints. These files may change if the thread that
   *     is scanning for Files is still ongoing.
   * @see #getVisibleFiles()
   */
  public File[] getAllFiles() {
    return fileList.toArray(File.class);
  }

  /**
   * Add a ListDataListener to be notified when the visible files change.
   *
   * @param listListener
   * @see #removeListDataListener(ListDataListener)
   */
  public void addListDataListener(ListDataListener listListener) {
    constrainedList.addListDataListener(listListener);
  }

  /**
   * Remove a ListDataListener.
   *
   * @param listListener
   * @see #addListDataListener(ListDataListener)
   */
  public void removeListDataListener(ListDataListener listListener) {
    constrainedList.removeListDataListener(listListener);
  }

  protected abstract JComponent createComponent(File file);

  public synchronized File[] getDirectories() {
    File[] copy = new File[currentDirectories.length];
    System.arraycopy(currentDirectories, 0, copy, 0, copy.length);
    return copy;
  }

  AtomicLong searchCounter = new AtomicLong(0);

  public synchronized void setDirectories(File[] files) {
    if (files == null) throw new NullPointerException();
    for (File f : files) {
      if (f == null) throw new NullPointerException();
    }
    if (Arrays.equals(currentDirectories, files)) return;
    currentDirectories = files;
    finishedSearch = false;

    long id;
    synchronized (FileList.this) {
      id = searchCounter.incrementAndGet();
    }
    executor.execute(new SearchRunnable(id));
    SwingUtilities.invokeLater(updateContentsRunnable);
  }
}
Beispiel #2
0
 /**
  * Returns all available files -- although some might not be visible.
  *
  * @return all known available files that pass the primary FileFilter. Note this includes files
  *     that do NOT pass the current SearchConstraints. These files may change if the thread that
  *     is scanning for Files is still ongoing.
  * @see #getVisibleFiles()
  */
 public File[] getAllFiles() {
   return fileList.toArray(File.class);
 }