TextComponents: triple-click-and-drag now extends selection by whole lines

triple-click-and-drag does not work in theme editor because drag is enabled, anyway a triple-click now selects the whole line before dragging starts
This commit is contained in:
Karl Tauber
2021-10-22 13:14:01 +02:00
parent e13fb25f14
commit 8fa1eae352
5 changed files with 165 additions and 70 deletions

View File

@@ -15,7 +15,8 @@ FlatLaf Change Log
- TextField, FormattedTextField and PasswordField: Support leading and trailing
icons (set client property `JTextField.leadingIcon` or
`JTextField.trailingIcon` to an `Icon`). (PR #378; issue #368)
- TextComponents: Double-click-and-drag now extends selection by whole words.
- TextComponents: Double/triple-click-and-drag now extends selection by whole
words/lines.
- Theming improvements: Reworks core themes to make it easier to create new
themes (e.g. reduced explicit colors by using color functions). **Note**:
There are minor incompatible changes in FlatLaf properties files. (PR #390)

View File

@@ -19,14 +19,18 @@ package com.formdev.flatlaf.ui;
import static com.formdev.flatlaf.FlatClientProperties.*;
import java.awt.EventQueue;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.MouseEvent;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.JFormattedTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.UIResource;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultCaret;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.Utilities;
@@ -52,8 +56,9 @@ public class FlatCaret
private boolean wasTemporaryLost;
private boolean isMousePressed;
private boolean isWordSelection;
private int beginInitialWord;
private int endInitialWord;
private boolean isLineSelection;
private int dragSelectionStart;
private int dragSelectionEnd;
public FlatCaret( String selectAllOnFocusPolicy, boolean selectAllOnMouseClick ) {
this.selectAllOnFocusPolicy = selectAllOnFocusPolicy;
@@ -153,11 +158,34 @@ public class FlatCaret
isMousePressed = true;
super.mousePressed( e );
JTextComponent c = getComponent();
// left double-click starts word selection
isWordSelection = e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton( e ) && !e.isConsumed();
if( isWordSelection ) {
beginInitialWord = getMark();
endInitialWord = getDot();
// left triple-click starts line selection
isLineSelection = e.getClickCount() == 3 && SwingUtilities.isLeftMouseButton( e ) && (!e.isConsumed() || c.getDragEnabled());
// select line
// (this is also done in DefaultCaret.mouseClicked(), but this event is
// sent when the mouse is released, which is too late for triple-click-and-drag)
if( isLineSelection ) {
ActionMap actionMap = c.getActionMap();
Action selectLineAction = (actionMap != null)
? actionMap.get( DefaultEditorKit.selectLineAction )
: null;
if( selectLineAction != null ) {
selectLineAction.actionPerformed( new ActionEvent( c,
ActionEvent.ACTION_PERFORMED, null, e.getWhen(), e.getModifiers() ) );
}
}
// remember selection where word/line selection starts to keep it always selected while dragging
if( isWordSelection || isLineSelection ) {
int mark = getMark();
int dot = getDot();
dragSelectionStart = Math.min( dot, mark );
dragSelectionEnd = Math.max( dot, mark );
}
}
@@ -165,33 +193,29 @@ public class FlatCaret
public void mouseReleased( MouseEvent e ) {
isMousePressed = false;
isWordSelection = false;
isLineSelection = false;
super.mouseReleased( e );
}
@Override
public void mouseDragged( MouseEvent e ) {
if( isWordSelection && !e.isConsumed() && SwingUtilities.isLeftMouseButton( e ) ) {
// fix Swing's double-click-and-drag behavior so that dragging after
// a double-click extends selection by whole words
if( (isWordSelection || isLineSelection) &&
!e.isConsumed() && SwingUtilities.isLeftMouseButton( e ) )
{
// fix Swing's double/triple-click-and-drag behavior so that dragging after
// a double/triple-click extends selection by whole words/lines
JTextComponent c = getComponent();
int pos = c.viewToModel( e.getPoint() );
if( pos < 0 )
return;
try {
int mark;
int dot;
if( pos > endInitialWord ) {
mark = beginInitialWord;
dot = Utilities.getWordEnd( c, pos );
} else if( pos < beginInitialWord ) {
mark = endInitialWord;
dot = Utilities.getWordStart( c, pos );
} else {
mark = beginInitialWord;
dot = endInitialWord;
}
select( mark, dot );
if( pos > dragSelectionEnd )
select( dragSelectionStart, isWordSelection ? Utilities.getWordEnd( c, pos ) : Utilities.getRowEnd( c, pos ) );
else if( pos < dragSelectionStart )
select( dragSelectionEnd, isWordSelection ? Utilities.getWordStart( c, pos ) : Utilities.getRowStart( c, pos ) );
else
select( dragSelectionStart, dragSelectionEnd );
} catch( BadLocationException ex ) {
UIManager.getLookAndFeel().provideErrorFeedback( c );
}

View File

@@ -81,6 +81,14 @@ public class FlatTextComponentsTest
}
}
private void dragEnabledChanged() {
boolean dragEnabled = dragEnabledCheckBox.isSelected();
textField.setDragEnabled( dragEnabled );
textArea.setDragEnabled( dragEnabled );
textPane.setDragEnabled( dragEnabled );
editorPane.setDragEnabled( dragEnabled );
}
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
JLabel textFieldLabel = new JLabel();
@@ -138,14 +146,15 @@ public class FlatTextComponentsTest
JComboBox<String> comboBox6 = new JComboBox<>();
JSpinner spinner5 = new JSpinner();
JLabel label5 = new JLabel();
JTextField textField4 = new JTextField();
textField = new JTextField();
dragEnabledCheckBox = new JCheckBox();
JLabel label6 = new JLabel();
JScrollPane scrollPane2 = new JScrollPane();
JTextArea textArea2 = new JTextArea();
textArea = new JTextArea();
JScrollPane scrollPane4 = new JScrollPane();
JTextPane textPane4 = new JTextPane();
textPane = new JTextPane();
JScrollPane scrollPane6 = new JScrollPane();
JEditorPane editorPane5 = new JEditorPane();
editorPane = new JEditorPane();
JPopupMenu popupMenu1 = new JPopupMenu();
JMenuItem cutMenuItem = new JMenuItem();
JMenuItem copyMenuItem = new JMenuItem();
@@ -535,10 +544,16 @@ public class FlatTextComponentsTest
label5.setName("label5");
add(label5, "cell 0 16");
//---- textField4 ----
textField4.setText("123 456 789 abc def");
textField4.setName("textField4");
add(textField4, "cell 1 16 2 1,growx");
//---- textField ----
textField.setText("123 456 789 abc def");
textField.setName("textField");
add(textField, "cell 1 16 2 1,growx");
//---- dragEnabledCheckBox ----
dragEnabledCheckBox.setText("Drag enabled");
dragEnabledCheckBox.setName("dragEnabledCheckBox");
dragEnabledCheckBox.addActionListener(e -> dragEnabledChanged());
add(dragEnabledCheckBox, "cell 3 16 2 1,alignx left,growx 0");
//---- label6 ----
label6.setText("<html>JTextArea<br>JTextPane<br>JEditorPane</html>");
@@ -549,10 +564,10 @@ public class FlatTextComponentsTest
{
scrollPane2.setName("scrollPane2");
//---- textArea2 ----
textArea2.setText("1 123 456 789 abc def\n2 123 456 789 abc def\n3 123 456 789 abc def\n4 123 456 789 abc def\n5 123 456 789 abc def\n6 123 456 789 abc def\n7 123 456 789 abc def\n8 123 456 789 abc def");
textArea2.setName("textArea2");
scrollPane2.setViewportView(textArea2);
//---- textArea ----
textArea.setText("1 123 456 789 abc def\n2 123 456 789 abc def\n3 123 456 789 abc def\n4 123 456 789 abc def\n5 123 456 789 abc def\n6 123 456 789 abc def\n7 123 456 789 abc def\n8 123 456 789 abc def");
textArea.setName("textArea");
scrollPane2.setViewportView(textArea);
}
add(scrollPane2, "cell 1 17 4 1,growx");
@@ -560,10 +575,10 @@ public class FlatTextComponentsTest
{
scrollPane4.setName("scrollPane4");
//---- textPane4 ----
textPane4.setText("1 123 456 789 abc def\n2 123 456 789 abc def\n3 123 456 789 abc def\n4 123 456 789 abc def\n5 123 456 789 abc def\n6 123 456 789 abc def\n7 123 456 789 abc def\n8 123 456 789 abc def");
textPane4.setName("textPane4");
scrollPane4.setViewportView(textPane4);
//---- textPane ----
textPane.setText("1 123 456 789 abc def\n2 123 456 789 abc def\n3 123 456 789 abc def\n4 123 456 789 abc def\n5 123 456 789 abc def\n6 123 456 789 abc def\n7 123 456 789 abc def\n8 123 456 789 abc def");
textPane.setName("textPane");
scrollPane4.setViewportView(textPane);
}
add(scrollPane4, "cell 1 17 4 1,growx");
@@ -571,10 +586,10 @@ public class FlatTextComponentsTest
{
scrollPane6.setName("scrollPane6");
//---- editorPane5 ----
editorPane5.setText("1 123 456 789 abc def\n2 123 456 789 abc def\n3 123 456 789 abc def\n4 123 456 789 abc def\n5 123 456 789 abc def\n6 123 456 789 abc def\n7 123 456 789 abc def\n8 123 456 789 abc def");
editorPane5.setName("editorPane5");
scrollPane6.setViewportView(editorPane5);
//---- editorPane ----
editorPane.setText("1 123 456 789 abc def\n2 123 456 789 abc def\n3 123 456 789 abc def\n4 123 456 789 abc def\n5 123 456 789 abc def\n6 123 456 789 abc def\n7 123 456 789 abc def\n8 123 456 789 abc def");
editorPane.setName("editorPane");
scrollPane6.setViewportView(editorPane);
}
add(scrollPane6, "cell 1 17 4 1,growx");
@@ -612,6 +627,11 @@ public class FlatTextComponentsTest
private JSpinner bottomPaddingField;
private JCheckBox leadingIconCheckBox;
private JCheckBox trailingIconCheckBox;
private JTextField textField;
private JCheckBox dragEnabledCheckBox;
private JTextArea textArea;
private JTextPane textPane;
private JEditorPane editorPane;
// JFormDesigner - End of variables declaration //GEN-END:variables
//---- TestIcon -----------------------------------------------------------

View File

@@ -423,11 +423,24 @@ new FormModel {
"value": "cell 0 16"
} )
add( new FormComponent( "javax.swing.JTextField" ) {
name: "textField4"
name: "textField"
"text": "123 456 789 abc def"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 16 2 1,growx"
} )
add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "dragEnabledCheckBox"
"text": "Drag enabled"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "dragEnabledChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 3 16 2 1,alignx left,growx 0"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "label6"
"text": "<html>JTextArea<br>JTextPane<br>JEditorPane</html>"
@@ -437,8 +450,11 @@ new FormModel {
add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
name: "scrollPane2"
add( new FormComponent( "javax.swing.JTextArea" ) {
name: "textArea2"
name: "textArea"
"text": "1 123 456 789 abc def\n2 123 456 789 abc def\n3 123 456 789 abc def\n4 123 456 789 abc def\n5 123 456 789 abc def\n6 123 456 789 abc def\n7 123 456 789 abc def\n8 123 456 789 abc def"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 17 4 1,growx"
@@ -446,8 +462,11 @@ new FormModel {
add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
name: "scrollPane4"
add( new FormComponent( "javax.swing.JTextPane" ) {
name: "textPane4"
name: "textPane"
"text": "1 123 456 789 abc def\n2 123 456 789 abc def\n3 123 456 789 abc def\n4 123 456 789 abc def\n5 123 456 789 abc def\n6 123 456 789 abc def\n7 123 456 789 abc def\n8 123 456 789 abc def"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 17 4 1,growx"
@@ -455,8 +474,11 @@ new FormModel {
add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
name: "scrollPane6"
add( new FormComponent( "javax.swing.JEditorPane" ) {
name: "editorPane5"
name: "editorPane"
"text": "1 123 456 789 abc def\n2 123 456 789 abc def\n3 123 456 789 abc def\n4 123 456 789 abc def\n5 123 456 789 abc def\n6 123 456 789 abc def\n7 123 456 789 abc def\n8 123 456 789 abc def"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 17 4 1,growx"

View File

@@ -18,12 +18,16 @@ package com.formdev.flatlaf.themeeditor;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.text.BadLocationException;
import javax.swing.text.Caret;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.JTextComponent;
import javax.swing.text.Utilities;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaUI;
@@ -70,59 +74,83 @@ class FlatRSyntaxTextAreaUI
extends ConfigurableCaret
{
private boolean isWordSelection;
private int beginInitialWord;
private int endInitialWord;
private boolean isLineSelection;
private int dragSelectionStart;
private int dragSelectionEnd;
@Override
public void mousePressed( MouseEvent e ) {
super.mousePressed( e );
JTextComponent c = getComponent();
// left double-click starts word selection
isWordSelection = e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton( e ) && !e.isConsumed();
if( isWordSelection ) {
beginInitialWord = getMark();
endInitialWord = getDot();
// left triple-click starts line selection
isLineSelection = e.getClickCount() == 3 && SwingUtilities.isLeftMouseButton( e ) && (!e.isConsumed() || c.getDragEnabled());
// select line
// (this is also done in DefaultCaret.mouseClicked(), but this event is
// sent when the mouse is released, which is too late for triple-click-and-drag)
if( isLineSelection ) {
ActionMap actionMap = c.getActionMap();
Action selectLineAction = (actionMap != null)
? actionMap.get( DefaultEditorKit.selectLineAction )
: null;
if( selectLineAction != null ) {
selectLineAction.actionPerformed( new ActionEvent( c,
ActionEvent.ACTION_PERFORMED, null, e.getWhen(), e.getModifiers() ) );
}
}
// remember selection where word/line selection starts to keep it always selected while dragging
if( isWordSelection || isLineSelection ) {
int mark = getMark();
int dot = getDot();
dragSelectionStart = Math.min( dot, mark );
dragSelectionEnd = Math.max( dot, mark );
}
}
@Override
public void mouseReleased( MouseEvent e ) {
isWordSelection = false;
isLineSelection = false;
super.mouseReleased( e );
}
@Override
public void mouseDragged( MouseEvent e ) {
if( isWordSelection && !e.isConsumed() && SwingUtilities.isLeftMouseButton( e ) ) {
// fix Swing's double-click-and-drag behavior so that dragging after
// a double-click extends selection by whole words
if( (isWordSelection || isLineSelection) &&
!e.isConsumed() && SwingUtilities.isLeftMouseButton( e ) )
{
// fix Swing's double/triple-click-and-drag behavior so that dragging after
// a double/triple-click extends selection by whole words/lines
JTextComponent c = getComponent();
int pos = c.viewToModel( e.getPoint() );
if( pos < 0 )
return;
try {
int mark;
int dot;
if( pos > endInitialWord ) {
mark = beginInitialWord;
dot = Utilities.getWordEnd( c, pos );
} else if( pos < beginInitialWord ) {
mark = endInitialWord;
dot = Utilities.getWordStart( c, pos );
} else {
mark = beginInitialWord;
dot = endInitialWord;
}
if( mark != getMark() )
setDot( mark );
if( dot != getDot() )
moveDot( dot );
if( pos > dragSelectionEnd )
select( dragSelectionStart, isWordSelection ? Utilities.getWordEnd( c, pos ) : Utilities.getRowEnd( c, pos ) );
else if( pos < dragSelectionStart )
select( dragSelectionEnd, isWordSelection ? Utilities.getWordStart( c, pos ) : Utilities.getRowStart( c, pos ) );
else
select( dragSelectionStart, dragSelectionEnd );
} catch( BadLocationException ex ) {
UIManager.getLookAndFeel().provideErrorFeedback( c );
}
} else
super.mouseDragged( e );
}
private void select( int mark, int dot ) {
if( mark != getMark() )
setDot( mark );
if( dot != getDot() )
moveDot( dot );
}
}
}