|
|
|
|
@@ -19,11 +19,19 @@ package com.formdev.flatlaf.ui;
|
|
|
|
|
import java.awt.BorderLayout;
|
|
|
|
|
import java.awt.Component;
|
|
|
|
|
import java.awt.Dimension;
|
|
|
|
|
import java.awt.Graphics;
|
|
|
|
|
import java.awt.Graphics2D;
|
|
|
|
|
import java.awt.Insets;
|
|
|
|
|
import java.awt.RenderingHints;
|
|
|
|
|
import java.beans.PropertyChangeEvent;
|
|
|
|
|
import java.beans.PropertyChangeListener;
|
|
|
|
|
import java.io.File;
|
|
|
|
|
import java.lang.reflect.Method;
|
|
|
|
|
import java.util.function.Function;
|
|
|
|
|
import javax.swing.AbstractButton;
|
|
|
|
|
import javax.swing.Box;
|
|
|
|
|
import javax.swing.BoxLayout;
|
|
|
|
|
import javax.swing.ButtonGroup;
|
|
|
|
|
import javax.swing.Icon;
|
|
|
|
|
import javax.swing.ImageIcon;
|
|
|
|
|
import javax.swing.JButton;
|
|
|
|
|
@@ -34,12 +42,16 @@ import javax.swing.JPanel;
|
|
|
|
|
import javax.swing.JScrollPane;
|
|
|
|
|
import javax.swing.JTable;
|
|
|
|
|
import javax.swing.JToggleButton;
|
|
|
|
|
import javax.swing.JToolBar;
|
|
|
|
|
import javax.swing.SwingConstants;
|
|
|
|
|
import javax.swing.UIManager;
|
|
|
|
|
import javax.swing.filechooser.FileSystemView;
|
|
|
|
|
import javax.swing.filechooser.FileView;
|
|
|
|
|
import javax.swing.plaf.ComponentUI;
|
|
|
|
|
import javax.swing.plaf.metal.MetalFileChooserUI;
|
|
|
|
|
import javax.swing.table.TableCellRenderer;
|
|
|
|
|
import com.formdev.flatlaf.FlatClientProperties;
|
|
|
|
|
import com.formdev.flatlaf.util.LoggingFacade;
|
|
|
|
|
import com.formdev.flatlaf.util.ScaledImageIcon;
|
|
|
|
|
import com.formdev.flatlaf.util.SystemInfo;
|
|
|
|
|
import com.formdev.flatlaf.util.UIScale;
|
|
|
|
|
@@ -133,12 +145,21 @@ import com.formdev.flatlaf.util.UIScale;
|
|
|
|
|
* @uiDefault FileChooser.listViewActionLabelText String
|
|
|
|
|
* @uiDefault FileChooser.detailsViewActionLabelText String
|
|
|
|
|
*
|
|
|
|
|
* <!-- FlatFileChooserUI -->
|
|
|
|
|
*
|
|
|
|
|
* @uiDefault FileChooser.shortcuts.buttonSize Dimension optional; default is 84,64
|
|
|
|
|
* @uiDefault FileChooser.shortcuts.iconSize Dimension optional; default is 32,32
|
|
|
|
|
* @uiDefault FileChooser.shortcuts.filesFunction Function<File[], File[]>
|
|
|
|
|
* @uiDefault FileChooser.shortcuts.displayNameFunction Function<File, String>
|
|
|
|
|
* @uiDefault FileChooser.shortcuts.iconFunction Function<File, Icon>
|
|
|
|
|
*
|
|
|
|
|
* @author Karl Tauber
|
|
|
|
|
*/
|
|
|
|
|
public class FlatFileChooserUI
|
|
|
|
|
extends MetalFileChooserUI
|
|
|
|
|
{
|
|
|
|
|
private final FlatFileView fileView = new FlatFileView();
|
|
|
|
|
private FlatShortcutsPanel shortcutsPanel;
|
|
|
|
|
|
|
|
|
|
public static ComponentUI createUI( JComponent c ) {
|
|
|
|
|
return new FlatFileChooserUI( (JFileChooser) c );
|
|
|
|
|
@@ -153,6 +174,25 @@ public class FlatFileChooserUI
|
|
|
|
|
super.installComponents( fc );
|
|
|
|
|
|
|
|
|
|
patchUI( fc );
|
|
|
|
|
|
|
|
|
|
if( !UIManager.getBoolean( "FileChooser.noPlacesBar" ) ) { // same as in Windows L&F
|
|
|
|
|
FlatShortcutsPanel panel = createShortcutsPanel( fc );
|
|
|
|
|
if( panel.getComponentCount() > 0 ) {
|
|
|
|
|
shortcutsPanel = panel;
|
|
|
|
|
fc.add( shortcutsPanel, BorderLayout.LINE_START );
|
|
|
|
|
fc.addPropertyChangeListener( shortcutsPanel );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void uninstallComponents( JFileChooser fc ) {
|
|
|
|
|
super.uninstallComponents( fc );
|
|
|
|
|
|
|
|
|
|
if( shortcutsPanel != null ) {
|
|
|
|
|
fc.removePropertyChangeListener( shortcutsPanel );
|
|
|
|
|
shortcutsPanel = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void patchUI( JFileChooser fc ) {
|
|
|
|
|
@@ -250,9 +290,19 @@ public class FlatFileChooserUI
|
|
|
|
|
return p;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @since 2.3 */
|
|
|
|
|
protected FlatShortcutsPanel createShortcutsPanel( JFileChooser fc ) {
|
|
|
|
|
return new FlatShortcutsPanel( fc );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Dimension getPreferredSize( JComponent c ) {
|
|
|
|
|
return UIScale.scale( super.getPreferredSize( c ) );
|
|
|
|
|
Dimension prefSize = super.getPreferredSize( c );
|
|
|
|
|
Dimension minSize = getMinimumSize( c );
|
|
|
|
|
int shortcutsPanelWidth = (shortcutsPanel != null) ? shortcutsPanel.getPreferredSize().width : 0;
|
|
|
|
|
return new Dimension(
|
|
|
|
|
Math.max( prefSize.width, minSize.width + shortcutsPanelWidth ),
|
|
|
|
|
Math.max( prefSize.height, minSize.height ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
@@ -316,4 +366,221 @@ public class FlatFileChooserUI
|
|
|
|
|
return icon;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//---- class FlatShortcutsPanel -------------------------------------------
|
|
|
|
|
|
|
|
|
|
/** @since 2.3 */
|
|
|
|
|
public static class FlatShortcutsPanel
|
|
|
|
|
extends JToolBar
|
|
|
|
|
implements PropertyChangeListener
|
|
|
|
|
{
|
|
|
|
|
private final JFileChooser fc;
|
|
|
|
|
|
|
|
|
|
private final Dimension buttonSize;
|
|
|
|
|
private final Dimension iconSize;
|
|
|
|
|
private final Function<File[], File[]> filesFunction;
|
|
|
|
|
private final Function<File, String> displayNameFunction;
|
|
|
|
|
private final Function<File, Icon> iconFunction;
|
|
|
|
|
|
|
|
|
|
protected final File[] files;
|
|
|
|
|
protected final JToggleButton[] buttons;
|
|
|
|
|
protected final ButtonGroup buttonGroup;
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings( "unchecked" )
|
|
|
|
|
public FlatShortcutsPanel( JFileChooser fc ) {
|
|
|
|
|
super( JToolBar.VERTICAL );
|
|
|
|
|
this.fc = fc;
|
|
|
|
|
setFloatable( false );
|
|
|
|
|
|
|
|
|
|
buttonSize = UIScale.scale( getUIDimension( "FileChooser.shortcuts.buttonSize", 84, 64 ) );
|
|
|
|
|
iconSize = getUIDimension( "FileChooser.shortcuts.iconSize", 32, 32 );
|
|
|
|
|
|
|
|
|
|
filesFunction = (Function<File[], File[]>) UIManager.get( "FileChooser.shortcuts.filesFunction" );
|
|
|
|
|
displayNameFunction = (Function<File, String>) UIManager.get( "FileChooser.shortcuts.displayNameFunction" );
|
|
|
|
|
iconFunction = (Function<File, Icon>) UIManager.get( "FileChooser.shortcuts.iconFunction" );
|
|
|
|
|
|
|
|
|
|
FileSystemView fsv = fc.getFileSystemView();
|
|
|
|
|
File[] files = getChooserShortcutPanelFiles( fsv );
|
|
|
|
|
if( filesFunction != null )
|
|
|
|
|
files = filesFunction.apply( files );
|
|
|
|
|
this.files = files;
|
|
|
|
|
|
|
|
|
|
// create toolbar buttons
|
|
|
|
|
buttons = new JToggleButton[files.length];
|
|
|
|
|
buttonGroup = new ButtonGroup();
|
|
|
|
|
for( int i = 0; i < files.length; i++ ) {
|
|
|
|
|
// wrap drive path
|
|
|
|
|
if( fsv.isFileSystemRoot( files[i] ) )
|
|
|
|
|
files[i] = fsv.createFileObject( files[i].getAbsolutePath() );
|
|
|
|
|
|
|
|
|
|
File file = files[i];
|
|
|
|
|
String name = getDisplayName( fsv, file );
|
|
|
|
|
Icon icon = getIcon( fsv, file );
|
|
|
|
|
|
|
|
|
|
// remove path from name
|
|
|
|
|
int lastSepIndex = name.lastIndexOf( File.separatorChar );
|
|
|
|
|
if( lastSepIndex >= 0 && lastSepIndex < name.length() - 1 )
|
|
|
|
|
name = name.substring( lastSepIndex + 1 );
|
|
|
|
|
|
|
|
|
|
// scale icon (if necessary)
|
|
|
|
|
if( icon instanceof ImageIcon )
|
|
|
|
|
icon = new ScaledImageIcon( (ImageIcon) icon, iconSize.width, iconSize.height );
|
|
|
|
|
else if( icon != null )
|
|
|
|
|
icon = new ShortcutIcon( icon, iconSize.width, iconSize.height );
|
|
|
|
|
|
|
|
|
|
// create button
|
|
|
|
|
JToggleButton button = createButton( name, icon );
|
|
|
|
|
button.addActionListener( e -> {
|
|
|
|
|
fc.setCurrentDirectory( file );
|
|
|
|
|
} );
|
|
|
|
|
|
|
|
|
|
add( button );
|
|
|
|
|
buttonGroup.add( button );
|
|
|
|
|
buttons[i] = button;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
directoryChanged( fc.getCurrentDirectory() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Dimension getUIDimension( String key, int defaultWidth, int defaultHeight ) {
|
|
|
|
|
Dimension size = UIManager.getDimension( key );
|
|
|
|
|
if( size == null )
|
|
|
|
|
size = new Dimension( defaultWidth, defaultHeight );
|
|
|
|
|
return size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected JToggleButton createButton( String name, Icon icon ) {
|
|
|
|
|
JToggleButton button = new JToggleButton( name, icon );
|
|
|
|
|
button.setVerticalTextPosition( SwingConstants.BOTTOM );
|
|
|
|
|
button.setHorizontalTextPosition( SwingConstants.CENTER );
|
|
|
|
|
button.setAlignmentX( Component.CENTER_ALIGNMENT );
|
|
|
|
|
button.setIconTextGap( 0 );
|
|
|
|
|
button.setPreferredSize( buttonSize );
|
|
|
|
|
button.setMaximumSize( buttonSize );
|
|
|
|
|
return button;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected File[] getChooserShortcutPanelFiles( FileSystemView fsv ) {
|
|
|
|
|
try {
|
|
|
|
|
if( SystemInfo.isJava_12_orLater ) {
|
|
|
|
|
Method m = fsv.getClass().getMethod( "getChooserShortcutPanelFiles" );
|
|
|
|
|
File[] files = (File[]) m.invoke( fsv );
|
|
|
|
|
|
|
|
|
|
// on macOS and Linux, files consists only of the user home directory
|
|
|
|
|
if( files.length == 1 && files[0].equals( new File( System.getProperty( "user.home" ) ) ) )
|
|
|
|
|
files = new File[0];
|
|
|
|
|
|
|
|
|
|
return files;
|
|
|
|
|
} else if( SystemInfo.isWindows ) {
|
|
|
|
|
Class<?> cls = Class.forName( "sun.awt.shell.ShellFolder" );
|
|
|
|
|
Method m = cls.getMethod( "get", String.class );
|
|
|
|
|
return (File[]) m.invoke( null, "fileChooserShortcutPanelFolders" );
|
|
|
|
|
}
|
|
|
|
|
} catch( Exception ex ) {
|
|
|
|
|
LoggingFacade.INSTANCE.logSevere( null, ex );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// fallback
|
|
|
|
|
return new File[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected String getDisplayName( FileSystemView fsv, File file ) {
|
|
|
|
|
if( displayNameFunction != null ) {
|
|
|
|
|
String name = displayNameFunction.apply( file );
|
|
|
|
|
if( name != null )
|
|
|
|
|
return name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fsv.getSystemDisplayName( file );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected Icon getIcon( FileSystemView fsv, File file ) {
|
|
|
|
|
if( iconFunction != null ) {
|
|
|
|
|
Icon icon = iconFunction.apply( file );
|
|
|
|
|
if( icon != null )
|
|
|
|
|
return icon;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Java 17+ supports getting larger system icons
|
|
|
|
|
if( SystemInfo.isJava_17_orLater ) {
|
|
|
|
|
try {
|
|
|
|
|
Method m = fsv.getClass().getMethod( "getSystemIcon", File.class, int.class, int.class );
|
|
|
|
|
return (Icon) m.invoke( fsv, file, iconSize.width, iconSize.height );
|
|
|
|
|
} catch( Exception ex ) {
|
|
|
|
|
LoggingFacade.INSTANCE.logSevere( null, ex );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// get system icon in default size 16x16
|
|
|
|
|
return fsv.getSystemIcon( file );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected void directoryChanged( File file ) {
|
|
|
|
|
if( file != null ) {
|
|
|
|
|
String absolutePath = file.getAbsolutePath();
|
|
|
|
|
for( int i = 0; i < files.length; i++ ) {
|
|
|
|
|
// also compare path because otherwise selecting "Documents"
|
|
|
|
|
// in "Look in" combobox would not select "Documents" shortcut item
|
|
|
|
|
if( files[i].equals( file ) || files[i].getAbsolutePath().equals( absolutePath ) ) {
|
|
|
|
|
buttons[i].setSelected( true );
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
buttonGroup.clearSelection();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void propertyChange( PropertyChangeEvent e ) {
|
|
|
|
|
switch( e.getPropertyName() ) {
|
|
|
|
|
case JFileChooser.DIRECTORY_CHANGED_PROPERTY:
|
|
|
|
|
directoryChanged( fc.getCurrentDirectory() );
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//---- class ShortcutIcon -------------------------------------------------
|
|
|
|
|
|
|
|
|
|
private static class ShortcutIcon
|
|
|
|
|
implements Icon
|
|
|
|
|
{
|
|
|
|
|
private final Icon icon;
|
|
|
|
|
private final int iconWidth;
|
|
|
|
|
private final int iconHeight;
|
|
|
|
|
|
|
|
|
|
ShortcutIcon( Icon icon, int iconWidth, int iconHeight ) {
|
|
|
|
|
this.icon = icon;
|
|
|
|
|
this.iconWidth = iconWidth;
|
|
|
|
|
this.iconHeight = iconHeight;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void paintIcon( Component c, Graphics g, int x, int y ) {
|
|
|
|
|
Graphics2D g2 = (Graphics2D) g.create();
|
|
|
|
|
try {
|
|
|
|
|
// set rendering hint for the case that the icon is a bitmap (not used for vector icons)
|
|
|
|
|
g2.setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC );
|
|
|
|
|
|
|
|
|
|
double scale = (double) getIconWidth() / (double) icon.getIconWidth();
|
|
|
|
|
g2.translate( x, y );
|
|
|
|
|
g2.scale( scale, scale );
|
|
|
|
|
|
|
|
|
|
icon.paintIcon( c, g2, 0, 0 );
|
|
|
|
|
} finally {
|
|
|
|
|
g2.dispose();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public int getIconWidth() {
|
|
|
|
|
return UIScale.scale( iconWidth );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public int getIconHeight() {
|
|
|
|
|
return UIScale.scale( iconHeight );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|