/*FreeMind - A Program for creating and viewing Mindmaps
*Copyright (C) 2000-2004 Joerg Mueller, Daniel Polansky, Christian Foltin and others.
*
*See COPYING for Details
*
*This program is free software; you can redistribute it and/or
*modify it under the terms of the GNU General Public License
*as published by the Free Software Foundation; either version 2
*of the License, or (at your option) any later version.
*
*This program is distributed in the hope that it will be useful,
*but WITHOUT ANY WARRANTY; without even the implied warranty of
*MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
*GNU General Public License for more details.
*
*You should have received a copy of the GNU General Public License
*along with this program; if not, write to the Free Software
*Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Created on 02.05.2004
*/
/*$Id: EditNodeTextField.java,v 1.1.4.3.10.25 2010/02/22 21:18:53 christianfoltin Exp $*/
package freemind.view.mindmapview;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JPopupMenu;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
import com.inet.jortho.SpellChecker;
import freemind.main.FreeMindCommon;
import freemind.main.Resources;
import freemind.main.Tools;
import freemind.modes.MindMapNode;
import freemind.modes.ModeController;
/**
* @author foltin
*
*/
public class EditNodeTextField extends EditNodeBase {
final int EDIT = 1;
final int CANCEL = 2;
int cursorWidth = 1;
int xOffset = 0;
int yOffset = -1; // Optimized for Windows style; basically ad hoc
int widthAddition = 2 * 0 + cursorWidth + 2;
int heightAddition = 2;
// minimal width for input field of leaf or folded node (PN)
final int MINIMAL_LEAF_WIDTH = 150;
final int MINIMAL_WIDTH = 50;
final int MINIMAL_HEIGHT = 20;
private KeyEvent firstEvent;
protected JTextField textfield;
protected JComponent mParent;
private final JComponent mFocusListener;
protected static java.util.logging.Logger logger = null;
private Tools.IntHolder mEventSource;
private UndoManager mUndoManager;
public EditNodeTextField(final NodeView node, final String text,
final KeyEvent firstEvent, ModeController controller,
EditControl editControl) {
this(node, text, firstEvent, controller, editControl, node.getMap(),
node);
}
public EditNodeTextField(final NodeView node, final String text,
final KeyEvent firstEvent, ModeController controller,
EditControl editControl, JComponent pParent,
JComponent pFocusListener) {
super(node, text, controller, editControl);
this.firstEvent = firstEvent;
mParent = pParent;
mFocusListener = pFocusListener;
if (logger == null) {
logger = freemind.main.Resources.getInstance().getLogger(
this.getClass().getName());
}
}
public void show() {
// Make fields for short texts editable
textfield = (getText().length() < 8) ? new JTextField(getText(), 8)
: new JTextField(getText());
// Set textFields's properties
final NodeView nodeView = getNode();
final MindMapNode model = nodeView.getModel();
int xSize = nodeView.getMainView().getTextWidth() + widthAddition;
xOffset += nodeView.getMainView().getTextX();
int xExtraWidth = 0;
if (MINIMAL_LEAF_WIDTH > xSize
&& (model.isFolded() || !model.hasChildren())) {
// leaf or folded node with small size
xExtraWidth = MINIMAL_LEAF_WIDTH - xSize;
xSize = MINIMAL_LEAF_WIDTH; // increase minimum size
if (nodeView.isLeft()) { // left leaf
xExtraWidth = -xExtraWidth;
textfield.setHorizontalAlignment(JTextField.RIGHT);
}
} else if (MINIMAL_WIDTH > xSize) {
// opened node with small size
xExtraWidth = MINIMAL_WIDTH - xSize;
xSize = MINIMAL_WIDTH; // increase minimum size
if (nodeView.isLeft()) { // left node
xExtraWidth = -xExtraWidth;
textfield.setHorizontalAlignment(JTextField.RIGHT);
}
}
int ySize = nodeView.getMainView().getHeight() + heightAddition;
if (ySize < MINIMAL_HEIGHT) {
ySize = MINIMAL_HEIGHT;
}
textfield.setSize(xSize, ySize);
Font font = nodeView.getTextFont();
final MapView mapView = nodeView.getMap();
final float zoom = mapView.getZoom();
if (zoom != 1F) {
font = font.deriveFont(font.getSize() * zoom
* MainView.ZOOM_CORRECTION_FACTOR);
}
textfield.setFont(font);
final Color nodeTextColor = nodeView.getTextColor();
textfield.setForeground(nodeTextColor);
final Color nodeTextBackground = nodeView.getTextBackground();
textfield.setBackground(nodeTextBackground);
textfield.setCaretColor(nodeTextColor);
// textField.selectAll(); // no selection on edit (PN)
mEventSource = new Tools.IntHolder();
mEventSource.setValue(EDIT);
// create the listener
final TextFieldListener textFieldListener = new TextFieldListener();
// Add listeners
this.textFieldListener = textFieldListener;
textfield.addKeyListener(textFieldListener);
textfield.addMouseListener(textFieldListener);
mUndoManager = new UndoManager();
textfield.getDocument().addUndoableEditListener(mUndoManager);
// Create an undo action and add it to the text component
textfield.getActionMap().put("Undo", new AbstractAction("Undo") {
public void actionPerformed(ActionEvent evt) {
try {
if (mUndoManager.canUndo()) {
mUndoManager.undo();
}
} catch (CannotUndoException e) {
}
}
});
// Bind the undo action to ctl-Z (or command-Z on mac)
textfield.getInputMap().put(
KeyStroke.getKeyStroke(KeyEvent.VK_Z, Toolkit
.getDefaultToolkit().getMenuShortcutKeyMask()), "Undo");
// Create a redo action and add it to the text component
textfield.getActionMap().put("Redo", new AbstractAction("Redo") {
public void actionPerformed(ActionEvent evt) {
try {
if (mUndoManager.canRedo()) {
mUndoManager.redo();
}
} catch (CannotRedoException e) {
}
}
});
// Bind the redo action to ctl-Y (or command-Y on mac)
textfield.getInputMap().put(
KeyStroke.getKeyStroke(KeyEvent.VK_Y, Toolkit
.getDefaultToolkit().getMenuShortcutKeyMask()), "Redo");
// screen positionining ---------------------------------------------
// SCROLL if necessary
getView().scrollNodeToVisible(nodeView, xExtraWidth);
Point mPoint = null;
if (mPoint == null) {
// NOTE: this must be calculated after scroll because the pane
// location
// changes
mPoint = new Point();
Tools.convertPointToAncestor(nodeView.getMainView(), mPoint,
mapView);
if (xExtraWidth < 0) {
mPoint.x += xExtraWidth;
}
mPoint.x += xOffset;
mPoint.y += yOffset;
}
setTextfieldLoaction(mPoint);
addTextfield();
textfield.repaint();
redispatchKeyEvents(textfield, firstEvent);
if (checkSpelling) {
SpellChecker.register(textfield, false, true, true, true);
}
EventQueue.invokeLater(new Runnable() {
public void run() {
textfield.requestFocus();
// Add listener now, as there are focus changes before.
textfield.addFocusListener(textFieldListener);
mFocusListener.addComponentListener(textFieldListener);
}
});
}
// listener class
class TextFieldListener implements KeyListener, FocusListener,
MouseListener, ComponentListener {
private boolean checkSpelling = Resources.getInstance()
.getBoolProperty(FreeMindCommon.CHECK_SPELLING);
public void focusGained(FocusEvent e) {
} // focus gained
public void focusLost(FocusEvent e) {
// %%% open problems:
// - adding of a child to the rightmost node
// - scrolling while in editing mode (it can behave just like
// other viewers)
// - block selected events while in editing mode
if (!textfield.isVisible() || mEventSource.getValue() == CANCEL) {
if (checkSpelling) {
mEventSource.setValue(EDIT); // allow focus lost again
}
return;
}
if (e == null) { // can be when called explicitly
hideMe();
getEditControl().ok(textfield.getText());
mEventSource.setValue(CANCEL); // disallow real focus lost
} else {
// always confirm the text if not yet
hideMe();
getEditControl().ok(textfield.getText());
}
}
public void keyPressed(KeyEvent e) {
// add to check meta keydown by koh 2004.04.16
// logger.info("Key " + e);
if (e.isAltDown() || e.isControlDown() || e.isMetaDown()
|| mEventSource.getValue() == CANCEL) {
return;
}
boolean commit = true;
switch (e.getKeyCode()) {
case KeyEvent.VK_ESCAPE:
commit = false;
case KeyEvent.VK_ENTER:
e.consume();
mEventSource.setValue(CANCEL);
hideMe();
// do not process loose of focus
if (commit) {
getEditControl().ok(textfield.getText());
} else {
getEditControl().cancel();
}
e.consume();
break;
case KeyEvent.VK_SPACE:
e.consume();
}
}
public void keyTyped(KeyEvent e) {
}
public void keyReleased(KeyEvent e) {
}
public void mouseClicked(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
conditionallyShowPopup(e);
}
public void mouseReleased(MouseEvent e) {
conditionallyShowPopup(e);
}
private void conditionallyShowPopup(MouseEvent e) {
if (e.isPopupTrigger()) {
JPopupMenu popupMenu = new EditPopupMenu(textfield);
if (checkSpelling) {
popupMenu.add(SpellChecker.createCheckerMenu());
popupMenu.add(SpellChecker.createLanguagesMenu());
mEventSource.setValue(CANCEL); // disallow real focus lost
}
popupMenu.show(e.getComponent(), e.getX(), e.getY());
e.consume();
}
}
public void componentHidden(ComponentEvent e) {
focusLost(null);
}
public void componentMoved(ComponentEvent e) {
focusLost(null);
}
public void componentResized(ComponentEvent e) {
focusLost(null);
}
public void componentShown(ComponentEvent e) {
focusLost(null);
}
}
protected void addTextfield() {
mParent.add(textfield, 0);
}
protected void setTextfieldLoaction(Point mPoint) {
textfield.setLocation(mPoint);
}
private void hideMe() {
final JComponent parent = (JComponent) textfield.getParent();
final Rectangle bounds = textfield.getBounds();
textfield.removeFocusListener(textFieldListener);
textfield.removeKeyListener((KeyListener) textFieldListener);
textfield.removeMouseListener((MouseListener) textFieldListener);
mFocusListener
.removeComponentListener((ComponentListener) textFieldListener);
// workaround for java bug
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7075600, see
// FreeMindStarter
parent.remove(textfield);
parent.revalidate();
parent.repaint(bounds);
textFieldListener = null;
}
}