Crossfire JXClient, Trunk  R20561
GUIText.java
Go to the documentation of this file.
1 /*
2  * This file is part of JXClient, the Fullscreen Java Crossfire Client.
3  *
4  * JXClient is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * JXClient is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with JXClient; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17  *
18  * Copyright (C) 2005-2008 Yann Chachkoff.
19  * Copyright (C) 2006-2011 Andreas Kirschbaum.
20  */
21 
22 package com.realtime.crossfire.jxclient.gui.textinput;
23 
30 import java.awt.Color;
31 import java.awt.Dimension;
32 import java.awt.Font;
33 import java.awt.Graphics;
34 import java.awt.Graphics2D;
35 import java.awt.Image;
36 import java.awt.Toolkit;
37 import java.awt.Transparency;
38 import java.awt.datatransfer.Clipboard;
39 import java.awt.datatransfer.DataFlavor;
40 import java.awt.datatransfer.Transferable;
41 import java.awt.datatransfer.UnsupportedFlavorException;
42 import java.awt.event.KeyEvent;
43 import java.awt.event.MouseEvent;
44 import java.awt.font.FontRenderContext;
45 import java.awt.geom.RectangularShape;
46 import java.io.IOException;
47 import org.jetbrains.annotations.NotNull;
48 import org.jetbrains.annotations.Nullable;
49 
57 public abstract class GUIText extends ActivatableGUIElement implements KeyListener {
58 
62  private static final long serialVersionUID = 1;
63 
68  private static final int SCROLL_CHARS = 8;
69 
73  @NotNull
75 
80  @Nullable
82 
86  @NotNull
87  private final Image activeImage;
88 
92  @NotNull
93  private final Image inactiveImage;
94 
98  @NotNull
99  private final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
100 
104  @Nullable
105  private final Clipboard selection = Toolkit.getDefaultToolkit().getSystemSelection();
106 
110  @NotNull
111  private final Font font;
112 
117  @NotNull
118  private final Color inactiveColor;
119 
124  @NotNull
125  private final Color activeColor;
126 
130  private final int margin;
131 
135  @NotNull
136  private final StringBuilder text;
137 
141  private final Dimension preferredSize;
142 
146  private boolean hideInput;
147 
151  private int cursor;
152 
156  private int offset;
157 
162  @NotNull
163  private final Object syncCursor = new Object();
164 
183  protected GUIText(@NotNull final CommandCallback commandCallback, @Nullable final CommandHistory commandHistory, @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) {
184  super(tooltipManager, elementListener, name, Transparency.TRANSLUCENT);
185  this.commandCallback = commandCallback;
186  this.commandHistory = commandHistory;
187  this.activeImage = activeImage;
188  this.inactiveImage = inactiveImage;
189  this.font = font;
190  this.inactiveColor = inactiveColor;
191  this.activeColor = activeColor;
192  this.margin = margin;
193  this.text = new StringBuilder(text);
194  preferredSize = new Dimension(activeImage.getWidth(null), activeImage.getHeight(null));
195  if (!preferredSize.equals(new Dimension(inactiveImage.getWidth(null), inactiveImage.getHeight(null)))) {
196  throw new IllegalArgumentException("active image size differs from inactive image size");
197  }
198  setCursor(this.text.length());
199  }
200 
205  public void setText(@NotNull final String text) {
206  this.text.setLength(0);
207  this.text.append(text);
208  setCursor(this.text.length());
209  }
210 
215  @NotNull
216  public String getText() {
217  return text.toString();
218  }
219 
223  @Override
224  public void paintComponent(@NotNull final Graphics g) {
225  super.paintComponent(g);
226 
227  final Graphics2D g2 = (Graphics2D)g;
228  g2.drawImage(isActive() ? activeImage : inactiveImage, 0, 0, null);
229  g2.setFont(font);
230  final String tmp;
231  final int y;
232  synchronized (syncCursor) {
233  tmp = getDisplayText();
234  final FontRenderContext fontRenderContext = g2.getFontRenderContext();
235  final RectangularShape rectangle = font.getStringBounds(tmp, fontRenderContext);
236  y = (int)Math.round(getHeight()-rectangle.getMaxY()-rectangle.getMinY())/2;
237  if (isActive()) {
238  final String tmpPrefix = tmp.substring(0, cursor-offset);
239  final String tmpCursor = tmp.substring(0, cursor-offset+1);
240  final RectangularShape rectanglePrefix = font.getStringBounds(tmpPrefix, fontRenderContext);
241  final RectangularShape rectangleCursor = font.getStringBounds(tmpCursor, fontRenderContext);
242  final int cursorX1 = (int)Math.round(rectanglePrefix.getWidth());
243  final int cursorX2 = (int)Math.round(rectangleCursor.getWidth());
244  g2.setColor(inactiveColor);
245  g2.fillRect(margin+cursorX1, 0, cursorX2-cursorX1, getHeight());
246  }
247  }
248  g2.setColor(isActive() ? activeColor : inactiveColor);
249  g2.drawString(tmp, margin, y);
250  }
251 
255  @NotNull
256  @Override
257  public Dimension getPreferredSize() {
258  return new Dimension(preferredSize);
259  }
260 
264  @NotNull
265  @Override
266  public Dimension getMinimumSize() {
267  return new Dimension(preferredSize);
268  }
269 
273  @NotNull
274  @Override
275  public Dimension getMaximumSize() {
276  return new Dimension(preferredSize);
277  }
278 
286  @NotNull
287  private String getDisplayText() {
288  final String tmpText = text.substring(offset);
289  if (!hideInput) {
290  return tmpText+" ";
291  }
292 
293  final String template = "****************************************************************************************************************************************************************";
294  final String hiddenText = template.substring(0, Math.min(tmpText.length(), template.length()));
295  return hiddenText+" ";
296  }
297 
301  @Override
302  public void mouseClicked(@NotNull final MouseEvent e) {
303  super.mouseClicked(e);
304  final int b = e.getButton();
305  switch (b) {
306  case MouseEvent.BUTTON1:
307  setActive(true);
308  setChanged();
309  break;
310 
311  case MouseEvent.BUTTON2:
312  break;
313 
314  case MouseEvent.BUTTON3:
315  break;
316  }
317  }
318 
322  @Override
323  public void execute() {
324  // ignore
325  }
326 
330  @Override
331  protected void activeChanged() {
332  setChanged();
333  }
334 
335  @Override
336  public boolean keyPressed(@NotNull final KeyEvent2 e) {
337  switch (e.getKeyCode()) {
338  case KeyEvent.VK_ENTER:
340  final String command = text.toString();
341  commandCallback.updatePlayerName(command);
342  execute(command);
343  if (!hideInput && commandHistory != null) {
344  commandHistory.addCommand(command);
345  }
347  return true;
348 
349  case KeyEvent.VK_BACK_SPACE:
350  synchronized (syncCursor) {
351  if (cursor > 0) {
352  text.delete(cursor-1, cursor);
353  setCursor(cursor-1);
354  }
355  }
356  return true;
357 
358  case KeyEvent.VK_DELETE:
359  synchronized (syncCursor) {
360  if (cursor < text.length()) {
361  text.delete(cursor, cursor+1);
362  setChanged();
363  }
364  }
365  return true;
366 
367  case KeyEvent.VK_KP_LEFT:
368  case KeyEvent.VK_LEFT:
369  synchronized (syncCursor) {
370  if (cursor > 0) {
371  setCursor(cursor-1);
372  }
373  }
374  return true;
375 
376  case KeyEvent.VK_KP_RIGHT:
377  case KeyEvent.VK_RIGHT:
378  synchronized (syncCursor) {
379  if (cursor < text.length()) {
380  setCursor(cursor+1);
381  }
382  }
383  return true;
384 
385  case KeyEvent.VK_KP_UP:
386  case KeyEvent.VK_UP:
387  if (historyPrev()) {
388  return true;
389  }
390  break;
391 
392  case KeyEvent.VK_KP_DOWN:
393  case KeyEvent.VK_DOWN:
394  if (historyNext()) {
395  return true;
396  }
397  break;
398 
399  case KeyEvent.VK_HOME:
400  synchronized (syncCursor) {
401  if (cursor > 0) {
402  setCursor(0);
403  }
404  }
405  return true;
406 
407  case KeyEvent.VK_END:
408  synchronized (syncCursor) {
409  if (cursor < text.length()) {
410  setCursor(text.length());
411  }
412  }
413  return true;
414 
415  case KeyEvent.VK_N: // CTRL-N
416  if ((e.getModifiers()&KeyEvent2.MASK) == KeyEvent2.CTRL && historyNext()) {
417  return true;
418  }
419  break;
420 
421  case KeyEvent.VK_P: // CTRL-P
422  if ((e.getModifiers()&KeyEvent2.MASK) == KeyEvent2.CTRL && historyPrev()) {
423  return true;
424  }
425  break;
426 
427  case KeyEvent.VK_V: // CTRL-V
428  if ((e.getModifiers()&KeyEvent2.MASK) == KeyEvent2.CTRL) {
429  paste();
430  return true;
431  }
432  break;
433  }
434 
435  final char ch = e.getKeyChar();
436  if (ch != KeyEvent.CHAR_UNDEFINED && ch != (char)127 && ch >= ' ') {
437  insertChar(ch);
438  return true;
439  }
440 
441  return false;
442  }
443 
448  private boolean historyPrev() {
449  if (commandHistory == null) {
450  return false;
451  }
452  final String commandUp = commandHistory.up();
453  if (commandUp != null) {
454  setText(commandUp);
455  }
456  return true;
457  }
458 
463  private boolean historyNext() {
464  if (commandHistory == null) {
465  return false;
466  }
467  final String commandDown = commandHistory.down();
468  setText(commandDown == null ? "" : commandDown);
469  return true;
470  }
471 
475  @Override
476  public boolean keyReleased(@NotNull final KeyEvent e) {
477  return false;
478  }
479 
484  private void insertChar(final char ch) {
485  synchronized (syncCursor) {
486  text.insert(cursor, ch);
487  setCursor(cursor+1);
488  }
489  }
490 
495  private void insertString(@NotNull final String str) {
496  synchronized (syncCursor) {
497  text.insert(cursor, str);
498  setCursor(cursor+str.length());
499  }
500  }
501 
506  protected abstract void execute(@NotNull final String command);
507 
512  public void setHideInput(final boolean hideInput) {
513  if (this.hideInput != hideInput) {
514  this.hideInput = hideInput;
515  setChanged();
516  }
517  }
518 
523  private void setCursor(final int cursor) {
524  synchronized (syncCursor) {
525  if (getGraphics() == null) { // XXX: hack
526  // ignore
527  } else if (this.cursor < cursor) {
528  // cursor moved right
529 
530  while (true) {
531  final String tmp = getDisplayText();
532  final String tmpCursor = tmp.substring(0, cursor-offset+1);
533  //final RectangularShape rectangleCursor = font.getStringBounds(tmpCursor, fontRenderContext);
534  final Dimension dimension = GuiUtils.getTextDimension(tmpCursor, getFontMetrics(font));
535  //final int cursorX = (int)Math.round(rectangleCursor.getWidth());
536  //noinspection NonPrivateFieldAccessedInSynchronizedContext
537  final int cursorX = dimension.width;
538  if (cursorX < getWidth()) {
539  break;
540  }
541 
542  if (offset+SCROLL_CHARS > cursor) {
543  offset = cursor;
544  break;
545  }
546 
547  offset += SCROLL_CHARS;
548  }
549  } else if (this.cursor > cursor) {
550  // cursor moved left
551 
552  while (cursor < offset) {
553  if (offset <= SCROLL_CHARS) {
554  offset = 0;
555  break;
556  }
557 
558  offset -= SCROLL_CHARS;
559  }
560  }
561 
562  this.cursor = cursor;
563  }
564  setChanged();
565  }
566 
570  private void paste() {
571  Transferable content = null;
572  if (selection != null) {
573  content = selection.getContents(this);
574  }
575  if (content == null) {
576  content = clipboard.getContents(this);
577  }
578  if (content == null) {
579  return;
580  }
581 
582  final String str;
583  try {
584  str = (String)content.getTransferData(DataFlavor.stringFlavor);
585  } catch (final IOException|UnsupportedFlavorException ignored) {
586  return;
587  }
588  insertString(str);
589  }
590 
591 }
boolean hideInput
If set, hide input; else show input.
Definition: GUIText.java:146
boolean keyReleased(@NotNull final KeyEvent e)
Invoked when a key has been released.the key event for the key whether the key event has been consume...
Definition: GUIText.java:476
GUIText(@NotNull final CommandCallback commandCallback, @Nullable final CommandHistory commandHistory, @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)
Creates a new instance.
Definition: GUIText.java:183
final TooltipManager tooltipManager
The TooltipManager to update.
final GUIElementListener elementListener
The GUIElementListener to notify.
static Dimension getTextDimension(@NotNull final String text, @NotNull final FontMetrics fontMetrics)
Returns the extents of a string when rendered in a given Font on this component.
Definition: GuiUtils.java:51
final CommandHistory commandHistory
The CommandHistory for this text field.
Definition: GUIText.java:81
void paintComponent(@NotNull final Graphics g)
Definition: GUIText.java:224
final Font font
The font for rendering displayed text.
Definition: GUIText.java:111
void setChanged()
Records that the contents have changed and must be repainted.
int offset
The display offset: this many characters are hidden.
Definition: GUIText.java:156
Interface for listeners for keyboard input.
final Clipboard clipboard
The clipboard for cut/copy/paste operations.
Definition: GUIText.java:99
final Object syncCursor
Object used to synchronize on access to text, cursor, and offset.
Definition: GUIText.java:163
void insertChar(final char ch)
Inserts a character at the cursor position.
Definition: GUIText.java:484
void insertString(@NotNull final String str)
Inserts a string at the cursor position.
Definition: GUIText.java:495
void setActive(final boolean active)
Sets the active state of a GUI element.
final Dimension preferredSize
The size of this component.
Definition: GUIText.java:141
Represents a pressed or released key.
Definition: KeyEvent2.java:33
boolean keyPressed(@NotNull final KeyEvent2 e)
Invoked when a key has been pressed.
Definition: GUIText.java:336
void updatePlayerName(@NotNull String playerName)
Sets the current player name.
boolean historyNext()
Activates the next command from the command history.
Definition: GUIText.java:463
void paste()
Performs a "paste" operation from the system clipboard.
Definition: GUIText.java:570
final StringBuilder text
The entered text.
Definition: GUIText.java:136
final Color activeColor
The color for rendering displayed text when the element is active.
Definition: GUIText.java:125
void setCursor(final int cursor)
Sets the cursor position.
Definition: GUIText.java:523
String getText()
Returns the entered text.
Definition: GUIText.java:216
final Color inactiveColor
The color for rendering displayed text when the element is inactive.
Definition: GUIText.java:118
void markInactivePending()
Marks this GUI element as pending inactive.
void mouseClicked(@NotNull final MouseEvent e)
Will be called when the user has clicked (pressed+released) this element.This event will be delivered...
Definition: GUIText.java:302
A GUIElement that can be set to active or inactive.
static final int SCROLL_CHARS
The number of characters to scroll left/right when the cursor would move outside of the visible area...
Definition: GUIText.java:68
static final long serialVersionUID
The serial version UID.
Definition: GUIText.java:62
void setInactiveIfPending()
Unsets the active state of this GUI element if is is pending.
Utility class for Gui related functions.
Definition: GuiUtils.java:35
Interface that defines callback functions needed by commands.
void addCommand(@NotNull final String command)
Adds a new command.
String getDisplayText()
Returns the displayed text.
Definition: GUIText.java:287
final Image activeImage
The element&#39;s background image when it is active.
Definition: GUIText.java:87
final Image inactiveImage
The element&#39;s background image when it is inactive.
Definition: GUIText.java:93
static final int CTRL
The mask for "ctrl".
Definition: KeyEvent2.java:53
Manages a list of previously entered commands.
final int margin
The left margin in pixels.
Definition: GUIText.java:130
void setText(@NotNull final String text)
Sets the entered text.
Definition: GUIText.java:205
final CommandCallback commandCallback
The CommandCallback to use.
Definition: GUIText.java:74
boolean historyPrev()
Activates the previous command from the command history.
Definition: GUIText.java:448
final Clipboard selection
The system selection for cut/copy/paste operations.
Definition: GUIText.java:105
Abstract base class for text input fields.
Definition: GUIText.java:57
boolean isActive()
Returns whether a GUI element is active.
static final int MASK
The mask for all used modifiers.
Definition: KeyEvent2.java:68
void setHideInput(final boolean hideInput)
Enables or disables hidden text.
Definition: GUIText.java:512