All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jdesktop.application.TextActions Maven / Gradle / Ivy

The newest version!

/*
 * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is
 * subject to license terms.
 */ 

package org.jdesktop.application;

import java.awt.AWTEvent;
import java.awt.EventQueue;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.FlavorEvent;
import java.awt.datatransfer.FlavorListener;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.ActionMap;
import javax.swing.JComponent;
import javax.swing.TransferHandler;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.text.Caret;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.JTextComponent;



/**
 * An ActionMap class that defines cut/copy/paste/delete.
 * 
 * This class only exists to paper over limitations in the standard JTextComponent
 * cut/copy/paste/delete javax.swing.Actions.  The standard cut/copy Actions don't 
 * keep their enabled property in sync with having the focus and (for copy) having
 * a non-empty text selection.  The standard paste Action's enabled property doesn't
 * stay in sync with the current contents of the clipboard.  The paste/copy/delete
 * actions must also track the JTextComponent editable property.
 * 
 * The new cut/copy/paste/delete are installed lazily, when a JTextComponent gets 
 * the focus, and before any other focus-change related work is done.  See
 * updateFocusOwner().
 * 
 * @author Hans Muller ([email protected])
 * @author Scott Violet ([email protected])
 */
class TextActions extends AbstractBean {
    private final ApplicationContext context;
    private final CaretListener textComponentCaretListener;
    private final PropertyChangeListener textComponentPCL;
    private final String markerActionKey = "TextActions.markerAction";
    private final javax.swing.Action markerAction;
    private boolean copyEnabled = false;    // see setCopyEnabled
    private boolean cutEnabled = false;     // see setCutEnabled
    private boolean pasteEnabled = false;   // see setPasteEnabled
    private boolean deleteEnabled = false;  // see setDeleteEnabled

    public TextActions(ApplicationContext context) {
        this.context = context;
	markerAction = new javax.swing.AbstractAction() { 
	    public void actionPerformed(ActionEvent e) { } 
        };
	textComponentCaretListener = new TextComponentCaretListener();
	textComponentPCL = new TextComponentPCL();
	getClipboard().addFlavorListener(new ClipboardListener());
    }

    private ApplicationContext getContext() {
        return context;
    }

    private JComponent getFocusOwner() {
	return 	getContext().getFocusOwner();
    }

    private Clipboard getClipboard() {
	return getContext().getClipboard();
    }

    /* Called by the KeyboardFocus PropertyChangeListener in ApplicationContext,
     * before any other focus-change related work is done.
     */
    void updateFocusOwner(JComponent oldOwner, JComponent newOwner) {
	if (oldOwner instanceof JTextComponent) {
	    JTextComponent text = (JTextComponent)oldOwner;
	    text.removeCaretListener(textComponentCaretListener);
	    text.removePropertyChangeListener(textComponentPCL);
	}
	if (newOwner instanceof JTextComponent) {
	    JTextComponent text = (JTextComponent)newOwner;
	    maybeInstallTextActions(text);
	    updateTextActions(text);
	    text.addCaretListener(textComponentCaretListener);
	    text.addPropertyChangeListener(textComponentPCL);
	}
	else if (newOwner == null) {
	    setCopyEnabled(false);
	    setCutEnabled(false);
	    setPasteEnabled(false);
	    setDeleteEnabled(false);
	}
    }

    private final class ClipboardListener implements FlavorListener {
	public void flavorsChanged(FlavorEvent e) {
	    JComponent c = getFocusOwner();
	    if (c instanceof JTextComponent) {
		updateTextActions((JTextComponent)c);
	    }
	}
    }

    private final class TextComponentCaretListener implements CaretListener {
        public void caretUpdate(CaretEvent e) {
	    updateTextActions((JTextComponent)(e.getSource()));
        }
    }

    private final class TextComponentPCL implements PropertyChangeListener {
	public void propertyChange(PropertyChangeEvent e) {
	    String propertyName = e.getPropertyName();
	    if ((propertyName == null) || "editable".equals(propertyName)) {
		updateTextActions((JTextComponent)(e.getSource()));
	    }
	}
    }

