/*
* Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.swing;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.*;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.util.*;
import java.util.List;
import java.util.concurrent.Callable;
import javax.accessibility.AccessibleContext;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import javax.swing.filechooser.*;
import javax.swing.plaf.basic.*;
import javax.swing.table.*;
import javax.swing.text.*;
import sun.awt.shell.*;
/**
* WARNING: This class is an implementation detail and is only
* public so that it can be used by two packages. You should NOT consider
* this public API.
*
* This component is intended to be used in a subclass of
* javax.swing.plaf.basic.BasicFileChooserUI. It realies heavily on the
* implementation of BasicFileChooserUI, and is intended to be API compatible
* with earlier implementations of MetalFileChooserUI and WindowsFileChooserUI.
*
* @author Leif Samuelsson
*/
@SuppressWarnings("serial") // JDK-implementation class
public class FilePane extends JPanel implements PropertyChangeListener {
// Constants for actions. These are used for the actions' ACTION_COMMAND_KEY
// and as keys in the action maps for FilePane and the corresponding UI classes
public final static String ACTION_APPROVE_SELECTION = "approveSelection";
public final static String ACTION_CANCEL = "cancelSelection";
public final static String ACTION_EDIT_FILE_NAME = "editFileName";
public final static String ACTION_REFRESH = "refresh";
public final static String ACTION_CHANGE_TO_PARENT_DIRECTORY = "Go Up";
public final static String ACTION_NEW_FOLDER = "New Folder";
public final static String ACTION_VIEW_LIST = "viewTypeList";
public final static String ACTION_VIEW_DETAILS = "viewTypeDetails";
private Action[] actions;
// "enums" for setViewType()
public static final int VIEWTYPE_LIST = 0;
public static final int VIEWTYPE_DETAILS = 1;
private static final int VIEWTYPE_COUNT = 2;
private int viewType = -1;
private JPanel[] viewPanels = new JPanel[VIEWTYPE_COUNT];
private JPanel currentViewPanel;
private String[] viewTypeActionNames;
private String filesListAccessibleName = null;
private String filesDetailsAccessibleName = null;
private JPopupMenu contextMenu;
private JMenu viewMenu;
private String viewMenuLabelText;
private String refreshActionLabelText;
private String newFolderActionLabelText;
private String kiloByteString;
private String megaByteString;
private String gigaByteString;
private String renameErrorTitleText;
private String renameErrorText;
private String renameErrorFileExistsText;
private static final Cursor waitCursor =
Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
private final KeyListener detailsKeyListener = new KeyAdapter() {
private final long timeFactor;
private final StringBuilder typedString = new StringBuilder();
private long lastTime = 1000L;
{
Long l = (Long) UIManager.get("Table.timeFactor");
timeFactor = (l != null) ? l : 1000L;
}
/**
* Moves the keyboard focus to the first element whose prefix matches
* the sequence of alphanumeric keys pressed by the user with delay
* less than value of timeFactor. Subsequent same key
* presses move the keyboard focus to the next object that starts with
* the same letter until another key is pressed, then it is treated
* as the prefix with appropriate number of the same letters followed
* by first typed another letter.
*/
public void keyTyped(KeyEvent e) {
BasicDirectoryModel model = getModel();
int rowCount = model.getSize();
if (detailsTable == null || rowCount == 0 ||
e.isAltDown() || e.isControlDown() || e.isMetaDown()) {
return;
}
InputMap inputMap = detailsTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
KeyStroke key = KeyStroke.getKeyStrokeForEvent(e);
if (inputMap != null && inputMap.get(key) != null) {
return;
}
int startIndex = detailsTable.getSelectionModel().getLeadSelectionIndex();
if (startIndex < 0) {
startIndex = 0;
}
if (startIndex >= rowCount) {
startIndex = rowCount - 1;
}
char c = e.getKeyChar();
long time = e.getWhen();
if (time - lastTime < timeFactor) {
if (typedString.length() == 1 && typedString.charAt(0) == c) {
// Subsequent same key presses move the keyboard focus to the next
// object that starts with the same letter.
startIndex++;
} else {
typedString.append(c);
}
} else {
startIndex++;
typedString.setLength(0);
typedString.append(c);
}
lastTime = time;
if (startIndex >= rowCount) {
startIndex = 0;
}
// Find next file
int index = getNextMatch(startIndex, rowCount - 1);
if (index < 0 && startIndex > 0) { // wrap
index = getNextMatch(0, startIndex - 1);
}
if (index >= 0) {
detailsTable.getSelectionModel().setSelectionInterval(index, index);
Rectangle cellRect = detailsTable.getCellRect(index,
detailsTable.convertColumnIndexToView(COLUMN_FILENAME), false);
detailsTable.scrollRectToVisible(cellRect);
}
}
private int getNextMatch(int startIndex, int finishIndex) {
BasicDirectoryModel model = getModel();
JFileChooser fileChooser = getFileChooser();
DetailsTableRowSorter rowSorter = getRowSorter();
String prefix = typedString.toString().toLowerCase();
// Search element
for (int index = startIndex; index <= finishIndex; index++) {
File file = (File) model.getElementAt(rowSorter.convertRowIndexToModel(index));
String fileName = fileChooser.getName(file).toLowerCase();
if (fileName.startsWith(prefix)) {
return index;
}
}
return -1;
}
};
private FocusListener editorFocusListener = new FocusAdapter() {
public void focusLost(FocusEvent e) {
if (! e.isTemporary()) {
applyEdit();
}
}
};
private static FocusListener repaintListener = new FocusListener() {
public void focusGained(FocusEvent fe) {
repaintSelection(fe.getSource());
}
public void focusLost(FocusEvent fe) {
repaintSelection(fe.getSource());
}
private void repaintSelection(Object source) {
if (source instanceof JList) {
repaintListSelection((JList)source);
} else if (source instanceof JTable) {
repaintTableSelection((JTable)source);
}
}
private void repaintListSelection(JList list) {
int[] indices = list.getSelectedIndices();
for (int i : indices) {
Rectangle bounds = list.getCellBounds(i, i);
list.repaint(bounds);
}
}
private void repaintTableSelection(JTable table) {
int minRow = table.getSelectionModel().getMinSelectionIndex();
int maxRow = table.getSelectionModel().getMaxSelectionIndex();
if (minRow == -1 || maxRow == -1) {
return;
}
int col0 = table.convertColumnIndexToView(COLUMN_FILENAME);
Rectangle first = table.getCellRect(minRow, col0, false);
Rectangle last = table.getCellRect(maxRow, col0, false);
Rectangle dirty = first.union(last);
table.repaint(dirty);
}
};
private boolean smallIconsView = false;
private Border listViewBorder;
private Color listViewBackground;
private boolean listViewWindowsStyle;
private boolean readOnly;
private boolean fullRowSelection = false;
private ListSelectionModel listSelectionModel;
private JList list;
private JTable detailsTable;
private static final int COLUMN_FILENAME = 0;
// Provides a way to recognize a newly created folder, so it can
// be selected when it appears in the model.
private File newFolderFile;
// Used for accessing methods in the corresponding UI class
private FileChooserUIAccessor fileChooserUIAccessor;
private DetailsTableModel detailsTableModel;
private DetailsTableRowSorter rowSorter;
public FilePane(FileChooserUIAccessor fileChooserUIAccessor) {
super(new BorderLayout());
this.fileChooserUIAccessor = fileChooserUIAccessor;
installDefaults();
createActionMap();
}
public void uninstallUI() {
if (getModel() != null) {
getModel().removePropertyChangeListener(this);
}
}
protected JFileChooser getFileChooser() {
return fileChooserUIAccessor.getFileChooser();
}
protected BasicDirectoryModel getModel() {
return fileChooserUIAccessor.getModel();
}
public int getViewType() {
return viewType;
}
public void setViewType(int viewType) {
if (viewType == this.viewType) {
return;
}
int oldValue = this.viewType;
this.viewType = viewType;
JPanel createdViewPanel = null;
Component newFocusOwner = null;
switch (viewType) {
case VIEWTYPE_LIST:
if (viewPanels[viewType] == null) {
createdViewPanel = fileChooserUIAccessor.createList();
if (createdViewPanel == null) {
createdViewPanel = createList();
}
list = (JList) findChildComponent(createdViewPanel, JList.class);
if (listSelectionModel == null) {
listSelectionModel = list.getSelectionModel();
if (detailsTable != null) {
detailsTable.setSelectionModel(listSelectionModel);
}
} else {
list.setSelectionModel(listSelectionModel);
}
}
list.setLayoutOrientation(JList.VERTICAL_WRAP);
newFocusOwner = list;
break;
case VIEWTYPE_DETAILS:
if (viewPanels[viewType] == null) {
createdViewPanel = fileChooserUIAccessor.createDetailsView();
if (createdViewPanel == null) {
createdViewPanel = createDetailsView();
}
detailsTable = (JTable) findChildComponent(createdViewPanel, JTable.class);
detailsTable.setRowHeight(Math.max(detailsTable.getFont().getSize() + 4, 16 + 1));
if (listSelectionModel != null) {
detailsTable.setSelectionModel(listSelectionModel);
}
}
newFocusOwner = detailsTable;
break;
}
if (createdViewPanel != null) {
viewPanels[viewType] = createdViewPanel;
recursivelySetInheritsPopupMenu(createdViewPanel, true);
}
boolean isFocusOwner = false;
if (currentViewPanel != null) {
Component owner = DefaultKeyboardFocusManager.
getCurrentKeyboardFocusManager().getPermanentFocusOwner();
isFocusOwner = owner == detailsTable || owner == list;
remove(currentViewPanel);
}
currentViewPanel = viewPanels[viewType];
add(currentViewPanel, BorderLayout.CENTER);
if (isFocusOwner && newFocusOwner != null) {
newFocusOwner.requestFocusInWindow();
}
revalidate();
repaint();
updateViewMenu();
firePropertyChange("viewType", oldValue, viewType);
}
@SuppressWarnings("serial") // JDK-implementation class
class ViewTypeAction extends AbstractAction {
private int viewType;
ViewTypeAction(int viewType) {
super(viewTypeActionNames[viewType]);
this.viewType = viewType;
String cmd;
switch (viewType) {
case VIEWTYPE_LIST: cmd = ACTION_VIEW_LIST; break;
case VIEWTYPE_DETAILS: cmd = ACTION_VIEW_DETAILS; break;
default: cmd = (String)getValue(Action.NAME);
}
putValue(Action.ACTION_COMMAND_KEY, cmd);
}
public void actionPerformed(ActionEvent e) {
setViewType(viewType);
}
}
public Action getViewTypeAction(int viewType) {
return new ViewTypeAction(viewType);
}
private static void recursivelySetInheritsPopupMenu(Container container, boolean b) {
if (container instanceof JComponent) {
((JComponent)container).setInheritsPopupMenu(b);
}
int n = container.getComponentCount();
for (int i = 0; i < n; i++) {
recursivelySetInheritsPopupMenu((Container)container.getComponent(i), b);
}
}
protected void installDefaults() {
Locale l = getFileChooser().getLocale();
listViewBorder = UIManager.getBorder("FileChooser.listViewBorder");
listViewBackground = UIManager.getColor("FileChooser.listViewBackground");
listViewWindowsStyle = UIManager.getBoolean("FileChooser.listViewWindowsStyle");
readOnly = UIManager.getBoolean("FileChooser.readOnly");
// TODO: On windows, get the following localized strings from the OS
viewMenuLabelText =
UIManager.getString("FileChooser.viewMenuLabelText", l);
refreshActionLabelText =
UIManager.getString("FileChooser.refreshActionLabelText", l);
newFolderActionLabelText =
UIManager.getString("FileChooser.newFolderActionLabelText", l);
viewTypeActionNames = new String[VIEWTYPE_COUNT];
viewTypeActionNames[VIEWTYPE_LIST] =
UIManager.getString("FileChooser.listViewActionLabelText", l);
viewTypeActionNames[VIEWTYPE_DETAILS] =
UIManager.getString("FileChooser.detailsViewActionLabelText", l);
kiloByteString = UIManager.getString("FileChooser.fileSizeKiloBytes", l);
megaByteString = UIManager.getString("FileChooser.fileSizeMegaBytes", l);
gigaByteString = UIManager.getString("FileChooser.fileSizeGigaBytes", l);
fullRowSelection = UIManager.getBoolean("FileView.fullRowSelection");
filesListAccessibleName = UIManager.getString("FileChooser.filesListAccessibleName", l);
filesDetailsAccessibleName = UIManager.getString("FileChooser.filesDetailsAccessibleName", l);
renameErrorTitleText = UIManager.getString("FileChooser.renameErrorTitleText", l);
renameErrorText = UIManager.getString("FileChooser.renameErrorText", l);
renameErrorFileExistsText = UIManager.getString("FileChooser.renameErrorFileExistsText", l);
}
/**
* Fetches the command list for the FilePane. These commands
* are useful for binding to events, such as in a keymap.
*
* @return the command list
*/
public Action[] getActions() {
if (actions == null) {
@SuppressWarnings("serial") // JDK-implementation class
class FilePaneAction extends AbstractAction {
FilePaneAction(String name) {
this(name, name);
}
FilePaneAction(String name, String cmd) {
super(name);
putValue(Action.ACTION_COMMAND_KEY, cmd);
}
public void actionPerformed(ActionEvent e) {
String cmd = (String)getValue(Action.ACTION_COMMAND_KEY);
if (cmd == ACTION_CANCEL) {
if (editFile != null) {
cancelEdit();
} else {
getFileChooser().cancelSelection();
}
} else if (cmd == ACTION_EDIT_FILE_NAME) {
JFileChooser fc = getFileChooser();
int index = listSelectionModel.getMinSelectionIndex();
if (index >= 0 && editFile == null &&
(!fc.isMultiSelectionEnabled() ||
fc.getSelectedFiles().length <= 1)) {
editFileName(index);
}
} else if (cmd == ACTION_REFRESH) {
getFileChooser().rescanCurrentDirectory();
}
}
public boolean isEnabled() {
String cmd = (String)getValue(Action.ACTION_COMMAND_KEY);
if (cmd == ACTION_CANCEL) {
return getFileChooser().isEnabled();
} else if (cmd == ACTION_EDIT_FILE_NAME) {
return !readOnly && getFileChooser().isEnabled();
} else {
return true;
}
}
}
ArrayList actionList = new ArrayList(8);
Action action;
actionList.add(new FilePaneAction(ACTION_CANCEL));
actionList.add(new FilePaneAction(ACTION_EDIT_FILE_NAME));
actionList.add(new FilePaneAction(refreshActionLabelText, ACTION_REFRESH));
action = fileChooserUIAccessor.getApproveSelectionAction();
if (action != null) {
actionList.add(action);
}
action = fileChooserUIAccessor.getChangeToParentDirectoryAction();
if (action != null) {
actionList.add(action);
}
action = getNewFolderAction();
if (action != null) {
actionList.add(action);
}
action = getViewTypeAction(VIEWTYPE_LIST);
if (action != null) {
actionList.add(action);
}
action = getViewTypeAction(VIEWTYPE_DETAILS);
if (action != null) {
actionList.add(action);
}
actions = actionList.toArray(new Action[actionList.size()]);
}
return actions;
}
protected void createActionMap() {
addActionsToMap(super.getActionMap(), getActions());
}
public static void addActionsToMap(ActionMap map, Action[] actions) {
if (map != null && actions != null) {
for (Action a : actions) {
String cmd = (String)a.getValue(Action.ACTION_COMMAND_KEY);
if (cmd == null) {
cmd = (String)a.getValue(Action.NAME);
}
map.put(cmd, a);
}
}
}
private void updateListRowCount(JList list) {
if (smallIconsView) {
list.setVisibleRowCount(getModel().getSize() / 3);
} else {
list.setVisibleRowCount(-1);
}
}
public JPanel createList() {
JPanel p = new JPanel(new BorderLayout());
final JFileChooser fileChooser = getFileChooser();
@SuppressWarnings("serial") // anonymous class
final JList