package DisplayProject.actions;
import java.awt.Component;
import java.awt.Container;
import java.awt.KeyboardFocusManager;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.JTextComponent;
import javax.swing.text.TextAction;
import DisplayProject.Constants;
/**
* This classes stores and accesses the CommandType
* @author Peter
* @author Tim
*@since 18/7/08
*/
//PM:18/07/2008: AXA-4
public class CommandType {
private static final String CMD_TYPE = "qq_MenuCommandType";
/**
* An interface used to enable and disable menu picks based on the state of an action
* @author Tim
*/
private static interface MenuAwareTextAction {
public abstract boolean shouldBeEnabled();
}
/**
* Return the JTextComponent that currently has focus. If the current focus owner is
* not a JTextComponent, then null will be returned. We tend not to use TextAction.getTextComponent
* as there is no guarantee that the returned item will actually have the focus; instead it returns
* the last focused text component
* @return
*/
private static JTextComponent getFocusedTextComponent() {
Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner();
if (owner instanceof JTextComponent) {
return (JTextComponent)owner;
}
return null;
}
/**
* Set the position of the caret on the text field. This will call <code>target.setCaretPosition</code>
* however it will delay the setting. This is useful in situations where the target cursor position is
* affected by a menu pick, and the focus change to the menu alters the caret position
* @param target
* @param position
*/
private static void setCaretPosition(final JTextComponent target, final int position) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
target.setCaretPosition(position);
}
});
}
/**
* Create a DeleteAction which can remove code from a text field. This will automatically
* enable and disable the field based on whether there is some text selected and the field
* is enabled and editable.
* @author Tim
*
*/
@SuppressWarnings("serial")
public static class DeleteAction extends TextAction implements MenuAwareTextAction {
public DeleteAction() {
super("Delete");
}
/**
* The operation to perform when this action is triggered. Remove the information
* from the field between the start and end of the selection
*
* @param e the action event
*/
public void actionPerformed(ActionEvent e) {
JTextComponent target = getFocusedTextComponent();
if (target != null && target.isEnabled() && target.isEditable()) {
int dot = target.getCaret().getDot();
int mark = target.getCaret().getMark();
try {
target.getDocument().remove(Math.min(dot, mark), Math.abs(mark-dot));
setCaretPosition(target, Math.min(mark, dot));
} catch (BadLocationException e1) {
e1.printStackTrace();
}
}
}
/**
* The delete action should be enabled on the menu only if we have selected text and the control is editable
*/
public boolean shouldBeEnabled() {
JTextComponent target = getFocusedTextComponent();
return (target != null && target.isEditable() && target.isEnabled() && target.getSelectedText() != null);
}
}
/**
* Create a CopyAction which can copy code from a text field. This will automatically
* enable and disable the field based on whether there is some text selected.
* @author Tim
*
*/
@SuppressWarnings("serial")
public static class CopyAction extends DefaultEditorKit.CopyAction implements MenuAwareTextAction {
@Override
public void actionPerformed(ActionEvent e) {
super.actionPerformed(e);
final JTextComponent target = getTextComponent(e);
final int start = target.getSelectionStart();
final int end = target.getSelectionEnd();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
target.select(start, end);
}
});
}
public boolean shouldBeEnabled() {
JTextComponent target = getFocusedTextComponent();
return (target != null && target.isEnabled() && target.getSelectedText() != null);
}
}
/**
* Create a CutAction which can remove code from a text field. This will automatically
* enable and disable the field based on whether there is some text selected and the field
* is enabled and editable.
* @author Tim
*/
@SuppressWarnings("serial")
public static class CutAction extends DefaultEditorKit.CutAction implements MenuAwareTextAction {
@Override
public void actionPerformed(ActionEvent e) {
super.actionPerformed(e);
JTextComponent target = getTextComponent(e);
setCaretPosition(target, target.getCaretPosition());
}
public boolean shouldBeEnabled() {
JTextComponent target = getFocusedTextComponent();
return (target != null && target.isEditable() && target.isEnabled() && target.getSelectedText() != null);
}
}
/**
* Create a PasteAction which can insert code from a text field. This will automatically
* enable and disable the field based on whether there is some text in the clipboard and
* the field is enabled and editable.
* @author Tim
*/
@SuppressWarnings("serial")
public static class PasteAction extends DefaultEditorKit.PasteAction implements MenuAwareTextAction {
@Override
public void actionPerformed(ActionEvent e) {
super.actionPerformed(e);
JTextComponent target = getTextComponent(e);
setCaretPosition(target, target.getCaretPosition());
}
/**
* The paste action will only be enabled if the control is editable and there is code in the clipboard
*/
public boolean shouldBeEnabled() {
JTextComponent target = getFocusedTextComponent();
return (target != null && target.isEditable() && target.isEnabled() && Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null) != null);
}
}
/**
* Install a monitor for the parent on the menu command. When the parent menu is shown, this monitor will
* invoke the <code>shouldBeEnabled</code> method on each MenuAwareTextAction, and enable or disable the
* menu based on this information
* @param menuCmd
*/
private static void installMonitorForParent(final JMenuItem menuCmd) {
Container c = menuCmd.getParent();
if (c instanceof JPopupMenu) {
c = (Container)((JPopupMenu)c).getInvoker();
}
if (c instanceof JMenu && menuCmd.getAction() instanceof MenuAwareTextAction) {
JMenu parentMenu = (JMenu)c;
parentMenu.addMenuListener(new MenuListener() {
public void menuCanceled(MenuEvent e) {}
public void menuDeselected(MenuEvent e) {}
public void menuSelected(MenuEvent e) {
boolean isEnabled = ((MenuAwareTextAction)menuCmd.getAction()).shouldBeEnabled();
menuCmd.setEnabled(isEnabled);
}
});
}
}
/**
* We need to monitor this menu, so that when the menu which contains this menu item
* is displayed, we need to invoke the isEnabled method on the action to enable / disable the menu
* @param menuCmd
*/
private static void monitor(final JMenuItem menuCmd) {
installMonitorForParent(menuCmd);
menuCmd.addHierarchyListener(new HierarchyListener() {
public void hierarchyChanged(HierarchyEvent e) {
if (e.getID() == HierarchyEvent.HIERARCHY_CHANGED && (e.getChangeFlags() & HierarchyEvent.PARENT_CHANGED) != 0) {
installMonitorForParent(menuCmd);
}
}
});
}
/**
* Remove any action listeners from the control which are the same class as the passed type.
* @param menuCmd
* @param actionClass
*/
private static void removeListener(JMenuItem menuCmd, Class<? extends AbstractAction> actionClass) {
ActionListener[] list = menuCmd.getActionListeners();
for (ActionListener item : list) {
if (actionClass.isAssignableFrom(item.getClass())) {
menuCmd.removeActionListener(item);
}
}
}
/**
* Set the type of this menu item. This will possibly install handlers to enable and disable
* menu items as appropriate, such as Cut, Copy, Delete and Paste menu items.
* @param menuCmd
* @param type
*/
public static void set(JMenuItem menuCmd, int type){
menuCmd.putClientProperty(CMD_TYPE, new Integer(type));
// CONV:TF:21 Jul 2009:Extended this to cover the command types and update the menus
switch (type) {
case Constants.CT_DELETE:
removeListener(menuCmd, DeleteAction.class);
menuCmd.setAction(new DeleteAction());
menuCmd.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
monitor(menuCmd);
break;
case Constants.CT_COPY:
removeListener(menuCmd, DefaultEditorKit.CopyAction.class);
menuCmd.setAction(new CopyAction());
menuCmd.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false));
monitor(menuCmd);
break;
case Constants.CT_CUT:
removeListener(menuCmd, DefaultEditorKit.CutAction.class);
menuCmd.setAction(new CutAction());
menuCmd.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false));
monitor(menuCmd);
break;
case Constants.CT_PASTE:
removeListener(menuCmd, DefaultEditorKit.PasteAction.class);
menuCmd.setAction(new PasteAction());
menuCmd.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false));
monitor(menuCmd);
break;
case Constants.CT_HELPCONTENTS:
// The help contents menus never appeared in Forte, so hide it
menuCmd.setVisible(false);
break;
}
}
/**
* Return the type of this menu item.
* @param menuCmd
* @return
*/
public static int get(JMenuItem menuCmd){
return ((Integer)menuCmd.getClientProperty(CMD_TYPE)).intValue();
}
}