diff --git a/CHANGELOG.md b/CHANGELOG.md index 20bea270..0e38dd63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ FlatLaf Change Log - TabbedPane: Support scrolling tabs with mouse wheel (if `tabLayoutPolicy` is `SCROLL_TAB_LAYOUT`). (issue #40) +- TabbedPane: Repeat scrolling as long as arrow buttons are pressed. (issue #40) ## 0.43 diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java index ec85c592..52cb8836 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTabbedPaneUI.java @@ -34,6 +34,7 @@ import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; import java.awt.event.MouseWheelEvent; import java.awt.geom.Path2D; import java.awt.geom.Rectangle2D; @@ -46,6 +47,7 @@ import javax.swing.JComponent; import javax.swing.JTabbedPane; import javax.swing.JViewport; import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; @@ -266,11 +268,7 @@ public class FlatTabbedPaneUI @Override protected JButton createScrollButton( int direction ) { - // this method is invoked before installDefaults(), so we can not use color fields here - return new FlatArrowButton( direction, UIManager.getString( "Component.arrowType" ), - UIManager.getColor( "TabbedPane.foreground" ), - UIManager.getColor( "TabbedPane.disabledForeground" ), null, - UIManager.getColor( "TabbedPane.hoverColor" ) ); + return new FlatScrollableTabButton( direction ); } @Override @@ -571,6 +569,63 @@ public class FlatTabbedPaneUI return tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT; } + //---- class FlatScrollableTabButton -------------------------------------- + + protected static class FlatScrollableTabButton + extends FlatArrowButton + implements MouseListener + { + private Timer autoRepeatTimer; + + protected FlatScrollableTabButton( int direction ) { + // this method is invoked before installDefaults(), so we can not use color fields here + super( direction, UIManager.getString( "Component.arrowType" ), + UIManager.getColor( "TabbedPane.foreground" ), + UIManager.getColor( "TabbedPane.disabledForeground" ), null, + UIManager.getColor( "TabbedPane.hoverColor" ) ); + + addMouseListener( this ); + } + + @Override + public void mousePressed( MouseEvent e ) { + if( SwingUtilities.isLeftMouseButton( e ) && isEnabled() ) { + if( autoRepeatTimer == null ) { + // using same delays as in BasicScrollBarUI and BasicSpinnerUI + autoRepeatTimer = new Timer( 60, e2 -> { + if( isEnabled() ) + doClick(); + } ); + autoRepeatTimer.setInitialDelay( 300 ); + } + + autoRepeatTimer.start(); + } + } + + @Override + public void mouseReleased( MouseEvent e ) { + if( autoRepeatTimer != null ) + autoRepeatTimer.stop(); + } + + @Override + public void mouseClicked( MouseEvent e ) { + } + + @Override + public void mouseEntered( MouseEvent e ) { + if( autoRepeatTimer != null && isPressed() ) + autoRepeatTimer.start(); + } + + @Override + public void mouseExited( MouseEvent e ) { + if( autoRepeatTimer != null ) + autoRepeatTimer.stop(); + } + } + //---- class FlatWheelTabScroller ----------------------------------------- protected class FlatWheelTabScroller diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.java index 4b89082a..96a0b0d1 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.java @@ -32,6 +32,9 @@ import net.miginfocom.swing.*; public class FlatContainerTest extends FlatTestPanel { + private final int initialTabCount; + private boolean autoMoreTabs; + public static void main( String[] args ) { SwingUtilities.invokeLater( () -> { FlatTestFrame frame = FlatTestFrame.create( args, "FlatContainerTest" ); @@ -43,6 +46,7 @@ public class FlatContainerTest initComponents(); addInitialTabs( tabbedPane1, tabbedPane2, tabbedPane3, tabbedPane4 ); + initialTabCount = tabbedPane1.getTabCount(); } private void tabScrollChanged() { @@ -63,8 +67,6 @@ public class FlatContainerTest } } - private boolean autoMoreTabs; - private void showTabSeparatorsChanged() { Boolean showTabSeparators = showTabSeparatorsCheckBox.isSelected() ? true : null; putTabbedPanesClientProperty( TABBED_PANE_SHOW_TAB_SEPARATORS, showTabSeparators ); @@ -88,28 +90,30 @@ public class FlatContainerTest } private void moreTabsChanged() { - boolean moreTabs = moreTabsCheckBox.isSelected(); - addRemoveMoreTabs( tabbedPane1, moreTabs ); - addRemoveMoreTabs( tabbedPane2, moreTabs ); - addRemoveMoreTabs( tabbedPane3, moreTabs ); - addRemoveMoreTabs( tabbedPane4, moreTabs ); + moreTabsSpinnerChanged(); autoMoreTabs = false; + + moreTabsSpinner.setEnabled( moreTabsCheckBox.isSelected() ); } - private void addRemoveMoreTabs( JTabbedPane tabbedPane, boolean add ) { - if( add ) { - addTab( tabbedPane, "Tab 4", "tab content 4" ); - addTab( tabbedPane, "Tab 5", "tab content 5" ); - addTab( tabbedPane, "Tab 6", "tab content 6" ); - addTab( tabbedPane, "Tab 7", "tab content 7" ); - addTab( tabbedPane, "Tab 8", "tab content 8" ); - } else { - int tabCount = tabbedPane.getTabCount(); - if( tabCount > 4 ) { - for( int i = 0; i < 5; i++ ) - tabbedPane.removeTabAt( tabbedPane.getTabCount() - 1 ); - } + private void moreTabsSpinnerChanged() { + addRemoveMoreTabs( tabbedPane1 ); + addRemoveMoreTabs( tabbedPane2 ); + addRemoveMoreTabs( tabbedPane3 ); + addRemoveMoreTabs( tabbedPane4 ); + } + + private void addRemoveMoreTabs( JTabbedPane tabbedPane ) { + int oldTabCount = tabbedPane.getTabCount(); + int newTabCount = initialTabCount + (moreTabsCheckBox.isSelected() ? (Integer) moreTabsSpinner.getValue() : 0); + + if( newTabCount > oldTabCount ) { + for( int i = oldTabCount + 1; i <= newTabCount; i++ ) + addTab( tabbedPane, "Tab " + i, "tab content " + i ); + } else if( newTabCount < oldTabCount ) { + while( tabbedPane.getTabCount() > 4 ) + tabbedPane.removeTabAt( tabbedPane.getTabCount() - 1 ); } } @@ -162,6 +166,17 @@ public class FlatContainerTest tabbedPane.setIconAt( 1, icon ); } + private void customBorderChanged() { + Border border = customBorderCheckBox.isSelected() + ? new LineBorder( Color.magenta, 20 ) + : null; + + tabbedPane1.setBorder( border ); + tabbedPane2.setBorder( border ); + tabbedPane3.setBorder( border ); + tabbedPane4.setBorder( border ); + } + private void initComponents() { // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents JPanel panel9 = new JPanel(); @@ -182,12 +197,14 @@ public class FlatContainerTest tabbedPane4 = new JTabbedPane(); JPanel panel14 = new JPanel(); moreTabsCheckBox = new JCheckBox(); + moreTabsSpinner = new JSpinner(); tabScrollCheckBox = new JCheckBox(); showTabSeparatorsCheckBox = new JCheckBox(); hideContentSeparatorCheckBox = new JCheckBox(); - hasFullBorderCheckBox = new JCheckBox(); tabIconsCheckBox = new JCheckBox(); tabIconSizeSpinner = new JSpinner(); + customBorderCheckBox = new JCheckBox(); + hasFullBorderCheckBox = new JCheckBox(); CellConstraints cc = new CellConstraints(); //======== this ======== @@ -294,14 +311,13 @@ public class FlatContainerTest "insets 0,hidemode 3", // columns "[]" + - "[]" + - "[]" + "[fill]" + "[]" + - "[fill]" + + "[]" + "[fill]", // rows - "[center]")); + "[center]" + + "[]")); //---- moreTabsCheckBox ---- moreTabsCheckBox.setText("More tabs"); @@ -309,37 +325,48 @@ public class FlatContainerTest moreTabsCheckBox.addActionListener(e -> moreTabsChanged()); panel14.add(moreTabsCheckBox, "cell 0 0"); + //---- moreTabsSpinner ---- + moreTabsSpinner.setModel(new SpinnerNumberModel(4, 0, null, 1)); + moreTabsSpinner.setEnabled(false); + moreTabsSpinner.addChangeListener(e -> moreTabsSpinnerChanged()); + panel14.add(moreTabsSpinner, "cell 1 0"); + //---- tabScrollCheckBox ---- tabScrollCheckBox.setText("Use scroll layout"); tabScrollCheckBox.setMnemonic('S'); tabScrollCheckBox.addActionListener(e -> tabScrollChanged()); - panel14.add(tabScrollCheckBox, "cell 1 0,alignx left,growx 0"); + panel14.add(tabScrollCheckBox, "cell 2 0,alignx left,growx 0"); //---- showTabSeparatorsCheckBox ---- showTabSeparatorsCheckBox.setText("Show tab separators"); showTabSeparatorsCheckBox.addActionListener(e -> showTabSeparatorsChanged()); - panel14.add(showTabSeparatorsCheckBox, "cell 2 0"); + panel14.add(showTabSeparatorsCheckBox, "cell 3 0"); //---- hideContentSeparatorCheckBox ---- hideContentSeparatorCheckBox.setText("Hide content separator"); hideContentSeparatorCheckBox.addActionListener(e -> hideContentSeparatorChanged()); - panel14.add(hideContentSeparatorCheckBox, "cell 3 0"); - - //---- hasFullBorderCheckBox ---- - hasFullBorderCheckBox.setText("Show content border"); - hasFullBorderCheckBox.addActionListener(e -> hasFullBorderChanged()); - panel14.add(hasFullBorderCheckBox, "cell 4 0,alignx left,growx 0"); + panel14.add(hideContentSeparatorCheckBox, "cell 4 0"); //---- tabIconsCheckBox ---- tabIconsCheckBox.setText("Tab icons"); tabIconsCheckBox.addActionListener(e -> tabIconsChanged()); - panel14.add(tabIconsCheckBox, "cell 5 0"); + panel14.add(tabIconsCheckBox, "cell 0 1"); //---- tabIconSizeSpinner ---- tabIconSizeSpinner.setModel(new SpinnerListModel(new String[] {"16", "24", "32", "48", "64"})); tabIconSizeSpinner.setEnabled(false); tabIconSizeSpinner.addChangeListener(e -> tabIconsChanged()); - panel14.add(tabIconSizeSpinner, "cell 6 0"); + panel14.add(tabIconSizeSpinner, "cell 1 1"); + + //---- customBorderCheckBox ---- + customBorderCheckBox.setText("Custom border"); + customBorderCheckBox.addActionListener(e -> customBorderChanged()); + panel14.add(customBorderCheckBox, "cell 2 1"); + + //---- hasFullBorderCheckBox ---- + hasFullBorderCheckBox.setText("Show content border"); + hasFullBorderCheckBox.addActionListener(e -> hasFullBorderChanged()); + panel14.add(hasFullBorderCheckBox, "cell 4 1,alignx left,growx 0"); } panel9.add(panel14, cc.xywh(1, 11, 3, 1)); } @@ -353,12 +380,14 @@ public class FlatContainerTest private JTabbedPane tabbedPane2; private JTabbedPane tabbedPane4; private JCheckBox moreTabsCheckBox; + private JSpinner moreTabsSpinner; private JCheckBox tabScrollCheckBox; private JCheckBox showTabSeparatorsCheckBox; private JCheckBox hideContentSeparatorCheckBox; - private JCheckBox hasFullBorderCheckBox; private JCheckBox tabIconsCheckBox; private JSpinner tabIconSizeSpinner; + private JCheckBox customBorderCheckBox; + private JCheckBox hasFullBorderCheckBox; // JFormDesigner - End of variables declaration //GEN-END:variables //---- class Tab1Panel ---------------------------------------------------- diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.jfd index a68eed26..aa07cade 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatContainerTest.jfd @@ -131,8 +131,8 @@ new FormModel { } ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "insets 0,hidemode 3" - "$columnConstraints": "[][][][fill][][fill][fill]" - "$rowConstraints": "[center]" + "$columnConstraints": "[][fill][][][fill]" + "$rowConstraints": "[center][]" } ) { name: "panel14" "opaque": false @@ -147,6 +147,20 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 0" } ) + add( new FormComponent( "javax.swing.JSpinner" ) { + name: "moreTabsSpinner" + "model": new javax.swing.SpinnerNumberModel { + minimum: 0 + value: 4 + } + "enabled": false + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "moreTabsSpinnerChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 0" + } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "tabScrollCheckBox" "text": "Use scroll layout" @@ -156,7 +170,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "tabScrollChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 0,alignx left,growx 0" + "value": "cell 2 0,alignx left,growx 0" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "showTabSeparatorsCheckBox" @@ -166,7 +180,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "showTabSeparatorsChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 2 0" + "value": "cell 3 0" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "hideContentSeparatorCheckBox" @@ -176,17 +190,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "hideContentSeparatorChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 3 0" - } ) - add( new FormComponent( "javax.swing.JCheckBox" ) { - name: "hasFullBorderCheckBox" - "text": "Show content border" - auxiliary() { - "JavaCodeGenerator.variableLocal": false - } - addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "hasFullBorderChanged", false ) ) - }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 4 0,alignx left,growx 0" + "value": "cell 4 0" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "tabIconsCheckBox" @@ -196,7 +200,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "tabIconsChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 5 0" + "value": "cell 0 1" } ) add( new FormComponent( "javax.swing.JSpinner" ) { name: "tabIconSizeSpinner" @@ -215,7 +219,27 @@ new FormModel { } addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "tabIconsChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 6 0" + "value": "cell 1 1" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "customBorderCheckBox" + "text": "Custom border" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "customBorderChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 1" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "hasFullBorderCheckBox" + "text": "Show content border" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "hasFullBorderChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 4 1,alignx left,growx 0" } ) }, new FormLayoutConstraints( class com.jgoodies.forms.layout.CellConstraints ) { "gridY": 11