package org.geogebra.desktop.gui.autocompletion;
import java.awt.Component;
import java.awt.Container;
import java.io.File;
import java.io.FileFilter;
import java.util.List;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JFileChooser;
import javax.swing.JTextField;
import javax.swing.ListCellRenderer;
import org.geogebra.common.util.debug.Log;
/**
* This class provides static methods for conveniently installing auto
* completion for {@link JTextField} and {@link JFileChooser} components.
*
* @author Julian Lettner
*/
public class AutoCompletion {
// --- Static section ---
private final static int POPUP_ROW_COUNT_FOR_FILE_CHOOSER = 8;
private final static FileChooserCompletionListCellRenderer FC_CELL_RENDERER = new FileChooserCompletionListCellRenderer();
private final static boolean caseInsensitivePaths = initCaseInsenitvePaths();
private static boolean initCaseInsenitvePaths() {
try {
return System.getProperty("os.name").toLowerCase()
.contains("windows");
} catch (SecurityException ex) {
Log.debug("Could not determine underlying os: " + ex);
return false;
}
}
/**
* Convenience method for adding auto completion to a {@link JFileChooser}.
* Path name completion will be case(in)sensitive depending on the operating
* system.
*
* @param fileChooser
* file chooser
*/
public static void install(JFileChooser fileChooser) {
install(fileChooser, caseInsensitivePaths);
}
/**
* Convenience method for adding auto completion to a {@link JFileChooser}.
*
* @param fileChooser
* The file chooser
* @param caseInsensitiveCompletion
* <code>true</code> if the casing of path names should be
* ignored for completion
*/
public static void install(final JFileChooser fileChooser,
final boolean caseInsensitiveCompletion) {
// Extract internal text field
JTextField textField = getInternalTextField(fileChooser);
if (null == textField) {
Log.debug(
"Could not find an instance of JTextField inside the file chooser: "
+ fileChooser);
return;
}
CompletionProvider<File> fileChooserCompletionProvider = new CompletionProvider<File>() {
@Override
public List<File> getCompletionOptions(String prefix) {
// Create adapter: javax.swing.filechooser.FileFilter -->
// java.io.FileFilter
final javax.swing.filechooser.FileFilter fileChooserFileFilter = fileChooser
.getFileFilter();
FileFilter fileFilter = new FileFilter() {
@Override
public boolean accept(File pathname) {
return fileChooserFileFilter.accept(pathname);
}
};
// All visible items in the file chooser are possible options
File[] options = fileChooser.getCurrentDirectory()
.listFiles(fileFilter);
// We cannot cache the above steps because the user could change
// the directory or file filter
if (options == null) {
return null;
}
CompletionProvider<File> completionProvider = new SortedArrayCompletionProvider<File>(
options, caseInsensitiveCompletion) {
@Override
public String toString(File option) {
return fileToString(option);
}
};
return completionProvider.getCompletionOptions(prefix);
}
@Override
public String toString(File option) {
return fileToString(option);
}
};
install(textField, fileChooserCompletionProvider, FC_CELL_RENDERER,
POPUP_ROW_COUNT_FOR_FILE_CHOOSER);
}
/**
* @param file
* file
* @return filename
*/
static String fileToString(File file) {
return file.getName();
}
// TODO-investigate: There should be a better way to get hold of the text
// field inside a JFileChooser
// This method assumes that there is exactly one internal JTextField
private static JTextField getInternalTextField(Container parent) {
if (parent instanceof JTextField) {
return (JTextField) parent;
}
// Decompose component tree
for (Component child : parent.getComponents()) {
if (child instanceof Container) {
JTextField textField = getInternalTextField((Container) child);
if (null != textField) {
return textField; // Return first JTextField found
}
}
}
// JTextField not found in this subtree
return null;
}
/**
* Adds auto completion support to a {@link JTextField}. If dynamic or user
* defined completion behavior is needed use
* {@link #install(JTextField, CompletionProvider, int)} and specify a
* custom {@link CompletionProvider}.
*
* @param textField
* The text field
* @param completionOptions
* The completion options, will be searched linearly for
* completion matches
* @param caseInsensitiveCompletion
* <code>true</code> for case insensitive completion
* @param maxPopupRowCount
* The maximum number of rows (height) of the completion popup,
* that is the number of options the user can see without
* scrolling
*/
public static void install(JTextField textField, String[] completionOptions,
boolean caseInsensitiveCompletion, int maxPopupRowCount) {
// Array will be changed (sorted) - create defensive copy
String[] optionsCopy = new String[completionOptions.length];
System.arraycopy(completionOptions, 0, optionsCopy, 0,
completionOptions.length);
// Wrap array in provider and install
CompletionProvider<String> arrayProvider = new SortedArrayCompletionProvider<String>(
optionsCopy, caseInsensitiveCompletion) {
@Override
public String toString(String option) {
return option;
}
};
install(textField, arrayProvider, maxPopupRowCount);
}
/**
* Adds auto completion support to a {@link JTextField}. If all you need is
* completion for a fixed set of options you may use
* {@link #install(JTextField, String[], boolean, int)} instead.
*
* @param textField
* The text field
* @param completionProvider
* A custom completion provider (for simple strings)
* @param maxPopupRowCount
* The maximum number of rows (height) of the completion popup,
* that is the number of options the user can see without
* scrolling
*/
public static void install(JTextField textField,
CompletionProvider<String> completionProvider,
int maxPopupRowCount) {
install(textField, completionProvider, new DefaultListCellRenderer(),
maxPopupRowCount);
}
/**
* Adds auto completion support to a {@link JTextField}. If all you need is
* completion for a fixed set of options you may use
* {@link #install(JTextField, String[], boolean, int)} instead. <br />
* This method offers the most flexibility. Completion options returned by
* the completion provider can be arbitrary objects which in turn are
* visualized by the supplied {@link ListCellRenderer}.
*
* @param <T>
* The objects returned by the completion provider are of this
* type. The list cell renderer can safely cast the
* <code>value</code> parameter of its method
* {@link ListCellRenderer#getListCellRendererComponent} to this
* type.
*
* @param textField
* The text field
* @param completionProvider
* A completion provider (The returned values will be the input
* for the supplied {@link ListCellRenderer})
* @param listCellRenderer
* A list cell renderer which visualizes the options returned by
* the provided {@link CompletionProvider}
* @param maxPopupRowCount
* The maximum number of rows (height) of the completion popup,
* that is the number of options the user can see without
* scrolling
* @return the popup
*/
@SuppressWarnings("rawtypes")
public static <T> Object install(JTextField textField,
CompletionProvider<T> completionProvider,
ListCellRenderer listCellRenderer, int maxPopupRowCount) {
return new OptionsPopup<T>(textField, completionProvider,
listCellRenderer,
maxPopupRowCount);
}
}