I followed approach 2 of this guide, so now I have a ModalInternalFrame that blocks input to all other frames, just as I wanted. However, I made one change from the example, and now I have two problems.
The Change
I removed the JOptionPane, because the whole point is to show my own pane. In order to make it close, I set closeable to true, and added an InternalFrameListener with the same code as the example's listener for the JOptionPane. That doesn't work, so I also added code at the end of doDefaultCloseAction.
The Problems
The ModalInternal frame never goes away. I think some exception is being thrown but...
I can't see any thrown exceptions, and I don't know where they're going. Usually when in debug mode, Eclipse will stop right before the exception is given to the UncaughtExceptionHandler, but that isn't happening in this case.
The Code
If my description of the problem doesn't help, here's my version of the ModalInternalFrame. If you want more code, I can post that as well. Sorry it's so long, but I tried to make it as concise as possible.
public class ModalInternalFrame extends JInternalFrame {
public ModalInternalFrame(String title, JRootPane rootPane,
Component desktop) {
super(title, false, true, false, false);
// create opaque glass pane
final JPanel glass = new JPanel();
glass.setOpaque(false);
// Attach mouse listeners
MouseInputAdapter adapter = new MouseInputAdapter() { };
glass.addMouseListener(adapter);
glass.addMouseMotionListener(adapter);
this.addInternalFrameListener(new InternalFrameListenerAdapter() {
public void internalFrameClosed(InternalFrameEvent e) { close(); }
public void internalFrameClosing(InternalFrameEvent e){ close(); }
});
// Change frame border
putClientProperty("JInternalFrame.frameType", "optionDialog");
// Size frame
Dimension size = getPreferredSize();
Dimension rootSize = desktop.getSize();
setBounds((rootSize.width - size.width) / 2,
(rootSize.height - size.height) / 2, size.width, size.height);
desktop.validate();
try { setSelected(true); }
catch (PropertyVetoException ignored) { }
glass.add(this); // Add modal internal frame to glass pane
rootPane.setGlassPane(glass); // Change glass pane to our panel
glass.setVisible(true); // Show glass pane, then modal dialog
}
private void close(){
if (isVisible()) {
try { setClosed(true); }
catch (PropertyVetoException ignored) { }
setVisible(false);
rootPane.getGlassPane().setVisible(false);
}
}
#Override public void doDefaultCloseAction() {
super.doDefaultCloseAction();
close();
}
#Override public void setVisible(boolean flag) {
super.setVisible(flag);
if (flag) startModal();
else stopModal();
}
private synchronized void startModal() {
try {
if (SwingUtilities.isEventDispatchThread()) {
EventQueue theQueue = getToolkit().getSystemEventQueue();
while (isVisible()) {
AWTEvent event = theQueue.getNextEvent();
Object source = event.getSource();
if (event instanceof ActiveEvent) {
((ActiveEvent) event).dispatch();
} else if (source instanceof Component) {
((Component) source).dispatchEvent(event);
} else if (source instanceof MenuComponent) {
((MenuComponent) source).dispatchEvent(event);
} else {
System.err.println("Unable to dispatch: " + event);
}
}
} else { while (isVisible()) { wait(); } }
} catch (InterruptedException ignored) {
}
}
private synchronized void stopModal() { notifyAll(); }
}
Update: I've discovered that modal dialog boxes suit my needs fine, but if anyone does have an idea, I'd be glad to hear it. One thing I haven't tried is wrapping every method in a try {} catch (Exception e){} which would probably help a lot.
I can't quite get your code to run, but here's a simpler version, based on the Sun example, that does work -- the main frame has a button in it (taking up all the available space), but clicking the button is blocked until the internal frame has been closed.
You can see, pretty much all I did was replace the new JOptionPane().createInternalFrame() business with my own frame. My guess is that you're overcomplicating things when you try to do your own event dispatching.
Or am I missing something?
public class Foo {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setSize(600, 400);
frame.setLocationByPlatform(true);
JButton desktop = new JButton(new AbstractAction("Click me if you can") {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("I have been clicked");
}
});
frame.getContentPane().add(desktop);
frame.setVisible(true);
JInternalFrame modal =
new JInternalFrame("Modal Popup", false, true, false, false);
JLabel popupContent = new JLabel("I am the popup");
popupContent.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
modal.add(popupContent);
modal.pack();
JPanel glass = new JPanel();
glass.setOpaque(false);
glass.add(modal);
frame.setGlassPane(glass);
glass.setVisible(true);
modal.setVisible(true);
modal.addInternalFrameListener(new ModalAdapter(glass));
}
}
class ModalAdapter extends InternalFrameAdapter {
Component glass;
public ModalAdapter(Component glass) {
this.glass = glass;
// Associate dummy mouse listeners
// Otherwise mouse events pass through
MouseInputAdapter adapter = new MouseInputAdapter() {
};
glass.addMouseListener(adapter);
glass.addMouseMotionListener(adapter);
}
public void internalFrameClosed(InternalFrameEvent e) {
glass.setVisible(false);
}
}
Try this. I got it from the Webby IT blog post on JInternal Frames: http://webbyit.blogspot.com/2011/03/managing-jinternalframes-within.html
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
import javax.swing.event.InternalFrameAdapter;
import javax.swing.event.InternalFrameEvent;
/**
* An extended <code>JInternalFrame</code> that provides modality in a child/parent hierarchy.
* source: webby it internal frames blog post
*
* #author webbyit
*/
public class ModalityInternalFrame extends JInternalFrame {
protected JDesktopPane desktopPane;
protected JComponent parent;
protected ModalityInternalFrame childFrame;
protected JComponent focusOwner;
private boolean wasCloseable;
public ModalityInternalFrame() {
init(); // here to allow netbeans to use class in gui builder
}
public ModalityInternalFrame(JComponent parent) {
this(parent, null);
}
public ModalityInternalFrame(JComponent parent, String title) {
this(parent, title, false);
}
public ModalityInternalFrame(JComponent parent, String title, boolean resizable) {
this(parent, title, resizable, false);
}
public ModalityInternalFrame(JComponent parent, String title, boolean resizable, boolean closeable) {
this(parent, title, resizable, closeable, false);
}
public ModalityInternalFrame(JComponent parent, String title, boolean resizable, boolean closeable,
boolean maximizable) {
this(parent, title, resizable, closeable, maximizable, false);
}
public ModalityInternalFrame(JComponent parent, String title, boolean resizable, boolean closeable,
boolean maximizable,
boolean iconifiable) {
super(title, resizable, closeable, maximizable, iconifiable);
setParentFrame(parent);
//setFocusTraversalKeysEnabled(false);
if (parent != null && parent instanceof ModalityInternalFrame) {
((ModalityInternalFrame) parent).setChildFrame(ModalityInternalFrame.this);
/*
* set focus to the new frame and show the frame Code added by Jasir
*/
try {
((ModalityInternalFrame) parent).setSelected(false);
setSelected(true);
setVisible(true);
} catch (PropertyVetoException ex) {
Logger.getLogger(ModalityInternalFrame.class.getName()).log(Level.SEVERE, null, ex);
}
}
// Add glass pane
ModalityInternalGlassPane glassPane = new ModalityInternalGlassPane(this);
setGlassPane(glassPane);
// Add frame listeners
addFrameListener();
// Add frame veto listenr
addFrameVetoListener();
init();
// calculate size and position
}
private void setParentFrame(JComponent parent) {
desktopPane = JOptionPane.getDesktopPaneForComponent(parent);
this.parent = parent == null ? JOptionPane.getDesktopPaneForComponent(parent) : parent; // default to desktop if no parent given
}
public JComponent getParentFrame() {
return parent;
}
public void setChildFrame(ModalityInternalFrame childFrame) {
this.childFrame = childFrame;
}
public ModalityInternalFrame getChildFrame() {
return childFrame;
}
public boolean hasChildFrame() {
return (childFrame != null);
}
protected void addFrameVetoListener() {
addVetoableChangeListener(new VetoableChangeListener() {
public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
if (evt.getPropertyName().equals(JInternalFrame.IS_SELECTED_PROPERTY)
&& evt.getNewValue().equals(Boolean.TRUE)) {
if (hasChildFrame()) {
//childFrame.setSelected(true);
if (childFrame.isIcon()) {
childFrame.setIcon(false);
}
throw new PropertyVetoException("no!", evt);
}
}
}
});
}
/**
* Method to control the display of the glass pane, dependant on the frame
* being active or not
*/
protected synchronized void addFrameListener() {
addInternalFrameListener(new InternalFrameAdapter() {
#Override
public void internalFrameActivated(InternalFrameEvent e) {
if (hasChildFrame() == true) {
getGlassPane().setVisible(true);
grabFocus();
} else {
getGlassPane().setVisible(false);
}
}
#Override
public void internalFrameOpened(InternalFrameEvent e) {
getGlassPane().setVisible(false);
try {
setSelected(true);
} catch (PropertyVetoException ex) {
Logger.getLogger(ModalityInternalFrame.class.getName()).log(Level.SEVERE, null, ex);
}
}
#Override
public void internalFrameClosing(InternalFrameEvent e) {
if (parent != null && parent instanceof ModalityInternalFrame) {
((ModalityInternalFrame) parent).childClosing();
}
}
});
}
/**
* Method to handle child frame closing and make this frame available for
* user input again with no glass pane visible
*/
protected void childClosing() {
setClosable(wasCloseable);
getGlassPane().setVisible(false);
if (focusOwner != null) {
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
moveToFront();
setSelected(true);
focusOwner.grabFocus();
} catch (PropertyVetoException ex) {
}
}
});
focusOwner.grabFocus();
}
getGlassPane().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
setChildFrame(null);
getDesktopPane().setSelectedFrame(this);
System.out.println(getDesktopPane().getSelectedFrame());
}
/*
* Method to handle child opening and becoming visible.
*/
protected void childOpening() {
// record the present focused component
wasCloseable = isClosable();
setClosable(false);
focusOwner = (JComponent) getMostRecentFocusOwner();
grabFocus();
getGlassPane().setVisible(true);
getGlassPane().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
}
#Override
public void show() {
if (parent != null && parent instanceof ModalityInternalFrame) {
// Need to inform parent its about to lose its focus due
// to child opening
((ModalityInternalFrame) parent).childOpening();
}
calculateBounds();
super.show();
}
protected void init() {
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGap(0, 394, Short.MAX_VALUE));
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGap(0, 274, Short.MAX_VALUE));
pack();
}
public void calculateBounds() {
Dimension frameSize = getPreferredSize();
Dimension parentSize = new Dimension();
Dimension rootSize = new Dimension(); // size of desktop
Point frameCoord = new Point();
if (desktopPane != null) {
rootSize = desktopPane.getSize(); // size of desktop
frameCoord = SwingUtilities.convertPoint(parent, 0, 0, desktopPane);
parentSize = parent.getSize();
}
//setBounds((rootSize.width - frameSize.width) / 2, (rootSize.height - frameSize.height) / 2, frameSize.width, frameSize.height);
// We want dialog centered relative to its parent component
int x = (parentSize.width - frameSize.width) / 2 + frameCoord.x;
int y = (parentSize.height - frameSize.height) / 2 + frameCoord.y;
// If possible, dialog should be fully visible
int ovrx = x + frameSize.width - rootSize.width;
int ovry = y + frameSize.height - rootSize.height;
x = Math.max((ovrx > 0 ? x - ovrx : x), 0);
y = Math.max((ovry > 0 ? y - ovry : y), 0);
setBounds(x, y, frameSize.width, frameSize.height);
}
/**
* Glass pane to overlay. Listens for mouse clicks and sets selected on
* associated modal frame. Also if modal frame has no children make class
* pane invisible
*/
class ModalityInternalGlassPane extends JComponent {
private ModalityInternalFrame modalFrame;
public ModalityInternalGlassPane(ModalityInternalFrame frame) {
modalFrame = frame;
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (modalFrame.isSelected() == false) {
try {
modalFrame.setSelected(true);
if (modalFrame.hasChildFrame() == false) {
setVisible(false);
}
} catch (PropertyVetoException e1) {
//e1.printStackTrace();
}
}
}
});
}
#Override
public void paint(Graphics g) {
super.paint(g);
g.setColor(new Color(255, 255, 255, 100));
g.fillRect(0, 0, getWidth(), getHeight());
}
}
}
I just had to do this thing for a project. All I did was passed the main window object to Jinternalframe. The main object has a semaphore that tracks whether modal is locked or not. Upon closing the Jinternalframe (extension) calls the main object's semaphore. Very simple.
This is not the main code but you get the idea :
//called frame
public CallingFrame parent;
public void setParent(CallingFrame parent_){
this.parent=parent_;
}
private void frameClosed(javax.swing.event.InternalFrameEvent evt) {
parent.modalLocked=false;
}
In my case the application uses a label with image-parts to call internalframes, so the code starts with
//calling frame
CalledFrame cf=new CalledFrame();
cf.setParent(this);
cf.setVisible(true);
modalLoacked=true;
private void jLabel1MouseReleased(java.awt.event.MouseEvent evt) {
if (modalLocked)
return;
else// (do your things)
}
I followed the tutorials but most of them are overcomplicating things when a single semaphore will do the tricks of not letting you click on any area while one Called Frame is not closed.
public void internalFrameClosing(InternalFrameEvent e){ close(); }
calling close() will cause internalFrameClosing() to be called again, until the stack overflows.
Try removing that listener altogether.
You can just add setClosable(true); in the constructor
Related
I'm developing a Java Swing app for an award winning password protection system, and I need a large custom cursor [ 80 x 80 ], you might ask why so large, there is an online web demo you may look at to learn why it needs to be so large : http://gatecybertech.net
That large cursor is used on the login page in the above link. Of course you need to create a test password first before you can try the login process.
But anyway, in my Swing app, I hit a limit of 32 x 32 for the largest possible custom cursor, my code looks like the following :
Image cursorImage = toolkit.getImage("Cursor_Crosshair.PNG");
Tabular_Panel.setCursor(Toolkit.getDefaultToolkit().createCustomCursor(cursorImage,new Point(0,0),"custom cursor"));
The image size of Cursor_Crosshair.PNG is : 80 x 80
But what shows up in the screen is a shrinked version of it at : 32 x 32
So my question is : how can I bypass the size limit on customer cursor image, and make the cursor to show up at the size of 80 x 80 ?
I know the OS might be the reason for the limit, is there a way to overcome it ?
Here's my take on the glass pane painting approach. This is set up to behave pretty much like setting a custom cursor. The default "arrow" cursor is hidden while the custom cursor is shown, and the custom cursor is hidden when a component has some other cursor set, such as a text box.
Unfortunately, it ended up seeming to require quite a bit of Swing black magic, so I don't like it very much, but it does seem to work correctly. I've done a cursor like this before, but it was for something simpler, so I didn't run in to these issues.
Some of the problems I ran in to are:
The glass pane intercepts cursor changes (described e.g. on SO here). The only solution I've been able to find is to override Component.contains(int,int) to return false (described here, shown here), but why that works and doesn't seem to break anything else is mysterious.
Mouse exit events sometimes return a location inside the bounds of the component, so I don't think there's a reliable way to know when the mouse leaves the window except to use a timer.
package mcve;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.imageio.*;
import java.net.*;
import java.io.*;
public class LargeCursor {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
JPanel glass = new CustomGlassPane();
glass.add(new CursorPanel(), BorderLayout.CENTER);
frame.setGlassPane(glass);
// This next call is necessary because JFrame.setGlassPane delegates to the root pane:
// - https://docs.oracle.com/javase/9/docs/api/javax/swing/RootPaneContainer.html#setGlassPane-java.awt.Component-
// - http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/javax/swing/JFrame.java#l738
// And JRootPane.setGlassPane may call setVisible(false):
// - https://docs.oracle.com/javase/9/docs/api/javax/swing/JRootPane.html#setGlassPane-java.awt.Component-
// - http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/javax/swing/JRootPane.java#l663
glass.setVisible(true);
JPanel content = createTestPanel();
content.setCursor(BlankCursor.INSTANCE);
frame.setContentPane(content);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
static class CustomGlassPane extends JPanel {
CustomGlassPane() {
super(new BorderLayout());
super.setOpaque(false);
}
#Override
public boolean contains(int x, int y) {
return false;
}
}
static class CursorPanel extends JPanel {
final BufferedImage cursorImage;
Point mouseLocation;
CursorPanel() {
try {
cursorImage = createTransparentImage(
ImageIO.read(new URL("https://i.stack.imgur.com/9h2oI.png")));
} catch (IOException x) {
throw new UncheckedIOException(x);
}
setOpaque(false);
long mask = AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK;
Toolkit.getDefaultToolkit().addAWTEventListener((AWTEvent e) -> {
switch (e.getID()) {
case MouseEvent.MOUSE_ENTERED:
case MouseEvent.MOUSE_EXITED:
case MouseEvent.MOUSE_MOVED:
case MouseEvent.MOUSE_DRAGGED:
capturePoint((MouseEvent) e);
break;
}
}, mask);
// This turned out to be necessary, because
// the 'mouse exit' events don't always have
// a Point location which is outside the pane.
Timer timer = new Timer(100, (ActionEvent e) -> {
if (mouseLocation != null) {
Point p = MouseInfo.getPointerInfo().getLocation();
SwingUtilities.convertPointFromScreen(p, this);
if (!contains(p)) {
setMouseLocation(null);
}
}
});
timer.setRepeats(true);
timer.start();
}
void capturePoint(MouseEvent e) {
Component comp = e.getComponent();
Point onThis = SwingUtilities.convertPoint(comp, e.getPoint(), this);
boolean drawCursor = contains(onThis);
if (drawCursor) {
Window window = SwingUtilities.windowForComponent(this);
if (window instanceof JFrame) {
Container content = ((JFrame) window).getContentPane();
Point onContent = SwingUtilities.convertPoint(comp, e.getPoint(), content);
Component deepest = SwingUtilities.getDeepestComponentAt(content, onContent.x, onContent.y);
if (deepest != null) {
if (deepest.getCursor() != BlankCursor.INSTANCE) {
drawCursor = false;
}
}
}
}
setMouseLocation(drawCursor ? onThis : null);
}
void setMouseLocation(Point mouseLocation) {
this.mouseLocation = mouseLocation;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (mouseLocation != null) {
int x = mouseLocation.x - (cursorImage.getWidth() / 2);
int y = mouseLocation.y - (cursorImage.getHeight() / 2);
g.drawImage(cursorImage, x, y, this);
}
}
}
static final class BlankCursor {
static final Cursor INSTANCE =
Toolkit.getDefaultToolkit().createCustomCursor(
new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB),
new Point(),
"BLANK");
}
static JPanel createTestPanel() {
JPanel panel = new JPanel(new GridLayout(3, 3));
panel.setBorder(BorderFactory.createEmptyBorder(100, 100, 100, 100));
for (int i = 0; i < 9; ++i) {
if ((i % 2) == 0) {
JTextField field = new JTextField("Text Field");
field.setHorizontalAlignment(JTextField.CENTER);
panel.add(field);
} else {
panel.add(new JButton("Button"));
}
}
return panel;
}
static BufferedImage createTransparentImage(BufferedImage img) {
BufferedImage copy =
GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice()
.getDefaultConfiguration()
.createCompatibleImage(img.getWidth(),
img.getHeight(),
Transparency.TRANSLUCENT);
for (int x = 0; x < img.getWidth(); ++x) {
for (int y = 0; y < img.getHeight(); ++y) {
int rgb = img.getRGB(x, y) & 0x00FFFFFF;
int bright = (((rgb >> 16) & 0xFF) + ((rgb >> 8) & 0xFF) + (rgb & 0xFF)) / 3;
int alpha = 255 - bright;
copy.setRGB(x, y, (alpha << 24) | rgb);
}
}
return copy;
}
}
OK, after some research and modification, I found the answer :
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.*;
import javax.swing.event.MouseInputAdapter;
public class Demo_Large_Custom_Cursor
{
static private MyGlassPane myGlassPane;
// Create the GUI and show it. For thread safety, this method should be invoked from the event-dispatching thread.
private static void createAndShowGUI()
{
//Create and set up the window.
JFrame frame=new JFrame("Demo_Large_Custom_Cursor");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Start creating and adding components.
JCheckBox changeButton=new JCheckBox("Custom Cursor \"visible\"");
changeButton.setSelected(false);
//Set up the content pane, where the "main GUI" lives.
Container contentPane=frame.getContentPane();
contentPane.setLayout(new FlowLayout());
contentPane.add(changeButton);
JButton Button_1=new JButton("<Html><Table Cellpadding=7><Tr><Td>A</Td><Td>B</Td></Tr><Tr><Td>C</Td><Td>D</Td></Tr></Table></Html>");
Button_1.setPreferredSize(new Dimension(80,80));
Button_1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { Out("Button 1"); } });
contentPane.add(Button_1);
JButton Button_2=new JButton("<Html><Table Cellpadding=7><Tr><Td>1</Td><Td>2</Td></Tr><Tr><Td>3</Td><Td>4</Td></Tr></Table></Html>");
Button_2.setPreferredSize(new Dimension(80,80));
Button_2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { Out("Button 2"); } });
contentPane.add(Button_2);
//Set up the menu bar, which appears above the content pane.
JMenuBar menuBar=new JMenuBar();
JMenu menu=new JMenu("Menu");
menu.add(new JMenuItem("Do nothing"));
menuBar.add(menu);
frame.setJMenuBar(menuBar);
//Set up the glass pane, which appears over both menu bar
//and content pane and is an item listener on the change
//button.
myGlassPane=new MyGlassPane(changeButton,menuBar,frame.getContentPane());
changeButton.addItemListener(myGlassPane);
frame.setGlassPane(myGlassPane);
//Show the window.
frame.setLocationRelativeTo(null);
frame.pack();
frame.setVisible(true);
}
private static void out(String message) { System.out.print(message); }
private static void Out(String message) { System.out.println(message); }
public static void main(String[] args)
{
//Schedule a job for the event-dispatching thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
}
}
/**
We have to provide our own glass pane so that it can paint.
*/
class MyGlassPane extends JComponent implements ItemListener
{
Point point;
//React to change button clicks.
public void itemStateChanged(ItemEvent e)
{
setVisible(e.getStateChange()==ItemEvent.SELECTED);
}
protected void paintComponent(Graphics g)
{
try
{
if (point!=null)
{
// g.setColor(Color.red);
// g.fillOval(point.x-10,point.y-10,20,20);
BufferedImage image=ImageIO.read(new File("C:/Cursor_Crosshair.PNG"));
g.drawImage(image,point.x-39,point.y-39,null);
}
}
catch (Exception e) { }
}
public void setPoint(Point p)
{
point=p;
}
public MyGlassPane(AbstractButton aButton,JMenuBar menuBar,Container contentPane)
{
CBListener listener=new CBListener(aButton,menuBar,this,contentPane);
addMouseListener(listener);
addMouseMotionListener(listener);
}
}
/**
Listen for all events that our check box is likely to be interested in. Redispatch them to the check box.
*/
class CBListener extends MouseInputAdapter
{
Toolkit toolkit;
Component liveButton;
JMenuBar menuBar;
MyGlassPane glassPane;
Container contentPane;
public CBListener(Component liveButton,JMenuBar menuBar,MyGlassPane glassPane,Container contentPane)
{
toolkit=Toolkit.getDefaultToolkit();
this.liveButton=liveButton;
this.menuBar=menuBar;
this.glassPane=glassPane;
this.contentPane=contentPane;
}
public void mouseMoved(MouseEvent e)
{
// redispatchMouseEvent(e,false);
redispatchMouseEvent(e,true);
}
public void mouseDragged(MouseEvent e)
{
redispatchMouseEvent(e,false);
}
public void mouseClicked(MouseEvent e)
{
redispatchMouseEvent(e,false);
}
public void mouseEntered(MouseEvent e)
{
redispatchMouseEvent(e,false);
}
public void mouseExited(MouseEvent e)
{
redispatchMouseEvent(e,false);
}
public void mousePressed(MouseEvent e)
{
redispatchMouseEvent(e,false);
}
public void mouseReleased(MouseEvent e)
{
redispatchMouseEvent(e,true);
}
//A basic implementation of redispatching events.
private void redispatchMouseEvent(MouseEvent e,boolean repaint)
{
Point glassPanePoint=e.getPoint();
Container container=contentPane;
Point containerPoint=SwingUtilities.convertPoint(glassPane,glassPanePoint,contentPane);
if (containerPoint.y<0)
{ //we're not in the content pane
if (containerPoint.y+menuBar.getHeight()>=0)
{
//The mouse event is over the menu bar.
//Could handle specially.
}
else
{
//The mouse event is over non-system window
//decorations, such as the ones provided by
//the Java look and feel.
//Could handle specially.
}
}
else
{
//The mouse event is probably over the content pane.
//Find out exactly which component it's over.
Component component=SwingUtilities.getDeepestComponentAt(container,containerPoint.x,containerPoint.y);
// if ((component!=null) && (component.equals(liveButton)))
if ((component!=null))
{
//Forward events over the check box.
Point componentPoint=SwingUtilities.convertPoint(glassPane,glassPanePoint,component);
component.dispatchEvent(new MouseEvent(component,e.getID(),e.getWhen(),e.getModifiers(),componentPoint.x,componentPoint.y,e.getClickCount(),e.isPopupTrigger()));
}
}
//Update the glass pane if requested.
if (repaint)
{
glassPane.setPoint(glassPanePoint);
glassPane.repaint();
}
}
}
And the Cursor_Crosshair.PNG is like this :
I have an application I'm making for a game to automatically update a game client.
Once you press Launch, it will open up my DownloadFrame (extends JDialog), and will look like this:
If you click the icon for the application in the taskbar, (maybe Windows 8 is the problem?) it will minimize the application like usual. However when you go to maximise the application again, the JDialog will be hidden, I'm assuming, behind the parent. It looks like this:
Here's my code for my extension of JDialog. Apologies in advance for it being messy.
public class DownloadFrame extends JDialog implements Runnable {
private static final long serialVersionUID = -8764984599528942303L;
private Background frame;
private ImageIcon[] gifs;
private JLabel spinner;
public DownloadFrame() {
super(Loader.application, false);
setLayout(null);
setUndecorated(true);
setAutoRequestFocus(true);
new Thread(this).start();
generateBackground();
generateButton();
generateGif();
}
private void generateBackground() {
frame = new Background("sub_background.png");
setSize(frame.getWidth(), frame.getHeight());
setBackground(new Color(1.0f, 1.0f, 1.0f, 0.0f));
setLocationRelativeTo(null);
setLocation(this.getX(), this.getY() + 5);
setLayout(null);
setContentPane(frame);
}
private void generateGif() {
gifs = Utils.generateGifImages();
spinner = new JLabel(gifs[0]);
spinner.setBounds(70, 30, gifs[0].getIconWidth(), gifs[0].getIconHeight());
add(spinner);
}
private HoverableButton cancel;
public HoverableButton getCancelButton() {
return cancel;
}
private void generateButton() {
cancel = new HoverableButton(Settings.CANCEL_BUTTON, 75, 145);
cancel.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent event) {
/*
* TODO -
* stop the download in progress
*/
for (HoverableButton button : Loader.application.getPrimaryButtons()) {
button.setActive(true);
button.setVisible(true);
}
dispose();
}
});
add(cancel);
}
private int cycleCount;
private void cycleGif() {
if (spinner == null) {
return;
}
cycleCount++;
if (cycleCount > 7) {
cycleCount = 0;
}
spinner.setIcon(gifs[cycleCount]);
}
#Override
public void run() {
while (true) {
cycleGif();
try {
Thread.sleep(100L);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
In case it's needed, here's my usage of it. Most of the stuff can be ignored I'm sure, it's simply there to hide the four buttons while the download is in progress.
((HoverableButton) components[2]).addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
HoverableButton source = (HoverableButton) components[2];
if (source.isActive()) {
try {
Thread.sleep(500L);
} catch (Exception ex) {
ex.printStackTrace();
}
if (panel == null) {
panel = new DownloadFrame();
panel.setVisible(true);
} else {
panel.setVisible(true);
panel.getCancelButton().removeHighlight();
}
for (HoverableButton button : getPrimaryButtons()) {
button.setActive(false);
button.setVisible(false);
button.removeHighlight();
}
/*
* TODO -
* handle checking for updates / downloading updates
*/
}
}
});
However when you go to maximise the application again, the JDialog will be hidden, I'm assuming, behind the parent
Yes. When you create the JDialog, you need to specify the "owner" JFrame of the dialog in the constructor.
So you must create and make the JFrame and make the frame visible before you create the dialog.
I have JTextPane (log) inside JScrollPane (logScrollPane) element. Log's content is set to "text/html".
I created a method that appends this log which looks like this:
public void appendLog(String someHTMLText)
{
HTMLDocument doc = (HTMLDocument) log.getDocument();
HTMLEditorKit editorKit = (HTMLEditorKit) log.getEditorKit();
try
{
editorKit.insertHTML(doc, doc.getLength(), someHTMLText, 0, 0, null);
}
catch (BadLocationException | IOException ex)
{
// handle exceptions
}
}
I want to improve this method and force logScrollPane's VerticalScrollBar to move_to_the_bottom/stay_at_it's_position depending on additional boolean argument.
Final method should look like this:
public void appendLog(String someHTMLText, boolean scroll)
{
if(scroll)
{
/*
* append log and set VerticalScrollBar to the bottom by
* log.setCaretPosition(log.getDocument().getLength());
*/
}
else
{
// append log BUT make VerticalScrollBar stay at it's previous position
}
}
Any suggestions? :)
Here is something similar to what I did when I wanted to achieve this. The key is to alter the behavior of scrollRectToVisible.
public class Docker extends JFrame {
boolean dockScrollbar = true;
MYTextPane textPane = new MYTextPane();
JScrollPane sp = new JScrollPane(textPane);
Docker() {
JCheckBox scrollbarDockCB = new JCheckBox("Dock scrollbar");
scrollbarDockCB.addItemListener(new DockScrollbarListener());
scrollbarDockCB.setSelected(true);
JButton insertText = new JButton("Insert text");
insertText.addActionListener(new TextInserter());
getContentPane().add(insertText, BorderLayout.PAGE_START);
getContentPane().add(sp);
getContentPane().add(scrollbarDockCB, BorderLayout.PAGE_END);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
pack();
setVisible(true);
}
class MYTextPane extends JTextPane {
MYTextPane() {
setEditorKit(new HTMLEditorKit());
}
#Override
public void scrollRectToVisible(Rectangle aRect) {
if (dockScrollbar)
super.scrollRectToVisible(aRect);
}
void insertText(String msg) {
HTMLEditorKit kit = (HTMLEditorKit) getEditorKit();
HTMLDocument doc = (HTMLDocument) getDocument();
try {
kit.insertHTML(doc, doc.getLength(), msg, 0, 0, null);
} catch (BadLocationException | IOException e1) {
e1.printStackTrace();
}
setCaretPosition(doc.getLength());
}
}
class TextInserter implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
textPane.insertText("AAA\n");
}
}
class DockScrollbarListener implements ItemListener {
#Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
dockScrollbar = true;
JScrollBar sb = sp.getVerticalScrollBar();
sb.setValue(sb.getMaximum());
}
else if (e.getStateChange() == ItemEvent.DESELECTED)
dockScrollbar = false;
}
}
public static void main(String[] args) {
new Docker();
}
}
Notes:
I made the docking set to false when you manually scroll in my code, you can add it here too. by adding a mouse listener to the vertical scrollbar.
I have boolean dockScrollbar as a field of the text pane because I have more than one.
I don't have a field for the JScrollPane, I get it through the text pane.
I've never tried it on a JEditorPane but you should be able to use the caret update policy to control this.
Check out Text Area Scrolling for more information.
I'm blocked with a probelm about Java and the use of JTree:
I want to create a JTree with, node by node, some JButtons components (or Images, I don't mind), like in the following picture. It will be 3 or 4 buttons in the same row. I succeed to do that.
But where I'm blocked is when I want to add a mouselistener on each of this button to manage their tooltip or an action on them.
In fact the JTree component is most of the time used to manage the action on the full node, but not on its inside components.
I did a short code, in comparaison at the real big code I have to work in, to quickly test what I say:
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.tree.*;
import java.awt.event.*;
import java.awt.*;
import java.io.IOException;
import java.net.URL;
public class TreeWithPopup extends JPanel {
DefaultMutableTreeNode root, node1, node2, node3;
public TreeWithPopup() {
MyJTree tree;
root = new DefaultMutableTreeNode("root", true);
node1 = new DefaultMutableTreeNode("node 1", true);
node2 = new DefaultMutableTreeNode("node 2", true);
node3 = new DefaultMutableTreeNode("node 3", true);
root.add(node1);
node1.add(node2);
root.add(node3);
setLayout(new BorderLayout());
tree = new MyJTree(root);
tree.setCellRenderer(new PCellRenderer());
add(new JScrollPane((JTree) tree), "Center");
}
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
public static void main(String s[]) {
JFrame frame = new JFrame("Tree with button");
TreeWithPopup panel = new TreeWithPopup();
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.setForeground(Color.black);
frame.setBackground(Color.lightGray);
frame.getContentPane().add(panel, "Center");
frame.setSize(panel.getPreferredSize());
frame.setVisible(true);
frame.addWindowListener(new WindowCloser());
}
}
class WindowCloser extends WindowAdapter {
public void windowClosing(WindowEvent e) {
Window win = e.getWindow();
win.setVisible(false);
System.exit(0);
}
}
class MyJTree extends JTree implements ActionListener {
MyJTree(DefaultMutableTreeNode dmtn) {
super(dmtn);
addMouseListener(new MouseListener() {
public void mouseClicked(MouseEvent arg0) {
System.out.println("JTree.MouseListener");
}
public void mouseEntered(MouseEvent arg0) {
System.out.println("JTree.MouseListener");
}
public void mouseExited(MouseEvent arg0) {
System.out.println("JTree.MouseListener");
}
public void mousePressed(MouseEvent arg0) {
System.out.println("JTree.MouseListener");
}
public void mouseReleased(MouseEvent arg0) {
System.out.println("JTree.MouseListener");
}
});
}
public void actionPerformed(ActionEvent ae) {
System.out.println("MyJTree.ActionPerformed");
}
}
class PCellRenderer extends DefaultTreeCellRenderer {
public Component getTreeCellRendererComponent(JTree ptree, Object pvalue, boolean psel, boolean pexpanded, boolean pleaf, int prow,
boolean phasFocus) {
Box myPanel = new Box(BoxLayout.X_AXIS);
JButton myButton = new JButton("test");
Image imgToUse = null;
Image imgRollOver = null;
try {
URL urlIcon = new URL("file:///C:/1.jpg"); // <===== change their the path to icons
imgToUse = ImageIO.read(urlIcon);
urlIcon = new URL("file:///C:/2.jpg"); // <===== change their the path to icons
imgRollOver = ImageIO.read(urlIcon);
} catch (IOException e) {
e.printStackTrace();
}
myButton.setRolloverIcon(new ImageIcon(imgRollOver));
myButton.setIcon(new ImageIcon(imgToUse));
myButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
System.out.println(" detected on ");
}
});
myButton.addMouseListener(new MouseListener() {
public void mouseClicked(MouseEvent arg0) {
System.out.println("myButton.MouseListener");
}
public void mouseEntered(MouseEvent arg0) {
System.out.println("myButton.MouseListener");
}
public void mouseExited(MouseEvent arg0) {
System.out.println("myButton.MouseListener");
}
public void mousePressed(MouseEvent arg0) {
System.out.println("myButton.MouseListener");
}
public void mouseReleased(MouseEvent arg0) {
System.out.println("myButton.MouseListener");
}
});
myPanel.add(myButton);
return myPanel;
}
}
You just have to change the icon path or put the two following icons at "c:/"
I also searched to use the x/y position of the row event but I was unable to find the position of my button after rendering.
If anyone can have an idea how to do that, he could be very helpful.
Thanks, at least for read this question ;-)
Tooltip support is actually provided by the TableCellRenderer directly. Your TableCellRenderer is a little muddled. It extends from DefaultTreeCellRenderer but makes no use of any of it's features, instead creating a new Box, JButton and loading icons each time a cell is rendered...
This is going to increase your memory usage a slow you application down...
Instead, try something like...
class PCellRenderer extends Box implements TreeCellRenderer {
private Image imgToUse = null;
private Image imgRollOver = null;
public PCellRenderer() {
super(BoxLayout.X_AXIS);
JButton myButton = new JButton("test");
try {
URL urlIcon = new URL("file:///C:/1.jpg"); // <===== change their the path to icons
imgToUse = ImageIO.read(urlIcon);
urlIcon = new URL("file:///C:/2.jpg"); // <===== change their the path to icons
imgRollOver = ImageIO.read(urlIcon);
} catch (IOException e) {
e.printStackTrace();
}
myButton.setRolloverIcon(new ImageIcon(imgRollOver));
myButton.setIcon(new ImageIcon(imgToUse));
add(myButton);
}
public Component getTreeCellRendererComponent(JTree ptree, Object pvalue, boolean psel, boolean pexpanded, boolean pleaf, int prow,
boolean phasFocus) {
// set the tooltip text here...
// Maybe change the icon...
return this;
}
}
Now, actually doing something...
Renderers are rubber stamps. That are not actually life components. Think of them like photos. You can take a snap shot of your friends, but you can't interact with them...same thing here...
Your idea of a MouseListener on the JTree is a correct one, in fact the JavaDocs actually have a demonstration of this...
public void mousePressed(MouseEvent e) {
int selRow = tree.getRowForLocation(e.getX(), e.getY());
TreePath selPath = tree.getPathForLocation(e.getX(), e.getY());
if(selRow != -1) {
if(e.getClickCount() == 1) {
mySingleClick(selRow, selPath);
}
else if(e.getClickCount() == 2) {
myDoubleClick(selRow, selPath);
}
}
}
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.