Intercept Paste Event on JavaFx HTMLEditor - java

Hello I want to know how to intercept the Paste Event in the JavaFX HTMLEditor.

You can't. The HTMLEditor uses a WebPage internally. Basically during a paste event it sends a "paste" command via
private boolean executeCommand(String command, String value) {
return webPage.executeCommand(command, value);
}
and then a
twkExecuteCommand(getPage(), command, value);
However, you could intercept everything that implicitly invokes the paste event like the button click or a CTRL+V key combination and depending on what you wish to do consume the event.
Example:
public class HTMLEditorSample extends Application {
#Override
public void start(Stage stage) {
final HTMLEditor htmlEditor = new HTMLEditor();
Scene scene = new Scene(htmlEditor, 800, 600);
stage.setScene(scene);
stage.show();
Button button = (Button) htmlEditor.lookup(".html-editor-paste");
button.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> {
System.out.println("paste pressed");
// e.consume();
});
htmlEditor.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if( e.isControlDown() && e.getCode() == KeyCode.V) {
System.out.println( "CTRL+V pressed");
// e.consume();
}
});
}
public static void main(String[] args) {
launch(args);
}
}
As to your other question with pasting only plain text to the html editor, you could do it like this:
public class HTMLEditorSample extends Application {
#Override
public void start(Stage stage) {
final HTMLEditor htmlEditor = new HTMLEditor();
Scene scene = new Scene(htmlEditor, 800, 600);
stage.setScene(scene);
stage.show();
Button button = (Button) htmlEditor.lookup(".html-editor-paste");
button.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> {
modifyClipboard();
});
htmlEditor.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if( e.isControlDown() && e.getCode() == KeyCode.V) {
modifyClipboard();
}
});
}
private void modifyClipboard() {
Clipboard clipboard = Clipboard.getSystemClipboard();
String plainText = clipboard.getString();
ClipboardContent content = new ClipboardContent();
content.putString(plainText);
clipboard.setContent(content);
}
public static void main(String[] args) {
launch(args);
}
}
It's a workaround and ugly because you should never modify the clipboard content unless the user wants to, but it works. On the other hand, it may be possible to revert the clipboard content back to its original state after the paste operation.
Edit:
Here's how you could access the contextmenu, e. g. for disabling it:
WebView webView = (WebView) htmlEditor.lookup(".web-view");
webView.setContextMenuEnabled(false);

Don't try this line, because it's always return a NULL:
Button button = (Button) htmlEditor.lookup(".html-editor-paste");
The only way to get the Paste button from HTMLEditor is the solution from #taha:
htmlEditor.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> { if (e.getTarget().toString().contains("html-editor-paste")) { System.out.println("paste pressed"); });

Related

Problem with JavaFX pane.getChildren().remove() at lambda

I have written a code:
public class MousePosition extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
Text text = new Text();
Scene scene = new Scene(pane, 300, 100);
primaryStage.setScene(scene);
primaryStage.setTitle("Exercise 8");
primaryStage.show();
scene.setOnMouseReleased(e -> pane.getChildren().clear());
scene.setOnMouseClicked(e -> {
showCoordinates(pane, text, e);
});
scene.setOnMousePressed(e -> {
showCoordinates(pane, text, e);
});
}
private void showCoordinates(Pane pane, Text text, MouseEvent e) {
text.setText("(" + e.getX() + ", " + e.getY() + ")");
text.setX(e.getX());
text.setY(e.getY());
if (!pane.getChildren().contains(text))
pane.getChildren().add(text);
}
}
It should:
display the mouse position when the mouse button is clicked (see
Figure 15.28a), and the other displays the mouse position when the
mouse button is pressed and ceases to display it when the mouse button
is released.
But my lambda doesn't work:
scene.setOnMouseReleased(e -> pane.getChildren().clear());
It simply does nothing.
When I changed it to scene.setOnMouseReleased(e -> text.setText("")); It also does nothing. But when I use scene.setOnMouseReleased(e -> System.out.println("0"));, it works.
please try to explicitly update the UI component(s) in your eventhandler on the javafx application thread:
scene.setOnMouseReleased(e -> Platform.runLater(() -> pane.getChildren().clear()));
https://docs.oracle.com/javase/8/javafx/api/javafx/application/Platform.html#runLater-java.lang.Runnable-

Get visible state of Node in JavaFX 8

