2 * This file is part of lanterna (http://code.google.com/p/lanterna/).
4 * lanterna is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program 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 Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 * Copyright (C) 2010-2015 Martin
19 package com
.googlecode
.lanterna
.terminal
.swing
;
21 import com
.googlecode
.lanterna
.*;
22 import com
.googlecode
.lanterna
.graphics
.TextGraphics
;
23 import com
.googlecode
.lanterna
.input
.KeyStroke
;
24 import com
.googlecode
.lanterna
.input
.KeyType
;
25 import com
.googlecode
.lanterna
.terminal
.IOSafeTerminal
;
26 import com
.googlecode
.lanterna
.terminal
.ResizeListener
;
29 import java
.awt
.event
.InputEvent
;
30 import java
.awt
.event
.KeyAdapter
;
31 import java
.awt
.event
.KeyEvent
;
32 import java
.awt
.image
.BufferedImage
;
33 import java
.io
.IOException
;
35 import java
.util
.List
;
36 import java
.util
.concurrent
.BlockingQueue
;
37 import java
.util
.concurrent
.CopyOnWriteArrayList
;
38 import java
.util
.concurrent
.LinkedBlockingQueue
;
39 import java
.util
.concurrent
.TimeUnit
;
42 * This is the class that does the heavy lifting for both {@link AWTTerminal} and {@link SwingTerminal}. It maintains
43 * most of the external terminal state and also the main back buffer that is copied to the components area on draw
48 @SuppressWarnings("serial")
49 abstract class GraphicalTerminalImplementation
implements IOSafeTerminal
{
50 private final TerminalEmulatorDeviceConfiguration deviceConfiguration
;
51 private final TerminalEmulatorColorConfiguration colorConfiguration
;
52 private final VirtualTerminal virtualTerminal
;
53 private final BlockingQueue
<KeyStroke
> keyQueue
;
54 private final List
<ResizeListener
> resizeListeners
;
56 private final String enquiryString
;
57 private final EnumSet
<SGR
> activeSGRs
;
58 private TextColor foregroundColor
;
59 private TextColor backgroundColor
;
61 private volatile boolean cursorIsVisible
;
62 private volatile Timer blinkTimer
;
63 private volatile boolean hasBlinkingText
;
64 private volatile boolean blinkOn
;
65 private volatile boolean flushed
;
67 // We use two different data structures to optimize drawing
68 // * A map (as a two-dimensional array) of all characters currently visible inside this component
69 // * A backbuffer with the graphics content
71 // The buffer is the most important one as it allows us to re-use what was drawn earlier. It is not reset on every
72 // drawing operation but updates just in those places where the map tells us the character has changed. Note that
73 // when the component is resized, we always update the whole buffer.
75 // DON'T RELY ON THESE FOR SIZE! We make it a big bigger than necessary to make resizing smoother. Use the AWT/Swing
76 // methods to get the correct dimensions or use {@code getTerminalSize()} to get the size in terminal space.
77 private CharacterState
[][] visualState
;
78 private BufferedImage backbuffer
;
81 * Creates a new GraphicalTerminalImplementation component using custom settings and a custom scroll controller. The
82 * scrolling controller will be notified when the terminal's history size grows and will be called when this class
83 * needs to figure out the current scrolling position.
84 * @param initialTerminalSize Initial size of the terminal, which will be used when calculating the preferred size
85 * of the component. If null, it will default to 80x25. If the AWT layout manager forces
86 * the component to a different size, the value of this parameter won't have any meaning
87 * @param deviceConfiguration Device configuration to use for this SwingTerminal
88 * @param colorConfiguration Color configuration to use for this SwingTerminal
89 * @param scrollController Controller to use for scrolling, the object passed in will be notified whenever the
90 * scrollable area has changed
92 public GraphicalTerminalImplementation(
93 TerminalSize initialTerminalSize
,
94 TerminalEmulatorDeviceConfiguration deviceConfiguration
,
95 TerminalEmulatorColorConfiguration colorConfiguration
,
96 TerminalScrollController scrollController
) {
98 //This is kind of meaningless since we don't know how large the
99 //component is at this point, but we should set it to something
100 if(initialTerminalSize
== null) {
101 initialTerminalSize
= new TerminalSize(80, 24);
103 this.virtualTerminal
= new VirtualTerminal(
104 deviceConfiguration
.getLineBufferScrollbackSize(),
107 this.keyQueue
= new LinkedBlockingQueue
<KeyStroke
>();
108 this.resizeListeners
= new CopyOnWriteArrayList
<ResizeListener
>();
109 this.deviceConfiguration
= deviceConfiguration
;
110 this.colorConfiguration
= colorConfiguration
;
112 this.activeSGRs
= EnumSet
.noneOf(SGR
.class);
113 this.foregroundColor
= TextColor
.ANSI
.DEFAULT
;
114 this.backgroundColor
= TextColor
.ANSI
.DEFAULT
;
115 this.cursorIsVisible
= true; //Always start with an activate and visible cursor
116 this.enquiryString
= "TerminalEmulator";
117 this.visualState
= new CharacterState
[48][160];
118 this.backbuffer
= null; // We don't know the dimensions yet
119 this.blinkTimer
= null;
120 this.hasBlinkingText
= false; // Assume initial content doesn't have any blinking text
122 this.flushed
= false;
124 //Set the initial scrollable size
125 //scrollObserver.newScrollableLength(fontConfiguration.getFontHeight() * terminalSize.getRows());
129 // First abstract methods that are implemented in AWTTerminalImplementation and SwingTerminalImplementation
133 * Used to find out the font height, in pixels
134 * @return Terminal font height in pixels
136 protected abstract int getFontHeight();
139 * Used to find out the font width, in pixels
140 * @return Terminal font width in pixels
142 protected abstract int getFontWidth();
145 * Used when requiring the total height of the terminal component, in pixels
146 * @return Height of the terminal component, in pixels
148 protected abstract int getHeight();
151 * Used when requiring the total width of the terminal component, in pixels
152 * @return Width of the terminal component, in pixels
154 protected abstract int getWidth();
157 * Returning the AWT font to use for the specific character. This might not always be the same, in case a we are
158 * trying to draw an unusual character (probably CJK) which isn't contained in the standard terminal font.
159 * @param character Character to get the font for
160 * @return Font to be used for this character
162 protected abstract Font
getFontForCharacter(TextCharacter character
);
165 * Returns {@code true} if anti-aliasing is enabled, {@code false} otherwise
166 * @return {@code true} if anti-aliasing is enabled, {@code false} otherwise
168 protected abstract boolean isTextAntiAliased();
171 * Called by the {@code GraphicalTerminalImplementation} when it would like the OS to schedule a repaint of the
174 protected abstract void repaint();
177 * Start the timer that triggers blinking
179 protected synchronized void startBlinkTimer() {
180 if(blinkTimer
!= null) {
184 blinkTimer
= new Timer("LanternaTerminalBlinkTimer", true);
185 blinkTimer
.schedule(new TimerTask() {
189 if(hasBlinkingText
) {
193 }, deviceConfiguration
.getBlinkLengthInMilliSeconds(), deviceConfiguration
.getBlinkLengthInMilliSeconds());
197 * Stops the timer the triggers blinking
199 protected synchronized void stopBlinkTimer() {
200 if(blinkTimer
== null) {
209 // First implement all the Swing-related methods
212 * Calculates the preferred size of this terminal
213 * @return Preferred size of this terminal
215 synchronized Dimension
getPreferredSize() {
216 return new Dimension(getFontWidth() * virtualTerminal
.getSize().getColumns(),
217 getFontHeight() * virtualTerminal
.getSize().getRows());
221 * Updates the back buffer (if necessary) and draws it to the component's surface
222 * @param componentGraphics Object to use when drawing to the component's surface
224 protected synchronized void paintComponent(Graphics componentGraphics
) {
225 //First, resize the buffer width/height if necessary
226 int fontWidth
= getFontWidth();
227 int fontHeight
= getFontHeight();
228 //boolean antiAliasing = fontConfiguration.isAntiAliased();
229 int widthInNumberOfCharacters
= getWidth() / fontWidth
;
230 int visibleRows
= getHeight() / fontHeight
;
231 boolean terminalResized
= false;
233 //Don't let size be less than 1
234 widthInNumberOfCharacters
= Math
.max(1, widthInNumberOfCharacters
);
235 visibleRows
= Math
.max(1, visibleRows
);
237 //scrollObserver.updateModel(currentBuffer.getNumberOfLines(), visibleRows);
238 TerminalSize terminalSize
= virtualTerminal
.getSize().withColumns(widthInNumberOfCharacters
).withRows(visibleRows
);
239 if(!terminalSize
.equals(virtualTerminal
.getSize())) {
240 virtualTerminal
.resize(terminalSize
);
241 for(ResizeListener listener
: resizeListeners
) {
242 listener
.onResized(this, terminalSize
);
244 terminalResized
= true;
245 ensureVisualStateHasRightSize(terminalSize
);
247 ensureBackbufferHasRightSize();
249 // At this point, if the user hasn't asked for an explicit flush, just paint the backbuffer. It's prone to
250 // problems if the user isn't flushing properly but it reduces flickering when resizing the window and the code
251 // is asynchronously responding to the resize
253 updateBackBuffer(fontWidth
, fontHeight
, terminalResized
, terminalSize
);
257 componentGraphics
.drawImage(backbuffer
, 0, 0, getWidth(), getHeight(), 0, 0, getWidth(), getHeight(), null);
259 // Dispose the graphic objects
260 componentGraphics
.dispose();
262 // Tell anyone waiting on us that drawing is complete
266 private void updateBackBuffer(int fontWidth
, int fontHeight
, boolean terminalResized
, TerminalSize terminalSize
) {
267 //Retrieve the position of the cursor, relative to the scrolling state
268 TerminalPosition translatedCursorPosition
= virtualTerminal
.getTranslatedCursorPosition();
270 //Setup the graphics object
271 Graphics2D backbufferGraphics
= backbuffer
.createGraphics();
273 if(isTextAntiAliased()) {
274 backbufferGraphics
.setRenderingHint(RenderingHints
.KEY_TEXT_ANTIALIASING
, RenderingHints
.VALUE_TEXT_ANTIALIAS_ON
);
275 backbufferGraphics
.setRenderingHint(RenderingHints
.KEY_RENDERING
, RenderingHints
.VALUE_RENDER_QUALITY
);
278 // Draw line by line, character by character
279 // Initiate the blink state to whatever the cursor is using, since if the cursor is blinking then we always want
280 // to do the blink repaint
281 boolean foundBlinkingCharacters
= deviceConfiguration
.isCursorBlinking();
283 for(List
<TextCharacter
> row
: virtualTerminal
.getLines()) {
284 for(int columnIndex
= 0; columnIndex
< row
.size(); columnIndex
++) {
285 //Any extra characters from the virtual terminal that doesn't fit can be discarded
286 if(columnIndex
>= terminalSize
.getColumns()) {
290 TextCharacter character
= row
.get(columnIndex
);
291 boolean atCursorLocation
= translatedCursorPosition
.equals(columnIndex
, rowIndex
);
292 //If next position is the cursor location and this is a CJK character (i.e. cursor is on the padding),
293 //consider this location the cursor position since otherwise the cursor will be skipped
294 if(!atCursorLocation
&&
295 translatedCursorPosition
.getColumn() == columnIndex
+ 1 &&
296 translatedCursorPosition
.getRow() == rowIndex
&&
297 TerminalTextUtils
.isCharCJK(character
.getCharacter())) {
298 atCursorLocation
= true;
300 int characterWidth
= fontWidth
* (TerminalTextUtils
.isCharCJK(character
.getCharacter()) ?
2 : 1);
302 Color foregroundColor
= deriveTrueForegroundColor(character
, atCursorLocation
);
303 Color backgroundColor
= deriveTrueBackgroundColor(character
, atCursorLocation
);
305 boolean drawCursor
= atCursorLocation
&&
306 (!deviceConfiguration
.isCursorBlinking() || //Always draw if the cursor isn't blinking
307 (deviceConfiguration
.isCursorBlinking() && blinkOn
)); //If the cursor is blinking, only draw when blinkOn is true
309 CharacterState characterState
= new CharacterState(character
, foregroundColor
, backgroundColor
, drawCursor
);
310 if(!characterState
.equals(visualState
[rowIndex
][columnIndex
]) || terminalResized
) {
311 drawCharacter(backbufferGraphics
,
321 visualState
[rowIndex
][columnIndex
] = characterState
;
322 if(TerminalTextUtils
.isCharCJK(character
.getCharacter())) {
323 visualState
[rowIndex
][columnIndex
+1] = characterState
;
327 if(character
.getModifiers().contains(SGR
.BLINK
)) {
328 foundBlinkingCharacters
= true;
330 if(TerminalTextUtils
.isCharCJK(character
.getCharacter())) {
331 columnIndex
++; //Skip the trailing space after a CJK character
337 // Take care of the left-over area at the bottom and right of the component where no character can fit
338 int leftoverHeight
= getHeight() % fontHeight
;
339 int leftoverWidth
= getWidth() % fontWidth
;
340 backbufferGraphics
.setColor(Color
.BLACK
);
341 if(leftoverWidth
> 0) {
342 backbufferGraphics
.fillRect(getWidth() - leftoverWidth
, 0, leftoverWidth
, getHeight());
344 if(leftoverHeight
> 0) {
345 backbufferGraphics
.fillRect(0, getHeight() - leftoverHeight
, getWidth(), leftoverHeight
);
347 backbufferGraphics
.dispose();
349 // Update the blink status according to if there were any blinking characters or not
350 this.hasBlinkingText
= foundBlinkingCharacters
;
353 private void ensureBackbufferHasRightSize() {
354 if(backbuffer
== null) {
355 backbuffer
= new BufferedImage(getWidth() * 2, getHeight() * 2, BufferedImage
.TYPE_INT_RGB
);
357 if(backbuffer
.getWidth() < getWidth() || backbuffer
.getWidth() > getWidth() * 4 ||
358 backbuffer
.getHeight() < getHeight() || backbuffer
.getHeight() > getHeight() * 4) {
359 BufferedImage newBackbuffer
= new BufferedImage(Math
.max(getWidth(), 1) * 2, Math
.max(getHeight(), 1) * 2, BufferedImage
.TYPE_INT_RGB
);
360 Graphics2D graphics
= newBackbuffer
.createGraphics();
361 graphics
.drawImage(backbuffer
, 0, 0, null);
363 backbuffer
= newBackbuffer
;
367 private void ensureVisualStateHasRightSize(TerminalSize terminalSize
) {
368 if(visualState
== null) {
369 visualState
= new CharacterState
[terminalSize
.getRows() * 2][terminalSize
.getColumns() * 2];
371 if(visualState
.length
< terminalSize
.getRows() || visualState
.length
> Math
.max(terminalSize
.getRows(), 1) * 4) {
372 visualState
= Arrays
.copyOf(visualState
, terminalSize
.getRows() * 2);
374 for(int rowIndex
= 0; rowIndex
< visualState
.length
; rowIndex
++) {
375 CharacterState
[] row
= visualState
[rowIndex
];
377 row
= new CharacterState
[terminalSize
.getColumns() * 2];
378 visualState
[rowIndex
] = row
;
380 if(row
.length
< terminalSize
.getColumns() || row
.length
> Math
.max(terminalSize
.getColumns(), 1) * 4) {
381 row
= Arrays
.copyOf(row
, terminalSize
.getColumns() * 2);
382 visualState
[rowIndex
] = row
;
385 // Make sure all items outside the 'real' terminal size are null
386 if(rowIndex
< terminalSize
.getRows()) {
387 Arrays
.fill(row
, terminalSize
.getColumns(), row
.length
, null);
390 Arrays
.fill(row
, null);
395 private void drawCharacter(
397 TextCharacter character
,
400 Color foregroundColor
,
401 Color backgroundColor
,
405 boolean drawCursor
) {
407 int x
= columnIndex
* fontWidth
;
408 int y
= rowIndex
* fontHeight
;
409 g
.setColor(backgroundColor
);
410 g
.setClip(x
, y
, characterWidth
, fontHeight
);
411 g
.fillRect(x
, y
, characterWidth
, fontHeight
);
413 g
.setColor(foregroundColor
);
414 Font font
= getFontForCharacter(character
);
416 FontMetrics fontMetrics
= g
.getFontMetrics();
417 g
.drawString(Character
.toString(character
.getCharacter()), x
, ((rowIndex
+ 1) * fontHeight
) - fontMetrics
.getDescent());
419 if(character
.isCrossedOut()) {
421 int lineStartY
= y
+ (fontHeight
/ 2);
422 int lineEndX
= lineStartX
+ characterWidth
;
423 g
.drawLine(lineStartX
, lineStartY
, lineEndX
, lineStartY
);
425 if(character
.isUnderlined()) {
427 int lineStartY
= ((rowIndex
+ 1) * fontHeight
) - fontMetrics
.getDescent() + 1;
428 int lineEndX
= lineStartX
+ characterWidth
;
429 g
.drawLine(lineStartX
, lineStartY
, lineEndX
, lineStartY
);
433 if(deviceConfiguration
.getCursorColor() == null) {
434 g
.setColor(foregroundColor
);
437 g
.setColor(colorConfiguration
.toAWTColor(deviceConfiguration
.getCursorColor(), false, false));
439 if(deviceConfiguration
.getCursorStyle() == TerminalEmulatorDeviceConfiguration
.CursorStyle
.UNDER_BAR
) {
440 g
.fillRect(x
, y
+ fontHeight
- 3, characterWidth
, 2);
442 else if(deviceConfiguration
.getCursorStyle() == TerminalEmulatorDeviceConfiguration
.CursorStyle
.VERTICAL_BAR
) {
443 g
.fillRect(x
, y
+ 1, 2, fontHeight
- 2);
449 private Color
deriveTrueForegroundColor(TextCharacter character
, boolean atCursorLocation
) {
450 TextColor foregroundColor
= character
.getForegroundColor();
451 TextColor backgroundColor
= character
.getBackgroundColor();
452 boolean reverse
= character
.isReversed();
453 boolean blink
= character
.isBlinking();
455 if(cursorIsVisible
&& atCursorLocation
) {
456 if(deviceConfiguration
.getCursorStyle() == TerminalEmulatorDeviceConfiguration
.CursorStyle
.REVERSED
&&
457 (!deviceConfiguration
.isCursorBlinking() || !blinkOn
)) {
462 if(reverse
&& (!blink
|| !blinkOn
)) {
463 return colorConfiguration
.toAWTColor(backgroundColor
, backgroundColor
!= TextColor
.ANSI
.DEFAULT
, character
.isBold());
465 else if(!reverse
&& blink
&& blinkOn
) {
466 return colorConfiguration
.toAWTColor(backgroundColor
, false, character
.isBold());
469 return colorConfiguration
.toAWTColor(foregroundColor
, true, character
.isBold());
473 private Color
deriveTrueBackgroundColor(TextCharacter character
, boolean atCursorLocation
) {
474 TextColor foregroundColor
= character
.getForegroundColor();
475 TextColor backgroundColor
= character
.getBackgroundColor();
476 boolean reverse
= character
.isReversed();
478 if(cursorIsVisible
&& atCursorLocation
) {
479 if(deviceConfiguration
.getCursorStyle() == TerminalEmulatorDeviceConfiguration
.CursorStyle
.REVERSED
&&
480 (!deviceConfiguration
.isCursorBlinking() || !blinkOn
)) {
483 else if(deviceConfiguration
.getCursorStyle() == TerminalEmulatorDeviceConfiguration
.CursorStyle
.FIXED_BACKGROUND
) {
484 backgroundColor
= deviceConfiguration
.getCursorColor();
489 return colorConfiguration
.toAWTColor(foregroundColor
, backgroundColor
== TextColor
.ANSI
.DEFAULT
, character
.isBold());
492 return colorConfiguration
.toAWTColor(backgroundColor
, false, false);
497 // Then delegate all Terminal interface methods to the virtual terminal implementation
499 // Some of these methods we need to pass to the AWT-thread, which makes the call asynchronous. Hopefully this isn't
500 // causing too much problem...
503 public KeyStroke
pollInput() {
504 return keyQueue
.poll();
508 public KeyStroke
readInput() throws IOException
{
510 return keyQueue
.take();
512 catch(InterruptedException ignore
) {
513 throw new IOException("Blocking input was interrupted");
518 public synchronized void enterPrivateMode() {
519 virtualTerminal
.switchToPrivateMode();
520 clearBackBufferAndVisualState();
525 public synchronized void exitPrivateMode() {
526 virtualTerminal
.switchToNormalMode();
527 clearBackBufferAndVisualState();
532 public synchronized void clearScreen() {
533 virtualTerminal
.clear();
534 clearBackBufferAndVisualState();
539 * Clears out the back buffer and the resets the visual state so next paint operation will do a full repaint of
542 protected void clearBackBufferAndVisualState() {
543 // Manually clear the backbuffer and visual state
544 if(backbuffer
!= null) {
545 Graphics2D graphics
= backbuffer
.createGraphics();
546 Color foregroundColor
= colorConfiguration
.toAWTColor(TextColor
.ANSI
.DEFAULT
, true, false);
547 Color backgroundColor
= colorConfiguration
.toAWTColor(TextColor
.ANSI
.DEFAULT
, false, false);
548 graphics
.setColor(backgroundColor
);
549 graphics
.fillRect(0, 0, getWidth(), getHeight());
552 for(CharacterState
[] line
: visualState
) {
553 Arrays
.fill(line
, new CharacterState(new TextCharacter(' '), foregroundColor
, backgroundColor
, false));
559 public synchronized void setCursorPosition(final int x
, final int y
) {
560 virtualTerminal
.setCursorPosition(new TerminalPosition(x
, y
));
564 public void setCursorVisible(final boolean visible
) {
565 cursorIsVisible
= visible
;
569 public synchronized void putCharacter(final char c
) {
570 virtualTerminal
.putCharacter(new TextCharacter(c
, foregroundColor
, backgroundColor
, activeSGRs
));
574 public TextGraphics
newTextGraphics() throws IOException
{
575 return new VirtualTerminalTextGraphics(virtualTerminal
);
579 public void enableSGR(final SGR sgr
) {
584 public void disableSGR(final SGR sgr
) {
585 activeSGRs
.remove(sgr
);
589 public void resetColorAndSGR() {
590 foregroundColor
= TextColor
.ANSI
.DEFAULT
;
591 backgroundColor
= TextColor
.ANSI
.DEFAULT
;
596 public void setForegroundColor(final TextColor color
) {
597 foregroundColor
= color
;
601 public void setBackgroundColor(final TextColor color
) {
602 backgroundColor
= color
;
606 public synchronized TerminalSize
getTerminalSize() {
607 return virtualTerminal
.getSize();
611 public byte[] enquireTerminal(int timeout
, TimeUnit timeoutUnit
) {
612 return enquiryString
.getBytes();
616 public void flush() {
622 public void addResizeListener(ResizeListener listener
) {
623 resizeListeners
.add(listener
);
627 public void removeResizeListener(ResizeListener listener
) {
628 resizeListeners
.remove(listener
);
632 // Remaining are private internal classes used by SwingTerminal
634 private static final Set
<Character
> TYPED_KEYS_TO_IGNORE
= new HashSet
<Character
>(Arrays
.asList('\n', '\t', '\r', '\b', '\33', (char)127));
637 * Class that translates AWT key events into Lanterna {@link KeyStroke}
639 protected class TerminalInputListener
extends KeyAdapter
{
641 public void keyTyped(KeyEvent e
) {
642 char character
= e
.getKeyChar();
643 boolean altDown
= (e
.getModifiersEx() & InputEvent
.ALT_DOWN_MASK
) != 0;
644 boolean ctrlDown
= (e
.getModifiersEx() & InputEvent
.CTRL_DOWN_MASK
) != 0;
646 if(!TYPED_KEYS_TO_IGNORE
.contains(character
)) {
648 //We need to re-adjust the character if ctrl is pressed, just like for the AnsiTerminal
649 character
= (char) ('a' - 1 + character
);
651 keyQueue
.add(new KeyStroke(character
, ctrlDown
, altDown
));
656 public void keyPressed(KeyEvent e
) {
657 boolean altDown
= (e
.getModifiersEx() & InputEvent
.ALT_DOWN_MASK
) != 0;
658 boolean ctrlDown
= (e
.getModifiersEx() & InputEvent
.CTRL_DOWN_MASK
) != 0;
659 if(e
.getKeyCode() == KeyEvent
.VK_ENTER
) {
660 keyQueue
.add(new KeyStroke(KeyType
.Enter
, ctrlDown
, altDown
));
662 else if(e
.getKeyCode() == KeyEvent
.VK_ESCAPE
) {
663 keyQueue
.add(new KeyStroke(KeyType
.Escape
, ctrlDown
, altDown
));
665 else if(e
.getKeyCode() == KeyEvent
.VK_BACK_SPACE
) {
666 keyQueue
.add(new KeyStroke(KeyType
.Backspace
, ctrlDown
, altDown
));
668 else if(e
.getKeyCode() == KeyEvent
.VK_LEFT
) {
669 keyQueue
.add(new KeyStroke(KeyType
.ArrowLeft
, ctrlDown
, altDown
));
671 else if(e
.getKeyCode() == KeyEvent
.VK_RIGHT
) {
672 keyQueue
.add(new KeyStroke(KeyType
.ArrowRight
, ctrlDown
, altDown
));
674 else if(e
.getKeyCode() == KeyEvent
.VK_UP
) {
675 keyQueue
.add(new KeyStroke(KeyType
.ArrowUp
, ctrlDown
, altDown
));
677 else if(e
.getKeyCode() == KeyEvent
.VK_DOWN
) {
678 keyQueue
.add(new KeyStroke(KeyType
.ArrowDown
, ctrlDown
, altDown
));
680 else if(e
.getKeyCode() == KeyEvent
.VK_INSERT
) {
681 keyQueue
.add(new KeyStroke(KeyType
.Insert
, ctrlDown
, altDown
));
683 else if(e
.getKeyCode() == KeyEvent
.VK_DELETE
) {
684 keyQueue
.add(new KeyStroke(KeyType
.Delete
, ctrlDown
, altDown
));
686 else if(e
.getKeyCode() == KeyEvent
.VK_HOME
) {
687 keyQueue
.add(new KeyStroke(KeyType
.Home
, ctrlDown
, altDown
));
689 else if(e
.getKeyCode() == KeyEvent
.VK_END
) {
690 keyQueue
.add(new KeyStroke(KeyType
.End
, ctrlDown
, altDown
));
692 else if(e
.getKeyCode() == KeyEvent
.VK_PAGE_UP
) {
693 keyQueue
.add(new KeyStroke(KeyType
.PageUp
, ctrlDown
, altDown
));
695 else if(e
.getKeyCode() == KeyEvent
.VK_PAGE_DOWN
) {
696 keyQueue
.add(new KeyStroke(KeyType
.PageDown
, ctrlDown
, altDown
));
698 else if(e
.getKeyCode() == KeyEvent
.VK_F1
) {
699 keyQueue
.add(new KeyStroke(KeyType
.F1
, ctrlDown
, altDown
));
701 else if(e
.getKeyCode() == KeyEvent
.VK_F2
) {
702 keyQueue
.add(new KeyStroke(KeyType
.F2
, ctrlDown
, altDown
));
704 else if(e
.getKeyCode() == KeyEvent
.VK_F3
) {
705 keyQueue
.add(new KeyStroke(KeyType
.F3
, ctrlDown
, altDown
));
707 else if(e
.getKeyCode() == KeyEvent
.VK_F4
) {
708 keyQueue
.add(new KeyStroke(KeyType
.F4
, ctrlDown
, altDown
));
710 else if(e
.getKeyCode() == KeyEvent
.VK_F5
) {
711 keyQueue
.add(new KeyStroke(KeyType
.F5
, ctrlDown
, altDown
));
713 else if(e
.getKeyCode() == KeyEvent
.VK_F6
) {
714 keyQueue
.add(new KeyStroke(KeyType
.F6
, ctrlDown
, altDown
));
716 else if(e
.getKeyCode() == KeyEvent
.VK_F7
) {
717 keyQueue
.add(new KeyStroke(KeyType
.F7
, ctrlDown
, altDown
));
719 else if(e
.getKeyCode() == KeyEvent
.VK_F8
) {
720 keyQueue
.add(new KeyStroke(KeyType
.F8
, ctrlDown
, altDown
));
722 else if(e
.getKeyCode() == KeyEvent
.VK_F9
) {
723 keyQueue
.add(new KeyStroke(KeyType
.F9
, ctrlDown
, altDown
));
725 else if(e
.getKeyCode() == KeyEvent
.VK_F10
) {
726 keyQueue
.add(new KeyStroke(KeyType
.F10
, ctrlDown
, altDown
));
728 else if(e
.getKeyCode() == KeyEvent
.VK_F11
) {
729 keyQueue
.add(new KeyStroke(KeyType
.F11
, ctrlDown
, altDown
));
731 else if(e
.getKeyCode() == KeyEvent
.VK_F12
) {
732 keyQueue
.add(new KeyStroke(KeyType
.F12
, ctrlDown
, altDown
));
734 else if(e
.getKeyCode() == KeyEvent
.VK_TAB
) {
735 if(e
.isShiftDown()) {
736 keyQueue
.add(new KeyStroke(KeyType
.ReverseTab
, ctrlDown
, altDown
));
739 keyQueue
.add(new KeyStroke(KeyType
.Tab
, ctrlDown
, altDown
));
743 //keyTyped doesn't catch this scenario (for whatever reason...) so we have to do it here
744 if(altDown
&& ctrlDown
&& e
.getKeyCode() >= 'A' && e
.getKeyCode() <= 'Z') {
745 char asLowerCase
= Character
.toLowerCase((char) e
.getKeyCode());
746 keyQueue
.add(new KeyStroke(asLowerCase
, true, true));
752 private static class CharacterState
{
753 private final TextCharacter textCharacter
;
754 private final Color foregroundColor
;
755 private final Color backgroundColor
;
756 private final boolean drawCursor
;
758 CharacterState(TextCharacter textCharacter
, Color foregroundColor
, Color backgroundColor
, boolean drawCursor
) {
759 this.textCharacter
= textCharacter
;
760 this.foregroundColor
= foregroundColor
;
761 this.backgroundColor
= backgroundColor
;
762 this.drawCursor
= drawCursor
;
766 public boolean equals(Object o
) {
770 if(o
== null || getClass() != o
.getClass()) {
773 CharacterState that
= (CharacterState
) o
;
774 if(drawCursor
!= that
.drawCursor
) {
777 if(!textCharacter
.equals(that
.textCharacter
)) {
780 if(!foregroundColor
.equals(that
.foregroundColor
)) {
783 return backgroundColor
.equals(that
.backgroundColor
);
787 public int hashCode() {
788 int result
= textCharacter
.hashCode();
789 result
= 31 * result
+ foregroundColor
.hashCode();
790 result
= 31 * result
+ backgroundColor
.hashCode();
791 result
= 31 * result
+ (drawCursor ?
1 : 0);
796 public String
toString() {
797 return "CharacterState{" +
798 "textCharacter=" + textCharacter
+
799 ", foregroundColor=" + foregroundColor
+
800 ", backgroundColor=" + backgroundColor
+
801 ", drawCursor=" + drawCursor
+