Featured Post

Applying Email Validation to a JavaFX TextField Using Binding

This example uses the same controller as in a previous post but adds a use case to support email validation.  A Commons Validator object is ...

Sunday, November 2, 2014

JavaFX Cut, Copy, and Paste from a ToolBar

Cut, Copy, and Paste work in JavaFX via the key commands without writing code or configuring FXML.  However, applications provide other means to the Clipboard functionality.  In a previous post, I wrote about adding Clipboard functionality to a MenuBar.  This post describes adding the Clipboard operations to a ToolBar.

In this app called "Maven POM Updater", there are three JavaFX buttons on the ToolBar: Cut, Copy, and Paste.  The following video shows them performing a Copy / Paste operation and a Cut / Paste operation.  Note that in the Cut case, the pasted text is inserted in the middle of a string.
video

FXML

In SceneBuilder, I add a ToolBar to the containing VBox.  I then add three Buttons to the ToolBar.  Each Button is assigned an fx:id and an onAction method.

ToolBar Structure as Defined in SceneBuilder

Selection and Copy

If you've read my previous post on adding Cut, Copy, and Paste to the MenuBar, you'd see that I polled a set of registered TextFields when the user initiates the Copy operation.  That works because the TextField retains its selected text when the MenuItem is displayed.  This approach doesn't work when pressing a Button; the selection disappears when the Button grabs the focus for highlighting (selection).

So, unfortunately, I have to keep track of the selected text and also the selected TextField component. I say "unfortunately", because maintaining data structures that keep track of UI events is often buggy.   A missed event can throw the data structures off.

My TextField-tracking code is initiated by an event on the TextFields: On Mouse Released.  Each TextField participating in Copy, Cut, and Paste has this method set in the FXML (for On Mouse Released).

    @FXML
    public void toolBarMouseReleased(MouseEvent evt) {
    
    TextField tf = (TextField)evt.getSource();
    String selectedText = tf.getSelectedText();
    IndexRange selectedRange = tf.getSelection();
    
    toolBarDelegate.updateToolBarForClipboard(tf, selectedText, selectedRange);
    }

toolBarDelegate is a Controller-like class that I employ to reduce the complexity of my main JavaFX Controller.  (See this post on applying the Delegate pattern to JavaFX Controllers.)  Though not a true Singleton, there is only one toolBarDelegate per Controller.  This is important to note because I'm saving the last selection information in member variables of this object (see "lastFocusedTF").

toolBarMouseReleased() queries the TextField source.  It records the selection and the last selected component.  This code will also work if there is no selection made.

The updateToolBarForClipboard() method records the selection and TextField instance.  It also updates the display of the Buttons themselves.  There isn't a convenient "hook" like I used for the MenuBar -- On Showing -- because the Buttons are visible throughout the running of the application. So, it's up to the TextFields to initiate a Button enable / disable operation when one is required.

TextField lastFocusedTF;
String lastSelectedText;
IndexRange lastSelectedRange;
public void updateToolBarForClipboard(TextField focusedTF, String selectedText, IndexRange selectedRange) {
if( systemClipboard == null ) {
systemClipboard = Clipboard.getSystemClipboard();
}
if( systemClipboard.hasString() ) {
adjustForClipboardContents();
} else {
adjustForEmptyClipboard();
}
if( StringUtils.isNotEmpty(focusedTF.getSelectedText()) ) {
adjustForSelection();

} else {
adjustForDeselection();
}
lastFocusedTF = focusedTF;
lastSelectedText = selectedText;
lastSelectedRange = selectedRange;
}

Finally, the tbCopy() operation is linked to the Button via On Action.  It delegates to toolBarDelegate.copy().  Since the copy() operation doesn't require any interaction with the TextField (pulling out the cut text for example), it only touches the lastSelectedText field.

public void copy() {
ClipboardContent content = new ClipboardContent();
content.putString(lastSelectedText);
systemClipboard.setContent(content);
}

Paste

The Paste operation is initiated from the On Action handler of the Paste ToolBar Button.  It's working with the member variables lastSelectedRange and lastFocusedTF.

public void paste() {
if( !systemClipboard.hasContent(DataFormat.PLAIN_TEXT) ) {
adjustForEmptyClipboard();
return;
}
String clipboardText = systemClipboard.getString();
String origText = lastFocusedTF.getText();
int endPos = 0;
String updatedText = "";
String firstPart = StringUtils.substring( origText, 0, lastSelectedRange.getStart() );
String lastPart = StringUtils.substring( origText, lastSelectedRange.getEnd(), StringUtils.length(origText) );

updatedText = firstPart + clipboardText + lastPart;
if( lastSelectedRange.getStart() == lastSelectedRange.getEnd() ) {
endPos = lastSelectedRange.getEnd() + StringUtils.length(clipboardText);
} else {
endPos = lastSelectedRange.getStart() + StringUtils.length(clipboardText);
}
lastFocusedTF.setText( updatedText );
lastFocusedTF.positionCaret( endPos );
}

Cut

Like copy(), cut() works with the lastSelectedText variable to fill the Clipboard.  However, it also needs to make some UI adjustments to the calling TextField which causes it to access the lastFocusedTF variable.

public void cut() {
if( log.isDebugEnabled() ) {
log.debug("[CUT]");
}
ClipboardContent content = new ClipboardContent();
content.putString(lastSelectedText);
systemClipboard.setContent(content);
String origText = lastFocusedTF.getText();
String firstPart = StringUtils.substring( origText, 0, lastSelectedRange.getStart() );
String lastPart = StringUtils.substring( origText, lastSelectedRange.getEnd(), StringUtils.length(origText) );
lastFocusedTF.setText( firstPart + lastPart );
lastFocusedTF.positionCaret( lastSelectedRange.getStart() );

}

You don't need any of this code to have Cut, Copy, and Paste work in your application.  However, most professional apps will provide several ways to call the same function.  A prior example presents MenuItems; this post showed ToolBar Buttons.

No comments:

Post a Comment