Tuesday, December 29, 2009

Default right-click in all text components of an application

First of all, Merry Christmas and Happy New Year everybody!

Many people are still surprised not to see a default right-click popup in all text components of a Java application. One way to do it, is to push a new EventQueue to the default one. One concern could be applets in general (unsigned applets, global EventQueue).
Toolkit.getDefaultToolkit().getSystemEventQueue().push(new PopupEventQueue());
Below is the code for the event queue.

import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.EventQueue;

import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;

import javax.swing.AbstractAction;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;

import javax.swing.text.JTextComponent;

public class PopupEventQueue extends EventQueue {

private final JPopupMenu popup = new JPopupMenu();
private final TextAction[] popupActions = new TextAction[4];

public PopupEventQueue() {
popupActions[0] = new TextAction("Cut") {
private static final long serialVersionUID = -3844049016540352208L;

public void actionPerformed(ActionEvent ae) {
textComponent.cut();
}

@Override
protected void postTextComponentInitialize() {
setEnabled(textComponent.isEditable() && isTextSelected());
}
};
popupActions[1] = new TextAction("Copy") {
private static final long serialVersionUID = -3844049016540352208L;

public void actionPerformed(ActionEvent ae) {
textComponent.copy();
}

@Override
protected void postTextComponentInitialize() {
setEnabled(isTextSelected());
}
};
popupActions[2] = new TextAction("Paste") {
private static final long serialVersionUID = -3844049016540352208L;

public void actionPerformed(ActionEvent ae) {
textComponent.paste();
}

@Override
protected void postTextComponentInitialize() {
setEnabled(textComponent.isEditable());
}
};
popupActions[3] = new TextAction("Select all") {
private static final long serialVersionUID = -3844049016540352208L;

public void actionPerformed(ActionEvent ae) {
textComponent.selectAll();
}

@Override
protected void postTextComponentInitialize() {
setEnabled(!textComponent.getText().trim().equals(""));
}
};

for (TextAction action : popupActions) {
popup.add(action);
}
}

@Override
protected void dispatchEvent(AWTEvent event) {
if (event.getID() == MouseEvent.MOUSE_RELEASED) {
MouseEvent e = (MouseEvent) event;

Component c = getSource(e);

if (c instanceof JTextComponent) {
if (SwingUtilities.isRightMouseButton(e)) {
final JTextComponent txtComp = (JTextComponent) c;
for (TextAction action : popupActions) {
action.setTextComponent(txtComp);
}
popup.show(e.getComponent(), e.getX(), e.getY());
}
}
}
super.dispatchEvent(event);
}

private Component getSource(MouseEvent e) {
return SwingUtilities.getDeepestComponentAt(
e.getComponent(),
e.getX(),
e.getY());
}

private static abstract class TextAction extends AbstractAction {

private static final long serialVersionUID = -7708937505251885197L;
protected JTextComponent textComponent;

public TextAction(String name) {
super(name);
}

public void setTextComponent(JTextComponent textComponent) {
this.textComponent = textComponent;
postTextComponentInitialize();
}

protected boolean isTextSelected() {
return (textComponent.getSelectionStart() != textComponent.getSelectionEnd());
}

protected abstract void postTextComponentInitialize();
}
}

2 comments:

Yves Zoundi said...

The things you might want to do are :
- consume the mouse event after displaying the popup

- Check for a client property on some components to avoid displaying the popup.
if(component.getClientProperty(POPUP_NOT_SHOWN -> dispatch -> else show and consume.

Linda said...

Nice and clear review. But I think beginners still need the basics to fully grasp what's going on here.

Helen Neely