Eclipse plugin development - drag and drop between components in multiple plugins - java

I have developed a two plugins, plugin1 and plugin2. In plugin1 there is a one view called pluginOneView and in plugin2 there is another view called pluginTwoView. So my requirement is there will be few dragabble components on pluginTwoView and I should be able to drag it to pluginOneView. Currently I am developing drag and drop for the same, my code is (in pluginView2) for DragSource :
DragSource ds = new DragSource(btn, DND.DROP_MOVE); //btn is a draggable component
ds.setTransfer(new Transfer[] { TextTransfer.getInstance() });
ds.addDragListener(new DragSourceAdapter() {
// There are dragStart and other methods here
}
}
But my problem lies in DropTarget method:
DropTarget target = new DropTarget(component, dtl);
Here in the place of component I need to add target as pluginOneView (which is in another view). My question is how can I get a component object of that view in the workspace, so that I can pass it as a arugment to DropTarget method?
I tried of getting
PlatformUI.getWorkbench().getViewRegistry().find("targetId");
But it returns me of IViewDescriptor type, where as I need of component type. Can anyone help me in this? Since I am new to Eclipse plugin development.

Well I think you misunderstood how to use a DropTarget. You don't need to know the Plugin you're dragging to. You also used a TextTransfer, but I assume you want to drag Java Objects not Strings. Therefore I made this little example that shows how to drag objects between multiple views (which could be in different plugins). In my example an Object of type ISomeClass is transferred. In order to transfer it, ISomeClass must be serializable. I have provided my own TransferType the SomeClassTransfer class that handles the transfer. Be aware that both view plugins need access to the ISomeClass and SomeClassTransfer definitions. The easiest way to accomplish this is to make a third plugin which holds these classes. Both view plugins could then hold a reference to this third plugin.
SomeClassTransfer:
public class SomeClassTransfer extends ByteArrayTransfer {
private final static String[] typeNames;
private final static int[] typeIds;
private final static SomeClassTransfer instance;
static {
String typeName = "SomeClassTransfer";
int id = registerType(typeName);
typeNames = new String[] { typeName };
typeIds = new int[] { id };
instance = new SomeClassTransfer();
}
public static SomeClassTransfer getInstance() {
return instance;
}
private SomeClassTransfer() {
}
#Override
protected int[] getTypeIds() {
return typeIds;
}
#Override
protected String[] getTypeNames() {
return typeNames;
}
#Override
protected void javaToNative(Object object, TransferData transferData) {
if (object instanceof ISomeClass) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutput out = null;
try {
out = new ObjectOutputStream(bos);
out.writeObject(object);
byte[] objectBytes = bos.toByteArray();
object = objectBytes;
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
out.close();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
super.javaToNative(object, transferData);
}
#Override
protected ISomeClass nativeToJava(TransferData transferData) {
ISomeClass someClass = null;
byte[] objectBytes = (byte[]) super.nativeToJava(transferData);
ByteArrayInputStream bis = new ByteArrayInputStream(objectBytes);
ObjectInput in = null;
try {
in = new ObjectInputStream(bis);
Object o = in.readObject();
if (o instanceof ISomeClass) {
someClass = (ISomeClass) o;
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
bis.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return someClass;
}
}
The source view:
int operations = DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK;
Transfer[] types = new Transfer[] { SomeClassTransfer.getInstance() };
DragSource source = new DragSource(tableViewer.getControl(), operations);
source.setTransfer(types);
source.addDragListener(new DragSourceListener() {
#Override
public void dragStart(DragSourceEvent event) {
if (tableViewer.getSelection().isEmpty()) {
// do not start drag
event.doit = false;
}
}
#Override
public void dragSetData(DragSourceEvent event) {
if (SomeClassTransfer.getInstance().isSupportedType(
event.dataType)) {
event.data = ((IStructuredSelection) tableViewer
.getSelection()).getFirstElement();
}
}
#Override
public void dragFinished(DragSourceEvent event) {
// A Move operation has been performed so remove the data
// from the source
if (event.detail == DND.DROP_MOVE) {
tableViewer.remove(((IStructuredSelection) tableViewer
.getSelection()).getFirstElement());
}
}
});
The destination view:
int operations = DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK;
Transfer[] types = new Transfer[] { SomeClassTransfer.getInstance() };
DropTarget target = new DropTarget(tableViewer.getControl(), operations);
target.setTransfer(types);
target.addDropListener(new DropTargetListener() {
#Override
public void dragEnter(DropTargetEvent event) {
}
#Override
public void dragOver(DropTargetEvent event) {
}
#Override
public void dragLeave(DropTargetEvent event) {
}
#Override
public void dragOperationChanged(DropTargetEvent event) {
}
#Override
public void dropAccept(DropTargetEvent event) {
}
#Override
public void drop(DropTargetEvent event) {
if (event.data == null) {
// no data to copy, indicate failure in event.detail
event.detail = DND.DROP_NONE;
return;
}
// data copied to viewer
tableViewer.add((ISomeClass) event.data);
}
});
This code enables you to drag data between two completely independent views.

Related

Spring boot app with Vaadin UI constantly refreshing screen

I have a boot app that I am adding some crud screens to. I decided to use Vaadin and it seems to work great until I deploy it to a multi-nodal production environment.
Once in the prod environment the screens constantly refresh for no apparent reason. For example there is a grid in one screen that when a row is clicked a dialog pops up that shows item details. but as soon as the dialog pops up the page refreshes numerous times.
This forum thread here https://vaadin.com/forum/thread/17586129/routerlayout-causing-page-refresh is an example of the same layout I am using and describes a very similar issue.
I have a base abstract class that extends VerticalLayout and all of the concrete classes extend this base abstract class. Each concrete class defines its own route and use a common layout class.
I have reached out on gitter, vaadin forum and opened a bug in github but no one from Vaadin want to respond to anything as far as I can tell.
Here are the versions of everything I am using:
Vaadin Flow version: 14
Java version: 12.0.1+12
OS version: Mac 10.14.5
Browser version: Fire Fox 70.0.1, Chrome 78.0.3904.97
Code snippets from my implementation:
Main View
#Slf4j
#RoutePrefix("v1/crud")
#Theme(value = Material.class, variant = Material.DARK)
public class MainView extends Div implements RouterLayout {
private H1 h1 = new H1("Vaadin Crud UI");
private HorizontalLayout header = new HorizontalLayout(h1);
private Div content = new Div();
private ApplicationContext context;
#Inject
public MainView(ApplicationContext context) {
this.context = context;
setSizeFull();
h1.setWidthFull();
content.setWidthFull();
header.setWidthFull();
header.setAlignItems(FlexComponent.Alignment.CENTER);
VerticalLayout navigationBar = new VerticalLayout();
navigationBar.setWidth("25%");
navigationBar.add(createNavigationButton("Home", VaadinIcon.HOME, ReportTab.class));
navigationBar.add(createNavigationButton("Batch Search", VaadinIcon.SEARCH, BatchSearchTab.class));
... a bunch more buttons
HorizontalLayout layout = new HorizontalLayout(navigationBar, content);
layout.setWidthFull();
VerticalLayout page = new VerticalLayout(header, layout);
page.setWidthFull();
add(page);
}
#Override
public void showRouterLayoutContent(HasElement hasElement) {
if (hasElement != null) {
Element newElement = hasElement.getElement();
if (newElement != null) {
content.removeAll();
content.getElement().appendChild(newElement);
}
}
}
private Button createNavigationButton(String caption, VaadinIcon icon, Class<? extends BaseEditor> editor) {
Button button = new Button(caption, icon.create());
button.addClickListener(event -> UI.getCurrent().navigate(editor));
button.addThemeVariants(ButtonVariant.MATERIAL_CONTAINED);
button.getStyle().set("background-color", "#00819D");
button.setWidthFull();
return button;
}
}
Base Component:
#Slf4j
#Data
#SpringComponent
#UIScope
public abstract class BaseEditor<P, B> extends VerticalLayout {
private final RememberMeService rememberMe;
private final P businessProcess;
protected Binder<B> binder;
protected Dialog editDialog = new Dialog();
protected Button save = new Button("Save", VaadinIcon.CHECK.create());
protected Button close = new Button("Close", VaadinIcon.EXIT.create());
protected Button delete = new Button("Delete", VaadinIcon.TRASH.create());
protected B bean;
private ChangeHandler changeHandler;
private boolean proceed = true;
public BaseEditor(P businessProcess, RememberMeService rememberMe) {
this.rememberMe = rememberMe;
this.businessProcess = businessProcess;
save.addClickListener(e -> save());
delete.addClickListener(e -> delete());
}
public abstract void delete();
public abstract void save();
protected abstract Component getContent();
protected void edit(B e) {
bean = e;
editDialog.open();
getBinder().setBean(e);
}
protected void initEditorPanel(Component... components) {
HorizontalLayout actions = new HorizontalLayout(save, close, delete);
VerticalLayout data = new VerticalLayout(components);
data.add(actions);
editDialog.removeAll();
editDialog.add(data);
getBinder().bindInstanceFields(this);
close.addClickListener(e -> editDialog.close());
}
public interface ChangeHandler {
void onChange();
}
void setChangeHandler(ChangeHandler h) {
changeHandler = h;
}
void errorDialog(String message) {
final Button close = new Button("Close", VaadinIcon.CLOSE.create());
H3 h3 = new H3(message);
final Dialog errorDialog = new Dialog(h3, close);
errorDialog.open();
close.addClickListener(e -> errorDialog.close());
}
BaseEditor filter(Predicate<B> predicate) {
Objects.requireNonNull(predicate);
proceed = predicate.test(bean);
return this;
}
void buttonConsumer(Consumer<B> consumer) {
if (!proceed) {
proceed = true;
return;
}
try {
consumer.accept(bean);
} catch (Exception e) {
errorDialog(e.getMessage());
} finally {
editDialog.close();
getChangeHandler().onChange();
}
}
void either(Consumer<B> whenTrue, Consumer<B> whenFalse) {
try {
if (proceed) {
whenTrue.accept(bean);
} else {
whenFalse.accept(bean);
}
} catch (Exception e) {
errorDialog(e.getMessage());
} finally {
proceed = true;
editDialog.close();
getChangeHandler().onChange();
}
}
}
Concrete Component:
#Slf4j
#Route(value = "search/batch", layout = MainView.class)
public class BatchSearchTab extends BaseEditor<BatchService, Batch> {
private TextField searchField1;
private TextField searchField2;
public BatchSearchTab(BatchService businessProcess, RememberMeService rememberMe) {
super(businessProcess, rememberMe);
binder = new Binder<>(Batch.class);
save.setIcon(VaadinIcon.REPLY.create());
save.setText("Replay");
delete.setIcon(VaadinIcon.CLOSE.create());
delete.setText("Cancel");
getContent();
}
#Override
public void delete() {
buttonConsumer(b -> getBusinessProcess().cancelBatch(b.getBatchId(), b.getUserAgent()));
}
#Override
public void save() {
filter(b -> b.isReplayable()).buttonConsumer(b -> getBusinessProcess().buildAndSendFile((getBean())));
}
#Override
public void edit(Batch batch) {
HorizontalLayout actions = new HorizontalLayout();
H2 h2 = new H2();
if (batch.isReplayable()) {
h2.setText("Would you like to replay the following.");
actions.add(save, delete, close);
} else {
h2.setText("This record is not eligible for replay.");
actions.add(close);
}
Label batchId = new Label("Correlation Id: " + batch.getBatchId());
Label txnCount = new Label("Transaction Count: " + batch.getTotalTxns());
Label txnAmount = new Label("Total: " + batch.getTotalBatchAmount());
VerticalLayout data = new VerticalLayout(h2, batchId, txnCount, txnAmount, actions);
data.add(actions);
editDialog.removeAll();
editDialog.add(data);
close.addClickListener(e -> editDialog.close());
editDialog.open();
getBinder().setBean(batch);
}
#Override
protected Component getContent() {
final H2 h2 = new H2("Locate Batches");
searchField1 = new TextField("Batch Code");
searchField2 = new TextField("User Agent");
searchField2.addKeyPressListener(Key.ENTER, e -> keyPressListener());
Button searchBtn = new Button("Search", VaadinIcon.SEARCH.create());
HorizontalLayout search = new HorizontalLayout(searchField1, searchField2);
searchBtn.addClickListener(e -> {
search(searchField1.getValue(), searchField2.getValue());
});
add(h2, search, searchBtn);
return this;
}
private void search(String code, String userAgent) {
log.info("Searching {} and {}", code, userAgent);
List<Batch> batches =
getBusinessProcess().getBatchesForUserAgent(code, userAgent, 60);
log.info("Found {} batches", batches.size());
if (batches.size() > 0) {
buildGrid(batches, "BatchId", "totalTxns", "totalBatchAmount", "status");
} else {
errorDialog("No Records found for criteria");
}
}
private void keyPressListener() {
String code = StringUtils.isNotBlank(searchField1.getValue()) ? searchField1.getValue() : null;
if (StringUtils.isNotBlank(searchField2.getValue())) {
search(code, searchField2.getValue());
}
}
private void buildGrid(Collection<Batch> records, String... columns) {
Component result;
if (records.size() == 0) {
result = new Label("NO REPORT DATA AVAILABLE.");
} else {
final Grid<Batch> grid = new Grid<>(Batch.class);
grid.setHeightByRows(records.size() < 10);
grid.setColumns(columns);
grid.setItems(records);
grid.setWidthFull();
grid.asSingleSelect().addValueChangeListener(l -> Optional.ofNullable(l.getValue()).ifPresent(this::edit));
result = grid;
}
if (getComponentCount() < 3) {
add(result);
} else {
replace(getComponentAt(2), result);
}
}
private void loadData(String code, String userAgent) {
if (StringUtils.isNotBlank(code)) {
search(null, userAgent);
} else {
search(code, userAgent);
}
}
}
Disclaimer: some further fact finding happended via IRC
The answer to the question is related to OP running multiple instances of the application behind an round robin load ballancer. The clients hit random servers and therefor had no session running there.
The solution to this is having a shared session store and ideally have the load ballancer dispatch on existing session, so "hot" backend servers get hit.

Disabling or Intercepting a Drop Outside Of A Window

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.

Jtable content is visible through different tab

I have JTabbedPane with 4 tabs. JtabbedPane is situated on a JLayeredPane. 1st and 4th tab contain JTable with custom models. Each of the tables is being refreshed every 5-10 seconds.
When 1st tab is active, and JTable on 4th has just finished refreshing, I can see content of the 4th on the 1st. Look at the screenshot.
When I click on the other tab, or minimize window, that strange effect is gone. Till the next refresh of that table on 4th tab. Refreshing is done using Future<> object.
I used Swing GUI builder in Netbeans, so I have huge amount of code. Would post any piece which could be useful.
I tried to revalidate jTabbedPane, is had no effect. Both tables and jScrollPanes has opaque property set to true. So I tried to use SwingUtilities.invokeLater(). It helped a little bit - now first content update goes well, but later - the same problem.
2nd table model has method to update it's content
public void setData(LinkedList<Object[]> __rows) {
NewDevsTableModel.__rows = __rows;
fireTableDataChanged();
}
It is used here (I added SwingUtilities here)
static class checkNew implements Callable<Boolean> {
#Override
public Boolean call() {
ServiceMessage sm = ServiceMessage.getNewList();
try {
connect();
os.write(sm.serialize());
for (int i=0; i<10; i++) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {}
if (is.available() > 0) {
break;
}
if (i == 9) {
disconnect();
return false;
}
}
byte[] actByte = new byte[is.available()];
is.read(actByte);
try {
sm = ServiceMessage.Deserialize(actByte); //may be there are no new devices
if (sm.getType() == ServiceMessageType.NODATA) {
MainWindow.jTabbedPane1.setEnabledAt(3, false);
if (MainWindow.jTabbedPane1.getSelectedIndex() == 3) {
MainWindow.jTabbedPane1.setSelectedIndex(0);
}
return true;
} else {
return false; //wrong answer type
}
} catch (ClassCastException | StreamCorruptedException e) {
//remember selection and scroll
final int scroll = MainWindow.jScrollPane3.getVerticalScrollBar().getValue();
final int[] rows = MainWindow.newDevsTable.getSelectedRows();
int col = MainWindow.devicesTable.getSelectedColumn();
String[] parts = new String(actByte).split("\n");
final LinkedList<Object[]> l = new LinkedList();
for (int i=0; i<parts.length; i++) {
String[] dev = parts[i].split(";", -1);
String descr = dev[2];
boolean iptype = (!dev[3].equals("-"));
String address = dev[4];
boolean atmtype = (dev[5].equals("+"));
if (MainWindow.newDevsTable.getRowCount() >= (i+1)) {
if ((MainWindow.newDevsTable.getValueAt(i, 4) != null) && !MainWindow.newDevsTable.getValueAt(i, 4).equals("")) {
descr = MainWindow.newDevsTable.getValueAt(i, 4).toString();
}
}
Object[] o = {dev[0], dev[1], MainWindow.language[180], MainWindow.language[4], descr, iptype, address, atmtype};
l.add(o);
}
if (!l.isEmpty()) {
SwingUtilities.invokeLater( new Runnable() {
#Override
public void run() {
MainWindow.newDevsPanel.setVisible(true);
MainWindow.jTabbedPane1.setEnabledAt(3, true);
((NewDevsTableModel)MainWindow.newDevsTable.getModel()).setData(l);
ButtonColumn buttonColumn = new ButtonColumn(MainWindow.newDevsTable, addAction, 2, true);
buttonColumn = new ButtonColumn(MainWindow.newDevsTable, rejAction, 3, false);
//put selection back
for (int i=0; i<rows.length; i++) {
MainWindow.newDevsTable.addRowSelectionInterval(rows[i], rows[i]);
}
MainWindow.jScrollPane3.getVerticalScrollBar().setValue(scroll);
}
});
} else {
MainWindow.jTabbedPane1.setEnabledAt(3, false);
if (MainWindow.jTabbedPane1.getSelectedIndex() == 3) {
MainWindow.jTabbedPane1.setSelectedIndex(0);
}
}
return true;
}
} catch (IOException e) {
disconnect();
return false;
} catch (ClassNotFoundException e) {
return false;
}
}
}
I submit the task this way
public static Future<Boolean> checkNewDevices() {
final Future<Boolean> task;
task = service.submit(new checkNew());
return task;
}
To refresh automatically I use separate thread
public class CheckNewPassThread extends Thread {
int pause = 10000;
#Override
public void run() {
for (;;) {
HostConnection.checkNewDevices();
try {
Thread.sleep(pause);
} catch (InterruptedException e) {}
}
}
}
Which is started when the window is opened
private void formWindowOpened(java.awt.event.WindowEvent evt) {
HostConnection.getData();
HostConnection.getDeviceAddress();
RefreshData refreshThread = new RefreshData();
refreshThread.start();
new CheckNewPassThread().start();
}
OMG, the problem was in calling jTabbedPane.setEnabledAt(3, true) to already enabled tab. Swing is fascinating

I want To insert hyperlink in JPanel

I want To insert hyperlink in JPanel (so that on clicking i go to web address)
Is it possible ?
If yes, how can i do this?
See the How to Use HTML in Swing Components
tutorial.
We've been using something like this:
public class UrlTextPane extends JTextPane {
private final Pattern urlPattern = Pattern.compile(UrlUtil.URL_REGEX);
public UrlTextPane() {
this.setEditable(false);
this.addHyperlinkListener(new UrlHyperlinkListener());
this.setContentType("text/html");
}
private class UrlHyperlinkListener implements HyperlinkListener {
#Override
public void hyperlinkUpdate(final HyperlinkEvent event) {
if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
try {
Desktop.getDesktop().browse(event.getURL().toURI());
} catch (final IOException e) {
throw new RuntimeException("Can't open URL", e);
} catch (final URISyntaxException e) {
throw new RuntimeException("Can't open URL", e);
}
}
}
};
#Override
/**
* Set the text, first translate it into HTML:
*/
public void setText(final String input) {
final StringBuilder answer = new StringBuilder();
answer.append("<html><body style=\"font-size: 8.5px;font-family: Tahoma, sans-serif\">");
final String content = StringEscapeUtils.escapeHtml(input);
int lastIndex = 0;
final Matcher matcher = urlPattern.matcher(content);
while(matcher.find()) {
//Append everything since last update to the url:
answer.append(content.substring(lastIndex, matcher.start()));
final String url = content.substring(matcher.start(), matcher.end()).trim();
if(UrlUtil.isValidURI(url)) {
answer.append(""+url+"");
} else {
answer.append(url);
}
lastIndex = matcher.end();
}
//Append end:
answer.append(content.substring(lastIndex));
answer.append("</body></html>");
super.setText(answer.toString().replace("\n", "<br />"));
}
}
I wrote a simple function that takes the text, link, and the position you want to display and returns a JLabel. When the mouse is over the text, it becomes blue with underline and makes cursor a pointing hand, otherwise it becomes black with no underline and default cursor.
public static JLabel makeHyperLink(final String s, final String link, int x, int y)
{
final JLabel l = new JLabel(s);
l.addMouseListener(new MouseAdapter()
{
#Override
public void mouseExited(MouseEvent arg0)
{
l.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
l.setText(s);
}
#Override
public void mouseEntered(MouseEvent arg0)
{
l.setCursor(new Cursor(Cursor.HAND_CURSOR));
l.setText(String.format("<HTML><FONT color = \"#000099\"><U>%s</U></FONT></HTML>", s));
}
#Override
public void mouseClicked(MouseEvent arg0)
{
try
{
URI uri = new URI(link);
if (Desktop.isDesktopSupported())
Desktop.getDesktop().browse(uri);
} catch (Exception e)
{
}
}
});
l.setBounds(x, y, s.length()*5, 20);
l.setToolTipText(String.format("go to %s", link));
return l;
}
If any part of code is not clear, contact me;)
Yes possible,
Use HTML
Add one JLabel use HTML as text and add it to Panel

Column moved [finished] event in JTable

How should I detect that column moved action is finished in JTable? I've added columnModeListener to my column model but the problem is columnMoved method is called every time a column moves (by certain pixels). I don't want this behavior. I just want to detect when the column dragging is finished.
columnModel.addColumnModelListener(new TableColumnModelListener() {
public void columnAdded(TableColumnModelEvent e) {
}
public void columnRemoved(TableColumnModelEvent e) {
}
public void columnMoved(TableColumnModelEvent e) {
//this is called so many times
//I don't want this, but something like column moved finished event
System.out.println("Moved "+e.getFromIndex()+", "+e.getToIndex());
}
public void columnMarginChanged(ChangeEvent e) {
}
public void columnSelectionChanged(ListSelectionEvent e) {
}
});
I hope it is clear what I'm looking for. Thanks.
This is what I ended up doing. I know it is dirty, but it fits for what I'm looking:
boolean dragComplete = false;
apTable.getTableHeader().addMouseListener(new MouseAdapter() {
#Override
public void mouseReleased(MouseEvent e) {
if (dragComplete) {
System.out.println("Drag completed");
}
dragComplete = false;
}
});
columnModel.addColumnModelListener(new TableColumnModelListener() {
public void columnAdded(TableColumnModelEvent e) {
}
public void columnRemoved(TableColumnModelEvent e) {
}
public void columnMoved(TableColumnModelEvent e) {
dragComplete = true;
}
public void columnMarginChanged(ChangeEvent e) {
}
public void columnSelectionChanged(ListSelectionEvent e) {
}
});
Here's an inner class I use to determine when the column ordering has changed. Note that the user may not have let go of the mouse at this point, so the dragging may continue further.
private class ColumnUpdateListener implements TableColumnModelListener {
int lastFrom = 0;
int lastTo = 0;
private void verifyChange(int from, int to) {
if (from != lastFrom || to != lastTo) {
lastFrom = from;
lastTo = to;
///////////////////////////////////////
// Column order has changed! Do something here
///////////////////////////////////////
}
}
public void columnMoved(TableColumnModelEvent e) {
verifyChange(e.getFromIndex(), e.getToIndex());
}
public void columnAdded(TableColumnModelEvent e) {
verifyChange(e.getFromIndex(), e.getToIndex());
}
public void columnRemoved(TableColumnModelEvent e) {
verifyChange(e.getFromIndex(), e.getToIndex());
}
public void columnMarginChanged(ChangeEvent e) {}
public void columnSelectionChanged(ListSelectionEvent e) {}
}
It's worked well for me.
This might be a better approach:
table.setTableHeader(new JTableHeader(table.getColumnModel()) {
#Override
public void setDraggedColumn(TableColumn column) {
boolean finished = draggedColumn != null && column == null;
super.setDraggedColumn(column);
if (finished) {
onColumnChange(table); // Handle the event here...
}
}
});
This is what works for me (both column movements and margin resizes):
I extend the table and override the columnMoved and columnMarginChanged
methods in the following way:
... first add some variables for state keeping
private int lastMovementDistance = 0;
private boolean bigMove = false;
private boolean resizeBegan = false;
...
#Override
public void columnMarginChanged(ChangeEvent e) {
super.columnMarginChanged(e);
if (isShowing()){
resizeBegan = true;
}
}
#Override
public void columnMoved(TableColumnModelEvent e) {
super.columnMoved(e);
//this will be set to 0 when the column is dragged
//to where it should begin if released
lastMovementDistance = Math.abs(getTableHeader().getDraggedDistance());
if (e.getFromIndex() != e.getToIndex()){
//note, this does not guarantee that the columns will be eventually
//swapped - the user may move the column back.
//but it prevents us from reacting to movements where
//the user hasn't even moved the column further then its nearby region.
//Works for me, because I don't care if the columns stay the same
//I only need the updates to be infrequent and don't want to miss
//changes to the column order
bigMove = true;
}
}
... then in the constructor of my table i do this:
public MyTable(){
...
getTableHeader().addMouseListener(new MouseAdapter(){
public void mouseReleased(MouseEvent evt) {
if (bigMove && lastMovementDistance == 0 ){
//react! the tables have possibly switched!
}
else if (resizeBegan){
//react! columns resized
}
resizeBegan = false;
bigMove = false;
}
});
...
}
It is kinda like a hack, but it works for me.
Nice answer on your own question ashokgelal. Just a little improvement I think. Your code also trigger on single click on the header. Using one more flag you can prevent the 'dragComplete' trigger when the column haven't really changed.
Modified code:
boolean mDraggingColumn = false;
boolean mColumnCHangedIndex = false;
tblObjects.getTableHeader().addMouseListener(new MouseAdapter() {
#Override
public void mouseReleased(MouseEvent e) {
if (mDraggingColumn && mColumnCHangedIndex) {
System.out.println("Column changed");
}
mDraggingColumn = false;
mColumnCHangedIndex = false;
}
});
tblObjects.getColumnModel().addColumnModelListener(new TableColumnModelListener() {
#Override
public void columnAdded(TableColumnModelEvent e) {}
#Override
public void columnRemoved(TableColumnModelEvent e) {}
#Override
public void columnMoved(TableColumnModelEvent e) {
mDraggingColumn = true;
if (e.getFromIndex() != e.getToIndex()) {
mColumnCHangedIndex = true;
}
}
#Override
public void columnMarginChanged(ChangeEvent e) {}
#Override
public void columnSelectionChanged(ListSelectionEvent e) {}
});
If I understand you correctly, maybe you want to look at the mouse listeners. Maybe the MOUSE_RELEASED event?
All answers fail at one use-case: if the table is in a layout filling up the entire window, then resizing the window will resize the table and thus its columns. By watching for mouse events on the column headers, we fail to receive the event when the user resize the window.
I looked at the JTable&friends source-code, and the columnMarginChanged() method is always called in a sub-sub-sub...-function called by JTable.doLayout().
Then, my solution is to watch for doLayout() calls that trigger at least one columnMarginChanged().
In fact, columnMarginChanged() is called for every columns.
Here is my solution:
private class ResizableJTable extends JTable {
private TableColumnModelListener columnModelListener;
private boolean columnsWereResized;
#Override
public void setColumnModel(TableColumnModel columnModel) {
if (getColumnModel() != null) {
getColumnModel().removeColumnModelListener(columnModelListener);
columnModelListener = null;
}
if (columnModel != null) {
columnModelListener = new TableColumnModelListener() {
public void columnSelectionChanged(ListSelectionEvent e) {
// Nothing to do
}
public void columnRemoved(TableColumnModelEvent e) {
// Nothing to do
}
public void columnMoved(TableColumnModelEvent e) {
// Nothing to do
}
public void columnMarginChanged(ChangeEvent e) {
columnsWereResized = true;
}
public void columnAdded(TableColumnModelEvent e) {
// Nothing to do
}
};
columnModel.addColumnModelListener(columnModelListener);
}
super.setColumnModel(columnModel);
}
#Override
public void doLayout() {
columnsWereResized = false;
super.doLayout();
if (columnsWereResized) {
onColumnsResized();
}
}
/**
* Sub-classes can override this method to
* get the columns-were-resized event.
* By default this method must be empty,
* but here we added debug code.
*/
protected void onColumnsResized() {
int[] columnSizes = getColumnSizes();
String sizes = "";
for (int i : columnSizes) {
sizes += i + " ";
}
System.out.println("COLUMNS RESIZED: [ " + sizes + "]");
}
protected int[] getColumnSizes() {
TableColumnModel columnModel = getTableHeader().getColumnModel();
int columnCount = columnModel.getColumnCount();
int[] columnSizes = new int[columnCount];
for(int i = 0; i < columnCount; i++) {
TableColumn column = columnModel.getColumn(i);
columnSizes[i] = column.getWidth();
}
return columnSizes;
}
}

Categories