00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 package com.realtime.crossfire.jxclient.gui.textinput;
00023
00024 import com.realtime.crossfire.jxclient.gui.gui.ActivatableGUIElement;
00025 import com.realtime.crossfire.jxclient.gui.gui.GUIElementListener;
00026 import com.realtime.crossfire.jxclient.gui.gui.GuiUtils;
00027 import com.realtime.crossfire.jxclient.gui.gui.TooltipManager;
00028 import com.realtime.crossfire.jxclient.settings.CommandHistory;
00029 import java.awt.Color;
00030 import java.awt.Dimension;
00031 import java.awt.Font;
00032 import java.awt.Graphics;
00033 import java.awt.Graphics2D;
00034 import java.awt.Image;
00035 import java.awt.Toolkit;
00036 import java.awt.Transparency;
00037 import java.awt.datatransfer.Clipboard;
00038 import java.awt.datatransfer.DataFlavor;
00039 import java.awt.datatransfer.Transferable;
00040 import java.awt.datatransfer.UnsupportedFlavorException;
00041 import java.awt.event.InputEvent;
00042 import java.awt.event.KeyEvent;
00043 import java.awt.event.MouseEvent;
00044 import java.awt.font.FontRenderContext;
00045 import java.awt.geom.RectangularShape;
00046 import java.io.IOException;
00047 import org.jetbrains.annotations.NotNull;
00048 import org.jetbrains.annotations.Nullable;
00049
00057 public abstract class GUIText extends ActivatableGUIElement implements KeyListener {
00058
00062 private static final long serialVersionUID = 1;
00063
00068 private static final int SCROLL_CHARS = 8;
00069
00073 @NotNull
00074 private final CommandCallback commandCallback;
00075
00079 @NotNull
00080 private final CommandHistory commandHistory;
00081
00085 @NotNull
00086 private final Image activeImage;
00087
00091 @NotNull
00092 private final Image inactiveImage;
00093
00097 @NotNull
00098 private final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
00099
00103 @Nullable
00104 private final Clipboard selection = Toolkit.getDefaultToolkit().getSystemSelection();
00105
00109 @NotNull
00110 private final Font font;
00111
00116 @NotNull
00117 private final Color inactiveColor;
00118
00123 @NotNull
00124 private final Color activeColor;
00125
00129 private final int margin;
00130
00134 @NotNull
00135 private final StringBuilder text;
00136
00141 private final boolean enableHistory;
00142
00146 private final Dimension preferredSize;
00147
00151 private boolean hideInput = false;
00152
00156 private int cursor;
00157
00161 private int offset = 0;
00162
00167 @NotNull
00168 private final Object syncCursor = new Object();
00169
00187 protected GUIText(@NotNull final CommandCallback commandCallback, @NotNull final TooltipManager tooltipManager, @NotNull final GUIElementListener elementListener, @NotNull final String name, @NotNull final Image activeImage, @NotNull final Image inactiveImage, @NotNull final Font font, @NotNull final Color inactiveColor, @NotNull final Color activeColor, final int margin, @NotNull final String text, final boolean enableHistory) {
00188 super(tooltipManager, elementListener, name, Transparency.TRANSLUCENT);
00189 this.commandCallback = commandCallback;
00190 commandHistory = new CommandHistory(name);
00191 this.activeImage = activeImage;
00192 this.inactiveImage = inactiveImage;
00193 this.font = font;
00194 this.inactiveColor = inactiveColor;
00195 this.activeColor = activeColor;
00196 this.margin = margin;
00197 this.text = new StringBuilder(text);
00198 this.enableHistory = enableHistory;
00199 preferredSize = new Dimension(activeImage.getWidth(null), activeImage.getHeight(null));
00200 if (!preferredSize.equals(new Dimension(inactiveImage.getWidth(null), inactiveImage.getHeight(null)))) {
00201 throw new IllegalArgumentException("active image size differs from inactive image size");
00202 }
00203 setCursor(this.text.length());
00204 }
00205
00210 public void setText(@NotNull final String text) {
00211 this.text.setLength(0);
00212 this.text.append(text);
00213 setCursor(this.text.length());
00214 }
00215
00220 @NotNull
00221 public String getText() {
00222 return text.toString();
00223 }
00224
00228 @Override
00229 public void paintComponent(@NotNull final Graphics g) {
00230 super.paintComponent(g);
00231
00232 final Graphics2D g2 = (Graphics2D)g;
00233 g2.drawImage(isActive() ? activeImage : inactiveImage, 0, 0, null);
00234 g2.setFont(font);
00235 final String tmp;
00236 final int y;
00237 synchronized (syncCursor) {
00238 tmp = getDisplayText();
00239 final FontRenderContext fontRenderContext = g2.getFontRenderContext();
00240 final RectangularShape rectangle = font.getStringBounds(tmp, fontRenderContext);
00241 y = (int)Math.round(getHeight()-rectangle.getMaxY()-rectangle.getMinY())/2;
00242 if (isActive()) {
00243 final String tmpPrefix = tmp.substring(0, cursor-offset);
00244 final String tmpCursor = tmp.substring(0, cursor-offset+1);
00245 final RectangularShape rectanglePrefix = font.getStringBounds(tmpPrefix, fontRenderContext);
00246 final RectangularShape rectangleCursor = font.getStringBounds(tmpCursor, fontRenderContext);
00247 final int cursorX1 = (int)Math.round(rectanglePrefix.getWidth());
00248 final int cursorX2 = (int)Math.round(rectangleCursor.getWidth());
00249 g2.setColor(inactiveColor);
00250 g2.fillRect(margin+cursorX1, 0, cursorX2-cursorX1, getHeight());
00251 }
00252 }
00253 g2.setColor(isActive() ? activeColor : inactiveColor);
00254 g2.drawString(tmp, margin, y);
00255 }
00256
00260 @NotNull
00261 @Override
00262 public Dimension getPreferredSize() {
00263 return new Dimension(preferredSize);
00264 }
00265
00269 @NotNull
00270 @Override
00271 public Dimension getMinimumSize() {
00272 return new Dimension(preferredSize);
00273 }
00274
00278 @NotNull
00279 @Override
00280 public Dimension getMaximumSize() {
00281 return new Dimension(preferredSize);
00282 }
00283
00291 @NotNull
00292 private String getDisplayText() {
00293 final String tmpText = text.substring(offset);
00294 if (!hideInput) {
00295 return tmpText+" ";
00296 }
00297
00298 final String template = "****************************************************************************************************************************************************************";
00299 final String hiddenText = template.substring(0, Math.min(tmpText.length(), template.length()));
00300 return hiddenText+" ";
00301 }
00302
00306 @Override
00307 public void mouseClicked(@NotNull final MouseEvent e) {
00308 super.mouseClicked(e);
00309 final int b = e.getButton();
00310 switch (b) {
00311 case MouseEvent.BUTTON1:
00312 setActive(true);
00313 setChanged();
00314 break;
00315
00316 case MouseEvent.BUTTON2:
00317 break;
00318
00319 case MouseEvent.BUTTON3:
00320 break;
00321 }
00322 }
00323
00327 @Override
00328 public void execute() {
00329
00330 }
00331
00335 @Override
00336 protected void activeChanged() {
00337 setChanged();
00338 }
00339
00340 @Override
00341 public boolean keyPressed(@NotNull final KeyEvent e) {
00342 switch (e.getKeyCode()) {
00343 case KeyEvent.VK_ENTER:
00344 final String command = text.toString();
00345 commandCallback.updatePlayerName(command);
00346 execute(command);
00347 if (!hideInput) {
00348 commandHistory.addCommand(command);
00349 }
00350 setActive(false);
00351 return true;
00352
00353 case KeyEvent.VK_BACK_SPACE:
00354 synchronized (syncCursor) {
00355 if (cursor > 0) {
00356 text.delete(cursor-1, cursor);
00357 setCursor(cursor-1);
00358 }
00359 }
00360 return true;
00361
00362 case KeyEvent.VK_DELETE:
00363 synchronized (syncCursor) {
00364 if (cursor < text.length()) {
00365 text.delete(cursor, cursor+1);
00366 setChanged();
00367 }
00368 }
00369 return true;
00370
00371 case KeyEvent.VK_KP_LEFT:
00372 case KeyEvent.VK_LEFT:
00373 synchronized (syncCursor) {
00374 if (cursor > 0) {
00375 setCursor(cursor-1);
00376 }
00377 }
00378 return true;
00379
00380 case KeyEvent.VK_KP_RIGHT:
00381 case KeyEvent.VK_RIGHT:
00382 synchronized (syncCursor) {
00383 if (cursor < text.length()) {
00384 setCursor(cursor+1);
00385 }
00386 }
00387 return true;
00388
00389 case KeyEvent.VK_KP_UP:
00390 case KeyEvent.VK_UP:
00391 if (enableHistory) {
00392 historyPrev();
00393 return true;
00394 }
00395 break;
00396
00397 case KeyEvent.VK_KP_DOWN:
00398 case KeyEvent.VK_DOWN:
00399 if (enableHistory) {
00400 historyNext();
00401 return true;
00402 }
00403 break;
00404
00405 case KeyEvent.VK_HOME:
00406 synchronized (syncCursor) {
00407 if (cursor > 0) {
00408 setCursor(0);
00409 }
00410 }
00411 return true;
00412
00413 case KeyEvent.VK_END:
00414 synchronized (syncCursor) {
00415 if (cursor < text.length()) {
00416 setCursor(text.length());
00417 }
00418 }
00419 return true;
00420
00421 case KeyEvent.VK_N:
00422 if ((e.getModifiers()&InputEvent.CTRL_MASK) == InputEvent.CTRL_MASK) {
00423 historyNext();
00424 return true;
00425 }
00426 break;
00427
00428 case KeyEvent.VK_P:
00429 if ((e.getModifiers()&InputEvent.CTRL_MASK) == InputEvent.CTRL_MASK) {
00430 historyPrev();
00431 return true;
00432 }
00433 break;
00434
00435 case KeyEvent.VK_V:
00436 if ((e.getModifiers()&InputEvent.CTRL_MASK) == InputEvent.CTRL_MASK) {
00437 paste();
00438 return true;
00439 }
00440 break;
00441 }
00442
00443 final char ch = e.getKeyChar();
00444 if (ch != KeyEvent.CHAR_UNDEFINED && ch != (char)127 && ch >= ' ') {
00445 insertChar(ch);
00446 return true;
00447 }
00448
00449 return false;
00450 }
00451
00455 private void historyPrev() {
00456 final String commandUp = commandHistory.up();
00457 if (commandUp != null) {
00458 setText(commandUp);
00459 }
00460 }
00461
00465 private void historyNext() {
00466 final String commandDown = commandHistory.down();
00467 setText(commandDown != null ? commandDown : "");
00468 }
00469
00473 @Override
00474 public boolean keyReleased(@NotNull final KeyEvent e) {
00475 return false;
00476 }
00477
00482 private void insertChar(final char ch) {
00483 synchronized (syncCursor) {
00484 text.insert(cursor, ch);
00485 setCursor(cursor+1);
00486 }
00487 }
00488
00493 private void insertString(@NotNull final String str) {
00494 synchronized (syncCursor) {
00495 text.insert(cursor, str);
00496 setCursor(cursor+str.length());
00497 }
00498 }
00499
00504 protected abstract void execute(@NotNull final String command);
00505
00510 public void setHideInput(final boolean hideInput) {
00511 if (this.hideInput != hideInput) {
00512 this.hideInput = hideInput;
00513 setChanged();
00514 }
00515 }
00516
00521 private void setCursor(final int cursor) {
00522 synchronized (syncCursor) {
00523 if (getGraphics() == null) {
00524
00525 } else if (this.cursor < cursor) {
00526
00527
00528 while (true) {
00529 final String tmp = getDisplayText();
00530 final String tmpCursor = tmp.substring(0, cursor-offset+1);
00531
00532 final Dimension dimension = GuiUtils.getTextDimension(tmpCursor, getFontMetrics(font));
00533
00534
00535 final int cursorX = dimension.width;
00536 if (cursorX < getWidth()) {
00537 break;
00538 }
00539
00540 if (offset+SCROLL_CHARS > cursor) {
00541 offset = cursor;
00542 break;
00543 }
00544
00545 offset += SCROLL_CHARS;
00546 }
00547 } else if (this.cursor > cursor) {
00548
00549
00550 while (cursor < offset) {
00551 if (offset <= SCROLL_CHARS) {
00552 offset = 0;
00553 break;
00554 }
00555
00556 offset -= SCROLL_CHARS;
00557 }
00558 }
00559
00560 this.cursor = cursor;
00561 }
00562 setChanged();
00563 }
00564
00568 private void paste() {
00569 Transferable content = null;
00570 if (selection != null) {
00571 content = selection.getContents(this);
00572 }
00573 if (content == null) {
00574 content = clipboard.getContents(this);
00575 }
00576 if (content == null) {
00577 return;
00578 }
00579
00580 final String str;
00581 try {
00582 str = (String)content.getTransferData(DataFlavor.stringFlavor);
00583 } catch (final UnsupportedFlavorException ignored) {
00584 return;
00585 } catch (final IOException ignored) {
00586 return;
00587 }
00588 insertString(str);
00589 }
00590
00591 }