I'm switching from JEditorPane to WebEngine(JavaFX).
I used to lock the text highlighting(selecting) in JEditorPane as following.
my_editor.setEditable(false);
my_editor.getInputMap().put(KeyStroke.getKeyStroke("control C"), "none");
Now I like to do the same with WebEngine, how may I do this? disabling copy, highlighting and editing mode. Thanks.
If you want to disable copy, highlighting and editing from JavaFX, without the use of Javascript, one way to do it is by trapping the events and deal accordingly with them, leaving the rest of the options intact.
Let's use an event dispatcher to filter a few events:
For key events:
Avoid copy with Ctrl+C or Ctrl+Insert
Avoid selection with shift+Arrow
For Mouse events:
Avoid selection of word, line, paragraph with mouse click
Avoid selection with mouse dragging, but allowing dragging the scrollbars
(Others could be added if you need to)
public class WebEventDispatcher implements EventDispatcher {
private final EventDispatcher oldDispatcher;
private Point2D limit;
public WebEventDispatcher(EventDispatcher oldDispatcher) {
this.oldDispatcher = oldDispatcher;
}
public void setLimit(Point2D limit){
this.limit = limit;
}
private boolean allowDrag=false;
#Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
if (event instanceof MouseEvent){
MouseEvent m = (MouseEvent)event;
if (event.getEventType().equals(MouseEvent.MOUSE_CLICKED) ||
event.getEventType().equals(MouseEvent.MOUSE_PRESSED)) {
Point2D origin=new Point2D(m.getX(),m.getY());
allowDrag=!(origin.getX()<limit.getX() && origin.getY()<limit.getY());
}
// avoid selection with mouse dragging, allowing dragging the scrollbars
if (event.getEventType().equals(MouseEvent.MOUSE_DRAGGED)) {
if(!allowDrag){
event.consume();
}
}
// Avoid selection of word, line, paragraph with mouse click
if(m.getClickCount()>1){
event.consume();
}
}
if (event instanceof KeyEvent && event.getEventType().equals(KeyEvent.KEY_PRESSED)){
KeyEvent k= (KeyEvent)event;
// Avoid copy with Ctrl+C or Ctrl+Insert
if((k.getCode().equals(KeyCode.C) || k.getCode().equals(KeyCode.INSERT)) && k.isControlDown()){
event.consume();
}
// Avoid selection with shift+Arrow
if(k.isShiftDown() && (k.getCode().equals(KeyCode.RIGHT) || k.getCode().equals(KeyCode.LEFT) ||
k.getCode().equals(KeyCode.UP) || k.getCode().equals(KeyCode.DOWN))){
event.consume();
}
}
return oldDispatcher.dispatchEvent(event, tail);
}
}
Now on your scene, disable context menu to avoid copy/paste options, find the content area of the webview without the scrollbars, if any, and set the custom event dispatcher.
private Point2D pLimit;
private double width, height;
#Override
public void start(Stage primaryStage) {
WebView webView = new WebView();
WebEngine webEngine = webView.getEngine();
// disable context menu (copy option)
webView.setContextMenuEnabled(false);
WebEventDispatcher webEventDispatcher = new WebEventDispatcher(webView.getEventDispatcher());
webEngine.getLoadWorker().stateProperty().addListener(new ChangeListener<State>() {
#Override
public void changed(ObservableValue<? extends State> observable, State oldValue, State newValue) {
if(newValue.equals(State.SUCCEEDED)){
// dispatch all events
webView.setEventDispatcher(webEventDispatcher);
}
}
});
webEngine.load("<URL>");
Scene scene = new Scene(webView);
primaryStage.setTitle("WebView scrollbar test");
primaryStage.setScene(scene);
primaryStage.show();
webView.getChildrenUnmodifiable().addListener(new ListChangeListener<Node>() {
#Override
public void onChanged(Change<? extends Node> c) {
pLimit=webView.localToScene(webView.getWidth(),webView.getHeight());
webView.lookupAll(".scroll-bar").stream()
.map(s->(ScrollBar)s).forEach(s->{
if(s.getOrientation().equals(VERTICAL)){
width=s.getBoundsInLocal().getWidth();
}
if(s.getOrientation().equals(HORIZONTAL)){
height=s.getBoundsInLocal().getHeight();
}
});
// dispatch all events
webEventDispatcher.setLimit(pLimit.subtract(width, height));
}
});
}
You can disable highlight and copy with the following CSS:
body {-webkit-user-select: none;}
Related
I am trying to build a simple planner app using JavaFX. My current goal is to be able to:
click on a panel of the calendar (already implemented)
type in a task, hit enter and have it show up as a Label (already implemented)
click on the currently placed labels and remove them from the calendar. (issue)
Step 3 is where I am having most trouble. I am confident that I am setting up my mouse event for the label correctly but when I click on one of the labels it runs the mouse event for the panel. I need a way to override the pane's mouse event so I can use the labels mouse event, but I'm not too sure how to go about that. Any feedback would be great!
this.setOnMouseClicked(e ->
{
TextField field = new TextField();
this.getChildren().add(field);
//sets field as a label
field.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent key) {
KeyCode k = key.getCode();
if ((k.equals(KeyCode.ENTER))) {
Label lab = new Label(field.getText());
getChildren().add(lab);
getChildren().remove(field);
}
}
});
//removes textfield and label
field.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent ke) {
KeyCode kc = ke.getCode();
if ((kc.equals(KeyCode.ESCAPE))) {
getChildren().remove(field);
}
}
});
});
if(lab != null)
{
lab.setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
setStyle("-fx-background-color: #00FF00;");
}
});
}
I'm trying to create my own implementation for an overlay dialog that opens up when the user clicks a button. The code you see below works perfectly fine but is not that pretty. I'm searching for an implementation where I don't have to create a Thread for each dialog I create. Is there any way to acchieve this?
I've been browsing through various Java source files like JOptionPane and JDialog to figure out what they do in order to block the thread until the user closes the dialog, but I didn't manage to understand it. Additionally I tried various code snippets including the EventQueue like for example EventQueue.invokeLater or EventQueue.invokeAndWait.
// MainViewController.java
#FXML
private void handleServerButton(ActionEvent evt){
Thread t = new Thread(() -> {
if (serverD.showDialog(overlay) == Dialog.OK_OPTION){
System.out.println("OK");
} else {
System.out.println("ABORT");
}
});
t.start();
}
// Dialog.java
public int showDialog(Pane parent) {
latch = new CountDownLatch(1);
this.result.set(NONE);
approveButton.setDefaultButton(true);
abortButton.setCancelButton(true);
container.setVisible(true);
parent.setVisible(true);
try {
latch.await();
} catch (InterruptedException ex){ }
approveButton.setDefaultButton(false);
abortButton.setCancelButton(false);
container.setVisible(false);
parent.setVisible(false);
return result.get();
}
#Override
public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue) {
if (newValue != NONE)
latch.countDown();
}
This is what it looks like (please note: the overlay dialog is not a window itself but rather a pane within the main window):
Final Result
Look at the Alert and Dialog documentation. Both provide functionality similar to what you want, and both can be customised if they don't quite match your use case.
Quick example:
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setTitle("title");
alert.setContent("content");
...
// More customisation available, read the docs
alert.show();. // Or showAndWait()
I solved the problem by deriving a Dialog class from Stage and implementing the logic there. The only thing that is left, is to extract the values from the controls of the view controller. But I already noticed that the dialog is passed as a Window through the ActionEvent - so that should be a minor issue.
public class Dialog extends Stage {
public static int OK_OPTION = 0;
public static int ABORT_OPTION = -1;
private int result;
public Dialog(Scene parent, String url) throws IOException{
super(StageStyle.TRANSPARENT);
Parent root = FXMLLoader.load(getClass().getResource(url));
Scene scene = new Scene(root);
if (System.getProperty("os.name").equals("Mac OS X")){
root.setStyle("-fx-background-radius: 0 0 3px 3px;");
}
scene.setFill(Color.TRANSPARENT);
setScene(scene);
initOwner(parent.getWindow());
double titlebar = parent.getWindow().getHeight() - parent.getHeight();
setX(parent.getWindow().getX());
setY(parent.getWindow().getY() + titlebar + 50);
}
public int showDialog(){
showAndWait();
return result;
}
}
I need to know whether a certain key is down while performing a drag & drop operation.
So I tried to use setOnKeyPressed / setOnKeyReleased of a Scene with a combination of HashMap, but I have a problem with this approach:
Imagine a scenario that one drags & drops a TableView item to somewhere while holding Control down. Now if I display a dialog at the end of the drop, while still holding Control down, the setOnKeyReleased is never called with this approach... as the Dialog is the one receiving the key released event.
How could I fix this?
Hope I understand your question here is a possible solution(work with any key):
public class Main extends Application {
SimpleBooleanProperty isKeyPress = new SimpleBooleanProperty(false);
#Override
public void start(Stage primaryStage) throws Exception{
Parent window = new VBox();
((VBox) window).getChildren().add(new Label("example of small window:"));
primaryStage.setTitle("example");
Scene scene=new Scene(window);
primaryStage.setScene(scene);
primaryStage.show();
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
System.out.println("Press");
isKeyPress.set(true);
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Information Dialog");
alert.setHeaderText(null);
alert.setContentText("I have a great message for you!");
Scene alertScene = alert.getDialogPane().getScene();
alertScene.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
System.out.println("Released on dialog");
isKeyPress.set(false);
}
});
alert.showAndWait();
}
});
scene.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
System.out.println("Released");
isKeyPress.set(false);
}
});
}
public static void main(String[] args) {
launch(args);
}
}
output exmple:
Press
Released on dialog
From your comment the goal is to change the behavior of the drag and drop depending on whether or not Ctrl is down. When it is do a copy operation, otherwise do a move operation. You do not need to deal with KeyEvents to implement this behavior. Instead, you would determine whether to copy or move in the onDragDetected handler. The onDragDetected handler uses a MouseEvent which has methods for querying the status of modifier keys—such as isControlDown(). Using this, we can specify what transfer modes are allowed based on the modifier keys.
Node node = ...;
node.setOnDragDetected(event -> {
Dragboard board;
if (event.isControlDown()) {
board = node.startDragAndDrop(TransferMode.COPY);
} else {
board = node.startDragAndDrop(TransferMode.MOVE);
}
// add contents to Dragboard
});
Note it may be more cross-platform to use isShortcutDown().
I'm trying to add a MouseClicked event listener to a Tree Cell so that It fires when a user clicks on either the Graphic or the String value of the cell.
TestApp.java
public class TestApp extends Application {
#Override
public void start(Stage stage) throws Exception {
Image image = new Image(getClass().getResourceAsStream("icon.png"));
TreeItem<String> root = new TreeItem<String>("Root");
root.setGraphic(new ImageView(image));
TreeItem<String> child = new TreeItem<String>("Child");
root.getChildren().add(child);
TreeView<String> tree = new TreeView<String>(root);
tree.setCellFactory(new TestTreeCellFactory());
StackPane pane = new StackPane();
pane.getChildren().add(tree);
stage.setScene(new Scene(pane, 300, 250));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
private class TestTreeCellFactory implements Callback<TreeView<String>, TreeCell<String>> {
#Override
public TreeCell<String> call(TreeView<String> treeView) {
CheckBoxTreeCell<String> tc = new CheckBoxTreeCell<String>();
tc.setOnMouseClicked((event) -> mousePressed(event, treeView));
return tc;
}
public void mousePressed(MouseEvent event, TreeView<String> treeView) {
if(event.getButton().equals(MouseButton.SECONDARY)) {
System.out.println("Right Mouse Button Clicked!");
}
}
}
}
The mouse event is currently firing when I click on the String Value but is not firing when I click on the Graphic icon. Upon trying to debug the issue, here are some of my observations:
It seems like the Graphic is somehow tied to the Checkbox (clicking on the Graphic will toggle the checkbox value).
Clicking the Graphic will not select the row while clicking on the text value will.
Is there a way to attach the listener to the Tree Cell's graphic once it gets populated or am I thinking about this in the wrong way?
I know how to deal with left or right click separately, dragging, double-clicking, but I can't figure out how to do something if the user clicks left and right mouse buttons at the same time without interfering/causing other events to fire.
#Override
public void handle(Event event) {
if (event.getSource() instanceof Tile) {
Tile tile = (Tile) event.getSource();
if (event.getEventType().equals(MouseEvent.MOUSE_CLICKED)) {
if (((MouseEvent) event).getButton().equals(MouseButton.SECONDARY))
tile.toggleFlag();
else if (((MouseEvent) event).getClickCount() == 2)
mineField.massClick(tile);
}
if (event.getEventType().equals(MouseEvent.DRAG_DETECTED))
if (!((MouseEvent) event).getButton().equals(MouseButton.SECONDARY))
tile.startFullDrag();
if (event.getEventType().equals(MouseDragEvent.MOUSE_DRAG_ENTERED))
tile.arm();
if (event.getEventType().equals(MouseDragEvent.MOUSE_DRAG_EXITED))
tile.disarm();
if (event.getEventType().equals(MouseDragEvent.MOUSE_DRAG_RELEASED))
mineField.clickedTile(tile);
if (event.getEventType().equals(ActionEvent.ANY))
mineField.clickedTile(tile);
}
}
Also, if you see a problem with my code feel free to point it out, always looking to improve.
The simple version is this:
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
StackPane root = new StackPane();
root.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> {
if( e.isPrimaryButtonDown() && e.isSecondaryButtonDown()) {
System.out.println( "Both down");
} else if( e.isPrimaryButtonDown()) {
System.out.println( "Primary down");
} else if( e.isSecondaryButtonDown()) {
System.out.println( "Secondary down");
}
});
Scene scene = new Scene(root,400,400);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
If you prefer your own event happening when both buttons are pressed, you could try it this way:
public class Main extends Application {
BooleanProperty primaryMouseButtonDown = new SimpleBooleanProperty();
BooleanProperty secondaryMouseButtonDown = new SimpleBooleanProperty();
#Override
public void start(Stage primaryStage) {
try {
StackPane root = new StackPane();
root.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> {
primaryMouseButtonDown.setValue( e.isPrimaryButtonDown());
secondaryMouseButtonDown.setValue( e.isSecondaryButtonDown());
});
root.addEventFilter(MouseEvent.MOUSE_RELEASED, e -> {
primaryMouseButtonDown.setValue( e.isPrimaryButtonDown());
secondaryMouseButtonDown.setValue( e.isSecondaryButtonDown());
});
BooleanBinding binding = Bindings.and(primaryMouseButtonDown, secondaryMouseButtonDown);
binding.addListener( new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
System.out.println( "Mouse Button Event: " + oldValue + " -> " + newValue);
}
});
Scene scene = new Scene(root,400,400);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
There are 2 boolean properties, one for the primary button down and one for the secondary button down. Both properties are connected via a BooleanBinding. Whenever one of the properties change via the mouse event, an event is fired. So what's left to do is for you to check if newValue is true and fire your handling code.
Do something more along the lines of, watch for mouse presses, and set a boolean to true when a mousePressed event is called for left/right mouse button. Then later in the event look to see if both booleans for left and right are true. If they are, act on it as if both were pressed at the same time.
boolean mouse_1, mouse_2 = false;
public void mousePressed(MouseEvent e){
//The numbers are just made up I don't remember the actual codes for the buttons but it's simple enough to figure out.
if(e.getButton()==1){
mouse_1 = true;
}
if(e.getButton()==2){
mouse_2 = true;
}
if(mouse_1&&mouse_2){
//Your code here
}
}
public void mouseReleased(MouseEvent e){
if(e.getButton() == 1){
mouse_1 = false;
}
if(e.getButton() == 2){
mouse_2 = false;
}
}
Assume this is some sort of handler class... But this is the short for how to implement it.
I'm probably late to answer this question, but I'm going to post my solution in order to demonstrate how to handle single-button clicks separately from both buttons being clicked at the same time
Existing answers already explained how to detect both mouse buttons being clicked at the same time. But mouse events (click, press, and release) are still triggered by individual buttons and previous posters didn't address how to avoid these events from interfering with each other.
My solution is to track both buttons being pressed on mouse press and detect mouse clicks of any kind on mouse release:
//flag to track both buttons being pressed
private boolean wereBothButtonsPressed = false;
private void onMousePressed(MouseEvent e) {
//single button press sets flag to false
wereBothButtonsPressed = e.isPrimaryButtonDown() && e.isSecondaryButtonDown();
}
private void onMouseReleased(MouseEvent e) {
if (e.isPrimaryButtonDown() || e.isSecondaryButtonDown()) {
//do nothing if user is still holding the button
return;
}
if (wereBothButtonsPressed) {
System.out.prinln("Both buttons");
} else if (e.getButton() == MouseButton.PRIMARY) {
System.out.prinln("Only primary");
} else if (e.getButton() == MouseButton.SECONDARY) {
System.out.prinln("Only secondary");
}
}
You can set these handlers for specific events on specific controls or fit them into your method:
private boolean wereBothButtonsPressed = false;
#Override
public void handle(Event event) {
...
if (event.getEventType().equals(MouseEvent.MOUSE_PRESSED)) {
MouseEvent me = (MouseEvent) event;
wereBothButtonsPressed = me.isPrimaryButtonDown() && me.isSecondaryButtonDown();
} else if (event.getEventType().equals(MouseEvent.MOUSE_RELEASED)) {
MouseEvent me = (MouseEvent) event;
if (!me.isPrimaryButtonDown() && !me.isSecondaryButtonDown()) {
if(wereBothButtonsPressed) {
mineField.massClick(tile);
} else if(me.getButton() == MouseButton.PRIMARY) {
mineField.clickedTile(tile);
} else if(me.getButton() == MouseButton.SECONDARY) {
tile.toggleFlag();
}
}
...