I need to detect if a node is currently displaying.
I.e. if my Node is in a TabPane, I need to know if it is in a selected tab or not.
In the example, I want to know when the HBox is displaying.The visibleProperty and managedProperty of Node, does not seem to help me:
public class VisibleTest extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
TabPane tabpane = new TabPane();
tabpane.getTabs().add(new Tab("Tab1", new Label("Label1")));
HBox hbox = new HBox(new Label("Label2"));
hbox.setStyle("-fx-background-color: aquamarine;");
hbox.visibleProperty().addListener((observable, oldValue, newValue) -> {
System.out.println("Hbox visible changed. newValue: " + newValue);
});
hbox.managedProperty().addListener((observable, oldValue, newValue) -> {
System.out.println("Hbox managed changed. newValue: " + newValue);
});
Tab tab2 = new Tab("tab2", hbox);
tabpane.getTabs().add(tab2);
primaryStage.setScene(new Scene(tabpane));
primaryStage.setWidth(600);
primaryStage.setHeight(500);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I know, it is possible to listen on the selectedProperty state of the tab, but this does not solve my real problem.
Node.impl_isTreeVisible() does what I want, but this is depricated API.
Any ideas?
------------------------------------ update--------------------
I realize the code example above does not explain well what I'm trying to accomplish. Below is some Swing code that kind of demonstrates what I am trying to accomplish in JavaFX. Detect if the JComponent/Node is visible/shown, and based on that state, start or stop background processes. How would the constructor look like if it was a javaFX class.
public class SwingVisible extends JComponent {
String instanceNR;
Thread instanceThread;
boolean doExpensiveStuff = false;
public SwingVisible(String instanceNR) {
this.instanceNR = instanceNR;
this.setLayout(new FlowLayout());
this.add(new JLabel(instanceNR));
instanceThread = new Thread(new Runnable() {
#Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (doExpensiveStuff) {
/*
* do expensive stuff.
*/
System.out.println(instanceNR + " is visible " + isVisible());
}
}
}
});
/*
* How to do this in FX?
*/
addComponentListener(new ComponentAdapter() {
#Override
public void componentShown(ComponentEvent e) {
if (!instanceThread.isAlive()) {
instanceThread.start();
}
doExpensiveStuff = true;
}
#Override
public void componentHidden(ComponentEvent e) {
doExpensiveStuff = false;
}
});
}
public static void main(String[] args) {
/*
* This block represents code that is external to my library. End user
* can put instances of SwingVisible in JTabbedPanes, JFrames, JWindows,
* or other JComponents. How many instances there will bee is not in my
* control.
*/
JTabbedPane jtp = new JTabbedPane();
jtp.add("tab1", new SwingVisible("1"));
jtp.add("tab2", new SwingVisible("2"));
jtp.add("tab3", new SwingVisible("3"));
JFrame f = new JFrame("test");
f.setContentPane(jtp);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(300, 300);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
Output when tab1 is selected:
1 is visible true
1 is visible true
1 is visible true
...
Output when tab2 is selected:
2 is visible true
2 is visible true
2 is visible true
...
You can use Tab's selectedProperty to know if it is selected or not, and by extension if its content is visible or not. It is a boolean property.
I've converted your Swing code to JavaFX based on your initial JavaFX example:
public class VisibleTest extends Application {
public class FXVisible extends Tab {
FXVisible(String id) {
super(id, new Label(id));
Timeline thread = new Timeline(
new KeyFrame(Duration.ZERO, e -> {
if (isSelected()) {
// do expensive stuff
System.out.println(id + " is visible");
}
}),
new KeyFrame(Duration.seconds(1))
);
thread.setCycleCount(Timeline.INDEFINITE);
selectedProperty().addListener((selectedProperty, wasSelected, isSelected) -> {
if (isSelected) {
if (thread.getStatus() != Status.RUNNING) {
System.out.println(id + " starting thread");
thread.play();
}
}
// else, it is not selected -> content not shown
});
}
}
#Override
public void start(Stage primaryStage) throws Exception {
TabPane tabpane = new TabPane();
tabpane.getTabs().add(new FXVisible("1"));
tabpane.getTabs().add(new FXVisible("2"));
tabpane.getTabs().add(new FXVisible("3"));
// add as many as you want
primaryStage.setScene(new Scene(tabpane));
primaryStage.setWidth(600);
primaryStage.setHeight(500);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I replaced your thread with a JavaFX Timeline. Your question is not about this topic so I won't go into details here, though it's self explanatory.
I don't understand why in the Swing example you have a listener changing a boolean that indicates if the component is visible or not when you can just call isVisible() directly in the thread (see comments below for a note about threading). This is why in my code above I took the approach of checking isSelected() directly with no self-declared boolean. If you need to revert to your design it's rather straightforward. Just noting this for clarity.
The ComponentListener can be replaced with a change listener on selectedProperty() and querying the new value. Just be sure that your example does what it's supposed to do: the first time the tab is selected the thread/timer starts. After that the thread/timer does nothing. You might have wanted to pause the computation for non-displaying content. Again, just noting it because it seemed like a potential mistake to me, otherwise you're fine.
Updated answer.
tab2.getContent().isVisible();
It seems to me that my original answer is correct. If not, you need to ask your question in a better way. You want to know when the hbox is visible(meaning you can see the hbox on the screen).
tabpane.getSelectionModel().selectedItemProperty().addListener((obsVal, oldTab, newTab)->{
System.out.println(newTab.getText());
if(newTab.getText().equals("tab2"))
{
//You can use this code to set the hbox visibility, that way you can force the behavior you are looking for.
hbox.setVisible(true);
System.out.println("hbox is visible!");
}
else
{
//You can use this code to set the hbox visibility, that way you can force the behavior you are looking for.
hbox.setVisible(false);
System.out.println("hbox is not visible!");
}
});
From quick checking this seemed to work for both checking the window is showing and that the tab it is in is displaying. I have also checked and it seems to work as expected for titled panes too that are collapsible.
public static boolean detectVisible( Node node )
{
Node current = node;
while( current != null ) {
if( !current.isVisible() ) {
return false;
}
current = current.getParent();
}
Window window = Optional.of( node ).map( Node::getScene ).map( Scene::getWindow ).orElse( null );
if( window == null ) {
return false;
}
if( window instanceof Stage && ( (Stage) window ).isIconified() ) {
return false;
}
return window.isShowing();
}

JavaFX: Disable all components while a process is running and show progress indicator

I have a method that read values from the the database and returns a Map<Integer,String>. This method takes some time to return the map.
Till the time values are getting read I want a progress indicator(only loading ring like indicator will be enough,no need for progress bar) to be displayed on screen and all other components should be disabled till the time progress bar is shown.
public void scanDevice() {
ObservableList<TextField> list = FXCollections.observableArrayList(vehicleId, vehicleName, deviceType,
offboardBroker1, offboardBroker2, postfixQueue, pKIServer);
editedValuesMap.clear();
// devicePlugged = true;
if (cbChooseProject.getSelectionModel().getSelectedItem() != null) {
try {
devicePlugged = dsAdapter.getAdapter();
if (devicePlugged) {
if (bScanDevice.isFocused()) {
readMap = new HashMap<Integer, String>();
//Process Start
readMap = dsAdapter.initScan();
//Process End
if (!readMap.isEmpty() && readMap != null) {
isWritten = true;
isDeviceSideEnabled();
editDeviceContents.setDisable(false);
vehicleId.setText(readMap.get(0));
vehicleName.setText(readMap.get(1));
deviceType.setText(readMap.get(2));
offboardBroker1.setText(readMap.get(3));
offboardBroker2.setText(readMap.get(4));
postfixQueue.setText(readMap.get(5));
pKIServer.setText(readMap.get(6));
lContentsSerialNo.setText(readMap.get(7));
}
}
}
You could disabled all nodes with a method like the following but if you are also wanting to wait while something is happening an overlay using StackPanes may be the preferred choice.
public void setNodesDiabled(boolean disable, Node... nodes) {
for(Node node : nodes) {
node.setDisable(disable);
}
}
With an arbitrary node count, you can disable and re-enable as many nodes that are relevant to the process. It also helps to clean up as you won't have several node.setDisable(true); node2.setDisable(true); and so on.
Here in this example you won't need setNodesDisabled() because the StackPane overlay prevents clicking anything other than what's inside it. The background color is gray with 70% alpha so that you can tell it's an overlay.
public class ProgressExample extends Application {
public StackPane layout, main, progress;
public StackPane createProgressPane() {
ProgressIndicator indicator = new ProgressIndicator();
indicator.setMaxHeight(50);
indicator.setMaxWidth(50);
StackPane pane = new StackPane();
pane.setAlignment(Pos.CENTER);
pane.setStyle("-fx-background-color: rgba(160,160,160,0.7)");
pane.getChildren().add(indicator);
Task<Void> task = new Task<Void>(){
protected Void call() throws Exception {
// Your process here.
// Any changes to UI components must be inside Platform.runLater() or else it will hang.
Thread.sleep(2000);
Platform.runLater(() -> {
layout.getChildren().remove(pane);
});
return null;
}
};
new Thread(task).start();
return pane;
}
public StackPane createMainPane() {
Label label = new Label("Hello World!");
label.setFont(Font.font("Tahoma", FontWeight.SEMI_BOLD, 16));
Button start = new Button("Start Process");
start.setOnAction(action -> {
progress = createProgressPane();
layout.getChildren().add(progress);
});
VBox vbox = new VBox(10);
vbox.setAlignment(Pos.CENTER);
vbox.getChildren().addAll(label, start);
vbox.setPadding(new Insets(10,10,10,10));
StackPane pane = new StackPane();
pane.getChildren().add(vbox);
return pane;
}
public void start(Stage stage) throws Exception {
main = createMainPane();
layout = new StackPane();
layout.getChildren().add(main);
Scene scene = new Scene(layout, 900, 550);
stage.setScene(scene);
stage.setTitle("Progress Example");
stage.setOnCloseRequest(e -> {
Platform.exit();
System.exit(0);
});
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I believe the problem is that you are trying to change the values of TextFields inside the Task which is not the FX application thread which is why you are getting Not on FX application thread. To fix this you need to put your lines that modify nodes inside a Platform.runLater() like the following to your if statement.
if (readMap != null && !readMap.isEmpty()) { // Swap the order, can't check empty if it's null.
isWritten = true;
isDeviceSideEnabled();
Platform.runLater(() -> {
editDeviceContents.setDisable(false);
vehicleId.setText(readMap.get(0));
vehicleName.setText(readMap.get(1));
deviceType.setText(readMap.get(2));
offboardBroker1.setText(readMap.get(3));
offboardBroker2.setText(readMap.get(4));
postfixQueue.setText(readMap.get(5));
pKIServer.setText(readMap.get(6));
lContentsSerialNo.setText(readMap.get(7));
});
}
Here is an SSCCE:
It uses a Service that can be started more than once. It is not completebut something to start with.
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
Service<Void> serv = new Service<Void>() {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
int maxWork = 10;
for (int i = 0; i < maxWork; i++) {
Thread.sleep(1000);
updateProgress(i + 1, maxWork);
}
return null;
}
#Override
protected void succeeded() {
super.succeeded();
updateProgress(1, 1);
}
#Override
protected void cancelled() {
super.cancelled();
updateProgress(1, 1);
}
#Override
protected void failed() {
super.failed();
updateProgress(1, 1);
}
};
}
};
ProgressIndicator pi = new ProgressIndicator();
pi.progressProperty().bind(serv.progressProperty());
Button bStart = new Button("Start");
bStart.setOnAction(e -> {
serv.reset();
serv.start();
});
root.setCenter(bStart);
root.setBottom(pi);
primaryStage.setScene(scene);
primaryStage.show();
pi.getScene().getRoot().disableProperty().bind(serv.runningProperty());
}
public static void main(String[] args) {
launch(args);
}
}
In CSS I added:
.progress-indicator:disabled {
-fx-opacity: 1;
}

How to change the position of a RadioButton's label?

By default RadioButtons have their text label to the right of the button. I want the label to appear below the button instead. I found an old discussion on the Oracle forums but the solutions aren't great or just don't work.
I can create a custom component with a text-less radio button and a separate text label and position them as in a VBox. But then only the button itself responds to user events and not the whole thing.
Is there no simple way to reposition the label?
There is no "simple" way to do this (simple means setting a single property or something like this).
As a workaround you could do something like you mentioned with a VBox, but with a Label: You can set the RadioButton as the graphic of the Label and set the contentDisplayProperty to TOP (RadioButton is placed on top of the Label). And then you can add an event handler on the Label to select the RadioButton on click.
An example with this approach
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root, 400, 400);
HBox hbox = new HBox();
hbox.getChildren().add(createRadioLabel("Radio on the left", ContentDisplay.LEFT));
hbox.getChildren().add(createRadioLabel("Radio on the top", ContentDisplay.TOP));
hbox.getChildren().add(createRadioLabel("Radio on the bottom", ContentDisplay.BOTTOM));
hbox.getChildren().add(createRadioLabel("Radio on the right", ContentDisplay.RIGHT));
hbox.setSpacing(30);
root.setCenter(hbox);
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
private Label createRadioLabel(String text, ContentDisplay cd) {
Label label = new Label(text);
label.setGraphic(new RadioButton());
label.setContentDisplay(cd);
label.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
RadioButton radioButton = (RadioButton) ((Label) e.getSource()).getGraphic();
radioButton.requestFocus();
radioButton.setSelected(!radioButton.isSelected());
});
return label;
}
public static void main(String[] args) {
launch(args);
}
}
And the produced RadioButtons:
Alternatively, if you want to have the text of the RadioButton rotated around the dot, you can use CSS rotations, with the attribute -fx-rotate:
.radio-button { -fx-rotate:180; }
.radio-button > .text { -fx-rotate: 180; }
The first selector will rotate the whole RadioButton, so as result the text will be placed on the left side of the "dot", upside down. The second selector rotates the text back to the normal direction.
Example
This example shows a RadioButton whose Text can be placed to any side of the "dot" specified by a ComboBox selection.
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
RadioButton rb = new RadioButton("In all directions);
ComboBox<PseudoClass> combo = new ComboBox<>();
combo.getItems().addAll(PseudoClass.getPseudoClass("left"),
PseudoClass.getPseudoClass("top"),
PseudoClass.getPseudoClass("right"),
PseudoClass.getPseudoClass("bottom"));
combo.valueProperty().addListener((obs, oldVal, newVal) -> {
if (oldVal != null)
rb.pseudoClassStateChanged(oldVal, false);
rb.pseudoClassStateChanged(newVal, true);
});
root.setTop(combo);
root.setCenter(rb);
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
application.css
.radio-button:left > .text { -fx-rotate: 180; }
.radio-button:left { -fx-rotate:180; }
.radio-button:right > .text { -fx-rotate: 0; }
.radio-button:right { -fx-rotate:0; }
.radio-button:top > .text { -fx-rotate: 0; }
.radio-button:top { -fx-rotate:-90; }
.radio-button:bottom > .text { -fx-rotate: 0; }
.radio-button:bottom { -fx-rotate:90; }
And the displayed RadioButton:

JavaFX : Keep listening to keyboard-input and on input perform action

I have JavaFX project in which I have to listen to keyboard-input as our barcode scanner is configured that way.
Are there any libraries in JavaFX where I can keep a listener active and perform suitable action upon reception of a String by barcode-scanner.
I searched on net, but didn't find any good solution unfortunately.
Here is my code :
public class Main extends Application {
private Scene scene;
MyBrowser myBrowser;
#Override
public void start(Stage primaryStage) throws Exception{
primaryStage.setTitle("Our Application");
java.net.CookieManager manager = new java.net.CookieManager();
java.net.CookieHandler.setDefault(manager);
myBrowser = new MyBrowser();
scene = new Scene(myBrowser, 1080, 1920);
primaryStage.setScene(scene);
primaryStage.setFullScreen(true);
primaryStage.show();
// # being the escape character
scene.setOnKeyTyped(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
String text = event.getCharacter();
if (text.equals("#")) {
String tempText = completeText;
completeText = "";
processText(tempText);
}else {
completeText = completeText+text;
}
}
});
}
private void processText(String text){
System.out.println("I will process "+text);
}
public static void main(String[] args) {
launch(args);
}
public class MyBrowser extends Region {
final String hellohtml = "index.html";
WebView webView = new WebView();
WebEngine webEngine = webView.getEngine();
public MyBrowser() {
webEngine.getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> {
if (newValue == Worker.State.SUCCEEDED) {
JSObject window = (JSObject) webEngine.executeScript("window");
window.setMember("app", this);
}
});
URL urlHello = getClass().getResource(hellohtml);
webEngine.load(urlHello.toExternalForm());
webView.setPrefSize(1080, 1920);
webView.setContextMenuEnabled(false);
getChildren().add(webView);
}
Kindly let me know. Thank you.
So I think I understand what your problem is, you have a barcode scanner that sends in barcodes as keyevents to your application, And then you need to respond to the whole bar code when you're done receiving the code?
If that's the case you can use a KeyListener to intercept the key press events. Then you just need to implement the logic to put the individual key events together.
class MyListener implements KeyListener{
#Override
public void keyPressed(KeyEvent e) {
// Logic goes here
}
#Override
public void keyReleased(KeyEvent e) {
// Logic goes here
}
#Override
public void keyTyped(KeyEvent e) {
// Logic goes here
}
}

Categories