/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package info.bonjean.beluga.gui.pivot.core; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.Toolkit; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.font.LineMetrics; import java.awt.geom.Area; import java.awt.geom.Rectangle2D; import org.apache.pivot.collections.Dictionary; import org.apache.pivot.text.CharSequenceCharacterIterator; import org.apache.pivot.util.Vote; import org.apache.pivot.wtk.ApplicationContext; import org.apache.pivot.wtk.Bounds; import org.apache.pivot.wtk.Component; import org.apache.pivot.wtk.Cursor; import org.apache.pivot.wtk.Dimensions; import org.apache.pivot.wtk.FocusTraversalDirection; import org.apache.pivot.wtk.GraphicsUtilities; import org.apache.pivot.wtk.HorizontalAlignment; import org.apache.pivot.wtk.Insets; import org.apache.pivot.wtk.Keyboard; import org.apache.pivot.wtk.Keyboard.KeyCode; import org.apache.pivot.wtk.Keyboard.Modifier; import org.apache.pivot.wtk.Mouse; import org.apache.pivot.wtk.Orientation; import org.apache.pivot.wtk.Platform; import org.apache.pivot.wtk.TextInput; import org.apache.pivot.wtk.TextInputContentListener; import org.apache.pivot.wtk.TextInputListener; import org.apache.pivot.wtk.TextInputSelectionListener; import org.apache.pivot.wtk.Theme; import org.apache.pivot.wtk.Window; import org.apache.pivot.wtk.skin.ComponentSkin; import org.apache.pivot.wtk.skin.terra.TerraTheme; import org.apache.pivot.wtk.validation.Validator; /** * Text input skin. * * Modified to prevent unwanted focus. */ public class BelugaTextInputSkin extends ComponentSkin implements TextInput.Skin, TextInputListener, TextInputContentListener, TextInputSelectionListener { private boolean focusable = true; @Override public boolean isFocusable() { return focusable; } private class BlinkCaretCallback implements Runnable { @Override public void run() { caretOn = !caretOn; if (caret != null) { TextInput textInput = (TextInput)getComponent(); textInput.repaint(caret.x, caret.y, caret.width, caret.height); } } } private class ScrollSelectionCallback implements Runnable { @Override public void run() { TextInput textInput = (TextInput)getComponent(); int selectionStart = textInput.getSelectionStart(); int selectionLength = textInput.getSelectionLength(); int selectionEnd = selectionStart + selectionLength - 1; switch (scrollDirection) { case FORWARD: { if (selectionEnd < textInput.getCharacterCount() - 1) { selectionEnd++; textInput.setSelection(selectionStart, selectionEnd - selectionStart + 1); scrollCharacterToVisible(selectionEnd); } break; } case BACKWARD: { if (selectionStart > 0) { selectionStart--; textInput.setSelection(selectionStart, selectionEnd - selectionStart + 1); scrollCharacterToVisible(selectionStart); } break; } default: { throw new RuntimeException(); } } } } private GlyphVector glyphVector = null; private int anchor = -1; private Rectangle caret = new Rectangle(); private Rectangle selection = null; private int scrollLeft = 0; private boolean caretOn = true; private FocusTraversalDirection scrollDirection = null; private BlinkCaretCallback blinkCaretCallback = new BlinkCaretCallback(); private ApplicationContext.ScheduledCallback scheduledBlinkCaretCallback = null; private ScrollSelectionCallback scrollSelectionCallback = new ScrollSelectionCallback(); private ApplicationContext.ScheduledCallback scheduledScrollSelectionCallback = null; private Font font; private Color color; private Color disabledColor; private Color promptColor; private Color backgroundColor; private Color disabledBackgroundColor; private Color invalidColor; private Color invalidBackgroundColor; private Color borderColor; private Color disabledBorderColor; private Color selectionColor; private Color selectionBackgroundColor; private Color inactiveSelectionColor; private Color inactiveSelectionBackgroundColor; private Color bevelColor; private Color disabledBevelColor; private Color invalidBevelColor; private Insets padding; private HorizontalAlignment horizontalAlignment; private Dimensions averageCharacterSize; private static final int SCROLL_RATE = 50; private static final char BULLET = 0x2022; public BelugaTextInputSkin() { TerraTheme theme = (TerraTheme)Theme.getTheme(); setFont(theme.getFont()); color = theme.getColor(1); promptColor = theme.getColor(7); disabledColor = theme.getColor(7); backgroundColor = theme.getColor(11); disabledBackgroundColor = theme.getColor(10); invalidColor = theme.getColor(4); invalidBackgroundColor = theme.getColor(22); borderColor = theme.getColor(7); disabledBorderColor = theme.getColor(7); padding = new Insets(2); horizontalAlignment = HorizontalAlignment.LEFT; selectionColor = theme.getColor(4); selectionBackgroundColor = theme.getColor(14); inactiveSelectionColor = theme.getColor(1); inactiveSelectionBackgroundColor = theme.getColor(9); bevelColor = TerraTheme.darken(backgroundColor); disabledBevelColor = disabledBackgroundColor; invalidBevelColor = TerraTheme.darken(invalidBackgroundColor); } @Override public void install(Component component) { super.install(component); TextInput textInput = (TextInput)component; textInput.getTextInputListeners().add(this); textInput.getTextInputContentListeners().add(this); textInput.getTextInputSelectionListeners().add(this); textInput.setCursor(Cursor.TEXT); updateSelection(); } @Override public int getPreferredWidth(int height) { TextInput textInput = (TextInput)getComponent(); int textSize = textInput.getTextSize(); return averageCharacterSize.width * textSize + (padding.left + padding.right) + 2; } @Override public int getPreferredHeight(int width) { return averageCharacterSize.height + (padding.top + padding.bottom) + 2; } @Override public Dimensions getPreferredSize() { return new Dimensions(getPreferredWidth(-1), getPreferredHeight(-1)); } @Override public int getBaseline(int width, int height) { FontRenderContext fontRenderContext = Platform.getFontRenderContext(); LineMetrics lm = font.getLineMetrics("", fontRenderContext); float ascent = lm.getAscent(); float textHeight = lm.getHeight(); int baseline = Math.round((height - textHeight) / 2 + ascent); return baseline; } @Override public void layout() { TextInput textInput = (TextInput)getComponent(); glyphVector = null; int n = textInput.getCharacterCount(); if (n > 0) { CharSequence characters; if (textInput.isPassword()) { StringBuilder passwordBuilder = new StringBuilder(n); for (int i = 0; i < n; i++) { passwordBuilder.append(BULLET); } characters = passwordBuilder; } else { characters = textInput.getCharacters(); } CharSequenceCharacterIterator ci = new CharSequenceCharacterIterator(characters); FontRenderContext fontRenderContext = Platform.getFontRenderContext(); glyphVector = font.createGlyphVector(fontRenderContext, ci); Rectangle2D textBounds = glyphVector.getLogicalBounds(); int textWidth = (int)textBounds.getWidth(); int width = getWidth(); if (textWidth - scrollLeft + padding.left + 1 < width - padding.right - 1) { // The right edge of the text is less than the right inset; align // the text's right edge with the inset scrollLeft = Math.max(textWidth + (padding.left + padding.right + 2) - width, 0); } else { // Scroll lead selection to visible int selectionStart = textInput.getSelectionStart(); if (selectionStart <= n && textInput.isFocused()) { scrollCharacterToVisible(selectionStart); } } } updateSelection(); showCaret(textInput.isFocused() && textInput.getSelectionLength() == 0); } private int getAlignmentDeltaX() { int alignmentDeltaX = 0; switch (horizontalAlignment) { case LEFT: break; case CENTER: { TextInput textInput = (TextInput)getComponent(); double txtWidth = glyphVector == null ? 0 : glyphVector.getLogicalBounds().getWidth(); int availWidth = textInput.getWidth() - (padding.left + padding.right + 2); alignmentDeltaX = (int)(availWidth - txtWidth) / 2; break; } case RIGHT: { TextInput textInput = (TextInput)getComponent(); double txtWidth = glyphVector == null ? 0 : glyphVector.getLogicalBounds().getWidth(); int availWidth = textInput.getWidth() - (padding.left + padding.right + 2 + caret.width); alignmentDeltaX = (int)(availWidth - txtWidth); break; } default: { break; } } return alignmentDeltaX; } @Override public void paint(Graphics2D graphics) { TextInput textInput = (TextInput)getComponent(); int width = getWidth(); int height = getHeight(); Color backgroundColorLocal; Color borderColorLocal; Color bevelColorLocal; if (textInput.isEnabled()) { if (textInput.isTextValid()) { backgroundColorLocal = this.backgroundColor; bevelColorLocal = this.bevelColor; } else { backgroundColorLocal = invalidBackgroundColor; bevelColorLocal = invalidBevelColor; } borderColorLocal = this.borderColor; } else { backgroundColorLocal = disabledBackgroundColor; borderColorLocal = disabledBorderColor; bevelColorLocal = disabledBevelColor; } graphics.setStroke(new BasicStroke()); // Paint the background graphics.setColor(backgroundColorLocal); graphics.fillRect(0, 0, width, height); // Paint the bevel graphics.setColor(bevelColorLocal); GraphicsUtilities.drawLine(graphics, 0, 0, width, Orientation.HORIZONTAL); // Paint the content FontRenderContext fontRenderContext = Platform.getFontRenderContext(); LineMetrics lm = font.getLineMetrics("", fontRenderContext); float ascent = lm.getAscent(); float textHeight = lm.getHeight(); String prompt = textInput.getPrompt(); Color caretColor; int alignmentDeltaX = getAlignmentDeltaX(); int xpos = padding.left - scrollLeft + 1 + alignmentDeltaX; if (glyphVector == null && prompt != null) { graphics.setFont(font); graphics.setColor(promptColor); graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, fontRenderContext.getAntiAliasingHint()); graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, fontRenderContext.getFractionalMetricsHint()); graphics.drawString(prompt, xpos, (height - textHeight) / 2 + ascent); caretColor = color; } else { boolean textValid = textInput.isTextValid(); Color colorLocal; if (textInput.isEnabled()) { if (!textValid) { colorLocal = invalidColor; } else { colorLocal = this.color; } } else { colorLocal = disabledColor; } caretColor = colorLocal; if (glyphVector != null) { graphics.setFont(font); if (selection == null) { // Paint the text graphics.setColor(colorLocal); graphics.drawGlyphVector(glyphVector, xpos, (height - textHeight) / 2 + ascent); } else { // Paint the unselected text Area unselectedArea = new Area(); unselectedArea.add(new Area(new Rectangle(0, 0, width, height))); unselectedArea.subtract(new Area(selection)); Graphics2D textGraphics = (Graphics2D)graphics.create(); textGraphics.setColor(colorLocal); textGraphics.clip(unselectedArea); textGraphics.drawGlyphVector(glyphVector, xpos, (height - textHeight) / 2 + ascent); textGraphics.dispose(); // Paint the selection Color selectionColorLocal; Color selectionBackgroundColorLocal; if (textInput.isFocused() && textInput.isEditable()) { selectionColorLocal = this.selectionColor; selectionBackgroundColorLocal = this.selectionBackgroundColor; } else { selectionColorLocal = inactiveSelectionColor; selectionBackgroundColorLocal = inactiveSelectionBackgroundColor; } graphics.setColor(selectionBackgroundColorLocal); graphics.fill(selection); Graphics2D selectedTextGraphics = (Graphics2D)graphics.create(); selectedTextGraphics.setColor(selectionColorLocal); selectedTextGraphics.clip(selection.getBounds()); selectedTextGraphics.drawGlyphVector(glyphVector, xpos, (height - textHeight) / 2 + ascent); selectedTextGraphics.dispose(); } } } // Paint the caret if (selection == null && caretOn && textInput.isFocused()) { graphics.setColor(caretColor); graphics.fill(caret); } // Paint the border graphics.setColor(borderColorLocal); GraphicsUtilities.drawRect(graphics, 0, 0, width, height); } @Override public int getInsertionPoint(int x) { int offset = -1; if (glyphVector == null) { offset = 0; } else { // Translate to glyph coordinates int xt = x - (padding.left - scrollLeft + 1 + getAlignmentDeltaX()); Rectangle2D textBounds = glyphVector.getLogicalBounds(); if (xt < 0) { offset = 0; } else if (xt > textBounds.getWidth()) { offset = glyphVector.getNumGlyphs(); } else { int n = glyphVector.getNumGlyphs(); int i = 0; while (i < n) { Shape glyphBounds = glyphVector.getGlyphLogicalBounds(i); Rectangle2D glyphBounds2D = glyphBounds.getBounds2D(); float glyphX = (float)glyphBounds2D.getX(); float glyphWidth = (float)glyphBounds2D.getWidth(); if (xt >= glyphX && xt < glyphX + glyphWidth) { if (xt - glyphX > glyphWidth / 2) { // The user clicked on the right half of the character; select // the next character i++; } offset = i; break; } i++; } } } return offset; } @Override public Bounds getCharacterBounds(int index) { Bounds characterBounds = null; if (glyphVector != null) { int x, width; if (index < glyphVector.getNumGlyphs()) { Shape glyphBounds = glyphVector.getGlyphLogicalBounds(index); Rectangle2D glyphBounds2D = glyphBounds.getBounds2D(); x = (int)Math.floor(glyphBounds2D.getX()); width = (int)Math.ceil(glyphBounds2D.getWidth()); } else { // This is the terminator character Rectangle2D glyphVectorBounds = glyphVector.getLogicalBounds(); x = (int)Math.floor(glyphVectorBounds.getWidth()); width = 0; } characterBounds = new Bounds(x + padding.left - scrollLeft + 1 + getAlignmentDeltaX(), padding.top + 1, width, getHeight() - (padding.top + padding.bottom + 2)); } return characterBounds; } private void setScrollLeft(int scrollLeft) { this.scrollLeft = scrollLeft; updateSelection(); repaintComponent(); } private void scrollCharacterToVisible(int offset) { int width = getWidth(); Bounds characterBounds = getCharacterBounds(offset); if (characterBounds != null) { int glyphX = characterBounds.x - (padding.left + 1) + scrollLeft; if (characterBounds.x < padding.left + 1) { setScrollLeft(glyphX); } else if (characterBounds.x + characterBounds.width > width - (padding.right + 1)) { setScrollLeft(glyphX + (padding.left + padding.right + 2) + characterBounds.width - width); } } } public Font getFont() { return font; } public void setFont(Font font) { if (font == null) { throw new IllegalArgumentException("font is null."); } this.font = font; int missingGlyphCode = font.getMissingGlyphCode(); FontRenderContext fontRenderContext = Platform.getFontRenderContext(); GlyphVector missingGlyphVector = font.createGlyphVector(fontRenderContext, new int[] {missingGlyphCode}); Rectangle2D textBounds = missingGlyphVector.getLogicalBounds(); Rectangle2D maxCharBounds = font.getMaxCharBounds(fontRenderContext); averageCharacterSize = new Dimensions((int)Math.ceil(textBounds.getWidth()), (int)Math.ceil(maxCharBounds.getHeight())); invalidateComponent(); } public final void setFont(String font) { if (font == null) { throw new IllegalArgumentException("font is null."); } setFont(decodeFont(font)); } public final void setFont(Dictionary<String, ?> font) { if (font == null) { throw new IllegalArgumentException("font is null."); } setFont(Theme.deriveFont(font)); } public Color getColor() { return color; } public void setColor(Color color) { if (color == null) { throw new IllegalArgumentException("color is null."); } this.color = color; repaintComponent(); } public final void setColor(String color) { if (color == null) { throw new IllegalArgumentException("color is null."); } setColor(GraphicsUtilities.decodeColor(color)); } public final void setColor(int color) { TerraTheme theme = (TerraTheme)Theme.getTheme(); setColor(theme.getColor(color)); } public Color getPromptColor() { return promptColor; } public void setPromptColor(Color promptColor) { if (promptColor == null) { throw new IllegalArgumentException("promptColor is null."); } this.promptColor = promptColor; repaintComponent(); } public final void setPromptColor(String promptColor) { if (promptColor == null) { throw new IllegalArgumentException("promptColor is null."); } setPromptColor(GraphicsUtilities.decodeColor(promptColor)); } public final void setPromptColor(int promptColor) { TerraTheme theme = (TerraTheme)Theme.getTheme(); setPromptColor(theme.getColor(promptColor)); } public Color getDisabledColor() { return disabledColor; } public void setDisabledColor(Color disabledColor) { if (disabledColor == null) { throw new IllegalArgumentException("disabledColor is null."); } this.disabledColor = disabledColor; repaintComponent(); } public final void setDisabledColor(String disabledColor) { if (disabledColor == null) { throw new IllegalArgumentException("disabledColor is null."); } setDisabledColor(GraphicsUtilities.decodeColor(disabledColor)); } public final void setDisabledColor(int disabledColor) { TerraTheme theme = (TerraTheme)Theme.getTheme(); setDisabledColor(theme.getColor(disabledColor)); } public Color getBackgroundColor() { return backgroundColor; } public void setBackgroundColor(Color backgroundColor) { if (backgroundColor == null) { throw new IllegalArgumentException("backgroundColor is null."); } this.backgroundColor = backgroundColor; bevelColor = TerraTheme.darken(backgroundColor); repaintComponent(); } public final void setBackgroundColor(String backgroundColor) { if (backgroundColor == null) { throw new IllegalArgumentException("backgroundColor is null."); } setBackgroundColor(GraphicsUtilities.decodeColor(backgroundColor)); } public final void setBackgroundColor(int color) { TerraTheme theme = (TerraTheme)Theme.getTheme(); setBackgroundColor(theme.getColor(color)); } public Color getInvalidColor() { return invalidColor; } public void setInvalidColor(Color color) { if (color == null) { throw new IllegalArgumentException("color is null."); } this.invalidColor = color; repaintComponent(); } public final void setInvalidColor(String color) { if (color == null) { throw new IllegalArgumentException("color is null."); } setInvalidColor(GraphicsUtilities.decodeColor(color)); } public final void setInvalidColor(int color) { TerraTheme theme = (TerraTheme)Theme.getTheme(); setInvalidColor(theme.getColor(color)); } public Color getInvalidBackgroundColor() { return invalidBackgroundColor; } public void setInvalidBackgroundColor(Color color) { if (color == null) { throw new IllegalArgumentException("color is null."); } this.invalidBackgroundColor = color; invalidBevelColor = TerraTheme.darken(color); repaintComponent(); } public final void setInvalidBackgroundColor(String color) { if (color == null) { throw new IllegalArgumentException("invalidBackgroundColor is null."); } setInvalidBackgroundColor(GraphicsUtilities.decodeColor(color)); } public final void setInvalidBackgroundColor(int color) { TerraTheme theme = (TerraTheme)Theme.getTheme(); setInvalidBackgroundColor(theme.getColor(color)); } public Color getDisabledBackgroundColor() { return disabledBackgroundColor; } public void setDisabledBackgroundColor(Color disabledBackgroundColor) { if (disabledBackgroundColor == null) { throw new IllegalArgumentException("disabledBackgroundColor is null."); } this.disabledBackgroundColor = disabledBackgroundColor; disabledBevelColor = disabledBackgroundColor; repaintComponent(); } public final void setDisabledBackgroundColor(String disabledBackgroundColor) { if (disabledBackgroundColor == null) { throw new IllegalArgumentException("disabledBackgroundColor is null."); } setDisabledBackgroundColor(GraphicsUtilities.decodeColor(disabledBackgroundColor)); } public final void setDisabledBackgroundColor(int color) { TerraTheme theme = (TerraTheme)Theme.getTheme(); setDisabledBackgroundColor(theme.getColor(color)); } public Color getBorderColor() { return borderColor; } public void setBorderColor(Color borderColor) { if (borderColor == null) { throw new IllegalArgumentException("borderColor is null."); } this.borderColor = borderColor; repaintComponent(); } public final void setBorderColor(String borderColor) { if (borderColor == null) { throw new IllegalArgumentException("borderColor is null."); } setBorderColor(GraphicsUtilities.decodeColor(borderColor)); } public final void setBorderColor(int color) { TerraTheme theme = (TerraTheme)Theme.getTheme(); setBorderColor(theme.getColor(color)); } public Color getDisabledBorderColor() { return disabledBorderColor; } public void setDisabledBorderColor(Color disabledBorderColor) { if (disabledBorderColor == null) { throw new IllegalArgumentException("disabledBorderColor is null."); } this.disabledBorderColor = disabledBorderColor; repaintComponent(); } public final void setDisabledBorderColor(String disabledBorderColor) { if (disabledBorderColor == null) { throw new IllegalArgumentException("disabledBorderColor is null."); } setDisabledBorderColor(GraphicsUtilities.decodeColor(disabledBorderColor)); } public final void setDisabledBorderColor(int color) { TerraTheme theme = (TerraTheme)Theme.getTheme(); setDisabledBorderColor(theme.getColor(color)); } public Color getSelectionColor() { return selectionColor; } public void setSelectionColor(Color selectionColor) { if (selectionColor == null) { throw new IllegalArgumentException("selectionColor is null."); } this.selectionColor = selectionColor; repaintComponent(); } public final void setSelectionColor(String selectionColor) { if (selectionColor == null) { throw new IllegalArgumentException("selectionColor is null."); } setSelectionColor(GraphicsUtilities.decodeColor(selectionColor)); } public final void setSelectionColor(int color) { TerraTheme theme = (TerraTheme)Theme.getTheme(); setSelectionColor(theme.getColor(color)); } public Color getSelectionBackgroundColor() { return selectionBackgroundColor; } public void setSelectionBackgroundColor(Color selectionBackgroundColor) { if (selectionBackgroundColor == null) { throw new IllegalArgumentException("selectionBackgroundColor is null."); } this.selectionBackgroundColor = selectionBackgroundColor; repaintComponent(); } public final void setSelectionBackgroundColor(String selectionBackgroundColor) { if (selectionBackgroundColor == null) { throw new IllegalArgumentException("selectionBackgroundColor is null."); } setSelectionBackgroundColor(GraphicsUtilities.decodeColor(selectionBackgroundColor)); } public final void setSelectionBackgroundColor(int color) { TerraTheme theme = (TerraTheme)Theme.getTheme(); setSelectionBackgroundColor(theme.getColor(color)); } public Color getInactiveSelectionColor() { return inactiveSelectionColor; } public void setInactiveSelectionColor(Color inactiveSelectionColor) { if (inactiveSelectionColor == null) { throw new IllegalArgumentException("inactiveSelectionColor is null."); } this.inactiveSelectionColor = inactiveSelectionColor; repaintComponent(); } public final void setInactiveSelectionColor(String inactiveSelectionColor) { if (inactiveSelectionColor == null) { throw new IllegalArgumentException("inactiveSelectionColor is null."); } setInactiveSelectionColor(GraphicsUtilities.decodeColor(inactiveSelectionColor)); } public final void setInactiveSelectionColor(int color) { TerraTheme theme = (TerraTheme)Theme.getTheme(); setInactiveSelectionColor(theme.getColor(color)); } public Color getInactiveSelectionBackgroundColor() { return inactiveSelectionBackgroundColor; } public void setInactiveSelectionBackgroundColor(Color inactiveSelectionBackgroundColor) { if (inactiveSelectionBackgroundColor == null) { throw new IllegalArgumentException("inactiveSelectionBackgroundColor is null."); } this.inactiveSelectionBackgroundColor = inactiveSelectionBackgroundColor; repaintComponent(); } public final void setInactiveSelectionBackgroundColor(String inactiveSelectionBackgroundColor) { if (inactiveSelectionBackgroundColor == null) { throw new IllegalArgumentException("inactiveSelectionBackgroundColor is null."); } setInactiveSelectionBackgroundColor(GraphicsUtilities.decodeColor(inactiveSelectionBackgroundColor)); } public final void setInactiveSelectionBackgroundColor(int color) { TerraTheme theme = (TerraTheme)Theme.getTheme(); setInactiveSelectionBackgroundColor(theme.getColor(color)); } public Insets getPadding() { return padding; } public void setPadding(Insets padding) { if (padding == null) { throw new IllegalArgumentException("padding is null."); } this.padding = padding; invalidateComponent(); } public final void setPadding(Dictionary<String, ?> padding) { if (padding == null) { throw new IllegalArgumentException("padding is null."); } setPadding(new Insets(padding)); } public final void setPadding(int padding) { setPadding(new Insets(padding)); } public final void setPadding(Number padding) { if (padding == null) { throw new IllegalArgumentException("padding is null."); } setPadding(padding.intValue()); } public final void setPadding(String padding) { if (padding == null) { throw new IllegalArgumentException("padding is null."); } setPadding(Insets.decode(padding)); } public HorizontalAlignment getHorizontalAlignment() { return horizontalAlignment; } public final void setHorizontalAlignment(HorizontalAlignment alignment) { if (alignment == null) { throw new IllegalArgumentException("horizontalAlignment is null."); } this.horizontalAlignment = alignment; invalidateComponent(); } @Override public boolean mouseMove(Component component, int x, int y) { boolean consumed = super.mouseMove(component, x, y); if (Mouse.getCapturer() == component) { TextInput textInput = (TextInput)getComponent(); int width = getWidth(); if (x >= 0 && x < width) { // Stop the scroll selection timer if (scheduledScrollSelectionCallback != null) { scheduledScrollSelectionCallback.cancel(); scheduledScrollSelectionCallback = null; } scrollDirection = null; int offset = getInsertionPoint(x); if (offset != -1) { // Select the range if (offset > anchor) { textInput.setSelection(anchor, offset - anchor); } else { textInput.setSelection(offset, anchor - offset); } } } else { if (scheduledScrollSelectionCallback == null) { scrollDirection = (x < 0) ? FocusTraversalDirection.BACKWARD : FocusTraversalDirection.FORWARD; scheduledScrollSelectionCallback = ApplicationContext.scheduleRecurringCallback(scrollSelectionCallback, SCROLL_RATE); // Run the callback once now to scroll the selection immediately scrollSelectionCallback.run(); } } } else { if (Mouse.isPressed(Mouse.Button.LEFT) && Mouse.getCapturer() == null && anchor != -1) { // Capture the mouse so we can select text Mouse.capture(component); } } return consumed; } @Override public boolean mouseDown(Component component, Mouse.Button button, int x, int y) { boolean consumed = super.mouseDown(component, button, x, y); if (button == Mouse.Button.LEFT) { TextInput textInput = (TextInput)getComponent(); anchor = getInsertionPoint(x); if (anchor != -1) { if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) { // Select the range int selectionStart = textInput.getSelectionStart(); if (anchor > selectionStart) { textInput.setSelection(selectionStart, anchor - selectionStart); } else { textInput.setSelection(anchor, selectionStart - anchor); } } else { // Move the caret to the insertion point textInput.setSelection(anchor, 0); consumed = true; } } // Set focus to the text input focusable = true; textInput.requestFocus(); } return consumed; } @Override public boolean mouseUp(Component component, Mouse.Button button, int x, int y) { boolean consumed = super.mouseUp(component, button, x, y); if (Mouse.getCapturer() == component) { // Stop the scroll selection timer if (scheduledScrollSelectionCallback != null) { scheduledScrollSelectionCallback.cancel(); scheduledScrollSelectionCallback = null; } Mouse.release(); } anchor = -1; return consumed; } @Override public boolean mouseClick(Component component, Mouse.Button button, int x, int y, int count) { if (button == Mouse.Button.LEFT && count > 1) { TextInput textInput = (TextInput)getComponent(); textInput.selectAll(); } return super.mouseClick(component, button, x, y, count); } @Override public boolean keyTyped(Component component, char character) { boolean consumed = super.keyTyped(component, character); TextInput textInput = (TextInput)getComponent(); if (textInput.isEditable()) { // Ignore characters in the control range and the ASCII delete // character as well as meta key presses if (character > 0x1F && character != 0x7F && !Keyboard.isPressed(Keyboard.Modifier.META)) { int selectionLength = textInput.getSelectionLength(); if (textInput.getCharacterCount() - selectionLength + 1 > textInput.getMaximumLength()) { Toolkit.getDefaultToolkit().beep(); } else { // NOTE We explicitly call getSelectionStart() twice here in case the remove // event is vetoed textInput.removeText(textInput.getSelectionStart(), selectionLength); textInput.insertText(Character.toString(character), textInput.getSelectionStart()); } } } return consumed; } /** * {@link KeyCode#DELETE DELETE} Delete the character after the caret or * the entire selection if there is one.<br> * {@link KeyCode#BACKSPACE BACKSPACE} Delete the character before the * caret or the entire selection if there is one.<p> * {@link KeyCode#HOME HOME} Move the caret to the beginning of the text. * <br> * {@link KeyCode#LEFT LEFT} + {@link Modifier#META META} Move the caret * to the beginning of the text.<p> * {@link KeyCode#HOME HOME} + {@link Modifier#SHIFT SHIFT} Select from * the caret to the beginning of the text.<br> * {@link KeyCode#LEFT LEFT} + {@link Modifier#META META} + * {@link Modifier#SHIFT SHIFT} Select from the caret to the beginning of * the text.<p> * {@link KeyCode#END END} Move the caret to the end of the text.<br> * {@link KeyCode#RIGHT RIGHT} + {@link Modifier#META META} Move the caret * to the end of the text.<p> * {@link KeyCode#END END} + {@link Modifier#SHIFT SHIFT} Select from the * caret to the end of the text.<br> * {@link KeyCode#RIGHT RIGHT} + {@link Modifier#META META} + * {@link Modifier#SHIFT SHIFT} Select from the caret to the end of the * text.<p> * {@link KeyCode#LEFT LEFT} Clear the selection and move the caret back * by one character.<br> * {@link KeyCode#LEFT LEFT} + {@link Modifier#SHIFT SHIFT} Add the * previous character to the selection.<br> * {@link KeyCode#LEFT LEFT} + {@link Modifier#CTRL CTRL} Clear the * selection and move the caret to the beginning of the text.<br> * {@link KeyCode#LEFT LEFT} + {@link Modifier#CTRL CTRL} + * {@link Modifier#SHIFT SHIFT} Add all preceding text to the selection. * <p> * {@link KeyCode#RIGHT RIGHT} Clear the selection and move the caret * forward by one character.<br> * {@link KeyCode#RIGHT RIGHT} + {@link Modifier#SHIFT SHIFT} Add the next * character to the selection.<br> * {@link KeyCode#RIGHT RIGHT} + {@link Modifier#CTRL CTRL} Clear the * selection and move the caret to the end of the text.<br> * {@link KeyCode#RIGHT RIGHT} + {@link Modifier#CTRL CTRL} + * {@link Modifier#SHIFT SHIFT} Add all subsequent text to the selection. * <p> * CommandModifier + {@link KeyCode#A A} Select all.<br> * CommandModifier + {@link KeyCode#X X} Cut selection to clipboard (if * not a password TextInput).<br> * CommandModifier + {@link KeyCode#C C} Copy selection to clipboard (if * not a password TextInput).<br> * CommandModifier + {@link KeyCode#V V} Paste from clipboard.<br> * CommandModifier + {@link KeyCode#Z Z} Undo. * * @see Platform#getCommandModifier() */ @Override public boolean keyPressed(Component component, int keyCode, Keyboard.KeyLocation keyLocation) { boolean consumed = super.keyPressed(component, keyCode, keyLocation); TextInput textInput = (TextInput)getComponent(); Keyboard.Modifier commandModifier = Platform.getCommandModifier(); Keyboard.Modifier wordNavigationModifier = Platform.getWordNavigationModifier(); if (keyCode == Keyboard.KeyCode.DELETE && textInput.isEditable()) { int index = textInput.getSelectionStart(); if (index < textInput.getCharacterCount()) { int count = Math.max(textInput.getSelectionLength(), 1); textInput.removeText(index, count); consumed = true; } } else if (keyCode == Keyboard.KeyCode.BACKSPACE && textInput.isEditable()) { int index = textInput.getSelectionStart(); int count = textInput.getSelectionLength(); if (count == 0 && index > 0) { textInput.removeText(index - 1, 1); consumed = true; } else { textInput.removeText(index, count); consumed = true; } } else if (keyCode == Keyboard.KeyCode.HOME || (keyCode == Keyboard.KeyCode.LEFT && Keyboard.isPressed(Keyboard.Modifier.META))) { // Move the caret to the beginning of the text if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) { textInput.setSelection(0, textInput.getSelectionStart()); } else { textInput.setSelection(0, 0); } scrollCharacterToVisible(0); consumed = true; } else if (keyCode == Keyboard.KeyCode.END || (keyCode == Keyboard.KeyCode.RIGHT && Keyboard.isPressed(Keyboard.Modifier.META))) { // Move the caret to the end of the text int n = textInput.getCharacterCount(); if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) { int selectionStart = textInput.getSelectionStart(); textInput.setSelection(selectionStart, n - selectionStart); } else { textInput.setSelection(n, 0); } scrollCharacterToVisible(n); consumed = true; } else if (keyCode == Keyboard.KeyCode.LEFT) { int selectionStart = textInput.getSelectionStart(); int selectionLength = textInput.getSelectionLength(); if (Keyboard.isPressed(wordNavigationModifier)) { // Move the caret to the start of the next word to the left if (selectionStart > 0) { // Skip over any space immediately to the left int index = selectionStart; while (index > 0 && Character.isWhitespace(textInput.getCharacterAt(index - 1))) { index--; } // Skip over any word-letters to the left while (index > 0 && !Character.isWhitespace(textInput.getCharacterAt(index - 1))) { index--; } if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) { selectionLength += selectionStart - index; } else { selectionLength = 0; } selectionStart = index; } } else if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) { // Add the previous character to the selection if (selectionStart > 0) { selectionStart--; selectionLength++; } } else { // Move the caret back by one character if (selectionLength == 0 && selectionStart > 0) { selectionStart--; } // Clear the selection selectionLength = 0; } if (selectionStart >= 0) { textInput.setSelection(selectionStart, selectionLength); scrollCharacterToVisible(selectionStart); consumed = true; } } else if (keyCode == Keyboard.KeyCode.RIGHT) { int selectionStart = textInput.getSelectionStart(); int selectionLength = textInput.getSelectionLength(); if (Keyboard.isPressed(wordNavigationModifier)) { // Move the caret to the start of the next word to the right if (selectionStart < textInput.getCharacterCount()) { int index = selectionStart + selectionLength; // Skip over any space immediately to the right while (index < textInput.getCharacterCount() && Character.isWhitespace(textInput.getCharacterAt(index))) { index++; } // Skip over any word-letters to the right while (index < textInput.getCharacterCount() && !Character.isWhitespace(textInput.getCharacterAt(index))) { index++; } if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) { selectionLength = index - selectionStart; } else { selectionStart = index; selectionLength = 0; } } } else if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) { // Add the next character to the selection selectionLength++; } else { // Move the caret forward by one character if (selectionLength == 0) { selectionStart++; } else { selectionStart += selectionLength; } // Clear the selection selectionLength = 0; } if (selectionStart + selectionLength <= textInput.getCharacterCount()) { textInput.setSelection(selectionStart, selectionLength); scrollCharacterToVisible(selectionStart + selectionLength); consumed = true; } } else if (Keyboard.isPressed(commandModifier)) { if (keyCode == Keyboard.KeyCode.A) { textInput.setSelection(0, textInput.getCharacterCount()); consumed = true; } else if (keyCode == Keyboard.KeyCode.X && textInput.isEditable()) { if (textInput.isPassword()) { Toolkit.getDefaultToolkit().beep(); } else { textInput.cut(); } consumed = true; } else if (keyCode == Keyboard.KeyCode.C) { if (textInput.isPassword()) { Toolkit.getDefaultToolkit().beep(); } else { textInput.copy(); } consumed = true; } else if (keyCode == Keyboard.KeyCode.V && textInput.isEditable()) { textInput.paste(); consumed = true; } else if (keyCode == Keyboard.KeyCode.Z && textInput.isEditable()) { if (!Keyboard.isPressed(Keyboard.Modifier.SHIFT)) { textInput.undo(); } consumed = true; } } else if (keyCode == Keyboard.KeyCode.INSERT) { if (Keyboard.isPressed(Keyboard.Modifier.SHIFT) && textInput.isEditable()) { textInput.paste(); consumed = true; } } else { consumed = super.keyPressed(component, keyCode, keyLocation); } return consumed; } // Component state events @Override public void enabledChanged(Component component) { super.enabledChanged(component); repaintComponent(); } @Override public void focusedChanged(Component component, Component obverseComponent) { super.focusedChanged(component, obverseComponent); TextInput textInput = (TextInput)component; Window window = textInput.getWindow(); if (component.isFocused()) { // If focus was permanently transferred within this window, // select all if (obverseComponent == null || obverseComponent.getWindow() == window) { if (Mouse.getCapturer() != component) { textInput.selectAll(); } } if (textInput.getSelectionLength() == 0) { int selectionStart = textInput.getSelectionStart(); if (selectionStart < textInput.getCharacterCount()) { scrollCharacterToVisible(selectionStart); } showCaret(true); } else { showCaret(false); } } else { // If focus was permanently transferred within this window, // clear the selection if (obverseComponent == null || obverseComponent.getWindow() == window) { textInput.clearSelection(); } showCaret(false); focusable = false; } repaintComponent(); } // Text input events @Override public void textSizeChanged(TextInput textInput, int previousTextSize) { invalidateComponent(); } @Override public void maximumLengthChanged(TextInput textInput, int previousMaximumLength) { // No-op } @Override public void passwordChanged(TextInput textInput) { layout(); repaintComponent(); } @Override public void promptChanged(TextInput textInput, String previousPrompt) { repaintComponent(); } @Override public void textValidatorChanged(TextInput textInput, Validator previousValidator) { repaintComponent(); } @Override public void strictValidationChanged(TextInput textInput) { // No-op } @Override public void textValidChanged(TextInput textInput) { repaintComponent(); } // Text input character events @Override public Vote previewInsertText(TextInput textInput, CharSequence text, int index) { Vote vote = Vote.APPROVE; if (textInput.isStrictValidation()) { Validator validator = textInput.getValidator(); if (validator != null) { StringBuilder textBuilder = new StringBuilder(); textBuilder.append(textInput.getText(0, index)); textBuilder.append(text); textBuilder.append(textInput.getText(index, textInput.getCharacterCount())); if (!validator.isValid(textBuilder.toString())) { vote = Vote.DENY; Toolkit.getDefaultToolkit().beep(); } } } return vote; } @Override public void insertTextVetoed(TextInput textInput, Vote reason) { // No-op } @Override public void textInserted(TextInput textInput, int index, int count) { // No-op } @Override public Vote previewRemoveText(TextInput textInput, int index, int count) { Vote vote = Vote.APPROVE; if (textInput.isStrictValidation()) { Validator validator = textInput.getValidator(); if (validator != null) { StringBuilder textBuilder = new StringBuilder(); textBuilder.append(textInput.getText(0, index)); textBuilder.append(textInput.getText(index + count, textInput.getCharacterCount())); if (!validator.isValid(textBuilder.toString())) { vote = Vote.DENY; Toolkit.getDefaultToolkit().beep(); } } } return vote; } @Override public void setSize(int width, int height) { boolean invalidate = (horizontalAlignment != HorizontalAlignment.LEFT) && (width != this.getWidth()); super.setSize(width, height); if (invalidate) { updateSelection(); invalidateComponent(); } } @Override public void removeTextVetoed(TextInput textInput, Vote reason) { // No-op } @Override public void textRemoved(TextInput textInput, int index, int count) { // No-op } @Override public void textChanged(TextInput textInput) { layout(); repaintComponent(); } @Override public void editableChanged(TextInput textInput) { repaintComponent(); } // Text input selection events @Override public void selectionChanged(TextInput textInput, int previousSelectionStart, int previousSelectionLength) { // If the text input is valid, repaint the selection state; otherwise, // the selection will be updated in layout() if (textInput.isValid()) { // Repaint any previous caret bounds if (caret != null) { textInput.repaint(caret.x, caret.y, caret.width, caret.height); } // Repaint any previous selection bounds if (selection != null) { Rectangle bounds = selection.getBounds(); textInput.repaint(bounds.x, bounds.y, bounds.width, bounds.height); } if (textInput.getSelectionLength() == 0) { updateSelection(); showCaret(textInput.isFocused()); } else { updateSelection(); showCaret(false); Rectangle bounds = selection.getBounds(); textInput.repaint(bounds.x, bounds.y, bounds.width, bounds.height); } } } private void updateSelection() { TextInput textInput = (TextInput)getComponent(); int height = getHeight(); int selectionStart = textInput.getSelectionStart(); int selectionLength = textInput.getSelectionLength(); int n = textInput.getCharacterCount(); Bounds leadingSelectionBounds; if (selectionStart < n) { leadingSelectionBounds = getCharacterBounds(selectionStart); } else { // The insertion point is after the last character int x; if (n == 0) { x = padding.left - scrollLeft + 1 + getAlignmentDeltaX(); } else { Rectangle2D textBounds = glyphVector.getLogicalBounds(); x = (int)Math.ceil(textBounds.getWidth()) + (padding.left - scrollLeft + 1 + getAlignmentDeltaX()); } int y = padding.top + 1; leadingSelectionBounds = new Bounds(x, y, 0, height - (padding.top + padding.bottom + 2)); } caret = leadingSelectionBounds.toRectangle(); caret.width = 1; if (selectionLength > 0) { Bounds trailingSelectionBounds = getCharacterBounds(selectionStart + selectionLength - 1); selection = new Rectangle(leadingSelectionBounds.x, leadingSelectionBounds.y, trailingSelectionBounds.x + trailingSelectionBounds.width - leadingSelectionBounds.x, trailingSelectionBounds.y + trailingSelectionBounds.height - leadingSelectionBounds.y); } else { selection = null; } } public void showCaret(boolean show) { if (scheduledBlinkCaretCallback != null) { scheduledBlinkCaretCallback.cancel(); } if (show) { caretOn = true; scheduledBlinkCaretCallback = ApplicationContext.scheduleRecurringCallback(blinkCaretCallback, Platform.getCursorBlinkRate()); // Run the callback once now to show the cursor immediately blinkCaretCallback.run(); } else { scheduledBlinkCaretCallback = null; } } }