/* * @(#)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); } } } } }