/*
* @(#)MacEditorKit.java 1.0 December 1, 2004
*
* Copyright (c) 2004 Werner Randelshofer
* Staldenmattweg 2, Immensee, CH-6405, Switzerland.
* All rights reserved.
*
* The copyright of this software is owned by Werner Randelshofer.
* You may not use, copy or modify this software, except in
* accordance with the license agreement you entered into with
* Werner Randelshofer. For details see accompanying license terms.
*
* Part of this software (as marked) has been derived from software by
* Dustin Sacks. These parts are used under license.
*/
package com.seaglasslookandfeel.util;
import java.awt.Component;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.util.HashMap;
import javax.swing.Action;
import javax.swing.text.BadLocationException;
import javax.swing.text.Caret;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.JTextComponent;
import javax.swing.text.TextAction;
import javax.swing.text.Utilities;
/**
* The MacEditorKit extends the Swing DefaultEditorKit with Mac OS X specific
* text editing actions.
*
* @author Werner Randelshofer
* @version 1.0 December 1, 2004 Created.
*/
public class MacEditorKit extends DefaultEditorKit {
private static final long serialVersionUID = 7463251678121983829L;
/**
* Name of the action to delete the word that precedes the current caret
* position.
*
* @see #getActions
*/
public static final String deletePrevWordAction = "delete-previous-word";
/**
* Name of the action to delete the word that follows the current caret
* position.
*
* @see #getActions
*/
public static final String deleteNextWordAction = "delete-next-word";
/** Default actions of the MacEditorKit. */
private static final Action[] actions;
static {
Action[] dekActions = new DefaultEditorKit().getActions();
HashMap dekActionMap = new HashMap();
for (int i = 0; i < dekActions.length; i++) {
dekActionMap.put(dekActions[i].getValue(Action.NAME), dekActions[i]);
}
HashMap actionMap = (HashMap) dekActionMap.clone();
actionMap.put(deleteNextWordAction, new MacEditorKit.DeleteNextWordAction());
actionMap.put(deletePrevWordAction, new MacEditorKit.DeletePrevWordAction());
actionMap.put(upAction,
new MacEditorKit.VerticalAction(upAction, (TextAction) dekActionMap.get(upAction),
(TextAction) dekActionMap.get(beginAction)));
actionMap.put(downAction,
new MacEditorKit.VerticalAction(downAction, (TextAction) dekActionMap.get(downAction),
(TextAction) dekActionMap.get(endAction)));
actionMap.put(selectionUpAction,
new MacEditorKit.VerticalAction(selectionUpAction, (TextAction) dekActionMap.get(selectionUpAction),
(TextAction) dekActionMap.get(selectionBeginAction)));
actionMap.put(selectionDownAction,
new MacEditorKit.VerticalAction(selectionDownAction, (TextAction) dekActionMap.get(selectionDownAction),
(TextAction) dekActionMap.get(selectionEndAction)));
actions = (Action[]) actionMap.values().toArray(new Action[0]);
// TO DO: Use this instead of the code above:
// actions = TextAction.augmentList(....)
}
/**
* Default constructor.
*/
public MacEditorKit() {
}
/**
* Fetches the set of commands that can be used on a text component that is
* using a model and view produced by this kit.
*
* @return the command list
*/
public Action[] getActions() {
return actions;
}
/**
* Invoked when the user attempts an invalid operation, such as pasting into
* an uneditable <code>JTextField</code> that has focus. The default
* implementation beeps. Subclasses that wish different behavior should
* override this and provide the additional feedback.
*
* @param component Component the error occured in, may be null indicating
* the error condition is not directly associated with a
* <code>Component</code>.
*/
static void provideErrorFeedback(Component component) {
Toolkit toolkit = null;
if (component != null) {
toolkit = component.getToolkit();
} else {
toolkit = Toolkit.getDefaultToolkit();
}
toolkit.beep();
} // provideErrorFeedback()
/**
* Deletes the word that follows the current caret position. Original code
* of this class by Dustin Sacks.
*
* @see MacEditorKit#deleteNextWordAction
* @see MacEditorKit#getActions
*/
static class DeleteNextWordAction extends TextAction {
private static final long serialVersionUID = 5038003137392979803L;
/**
* Creates this object with the appropriate identifier.
*/
DeleteNextWordAction() {
super(deleteNextWordAction);
}
/**
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
public void actionPerformed(ActionEvent e) {
JTextComponent target = getTextComponent(e);
boolean beep = true;
if ((target != null) && (target.isEditable())) {
try {
// select the next word
int offs = target.getCaretPosition();
int endOffs;
String s = target.getDocument().getText(offs, 1);
if (Character.isWhitespace(s.charAt(0))) {
endOffs = Utilities.getNextWord(target, offs);
endOffs = Utilities.getWordEnd(target, endOffs);
} else {
endOffs = Utilities.getWordEnd(target, offs);
}
target.moveCaretPosition(endOffs);
// and then delete it
target.replaceSelection("");
beep = false;
} catch (BadLocationException exc) {
// nothing to do, because we set beep to true already
}
}
if (beep) {
provideErrorFeedback(target);
}
}
}
/**
* Deletes the word that precedes the current caret position. Original code
* of this class by Dustin Sacks.
*
* @see MacEditorKit#deletePrevWordAction
* @see MacEditorKit#getActions
*/
static class DeletePrevWordAction extends TextAction {
private static final long serialVersionUID = -6352466583853318023L;
/**
* Creates this object with the appropriate identifier.
*/
DeletePrevWordAction() {
super(deletePrevWordAction);
}
/**
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
public void actionPerformed(ActionEvent e) {
JTextComponent target = getTextComponent(e);
boolean beep = true;
if ((target != null) && (target.isEditable())) {
int offs = target.getCaretPosition();
boolean failed = false;
try {
offs = Utilities.getPreviousWord(target, offs);
} catch (BadLocationException bl) {
if (offs != 0) {
offs = 0;
} else {
failed = true;
}
}
if (!failed) {
target.moveCaretPosition(offs);
// and then delete it
target.replaceSelection("");
beep = false;
}
}
if (beep) {
provideErrorFeedback(target);
}
}
}
/**
* Action to move the selection up or down. This is very similar to the
* NextVisualPositionAction of class DefaultEditorKit. The differences is,
* that we move the cursor to the beginning of the text, if the user wants
* to move upwards and is already at the first line of the text. We move the
* cursor to the end of the text, if the user wants to move downwards and is
* already at the last line of the text. Note that we delegate actions to
* DefaultEditorKit actions. We can not implement all the required code by
* ourself, because method DefaultCaret.getDotBias() is not accessible from
* outside the javax.swing.text package.
*/
static class VerticalAction extends TextAction {
private static final long serialVersionUID = -6471615785308538422L;
private TextAction verticalAction;
private TextAction beginEndAction;
/**
* Create this action with the appropriate identifier.
*
* @param name DOCUMENT ME!
* @param verticalAction DOCUMENT ME!
* @param beginEndAction DOCUMENT ME!
*/
VerticalAction(String name, TextAction verticalAction, TextAction beginEndAction) {
super(name);
this.verticalAction = verticalAction;
this.beginEndAction = beginEndAction;
}
/**
* The operation to perform when this action is triggered.
*
* @param e DOCUMENT ME!
*/
public void actionPerformed(ActionEvent e) {
JTextComponent target = getTextComponent(e);
if (target != null) {
// target.getUI().getNextVisualPositionFrom(t
Caret caret = target.getCaret();
int dot = caret.getDot();
verticalAction.actionPerformed(e);
if (dot == caret.getDot()) {
Point magic = caret.getMagicCaretPosition();
beginEndAction.actionPerformed(e);
caret.setMagicCaretPosition(magic);
}
}
}
}
}