    private void updateTextActions(JTextComponent text) {
	Caret caret = text.getCaret();
	boolean selection = (caret.getDot() != caret.getMark());
	boolean editable = text.isEditable();
	boolean data = getClipboard().isDataFlavorAvailable(DataFlavor.stringFlavor);
	setCopyEnabled(selection);
	setCutEnabled(editable && selection);
	setDeleteEnabled(editable && selection);
	setPasteEnabled(editable && data);
    }

    // TBD: what if text.getActionMap is null, or if it's parent isn't the UI-installed actionMap
    private void maybeInstallTextActions(JTextComponent text) {
	ActionMap actionMap = text.getActionMap();
	if (actionMap.get(markerActionKey) == null) {
	    actionMap.put(markerActionKey, markerAction);
	    ActionMap textActions = getContext().getActionMap(getClass(), this);
	    for(Object key : textActions.keys()) {
		actionMap.put(key, textActions.get(key));
	    }
	}
    }


    /* This method lifted from JTextComponent.java 
     */
    private int getCurrentEventModifiers() {
        int modifiers = 0;
        AWTEvent currentEvent = EventQueue.getCurrentEvent();
        if (currentEvent instanceof InputEvent) {
            modifiers = ((InputEvent)currentEvent).getModifiers();
        } 
	else if (currentEvent instanceof ActionEvent) {
            modifiers = ((ActionEvent)currentEvent).getModifiers();
        }
        return modifiers;
    }

    private void invokeTextAction(JTextComponent text, String actionName) {
        ActionMap actionMap = text.getActionMap().getParent();
	long eventTime = EventQueue.getMostRecentEventTime();
	int eventMods = getCurrentEventModifiers();
	ActionEvent actionEvent = 
	    new ActionEvent(text, ActionEvent.ACTION_PERFORMED, actionName, eventTime, eventMods);
	actionMap.get(actionName).actionPerformed(actionEvent);
    }

    @Action(enabledProperty = "cutEnabled")
    public void cut(ActionEvent e) {
	Object src = e.getSource();
	if (src instanceof JTextComponent) {
	    invokeTextAction((JTextComponent)src, "cut");
	}
    }

    public boolean isCutEnabled() { return cutEnabled; }

    public void setCutEnabled(boolean cutEnabled) { 
	boolean oldValue = this.cutEnabled; 
	this.cutEnabled = cutEnabled;
	firePropertyChange("cutEnabled", oldValue, this.cutEnabled);
    }

    @Action(enabledProperty = "copyEnabled")
    public void copy(ActionEvent e) {
	Object src = e.getSource();
	if (src instanceof JTextComponent) {
	    invokeTextAction((JTextComponent)src, "copy");
	}
    }

    public boolean isCopyEnabled() { return copyEnabled; }

    public void setCopyEnabled(boolean copyEnabled) { 
	boolean oldValue = this.copyEnabled; 
	this.copyEnabled = copyEnabled;
	firePropertyChange("copyEnabled", oldValue, this.copyEnabled);
    }

    @Action(enabledProperty = "pasteEnabled")
    public void paste(ActionEvent e) {
	Object src = e.getSource();
	if (src instanceof JTextComponent) {
	    invokeTextAction((JTextComponent)src, "paste");
	}
    }

    public boolean isPasteEnabled() { return pasteEnabled; }

    public void setPasteEnabled(boolean pasteEnabled) { 
	boolean oldValue = this.pasteEnabled; 
	this.pasteEnabled = pasteEnabled;
	firePropertyChange("pasteEnabled", oldValue, this.pasteEnabled);
    }

    @Action(enabledProperty = "deleteEnabled")
    public void delete(ActionEvent e) {
	Object src = e.getSource();
	if (src instanceof JTextComponent) {
	    /* The deleteNextCharAction is bound to the delete key in
	     * text components.  The name appears to be a misnomer,
	     * however it's really a compromise.  Calling the method
	     * by a more accurate name,
	     *   "IfASelectionExistsThenDeleteItOtherwiseDeleteTheNextCharacter"
	     * would be rather unwieldy.
	     */
	    invokeTextAction((JTextComponent)src, DefaultEditorKit.deleteNextCharAction);
	}
    }

    public boolean isDeleteEnabled() { return deleteEnabled; }

    public void setDeleteEnabled(boolean deleteEnabled) { 
	boolean oldValue = this.deleteEnabled; 
	this.deleteEnabled = deleteEnabled;
	firePropertyChange("deleteEnabled", oldValue, this.deleteEnabled);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy