Im a littlebit stuck with that.
Im using JxBrowser for my JavaFX project.
Im also using JFoenix to push the look.
Now when i browse to some sites, the URLBar which is a JFXTextField becomes unusable. I cant write anymore in it.
But the default JavaFX contextmenu still works.
Whats interesting is that key listeners on the website, like F for fullscreen on youtube still works.
To fix this, the only way that works, is to click a completely different Window on the PC.
For example im clicking to IntelliJ than back to the JavaFX frame, than its working again. As i wrote its only on specefic sites.
No Exceptions are thrown.
If its maybew important, im using 2 TabPanes, One for the Tab and another for the Content.
Do someone has an idea?
The class that generates the Tab:
package de.liz3.liz3web.browser;
import com.teamdev.jxbrowser.chromium.Browser;
import com.teamdev.jxbrowser.chromium.javafx.BrowserView;
import de.liz3.liz3web.gui.controller.MainController;
import javafx.application.Platform;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.input.KeyCode;
import java.util.Vector;
/**
* Created by Liz3 on 22.02.2017.
*/
public class TabManager {
private Vector<BrowserTab> activeTabs;
private MainController controller;
public TabManager(MainController controller) {
this.controller = controller;
activeTabs = new Vector<>();
controller.getUrlField().setOnMouseClicked(event -> controller.getUrlField().requestFocus());
TabPane head = controller.getHeaderTabPane();
controller.getUrlField().setOnKeyReleased(event -> {
if (event.getCode() == KeyCode.ENTER) {
selectedBrowserTab().browseOrSearch(TabManager.this.controller.getUrlField().getText());
}
});
controller.getBackBtn().setOnAction(event -> selectedBrowserTab().getEngine().goBack());
controller.getForwardBtn().setOnAction(event -> selectedBrowserTab().getEngine().goForward());
head.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
if (newValue == controller.getNewTab()) {
newTab(null);
return;
}
controller.getMainTabPane().getSelectionModel().select(getContentTab(newValue));
controller.getUrlField().setText(selectedBrowserTab().getCurrentUrl());
});
}
public void newTab(String url) {
new Thread(() -> {
Browser browser = new Browser();
BrowserView view = new BrowserView(browser);
Tab headerTab = new Tab("New Tab");
Tab contentTab = new Tab();
contentTab.setContent(view);
BrowserTab t = new BrowserTab(browser, view, contentTab, headerTab);
activeTabs.add(t);
Platform.runLater(() -> {
controller.getHeaderTabPane().getTabs().add(headerTab);
controller.getMainTabPane().getTabs().add(contentTab);
controller.getHeaderTabPane().getTabs().remove(controller.getNewTab());
controller.getHeaderTabPane().getTabs().add(controller.getNewTab());
controller.getMainTabPane().getSelectionModel().select(contentTab);
controller.getHeaderTabPane().getSelectionModel().select(headerTab);
controller.getUrlField().setEditable(true);
controller.getUrlField().setText("");
t.browseOrSearch(url);
});
}).start();
}
public BrowserTab selectedBrowserTab() {
for (BrowserTab tab : activeTabs) {
if (tab.getHeaderTab() == controller.getHeaderTabPane().getSelectionModel().getSelectedItem()) {
return tab;
}
}
return null;
}
public Tab getSelectedHeaderTab() {
return controller.getHeaderTabPane().getSelectionModel().getSelectedItem();
}
public Tab getSelectedContentTab() {
return controller.getMainTabPane().getSelectionModel().getSelectedItem();
}
public Tab getHeaderTab(Tab contentTab) {
for (BrowserTab tab : activeTabs) {
if (tab.getContentTab() == contentTab) {
return tab.getHeaderTab();
}
}
return null;
}
public Tab getContentTab(Tab headerTab) {
for (BrowserTab tab : activeTabs) {
if (tab.getHeaderTab() == headerTab) {
return tab.getContentTab();
}
}
return null;
}
public MainController getController() {
return controller;
}
public Vector<BrowserTab> getActiveTabs() {
return activeTabs;
}
}
The Browser class:
package de.liz3.liz3web.browser;
import com.teamdev.jxbrowser.chromium.Browser;
import com.teamdev.jxbrowser.chromium.events.*;
import com.teamdev.jxbrowser.chromium.javafx.BrowserView;
import de.liz3.liz3web.Main;
import javafx.application.Platform;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.control.Tab;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
/**
* Created by Liz3 on 21.02.2017.
*/
public class BrowserTab {
private Browser engine;
private BrowserView view;
private Tab contentTab;
private Tab headerTab;
private String currentUrl;
public BrowserTab(Browser engine, BrowserView view, Tab contentTab, Tab headerTab) {
this.engine = engine;
this.view = view;
this.contentTab = contentTab;
this.headerTab = headerTab;
this.currentUrl = "";
setUpTab();
}
public void browseOrSearch(String address) {
if (address == null) {
return;
}
if (address.startsWith("http://") || address.startsWith("https://")) {
this.engine.loadURL(address);
return;
}
if (!address.contains(" ") && address.contains(".")) {
address = "http://" + address;
this.engine.loadURL(address);
return;
}
String encode = null;
try {
encode = URLEncoder.encode(address, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
this.engine.loadURL("https//google.com/search?q=" + encode);
}
private void setUpTab() {
this.headerTab.setClosable(true);
this.headerTab.setOnCloseRequest(new EventHandler<Event>() {
#Override
public void handle(Event event) {
Main.tm.getController().getMainTabPane().getTabs().remove(contentTab);
Main.tm.getActiveTabs().remove(this);
}
});
this.engine.addTitleListener(titleEvent -> Platform.runLater(() -> BrowserTab.this.headerTab.setText(titleEvent.getTitle())));
this.engine.addLoadListener(new LoadListener() {
#Override
public void onStartLoadingFrame(StartLoadingEvent startLoadingEvent) {
Platform.runLater(() -> {
BrowserTab.this.currentUrl = BrowserTab.this.engine.getURL();
if (Main.tm.getSelectedHeaderTab() == BrowserTab.this.headerTab) {
Main.tm.getController().getUrlField().setText(BrowserTab.this.currentUrl);
}
});
}
#Override
public void onProvisionalLoadingFrame(ProvisionalLoadingEvent provisionalLoadingEvent) {
}
#Override
public void onFinishLoadingFrame(FinishLoadingEvent finishLoadingEvent) {
Platform.runLater(() -> {
BrowserTab.this.currentUrl = BrowserTab.this.engine.getURL();
if (Main.tm.getSelectedHeaderTab() == BrowserTab.this.headerTab) {
Main.tm.getController().getUrlField().setText(BrowserTab.this.currentUrl);
}
});
}
#Override
public void onFailLoadingFrame(FailLoadingEvent failLoadingEvent) {
}
#Override
public void onDocumentLoadedInFrame(FrameLoadEvent frameLoadEvent) {
}
#Override
public void onDocumentLoadedInMainFrame(LoadEvent loadEvent) {
}
});
this.engine.setPopupHandler(popupParams -> {
Platform.runLater(() -> Main.tm.newTab(popupParams.getURL()));
return null;
});
}
public Browser getEngine() {
return engine;
}
public BrowserView getView() {
return view;
}
public Tab getContentTab() {
return contentTab;
}
public Tab getHeaderTab() {
return headerTab;
}
public String getCurrentUrl() {
return currentUrl;
}
}
By default JxBrowser is running in HEAVYWEIGHT rendering mode. It's not recommended to use HEAVYWEIGHT rendering mode in applications like yours with tabs and layers. Please use LIGHTWEIGHT rendering mode. It should work without any issues:
Browser browser = new Browser(BrowserType.LIGHTWEIGHT);
BrowserView view = new BrowserView(browser);
Related
I've created a simple terminal app using lanterna that simply displays a custom button. But even though in the custom button's class I extend the Button class and override the Button class' createDefaultRenderer method to return an instance of the Button class' FlatButtonRenderer class, it is displaying the button using the DefaultButtonRenderer class.
Can you help me understand how to create a custom button in lanterna that displays the button using the FlatButtonRenderer?
import java.io.IOException;
import java.util.ArrayList;
import com.googlecode.lanterna.terminal.*;
import com.googlecode.lanterna.screen.*;
import com.googlecode.lanterna.gui2.*;
class Test {
public static void main(String[] args) {
DefaultTerminalFactory terminalFactory = new DefaultTerminalFactory();
Screen screen = null;
try {
screen = terminalFactory.createScreen();
screen.startScreen();
final WindowBasedTextGUI textGUI = new MultiWindowTextGUI(screen);
final Window window = new GUIAppWindow();
textGUI.addWindowAndWait(window);
}
catch (IOException e) {
e.printStackTrace();
}
finally {
if(screen != null) {
try {
screen.stopScreen();
}
catch(IOException e) {
e.printStackTrace();
}
}
}
}
private static class GUIAppWindow extends BasicWindow {
GUIAppWindow() {
ArrayList<Window.Hint> hints = new ArrayList<>();
hints.add(Window.Hint.CENTERED);
setHints(hints);
Panel mainPanel = new Panel(new LinearLayout(Direction.VERTICAL));
XButton b = new XButton(new String("."));
b.addListener(new ButtonHandler("data"));
mainPanel.addComponent(b);
this.setComponent(mainPanel);
}
private class XButton extends Button {
public XButton(String label) {
super(label);
}
#Override
protected ButtonRenderer createDefaultRenderer() {
return new Button.FlatButtonRenderer();
}
}
private class ButtonHandler implements Button.Listener {
final String loc;
ButtonHandler(String loc) {
this.loc = loc;
}
public void onTriggered(Button button) {
button.setLabel(button.getLabel().equals(".") ? "x" : new String("."));
}
}
}
}
I believe the correct way to use FlatButtonRenderer is to call setRenderer().
private class XButton extends Button {
public XButton(String label) {
super(label);
setRenderer(new Button.FlatButtonRenderer());
}
}
I am making a highly interactive TabPane for viewing contact lists in JavaFX 8. For this I have made my own subclass of Tab, EditableTab, which has functionality for changing the name of the tab by double clicking on the name in the overview. When the user clicks the + sign to create a new contact list, I want the program to create a new tab, select it, then focus the name and select all the text - it is natural to name the contact list at once (similar to when you create a new file in windows).
My problem: This seems to be very unstable. Most of the times, it seems some kind of animation/transition problem arises, and the tab name ends up empty. Here is a screenshot of what usually, but not always, happens when the + button is clicked:
And here is what I want:
Here is the code for my EditableTab:
public class EditableTab extends Tab {
private Label lbl;
private TextField txtField;
public EditableTab(String text, Node content) {
super();
setContent(content);
lbl = new Label(text);
txtField = new TextField(text);
txtField.setStyle("-fx-background-color: transparent");
setGraphic(lbl);
setupInteractivity();
}
public TextField getTextField() {
return txtField;
}
private void setupInteractivity() {
lbl.setOnMouseClicked((mouseEvent) -> {
if (mouseEvent.getClickCount() == 2) {
showTextField();
}
});
txtField.setOnAction(event -> setGraphic(lbl));
txtField.focusedProperty().addListener(
(observable, oldValue, newValue) -> {
if (! newValue) {
lbl.setText(txtField.getText());
setGraphic(lbl);
}
});
}
public void showTextField() {
txtField.setPrefWidth(lbl.getWidth());
txtField.setText(lbl.getText());
setGraphic(txtField);
txtField.selectAll();
txtField.requestFocus();
}
}
And here is the code where the functionality is implemented:
private void addNewContactlist() {
Contactlist newList = new Contactlist();
newList.setName("New contact list");
contactlistApp.getContactlistData().add(newList);
ListView<Person> lv = new ListView<Person>(newList.getContacts());
setupListView(lv);
int position = tabPane.getTabs().size() - 1;
EditableTab tab = createEditableTab("New contact list", lv);
tabPane.getTabs().add(position, tab);
tabPane.getSelectionModel().select(tab);
tab.showTextField();
}
I suspect that the problem comes from some animation/transition timings, but that is really just a guess. I tried wrapping the showTextField() call in a Platform.runLater() with no luck.
Here is a small test app to replicate the issue:
public class TestApp extends Application {
TabPane tabPane = new TabPane();
#Override
public void start(Stage primaryStage) throws Exception {
Tab addNewContactlistTab = new Tab();
addNewContactlistTab.setClosable(false);
Label lbl = new Label("\u2795");
lbl.setOnMouseClicked(mouseEvent -> {
if (tabPane.getTabs().size() == 1) {
addNewTab();
}
});
addNewContactlistTab.setGraphic(lbl);
tabPane.getTabs().add(addNewContactlistTab);
addNewContactlistTab.selectedProperty().addListener(
(observable, oldValue, newValue) -> {
if (newValue && tabPane.getTabs().size() != 1) {
addNewTab();
}
});
Scene scene = new Scene(tabPane);
primaryStage.setScene(scene);
primaryStage.setWidth(600);
primaryStage.setHeight(400);
primaryStage.show();
}
private void addNewTab() {
int insertionIndex = tabPane.getTabs().size() - 1;
ListView<String> lv = new ListView<String>();
EditableTab tab = new EditableTab("Unnamed", lv);
tabPane.getTabs().add(insertionIndex, tab);
tabPane.getSelectionModel().select(tab);
tab.showTextField();
}
public static void main(String[] args) {
launch();
}
}
Here is my code for the RenamableTab class:
import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TextField;
public class RenamableTab extends Tab {
private final Label label;
private final TextField textField;
public RenamableTab() {
this("New Tab", null);
}
public RenamableTab(String text) {
this(text, null);
}
public RenamableTab(String text, Node content) {
super();
label = new Label(text);
textField = new TextField(text);
setContent(content);
textField.setStyle("-fx-background-color: transparent");
setGraphic(label);
label.setOnMouseClicked((mouseEvent) -> {
if (mouseEvent.getClickCount() == 2) {
rename();
}
});
textField.setOnAction(event -> setGraphic(label));
textField.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
label.setText(textField.getText());
setGraphic(label);
}
});
}
public TextField getTextField() {
return textField;
}
public void rename() {
//textField.setPrefWidth(label.getWidth());
//textField.setText(label.getText());
setGraphic(textField);
new Thread() {
#Override
public void run() {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
Platform.runLater(new Runnable() {
#Override
public void run() {
textField.selectAll();
textField.requestFocus();
}
});
}
}.start();
}
}
And here is my code for the FancyTabPane:
import javafx.event.EventHandler;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.input.MouseEvent;
public class FancyTabPane extends TabPane {
public FancyTabPane() {
Tab newTabTab = new Tab();
newTabTab.setClosable(false);
Label addLabel = new Label("\u2795");
addLabel.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent paramT) {
System.out.println("mouse click");
addTab();
}
});
/*
* getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Tab>() {
* #Override
* public void changed(ObservableValue<? extends Tab> paramObservableValue, Tab paramT1, Tab
* paramT2) {
* System.out.println("model");
* if (paramT1 == newTabTab) {
* System.out.println("tab");
* addTab();
* }
* }
* });
*/
newTabTab.setGraphic(addLabel);
getTabs().add(newTabTab);
}
public void addTab() {
RenamableTab newTab = new RenamableTab();
getTabs().add(getTabs().size() - 1, newTab);
getSelectionModel().select(newTab);
newTab.rename();
}
}
I am having issued with the new tab button when another tab is selected, not sure how you overcame that.
I have tried to google to find some hints and look at the Vaadin's website, but I didn't find anything related to this king of issue:
In the console when I launch the application, I see this warning several times:
Ignoring RPC call for disabled connector com.vaadin.ui.Window, caption=Window's caption
I am using the Refresher addon which polls my server at an interval of 2000 millisec. and the ICEPush addon which implements pushing to the Vaadin UI.
I think that this is related somehow to the Refresher addon, because if I interact with a component I have created for test (below the code), warnings are added to the console.
Here is the code:
package com.example.events;
import java.util.ArrayList;
import java.util.List;
import com.github.wolfie.refresher.Refresher;
import com.vaadin.event.FieldEvents.BlurListener;
import com.vaadin.event.FieldEvents.FocusListener;
import com.vaadin.event.FieldEvents.TextChangeEvent;
import com.vaadin.event.FieldEvents.TextChangeListener;
import com.vaadin.event.LayoutEvents.LayoutClickNotifier;
import com.vaadin.ui.AbstractTextField.TextChangeEventMode;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.Component;
import com.vaadin.ui.Label;
import com.vaadin.ui.Layout;
import com.vaadin.ui.Notification;
import com.vaadin.ui.TextArea;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
public class Noticeboard extends VerticalLayout {
/**
*
*/
private static final long serialVersionUID = -6023888496081433464L;
private static List<Note> notes = new ArrayList<Note>();
private List<Window> windows = new ArrayList<Window>();
private static int userCount;
private int userId;
private static int noteId;
private Refresher refresher = new Refresher();
private static final int UPDATE_INTERVAL = 2000;
private Note currentlyFocusedNote;
public class NoticeboardUpdater extends Thread {
#Override
public void run() {
while (true) {
try {
Thread.sleep(UPDATE_INTERVAL);
} catch (Exception e) {
e.printStackTrace();
}
getUI().getSession().getLockInstance().lock();
try {
updateNoticeboard();
} finally {
getUI().getSession().getLockInstance().unlock();
}
}
}
}
public Noticeboard() {
refresher.setRefreshInterval(UPDATE_INTERVAL);
userId = ++userCount;
setSpacing(true);
setMargin(true);
addComponent(new Label("Logged in as User " + userId));
Button addNoteButton = new Button("Add note");
addNoteButton.addClickListener(new ClickListener() {
/**
*
*/
private static final long serialVersionUID = -4927018162887570343L;
#Override
public void buttonClick(ClickEvent event) {
Note note = new Note(++noteId);
note.setCaption("Note " + note.getId());
notes.add(note);
Window window = createWindow(note);
windows.add(window);
UI.getCurrent().addWindow(window);
}
});
addComponent(addNoteButton);
addExtension(refresher);
new NoticeboardUpdater().start();
}
private Window createWindow(final Note note) {
final Window window = new Window(note.getCaption());
VerticalLayout layout = new VerticalLayout();
layout.addComponent(createContentNote(note, window));
window.setContent(layout);
window.setWidth(300, Unit.PIXELS);
window.setResizable(false);
window.setPositionX(note.getPositionX());
window.setPositionY(note.getPositionY());
window.setData(note);
window.addBlurListener(createBlurListener(window));
window.addFocusListener(createFocusListener(window));
LayoutClickNotifier mainLayout = (LayoutClickNotifier) getUI().getContent();
mainLayout.addLayoutClickListener(e -> {
if (note.isNoteFocusedWindow() || note.isNoteFocusedTextArea()) {
if (note.getLockedByUser() > -1 && note.getLockedByUser() == userId) {
unlockNote(getWindow(currentlyFocusedNote));
note.setNoteFocusedWindow(false);
note.setNoteBlurredWindow(false);
note.setNoteFocusedTextArea(false);
note.setNoteBlurredTextArea(false);
currentlyFocusedNote = null;
}
}
});
return window;
}
private TextArea createContentNote(final Note note, final Window window) {
TextArea contentNote = new TextArea();
contentNote.setSizeFull();
contentNote.setValue(note.getText());
contentNote.setImmediate(true);
contentNote.setTextChangeEventMode(TextChangeEventMode.EAGER);
contentNote.addBlurListener(createBlurListener(window));
contentNote.addFocusListener(createFocusListener(window));
contentNote.addTextChangeListener(new TextChangeListener() {
/**
*
*/
private static final long serialVersionUID = 8552875156973567499L;
#Override
public void textChange(TextChangeEvent event) {
note.setText(event.getText());
}
});
return contentNote;
}
private BlurListener createBlurListener(Window window) {
return e -> {
Component blurredComponent = e.getComponent();
if (blurredComponent == window) {
currentlyFocusedNote.setNoteBlurredWindow(true);
}
else if (blurredComponent == (((Layout) window.getContent())).iterator().next()) {
currentlyFocusedNote.setNoteBlurredTextArea(true);
}
};
}
private FocusListener createFocusListener(Window window) {
return e -> {
Component focusedComponent = e.getComponent();
Note note = (Note) window.getData();
if (currentlyFocusedNote != null && currentlyFocusedNote != note) {
unlockNote(getWindow(currentlyFocusedNote));
currentlyFocusedNote.setNoteFocusedWindow(false);
currentlyFocusedNote.setNoteBlurredWindow(false);
currentlyFocusedNote.setNoteFocusedTextArea(false);
currentlyFocusedNote.setNoteBlurredTextArea(false);
}
currentlyFocusedNote = note;
if (focusedComponent == window) {
Notification.show("Focused Note Window");
currentlyFocusedNote.setNoteFocusedWindow(true);
}
else if (focusedComponent == (((Layout) window.getContent())).iterator().next()) {
Notification.show("Focused Note TextArea");
currentlyFocusedNote.setNoteFocusedTextArea(true);
}
if (currentlyFocusedNote.isNoteFocusedWindow() && currentlyFocusedNote.isNoteBlurredTextArea() ||
currentlyFocusedNote.isNoteFocusedTextArea() && currentlyFocusedNote.isNoteBlurredWindow()) {
// Lock is already set here, skipping
return;
}
lockNote(window);
};
}
private void lockNote(Window window) {
Note note = (Note) window.getData();
note.setLockedByUser(userId);
String caption = "Locked by User " + userId;
note.setCaption(caption);
window.setCaption(caption);
}
private void unlockNote(Window window) {
Note note = (Note) window.getData();
note.setLockedByUser(-1);
note.setPositionX(window.getPositionX());
note.setPositionY(window.getPositionY());
note.setCaption("Note " + note.getId());
window.setCaption("Note " + note.getId());
}
private void updateNoticeboard() {
for (Note note : notes) {
Window window = getWindow(note);
if (window == null) {
window = createWindow(note);
windows.add(window);
UI.getCurrent().addWindow(window);
}
if (note.getLockedByUser() > -1) {
if (note.getLockedByUser() != userId) {
// If the note is locked by another user, then we disable this window.
window.setEnabled(false);
updateTextArea(window, note);
updateWindowPosition(window, note);
}
else {
// Otherwise the window is enabled.
window.setEnabled(true);
Note focusedNote = (Note) window.getData();
updateFocusedNotePosition(focusedNote, window);
}
}
else {
window.setEnabled(true);
updateTextArea(window, note);
updateWindowPosition(window, note);
}
}
}
private void updateTextArea(Window window, Note note) {
Layout layout = (Layout) window.getContent();
TextArea area = (TextArea) layout.iterator().next();
area.setValue(note.getText());
}
private Window getWindow(Note note) {
for (Window window : windows) {
if (window.getData().equals(note))
return window;
}
return null;
}
private void updateWindowPosition(Window window, Note note) {
window.setPositionX(note.getPositionX());
window.setPositionY(note.getPositionY());
window.setCaption(note.getCaption());
}
private void updateFocusedNotePosition(Note focusedNote, Window window) {
focusedNote.setPositionX(window.getPositionX());
focusedNote.setPositionY(window.getPositionY());
focusedNote.setCaption(window.getCaption());
}
}
And inside the UI's init method I simply use this Noticeboard class:
#Override
protected void init(VaadinRequest request) {
setContent(new Noticeboard());
}
When I move a window created with the "Add note" button or change the focus from a window to another, I experience the warning.
What could be the reason of such an issue?
I know, code is not of the better ones, it is just to see how the this Vaadin addons behave.
When you disable a component, the disabled state is handled both on the client and the server side, meaning, a component isn't just visually disabled, but the server also refuses to handle any requests to a disabled component.
The warning you are seeing means, that there is an RPC request (HTTP request from the client-side) that is targeted for a disabled component. Since the component is disabled, the RPC request will be ignored.
This typically happens when you have a background thread that disables a component and then also do polling from the client-side.
I've implemented a set of draggable tabs, following the form of this example:
How to implement draggable tab using Java Swing?
Everything appears to work as I desire, however,when I drag outside of the main panel, the desktop will become a valid drop target (the resulting drop is accepted and marked successful).
Is there a way to intercept this drop to react to dropping outside of our root pane? It's simple enough to detect, but it's not clear to me how to actually capture the drop before the outside world does.
By the time DragSourceListener's dragDropEnd is called, the drop is already executed and there doesn't appear to be a good way to end dragging in dragOver/Exit/Whatever.
Gee, it'd be nice if something like this worked:
#Override
public void dragOver(DragSourceDragEvent dragEvent)
{
DragEnabledTabTransferData data = getTabTransferData(dragEvent);
DragSourceContext dragSourceContext = dragEvent.getDragSourceContext();
if (data == null)
{
dragSourceContext.setCursor(DragSource.DefaultMoveNoDrop);
return;
}
if (!data.getTabbedPane().getRootPane().getBounds().contains(dragEvent.getLocation()))
{
dragSourceContext.dragDropEnd(new DragSourceDropEvent(dragSourceContext, 999, true));
}
}
Instead the drag continues dragging along. I do, however get a dragDropEnd for my troubles.
Any ideas? I'd be pretty sad to hear that the only solution would be to have some hidden maximized global pane that acted only as a drop target to capture out-of-window events.
Here is a working example. If you drag a tab out to, say, the desktop in Linux, it'll try to cast the transfer data into a Serializable and not be happy. The drag over I was playing with is commented with "This is where I'd assume we'd be able to intercept stuff" if you want to jump straight to what I'd pointed to above.
/** "Simple" example of DnD tabbed panes. Sourced from Eugene Yokota:
* http:stackoverflow.com/questions/60269/how-to-implement-draggable-tab-using-java-swing */
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import javax.swing.*;
public class DnDTabbedPane extends JTabbedPane {
private static final String NAME = "TabTransferData";
private final DataFlavor FLAVOR = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType, NAME);
public DnDTabbedPane() {
super();
final DragSourceListener dsl = new DragSourceListener() {
public void dragEnter(DragSourceDragEvent e) {
e.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
}
public void dragExit(DragSourceEvent e) {
e.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
}
/**
* This is where I'd assume we'd be able to intercept stuff
* so drops don't happen where we don't want them to.
*/
public void dragOver(DragSourceDragEvent e) {
TabTransferData data = getTabTransferData(e);
if (data == null) {
e.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
return;
}
//This is where I ended up robokilling the drag via hackery
e.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
}
public void dragDropEnd(DragSourceDropEvent e) {}
public void dropActionChanged(DragSourceDragEvent e) {}
};
final DragGestureListener dgl = new DragGestureListener() {
public void dragGestureRecognized(DragGestureEvent e) {
Point tabPt = e.getDragOrigin();
int dragTabIndex = indexAtLocation(tabPt.x, tabPt.y);
if (dragTabIndex < 0) {
return;
}
e.startDrag(DragSource.DefaultMoveDrop,new TabTransferable(DnDTabbedPane.this, dragTabIndex), dsl);
}
};
new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, new CDropTargetListener(), true);
new DragSource().createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_COPY_OR_MOVE, dgl);
}
private TabTransferData getTabTransferData(DropTargetDropEvent a_event) {
try {
return (TabTransferData) a_event.getTransferable().getTransferData(FLAVOR);
} catch (Exception e) {}
return null;
}
private TabTransferData getTabTransferData(DropTargetDragEvent a_event) {
try {
return (TabTransferData) a_event.getTransferable().getTransferData(FLAVOR);
} catch (Exception e) {}
return null;
}
private TabTransferData getTabTransferData(DragSourceDragEvent a_event) {
try {
return (TabTransferData) a_event.getDragSourceContext().getTransferable().getTransferData(FLAVOR);
} catch (Exception e) {}
return null;
}
class TabTransferable implements Transferable {
private TabTransferData m_data = null;
private DataFlavor[] flavors = {FLAVOR};
public TabTransferable(DnDTabbedPane a_tabbedPane, int a_tabIndex) {
m_data = new TabTransferData(DnDTabbedPane.this, a_tabIndex);
}
public Object getTransferData(DataFlavor flavor) {
return m_data;
}
public DataFlavor[] getTransferDataFlavors() {
return flavors;
}
public boolean isDataFlavorSupported(DataFlavor flavor) {
return flavor.getHumanPresentableName().equals(NAME);
}
}
class TabTransferData {
DnDTabbedPane m_tabbedPane = null;
int m_tabIndex = -1;
public TabTransferData(DnDTabbedPane a_tabbedPane, int a_tabIndex) {
m_tabbedPane = a_tabbedPane;
m_tabIndex = a_tabIndex;
}
}
class CDropTargetListener implements DropTargetListener {
public void dragEnter(DropTargetDragEvent e) {
if (isDragAcceptable(e)) {
e.acceptDrag(e.getDropAction());
} else {
e.rejectDrag();
}
}
public void drop(DropTargetDropEvent a_event) {
if (isDropAcceptable(a_event)) {
convertTab(getTabTransferData(a_event),
getTargetTabIndex(a_event.getLocation()));
a_event.dropComplete(true);
} else {
a_event.dropComplete(false);
}
}
private boolean isTransferableGood(Transferable t, DataFlavor flavor)
{
return t == null || t.isDataFlavorSupported(flavor);
}
private boolean isDataGood(TabTransferData data)
{
if (DnDTabbedPane.this == data.m_tabbedPane && data.m_tabIndex >= 0) {
return true;
}
return false;
}
public boolean isDragAcceptable(DropTargetDragEvent e) {
Transferable t = e.getTransferable();
if (!isTransferableGood(t, e.getCurrentDataFlavors()[0])) {
return false;
}
return isDataGood(getTabTransferData(e));
}
public boolean isDropAcceptable(DropTargetDropEvent e) {
Transferable t = e.getTransferable();
if (!isTransferableGood(t, e.getCurrentDataFlavors()[0])) {
return false;
}
return isDataGood(getTabTransferData(e));
}
public void dragExit(DropTargetEvent e) {}
public void dropActionChanged(DropTargetDragEvent e) {}
public void dragOver(final DropTargetDragEvent e) {}
}
private int getTargetTabIndex(Point a_point) {
for (int i = 0; i < getTabCount(); i++) {
Rectangle r = getBoundsAt(i);
r.setRect(r.x - r.width / 2, r.y, r.width, r.height);
if (r.contains(a_point)) {
return i;
}
}
return -1;
}
private void convertTab(TabTransferData a_data, int a_targetIndex) {
DnDTabbedPane source = a_data.m_tabbedPane;
int sourceIndex = a_data.m_tabIndex;
if (sourceIndex < 0) {
return;
}
Component cmp = source.getComponentAt(sourceIndex);
String str = source.getTitleAt(sourceIndex);
if (a_targetIndex < 0 || sourceIndex == a_targetIndex) {
return;
}
source.remove(sourceIndex);
if (a_targetIndex == getTabCount()) {
addTab(str, cmp);
} else if (sourceIndex > a_targetIndex) {
insertTab(str, null, cmp, null, a_targetIndex);
} else {
insertTab(str, null, cmp, null, a_targetIndex - 1);
}
}
public static void main(String[] args)
{
JFrame window = new JFrame();
DnDTabbedPane tabbedPane = new DnDTabbedPane();
for(int i=0; i< 5; i++)
{
tabbedPane.addTab("I'm tab "+i, new JLabel("I'm tab "+i));
}
window.add(tabbedPane);
window.setSize(400, 200);
window.setVisible(true);
}
}
Thus far, the best I can do is call something to this effect when we hop out of the parent.
Component rootPane = SwingUtilities.getRoot(component);
Rectangle bounds = rootPane.getBounds();
if (!bounds.contains(location))
{
Robot robot = null;
try
{
robot = new Robot();
} catch (AWTException e)
{
return;
}
robot.keyPress(KeyEvent.VK_ESCAPE);
robot.keyRelease(KeyEvent.VK_ESCAPE);
}
It's a total hack, and doesn't solve my issue. I'd like to intercept the final drop event, see if it was outside of the frame and spawn the tab in its own JFrame.
If I was using the NetBeans, MyDoggy, or Eclipse frameworks, I guess this would all be magically handled for me. Alas.
There is no Way to Cancel the Drag directly. see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4502185
I would prefer to show the User that Drop on Desktop is not allowed, by changing the Cursor.
Your DragSourceListener dsl has in the dragOver method a DragSourceDragEvent which tells you
that the target action is NONE over the Desktop.
Change to this:
public void dragOver(DragSourceDragEvent e) {
TabTransferData data = getTabTransferData(e);
if( data == null || e.getTargetActions() == DnDConstants.ACTION_NONE ) {
e.getDragSourceContext().setCursor( DragSource.DefaultMoveNoDrop );
return;
}
e.getDragSourceContext().setCursor( DragSource.DefaultMoveDrop);
}
If you really want to Cancel, than you have to use your ESC solution or something like that:
try {
new Robot().mouseRelease( InputEvent.BUTTON1_MASK ); // if Button1 was the only Button to start a Drag
} catch( AWTException e1 ) {
}
As confirmed by #oliholz, there just isn't a way to do it without having to force a cancel via a keystroke.
However, for my needs of creating a tear-off tab, I found that creating a floating pane that was, itself, a drop target listener felt like the cleanest solution:
package com.amish.whatever;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JLabel;
import javax.swing.JWindow;
import javax.swing.Timer;
public class TearAwayTab extends JWindow {
MousePoller mousePoller = new MousePoller();
public TearAwayTab() {
this.add(new JLabel("FLONT"));
this.pack();
new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, new EasyDropTarget(), true);
this.setVisible(false);
}
private void center(Point location)
{
Point center = new Point();
center.setLocation(location.x-this.getWidth()/2, location.y-this.getHeight()/2);
TearAwayTab.this.setLocation(center);
}
public void attach(Point location)
{
center(location);
mousePoller.start();
this.setVisible(true);
}
public void detach()
{
mousePoller.stop();
this.setVisible(false);
}
private int DELAY = 10;
private class MousePoller extends Timer{
public MousePoller(){
super(DELAY, new ActionListener() {
private Point lastPoint = MouseInfo.getPointerInfo().getLocation();
#Override
public void actionPerformed(ActionEvent e) {
Point point = MouseInfo.getPointerInfo().getLocation();
if (!point.equals(lastPoint)) {
center(point);
}
lastPoint = point;
}
});
}
}
private class EasyDropTarget implements DropTargetListener
{
#Override
public void dragEnter(DropTargetDragEvent dtde) {
dtde.acceptDrag(dtde.getDropAction());
}
#Override
public void dragOver(DropTargetDragEvent dtde) {}
#Override
public void dropActionChanged(DropTargetDragEvent dtde) {}
#Override
public void dragExit(DropTargetEvent dte) {}
#Override
public void drop(DropTargetDropEvent dtde) {
dtde.dropComplete(true);
detach();
System.out.println("DROP Intercepted");
}
}
}
The bit with the MousePoller works around scrubbing the mouse too fast for mouse listeners to reliably update the location. I'd tried with a motion listener and was able to escape the bounds of the floater quite easily.
Back in the first example, I now include the tear away tab as a private member of the tabbed pane, and call attach and detach when exiting or entering my drop areas:
final DragSourceListener dsl = new DragSourceListener() {
public void dragEnter(DragSourceDragEvent e) {
e.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
Rectangle bounds = SwingUtilities.getRoot(DnDTabbedPane.this).getBounds();
if(bounds.contains(e.getLocation())){
tearTab.detach();
}
}
public void dragExit(DragSourceEvent e) {
e.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
tearTab.attach(e.getLocation());
}
...
This also has the added benefit of preserving the DnD operation in the case of dragging out, and then back in.
Thanks for the input. If you have any other ideas/comments, I'm all ears.
This doesn't directly relate to tabs, but one way to stop drags from being able to be dragged to the desktop is to wrap whatever you're dragging in a custom wrapper class. Then, when you make your TransferHandler, make a DataFlavor localFlavor = new ActivationDataFlavor(YourWrapperClass.class, DataFlavor.javaJVMLocalObjectMimeType, "description"); Next, override the createTransferable method to have new DataHandler(passedInComponent, localFlavor.getMimeType()); and return a new Transferable in which you've overridden all the methods to only have your localFlavor. Finally, in the importData method, make sure to import your data as your localFlavor type. This will prevent dragging to the deaktop as the flavor you defined is local to the JVM.
I'm writing an eclipse-plugin which creating a new Console. Please see my source code:
CliConsoleFactory.java
import java.io.IOException;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.console.ConsolePlugin;
import org.eclipse.ui.console.IConsole;
import org.eclipse.ui.console.IConsoleConstants;
import org.eclipse.ui.console.IConsoleFactory;
import org.eclipse.ui.console.IConsoleView;
import org.eclipse.ui.console.IOConsoleOutputStream;
public class CliConsoleFactory implements IConsoleFactory {
private static final String ENTER_KEY = "\r\n";
private static final String CLI_PROMPT = "CLI> ";
private IConsoleView m_consoleView = null;
#Override
public void openConsole() {
IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
try {
m_consoleView = (IConsoleView) page.showView(IConsoleConstants.ID_CONSOLE_VIEW);
} catch (PartInitException e1) {
e1.printStackTrace();
}
if (m_consoleView == null) {
return;
}
final MyIOConsole myConsole = new MyIOConsole("CLI", null);
final IDocument document = myConsole.getDocument();
document.addDocumentListener(new IDocumentListener() {
#Override
public void documentChanged(DocumentEvent event) {
if (ENTER_KEY.equals(event.getText())) {
// Print the Prompt
writeToConsole(myConsole, CLI_PROMPT);
}
}
#Override
public void documentAboutToBeChanged(DocumentEvent event) {
}
});
ConsolePlugin.getDefault().getConsoleManager().addConsoles(new IConsole[] { myConsole });
m_consoleView.display(myConsole);
writeToConsole(myConsole, CLI_PROMPT);
}
private void writeToConsole(final MyIOConsole myConsole, String msg) {
IOConsoleOutputStream stream = myConsole.newOutputStream();
stream.setActivateOnWrite(true);
try {
stream.write(msg);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
MyIOConsole.java
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.ui.console.IOConsole;
public class MyIOConsole extends IOConsole {
public MyIOConsole(String name, ImageDescriptor imageDescriptor) {
super(name, imageDescriptor);
}
}
It works great. When I enter to a new line, the Prompt is "CLI> ", but the Caret position is not okie, it is at the first position of the line instead of the last position. I want to make the Caret move to the last position. Who know please help me.!!!
To gain access to the caret position, you will need to implement a console viewer.
This is the setup I have for my custom console,
public class MyConsole extends IOConsole
{
....
#Override
public IPageBookViewPage createPage(IConsoleView view) {
return new MyConsolePage(this, view);
}
}
public class MyConsolePage extends TextConsolePage
{
....
#Override
protected TextConsoleViewer createViewer(Composite parent) {
return new MyConsoleViewer(parent, (MyConsole) this.getConsole());
}
}
public class MyConsoleViewer extends TextConsoleViewer
{
//This class gives you access to setting the caret position
//by getting the styled text widget and then using setCaretOffset
}
There are multiple ways of getting the styled text widget depending on which method you are overriding. I also created my own Console history class which kept track of the caret offset since I needed additional functionality of using the up and down arrow keys to navigate through previously entered commands.
The best way to implement the MyConsoleViewer is to use Eclipse's vast source code that sets a perfect example. I practically reused all of this class org.eclipse.ui.internal.console.IOConsoleViewer. It even shows examples of setting the caret.
Hope this still helps as your question was a while ago.