mirror of
https://github.com/JFormDesigner/FlatLaf.git
synced 2025-12-08 23:08:42 +03:00
FileChooser: added (optional) shortcuts panel (issue #100)
This commit is contained in:
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ public class SystemInfo
|
||||
public static final long javaVersion;
|
||||
public static final boolean isJava_9_orLater;
|
||||
public static final boolean isJava_11_orLater;
|
||||
/** @since 2.3 */ public static final boolean isJava_12_orLater;
|
||||
public static final boolean isJava_15_orLater;
|
||||
/** @since 2 */ public static final boolean isJava_17_orLater;
|
||||
/** @since 2 */ public static final boolean isJava_18_orLater;
|
||||
@@ -92,6 +93,7 @@ public class SystemInfo
|
||||
javaVersion = scanVersion( System.getProperty( "java.version" ) );
|
||||
isJava_9_orLater = (javaVersion >= toVersion( 9, 0, 0, 0 ));
|
||||
isJava_11_orLater = (javaVersion >= toVersion( 11, 0, 0, 0 ));
|
||||
isJava_12_orLater = (javaVersion >= toVersion( 12, 0, 0, 0 ));
|
||||
isJava_15_orLater = (javaVersion >= toVersion( 15, 0, 0, 0 ));
|
||||
isJava_17_orLater = (javaVersion >= toVersion( 17, 0, 0, 0 ));
|
||||
isJava_18_orLater = (javaVersion >= toVersion( 18, 0, 0, 0 ));
|
||||
|
||||
Reference in New Issue
Block a user