I have made a custom BasicScrollBarUI.
I have replaced the incrButton / decrButton by JLabels.
Therefore i had to override the InstallListeners(), to add the Listener to the incr/decrLabel instead of the JButtons.
//TODO
protected void installListeners() {
trackListener = createTrackListener();
buttonListenerCustom = createArrowButtonListenerCustom();
modelListener = createModelListener();
propertyChangeListener = createPropertyChangeListener();
scrollbar.addMouseListener(trackListener);
scrollbar.addMouseMotionListener(trackListener);
scrollbar.getModel().addChangeListener(modelListener);
scrollbar.addPropertyChangeListener(propertyChangeListener);
// scrollbar.addFocusListener(getHandler());
if (incrLabel != null) {
incrLabel.addMouseListener(buttonListenerCustom);
System.out.println("OK gemacht");
}
if (decrLabel != null) {
decrLabel.addMouseListener(buttonListenerCustom);
}
scrollListener = createScrollListener();
scrollTimer = new Timer(scrollSpeedThrottle, scrollListener);
scrollTimer.setInitialDelay(300); // default InitialDelay?
}
Therefore i had to override the ArrowButtonListener, to react on the Labels.
protected class ArrowButtonListenerCustom extends MouseAdapter {
boolean handledEvent;
public void mousePressed(MouseEvent e) {
if (!scrollbar.isEnabled()) {
return;
}
// not an unmodified left mouse button
// if(e.getModifiers() != InputEvent.BUTTON1_MASK) {return; }
if (!SwingUtilities.isLeftMouseButton(e)) {
return;
}
int direction;
if (e.getSource() == incrLabel) {
direction = 1;
incrLabel.setIcon(new ImageIcon(increaseButtonPressedImage));
} else {
direction = -1;
decrLabel.setIcon(new ImageIcon(decreaseButtonPressedImage));
}
scrollByUnit(direction);
scrollTimer.stop();
scrollListener.setDirection(direction);
scrollListener.setScrollByBlock(false);
scrollTimer.start();
handledEvent = true;
if (!scrollbar.hasFocus() && scrollbar.isRequestFocusEnabled()) {
scrollbar.requestFocus();
}
}
public void mouseReleased(MouseEvent e) {
scrollTimer.stop();
handledEvent = false;
scrollbar.setValueIsAdjusting(false);
incrLabel.setIcon(new ImageIcon(increaseButtonImage));
decrLabel.setIcon(new ImageIcon(decreaseButtonImage));
}
}
Of course createArrowButtonListenerCustom() returns a new instance of ArrowButtonListenerCustom.
Now my Problem:
When I click on the incr/decrLabel, the List scrolls correctly, but the thumb of the ScrollBar doesn't move (or better: the Thumb isn't repainted. If I move the Mouse over the thumb, it gets repainted on the right place). I have the same problem, when I scroll with the MouseWheel.
I don't understand, why this doesn't work.
Thanks for your help!
Related
I'm trying to catch the event when the small "minimize" or "maximize" arrows of a JSplitPane's divider are clicked.
I found a way to listen to a click elsewhere on the divider bar. Something like :
SplitPaneUI spui = splitPane.getUI();
if (spui instanceof BasicSplitPaneUI) {
((BasicSplitPaneUI) spui).getDivider().addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
// do something...
}
});
}
But is there a way to listen to clicks on the arrows?
I'm trying to catch the event when the small "minimize" or "maximize" arrows of a JSplitPane's divider are clicked.
Maybe you could add a PropertyChangeListener to the JSPlitPane and listen for dividerLocation changes (assuming you don't care if the divider was dragged or "one clicked" to the start or end). Then you can check if the divider location is at 0 or the maximum.
splitPane.addPropertyChangeListener("dividerLocation", this);
...
public void propertyChange(PropertyChangeEvent e)
{
// Get the new divider location of the split pane
int location = ((Integer)e.getNewValue()).intValue();
if (location == 0)
// do something
else if (location == splitPane.getMaximumDividerLocation())
// do something else
}
If you do care about the difference between dragging and clicking, then maybe you can compare the old/new values and look for a change in the location greater than a specified value.
Another option is to get the button from the divider:
JSplitPane splitPane = (JSplitPane)e.getSource();
BasicSplitPaneUI ui = (BasicSplitPaneUI)splitPane.getUI();
BasicSplitPaneDivider divider = ui.getDivider();
JButton button1 = (JButton)divider.getComponent(0)
Now you can add another ActionListener to the button to do your custom code.
Reflection is the Only way I guess to listen to One Touch Expandable Buttons
Just pass addSplitPaneListener and it is done.
public void addSplitPaneListener(JSplitPane splitPane) {
addSplitPaneListener("leftButton", splitPane);
addSplitPaneListener("rightButton", splitPane);
}
public void addSplitPaneListener(String button, JSplitPane splitPane) {
try {
Field field = BasicSplitPaneDivider.class.getDeclaredField(button);
field.setAccessible(true);
JButton onetouchButton = (JButton) field.get(((BasicSplitPaneUI) splitPane.getUI()).getDivider());
onetouchButton.setActionCommand(button);
onetouchButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
buttonClicked(e);
}
});
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) {
//Logger.getLogger(NewJFrame1.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void buttonClicked(ActionEvent e) {
System.out.println("Clicked " + e.getActionCommand());
}
Here is a derived class that deals with expanding/hiding either using the one touch expander and/or an additional keylistener bound to a certain key to hide/unhide the RIGHT panel. Doing it for the left should easy to adapt ;)
Interestingly on Linux the right part will never get its theoretical maximum value but will be one less (BUGCLICKONETOUCH). Didn't test if this is also true on Windows.
import java.awt.Component;
import javax.swing.JSplitPane;
public class JToggleSplitPane extends JSplitPane {
private int restoreWidth = -1;
private final static int BUGCLICKONETOUCH = 1;
public JToggleSplitPane(int splittype, Component c1, Component c2) {
super(splittype, c1, c2);
setOneTouchExpandable(true);
}
public void toggle() {
try {
if (getDividerLocation() < getWidth() - getDividerSize() - BUGCLICKONETOUCH) {
restoreWidth = getWidth() - getDividerSize() - getDividerLocation();
setDividerLocation(1.0d);
} else {
setDividerLocation(getWidth() - getDividerSize() - restoreWidth);
}
updateUI();
doLayout();
} catch (Exception e) {
e.printStackTrace();
}
}
public void doLayout() {
super.doLayout();
if (restoreWidth == -1) {
restoreWidth = rightComponent.getWidth();
}
}
}
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();
}
}
...
I am trying to write a Jython script in Fiji (ImageJ) that allows a user to place a point and upon placing the point adds it to the region of interest manager. This I can do but I also wish the user to be able to drag and drop already placed points without adding a new entry in the region of interest manager. Basically I want to be able to call mouseClicked and mouseDragged independently from each other whereas currently dragging the mouse will still activate a mouseClicked event. (I didn't know whether to put down Java as one of the tags but I feel it's closely related enough, I apologize if wrong).
Cheers!
class ML(MouseAdapter):
def mouseClicked(self, event):
canvas = event.getSource()
imp = canvas.getImage()
print click
roi.runCommand("Add")
roi.runCommand("UseNames", "true")
class ML2(MouseAdapter):
def mouseDragged(self, event):
canvas = event.getSource()
imp = canvas.getImage()
print "move!"
roi = ij.plugin.frame.RoiManager()
listener = ML()
listener2 = ML2()
for imp in map(WindowManager.getImage, WindowManager.getIDList()):
win = imp.getWindow()
if win is None:
continue
win.getCanvas().addMouseListener(listener)
win.getCanvas().addMouseMotionListener(listener2)
public void mouseClicked(MouseEvent arg0) {
}
/**
* handles mouse pressed event
*/
public void mousePressed(MouseEvent arg0)
{
// if filling we shouldn't add anything
if(b_Filling == true)
return;
// if dragging we shouldn't add anything
if(isDragging == true)
return;
// handles first vertex for each polygon
if(i_ThreeVertices == 0)
{
p_Start = arg0.getPoint();
p_End = new Point();
p_FirstVertex = new Point(p_Start);
}
else
{
// not the first vertex
p_Start = new Point(p_End);
p_End = arg0.getPoint();
}
// adds the vertex
b_Drawing = true;
addAPoint();
b_Drawing = false;
b_repaintFlag = true;
// repaints
this.repaint();
}
/**
* handles mouse released event
*/
public void mouseReleased(MouseEvent arg0)
{
// if filling we shouldn't add a vertex.
if(b_Filling == true)
return;
if(b_FirstVertexInPolygon == true && isDragging == false)
{
// unlocks first vertex state
b_FirstVertexInPolygon = false;
return;
}
// save previous vertex and add current if mouse is dragged
// and more than non vertices
if(isDragging == true && i_ThreeVertices != 0)
{
p_Start = new Point(p_End);
p_End = arg0.getPoint();
isDragging = false;
b_Drawing = true;
// adds a vertex
addAPoint();
b_Drawing = false;
}
else
p_End = arg0.getPoint();
b_repaintFlag = true;
// repaint
this.repaint();
}
/**
* handles mouse dragged event
*/
public void mouseDragged(MouseEvent arg0)
{
// repaints if not filling
if(b_Filling == true)
return;
p_End = arg0.getPoint();
isDragging = true;
b_repaintFlag = true;
this.repaint();
}
/**
* handles mouse moved event
*/
public void mouseMoved(MouseEvent arg0)
{
// if not filling, and we have at least one vertex
// and not dragging mouse, then repaint.
if(b_Filling == true)
return;
if(i_ThreeVertices == 0)
return;
if(isDragging == true)
return;
p_End = arg0.getPoint();
b_repaintFlag = true;
this.repaint();
}
public void mouseEntered(MouseEvent arg0){
}
public void mouseExited(MouseEvent arg0){
}
I would like to make the point info tooltip appear faster. How can i do it? with the default setting I have to hover the mouse onto the point, then wait to be able to see point coordinate information. I want the point coordinates to be immediately available. How can i do that?
ChartPanel provides getInitialDelay() and setInitialDelay() to query and alter "the initial tooltip delay value used inside this chart panel." As a concrete example based on BarChartDemo1, the following change to the constructor eliminates the initial delay entirely:
public BarChartDemo1(String title) {
super(title);
…
chartPanel.setInitialDelay(0);
setContentPane(chartPanel);
}
It's a late solution but here it is.
Here is how i handled with JavaFX.
It shows the tooltip fast instant and tooltip does not fade after a while.
/**
* The "tooltip" is the hard-coded id for the tooltip object.
* It's set inside the JFreeChart Lib.
* */
public static String TOOLTIP_ID = "tooltip";
public static void removeTooltipHandler(ChartViewer chartViewer) {
chartViewer.getCanvas().removeAuxiliaryMouseHandler(chartViewer.getCanvas().getMouseHandler(TOOLTIP_ID));
}
public static void addFasterTooltipHandler(ChartViewer chartViewer) {
if(chartViewer.getCanvas().getMouseHandler(TOOLTIP_ID) != null) {
removeTooltipHandler(chartViewer);
}
chartViewer.getCanvas().addAuxiliaryMouseHandler(new TooltipHandlerFX(TOOLTIP_ID) {
Tooltip tooltip;
boolean isVisible = false;
#Override
public void handleMouseMoved(ChartCanvas canvas, MouseEvent e) {
if (!canvas.isTooltipEnabled()) {
return;
}
String text = getTooltipText(canvas, e.getX(), e.getY());
setTooltip(canvas, text, e.getScreenX(), e.getScreenY());
}
private String getTooltipText(ChartCanvas canvas, double x, double y) {
ChartRenderingInfo info = canvas.getRenderingInfo();
if (info == null) {
return null;
}
EntityCollection entities = info.getEntityCollection();
if (entities == null) {
return null;
}
ChartEntity entity = entities.getEntity(x, y);
if (entity == null) {
return null;
}
return entity.getToolTipText();
}
// This function is copied from Canvas.setTooltip and manipulated as needed.
public void setTooltip(ChartCanvas canvas, String text, double x, double y) {
if (text != null) {
if (this.tooltip == null) {
this.tooltip = new Tooltip(text);
this.tooltip.autoHideProperty().set(false); // Disable auto hide.
Tooltip.install(canvas, this.tooltip);
} else {
this.tooltip.setText(text);
this.tooltip.setAnchorX(x);
this.tooltip.setAnchorY(y);
}
this.tooltip.show(canvas, x, y);
isVisible = true;
} else {
if(isVisible) {
this.tooltip.hide();
isVisible = false;
}
}
}
});
}
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.