diff --git a/CHANGELOG.md b/CHANGELOG.md index 20c63384..67cb672b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,12 @@ FlatLaf Change Log #### New features and improvements -- Button and ToggleButton: Added missing border colors for pressed and selected - states. (issue #848) +- Button and ToggleButton: Added border colors for pressed and selected states. + (issue #848) - Label: Support painting background with rounded corners. (issue #842) - Popup: Fixed flicker of popups (e.g. tooltips) while they are moving (e.g. following mouse pointer). (issues #832 and #672) +- FileChooser: Wrap shortcuts in scroll pane. (issue #828) - Theme Editor: On macOS, use larger window title bar. (PR #779) #### Fixed bugs diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatFileChooserUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatFileChooserUI.java index aed37f0b..59f360ad 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatFileChooserUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatFileChooserUI.java @@ -24,6 +24,7 @@ import java.awt.Graphics2D; import java.awt.Image; import java.awt.Insets; import java.awt.LayoutManager; +import java.awt.Rectangle; import java.awt.RenderingHints; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; @@ -32,6 +33,7 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.function.Function; import javax.swing.AbstractButton; +import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.ButtonGroup; @@ -46,6 +48,7 @@ import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JToggleButton; import javax.swing.JToolBar; +import javax.swing.Scrollable; import javax.swing.SwingConstants; import javax.swing.UIManager; import javax.swing.filechooser.FileSystemView; @@ -164,6 +167,7 @@ public class FlatFileChooserUI { private final FlatFileView fileView = new FlatFileView(); private FlatShortcutsPanel shortcutsPanel; + private JScrollPane shortcutsScrollPane; public static ComponentUI createUI( JComponent c ) { return new FlatFileChooserUI( (JFileChooser) c ); @@ -183,7 +187,10 @@ public class FlatFileChooserUI FlatShortcutsPanel panel = createShortcutsPanel( fc ); if( panel.getComponentCount() > 0 ) { shortcutsPanel = panel; - fc.add( shortcutsPanel, BorderLayout.LINE_START ); + shortcutsScrollPane = new JScrollPane( shortcutsPanel, + JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER ); + shortcutsScrollPane.setBorder( BorderFactory.createEmptyBorder() ); + fc.add( shortcutsScrollPane, BorderLayout.LINE_START ); fc.addPropertyChangeListener( shortcutsPanel ); } } @@ -196,6 +203,7 @@ public class FlatFileChooserUI if( shortcutsPanel != null ) { fc.removePropertyChangeListener( shortcutsPanel ); shortcutsPanel = null; + shortcutsScrollPane = null; } } @@ -324,7 +332,7 @@ public class FlatFileChooserUI public Dimension getPreferredSize( JComponent c ) { Dimension prefSize = super.getPreferredSize( c ); Dimension minSize = getMinimumSize( c ); - int shortcutsPanelWidth = (shortcutsPanel != null) ? shortcutsPanel.getPreferredSize().width : 0; + int shortcutsPanelWidth = (shortcutsScrollPane != null) ? shortcutsScrollPane.getPreferredSize().width : 0; return new Dimension( Math.max( prefSize.width, minSize.width + shortcutsPanelWidth ), Math.max( prefSize.height, minSize.height ) ); @@ -401,7 +409,7 @@ public class FlatFileChooserUI /** @since 2.3 */ public static class FlatShortcutsPanel extends JToolBar - implements PropertyChangeListener + implements PropertyChangeListener, Scrollable { private final JFileChooser fc; @@ -420,6 +428,7 @@ public class FlatFileChooserUI super( JToolBar.VERTICAL ); this.fc = fc; setFloatable( false ); + putClientProperty( FlatClientProperties.STYLE, "hoverButtonGroupBackground: null" ); buttonSize = UIScale.scale( getUIDimension( "FileChooser.shortcuts.buttonSize", 84, 64 ) ); iconSize = getUIDimension( "FileChooser.shortcuts.iconSize", 32, 32 ); @@ -461,7 +470,7 @@ public class FlatFileChooserUI icon = new ShortcutIcon( icon, iconSize.width, iconSize.height ); // create button - JToggleButton button = createButton( name, icon ); + JToggleButton button = createButton( name, icon, file.toString() ); File f = file; button.addActionListener( e -> { fc.setCurrentDirectory( f ); @@ -487,8 +496,10 @@ public class FlatFileChooserUI return size; } - protected JToggleButton createButton( String name, Icon icon ) { + /** @since 3.5 */ + protected JToggleButton createButton( String name, Icon icon, String toolTip ) { JToggleButton button = new JToggleButton( name, icon ); + button.setToolTipText( toolTip ); button.setVerticalTextPosition( SwingConstants.BOTTOM ); button.setHorizontalTextPosition( SwingConstants.CENTER ); button.setAlignmentX( Component.CENTER_ALIGNMENT ); @@ -566,6 +577,8 @@ public class FlatFileChooserUI buttonGroup.clearSelection(); } + //---- interface PropertyChangeListener ---- + @Override public void propertyChange( PropertyChangeEvent e ) { switch( e.getPropertyName() ) { @@ -574,6 +587,41 @@ public class FlatFileChooserUI break; } } + + //---- interface Scrollable ---- + + @Override + public Dimension getPreferredScrollableViewportSize() { + if( getComponentCount() > 0 ) { + Insets insets = getInsets(); + int height = (getComponent( 0 ).getPreferredSize().height * 5) + insets.top + insets.bottom; + return new Dimension( getPreferredSize().width, height ); + } + return getPreferredSize(); + } + + @Override + public int getScrollableUnitIncrement( Rectangle visibleRect, int orientation, int direction ) { + if( orientation == SwingConstants.VERTICAL && getComponentCount() > 0 ) + return getComponent( 0 ).getPreferredSize().height; + + return getScrollableBlockIncrement( visibleRect, orientation, direction ) / 10; + } + + @Override + public int getScrollableBlockIncrement( Rectangle visibleRect, int orientation, int direction ) { + return (orientation == SwingConstants.VERTICAL) ? visibleRect.height : visibleRect.width; + } + + @Override + public boolean getScrollableTracksViewportWidth() { + return true; + } + + @Override + public boolean getScrollableTracksViewportHeight() { + return false; + } } //---- class ShortcutIcon ------------------------------------------------- diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatFileChooserTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatFileChooserTest.java index cdc0b3d9..1ef598e7 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatFileChooserTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatFileChooserTest.java @@ -40,6 +40,8 @@ import net.miginfocom.swing.*; public class FlatFileChooserTest extends FlatTestPanel { + private static ShortcutsCount shortcutsCount = ShortcutsCount.home; + public static void main( String[] args ) { // Locale.setDefault( Locale.FRENCH ); // Locale.setDefault( Locale.GERMAN ); @@ -52,8 +54,21 @@ public class FlatFileChooserTest FlatTestFrame frame = FlatTestFrame.create( args, "FlatFileChooserTest" ); UIManager.put( "FileChooser.shortcuts.filesFunction", (Function) files -> { + if( shortcutsCount == null ) + return files; + if( shortcutsCount == ShortcutsCount.empty ) + return new File[0]; + ArrayList list = new ArrayList<>( Arrays.asList( files ) ); - list.add( 0, new File( System.getProperty( "user.home" ) ) ); + if( shortcutsCount == ShortcutsCount.home ) + list.add( 0, new File( System.getProperty( "user.home" ) ) ); + else { + File home = new File( System.getProperty( "user.home" ) ); + File[] homeFiles = home.listFiles(); + int count = shortcutsCount.value; + for( int i = 0; i < count; i++ ) + list.add( i < homeFiles.length ? homeFiles[i] : new File( "Dummy " + i ) ); + } return list.toArray( new File[list.size()] ); } ); @@ -78,6 +93,8 @@ public class FlatFileChooserTest dialogTypeField.init( DialogType.class, false ); fileSelectionModeField.init( FileSelectionMode.class, false ); localesField.init( Locales.class, false ); + shortcutsCountField.init( ShortcutsCount.class, true ); + shortcutsCountField.setSelectedValue( shortcutsCount ); showControlButtonsCheckBox.setSelected( fileChooser1.getControlButtonsAreShown() ); multiSelectionCheckBox.setSelected( fileChooser1.isMultiSelectionEnabled() ); @@ -197,6 +214,11 @@ public class FlatFileChooserTest } ); } + private void shortcutsCountChanged() { + shortcutsCount = shortcutsCountField.getSelectedValue(); + fileChooser1.updateUI(); + } + private void printShortcutFiles() { printFiles( JavaCompatibility2.getChooserShortcutPanelFiles( fileChooser1.getFileSystemView() ) ); } @@ -263,6 +285,8 @@ public class FlatFileChooserTest printRootsButton = new JButton(); JLabel localesLabel = new JLabel(); localesField = new FlatTestEnumSelector<>(); + JLabel label12 = new JLabel(); + shortcutsCountField = new FlatTestEnumSelector<>(); JLabel label1 = new JLabel(); JLabel label2 = new JLabel(); JLabel label3 = new JLabel(); @@ -291,6 +315,7 @@ public class FlatFileChooserTest "[]" + "[]" + "[]" + + "[]" + "[]para" + "[]")); @@ -432,49 +457,57 @@ public class FlatFileChooserTest localesField.addActionListener(e -> localesChanged()); add(localesField, "cell 1 8 2 1"); + //---- label12 ---- + label12.setText("Shortcuts:"); + add(label12, "cell 0 9"); + + //---- shortcutsCountField ---- + shortcutsCountField.addActionListener(e -> shortcutsCountChanged()); + add(shortcutsCountField, "cell 1 9 2 1"); + //---- label1 ---- label1.setText("icons:"); - add(label1, "cell 0 9"); + add(label1, "cell 0 10"); //---- label2 ---- label2.setIcon(UIManager.getIcon("FileView.directoryIcon")); - add(label2, "cell 1 9 2 1"); + add(label2, "cell 1 10 2 1"); //---- label3 ---- label3.setIcon(UIManager.getIcon("FileView.fileIcon")); - add(label3, "cell 1 9 2 1"); + add(label3, "cell 1 10 2 1"); //---- label4 ---- label4.setIcon(UIManager.getIcon("FileView.computerIcon")); - add(label4, "cell 1 9 2 1"); + add(label4, "cell 1 10 2 1"); //---- label5 ---- label5.setIcon(UIManager.getIcon("FileView.hardDriveIcon")); - add(label5, "cell 1 9 2 1"); + add(label5, "cell 1 10 2 1"); //---- label6 ---- label6.setIcon(UIManager.getIcon("FileView.floppyDriveIcon")); - add(label6, "cell 1 9 2 1"); + add(label6, "cell 1 10 2 1"); //---- label7 ---- label7.setIcon(UIManager.getIcon("FileChooser.newFolderIcon")); - add(label7, "cell 1 9 2 1"); + add(label7, "cell 1 10 2 1"); //---- label8 ---- label8.setIcon(UIManager.getIcon("FileChooser.upFolderIcon")); - add(label8, "cell 1 9 2 1"); + add(label8, "cell 1 10 2 1"); //---- label9 ---- label9.setIcon(UIManager.getIcon("FileChooser.homeFolderIcon")); - add(label9, "cell 1 9 2 1"); + add(label9, "cell 1 10 2 1"); //---- label10 ---- label10.setIcon(UIManager.getIcon("FileChooser.detailsViewIcon")); - add(label10, "cell 1 9 2 1"); + add(label10, "cell 1 10 2 1"); //---- label11 ---- label11.setIcon(UIManager.getIcon("FileChooser.listViewIcon")); - add(label11, "cell 1 9 2 1"); + add(label11, "cell 1 10 2 1"); // JFormDesigner - End of component initialization //GEN-END:initComponents } @@ -500,6 +533,7 @@ public class FlatFileChooserTest private JButton printComboBoxFilesButton; private JButton printRootsButton; private FlatTestEnumSelector localesField; + private FlatTestEnumSelector shortcutsCountField; // JFormDesigner - End of variables declaration //GEN-END:variables //---- enum DialogType ---------------------------------------------------- @@ -553,4 +587,26 @@ public class FlatFileChooserTest this.value = value; } } + + //---- enum ShortcutsCount ------------------------------------------------ + + enum ShortcutsCount { + empty( -1 ), + home( -2 ), + zero( 0 ), + one( 1 ), + two( 2 ), + three( 3 ), + four( 4 ), + five( 5 ), + ten( 10 ), + twenty( 20 ), + thirty( 30 ); + + public final int value; + + ShortcutsCount( int value ) { + this.value = value; + } + } } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatFileChooserTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatFileChooserTest.jfd index 09f96167..76761fcc 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatFileChooserTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatFileChooserTest.jfd @@ -1,4 +1,4 @@ -JFDML JFormDesigner: "8.2.0.0.331" Java: "21" encoding: "UTF-8" +JFDML JFormDesigner: "8.2.3.0.386" Java: "21" encoding: "UTF-8" new FormModel { contentType: "form/swing" @@ -9,7 +9,7 @@ new FormModel { add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "ltr,insets dialog,hidemode 3" "$columnConstraints": "[][][grow]" - "$rowConstraints": "[grow,fill][][][][][][][][]para[]" + "$rowConstraints": "[grow,fill][][][][][][][][][]para[]" } ) { name: "this" add( new FormComponent( "javax.swing.JLabel" ) { @@ -273,71 +273,87 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 1 8 2 1" } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "label12" + "text": "Shortcuts:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 9" + } ) + add( new FormComponent( "com.formdev.flatlaf.testing.FlatTestEnumSelector" ) { + name: "shortcutsCountField" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + "JavaCodeGenerator.typeParameters": "ShortcutsCount" + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "shortcutsCountChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 9 2 1" + } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "label1" "text": "icons:" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 9" + "value": "cell 0 10" } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "label2" "icon": new com.jformdesigner.model.SwingIcon( 2, "FileView.directoryIcon" ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 9 2 1" + "value": "cell 1 10 2 1" } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "label3" "icon": new com.jformdesigner.model.SwingIcon( 2, "FileView.fileIcon" ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 9 2 1" + "value": "cell 1 10 2 1" } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "label4" "icon": new com.jformdesigner.model.SwingIcon( 2, "FileView.computerIcon" ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 9 2 1" + "value": "cell 1 10 2 1" } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "label5" "icon": new com.jformdesigner.model.SwingIcon( 2, "FileView.hardDriveIcon" ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 9 2 1" + "value": "cell 1 10 2 1" } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "label6" "icon": new com.jformdesigner.model.SwingIcon( 2, "FileView.floppyDriveIcon" ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 9 2 1" + "value": "cell 1 10 2 1" } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "label7" "icon": new com.jformdesigner.model.SwingIcon( 2, "FileChooser.newFolderIcon" ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 9 2 1" + "value": "cell 1 10 2 1" } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "label8" "icon": new com.jformdesigner.model.SwingIcon( 2, "FileChooser.upFolderIcon" ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 9 2 1" + "value": "cell 1 10 2 1" } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "label9" "icon": new com.jformdesigner.model.SwingIcon( 2, "FileChooser.homeFolderIcon" ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 9 2 1" + "value": "cell 1 10 2 1" } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "label10" "icon": new com.jformdesigner.model.SwingIcon( 2, "FileChooser.detailsViewIcon" ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 9 2 1" + "value": "cell 1 10 2 1" } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "label11" "icon": new com.jformdesigner.model.SwingIcon( 2, "FileChooser.listViewIcon" ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 9 2 1" + "value": "cell 1 10 2 1" } ) }, new FormLayoutConstraints( null ) { "location": new java.awt.Point( 0, 0 ) diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTestEnumSelector.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTestEnumSelector.java index 3298dd22..7aaaf263 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTestEnumSelector.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTestEnumSelector.java @@ -21,6 +21,7 @@ import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.Beans; +import java.util.Objects; import javax.swing.ButtonGroup; import javax.swing.JPanel; import javax.swing.JToggleButton; @@ -85,6 +86,17 @@ public class FlatTestEnumSelector return null; } + public void setSelectedValue( E value ) { + for( Component c : toolBar.getComponents() ) { + if( c instanceof JToggleButton && + Objects.equals( value, ((JToggleButton)c).getClientProperty( "FlatTestEnumSelector.value" ) ) ) + { + ((JToggleButton)c).setSelected( true ); + return; + } + } + } + public void addActionListener( ActionListener l ) { listenerList.add( ActionListener.class, l ); }