Java tutorial
/***************************************************************************** * Copyright (c) 2006, 2007 g-Eclipse Consortium * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Initial development of the original code was made for the * g-Eclipse project founded by European Union * project number: FP6-IST-034327 http://www.geclipse.eu/ * * Contributors: * Thomas Koeckerbauer GUP, JKU - initial API and implementation *****************************************************************************/ package eu.geclipse.terminal.internal; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PushbackInputStream; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.SortedSet; import java.util.StringTokenizer; import java.util.TreeSet; import org.eclipse.core.runtime.IStatus; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.Clipboard; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.TextTransfer; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.MenuAdapter; import org.eclipse.swt.events.MenuEvent; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.PlatformUI; import eu.geclipse.terminal.ITerminalListener; /** * VT100 terminal emulator widget. */ public class Terminal extends Canvas implements ISelectionProvider { private static final int BEL = 7; private static final int FF = 12; private static final int SO = 14; private static final int SI = 15; private static final int TAB_WIDTH = 8; OutputStream output; KeyPadMode keyPadMode; CursorKeyMode cursorKeyMode; ScrollMode scrollMode; boolean newLineMode; TerminalPainter terminalPainter; int topMargin; int bottomMargin; int historySize; private PushbackInputStream input; private Char[][] screenBuffer; private LineHeightMode[] lineHeightMode; private LineWidthMode[] lineWidthMode; private int fontHeight; private int fontWidth; private int numCols; private int numLines; private final boolean[] leds = new boolean[4]; private Cursor cursor; private Cursor savedCursor; private Color[] systemColors; private Color defaultBgColor; private Color defaultFgColor; private OriginMode originMode; private final CharSet[] charSetG = new CharSet[4]; private boolean reverseScreenMode; private String windowTitle; private final SortedSet<Integer> tabulatorPositons = new TreeSet<Integer>(); private List<ITerminalListener> terminalListeners; private boolean wraparound; private boolean reverseWraparound; private boolean running; private Clipboard clipboard; private List<ISelectionChangedListener> selectionListeners; private TerminalSelection terminalSelection; /** * Creates a new terminal widget. * * @param parent parent widget. * @param style widget style. * @param initFgColor default foreground color. * @param initBgColor default background color. * @param historySize size of history in lines. */ public Terminal(final Composite parent, final int style, final Color initFgColor, final Color initBgColor, final int historySize) { super(parent, style | SWT.NO_BACKGROUND | SWT.V_SCROLL); this.clipboard = new Clipboard(getDisplay()); this.historySize = historySize; initMenu(); initSystemColorTable(); if (initBgColor != null) this.defaultBgColor = initBgColor; if (initFgColor != null) this.defaultFgColor = initFgColor; this.cursor = new Cursor(this.defaultFgColor, this.defaultBgColor); this.savedCursor = new Cursor(this.defaultFgColor, this.defaultBgColor); this.screenBuffer = new Char[0][]; this.terminalListeners = new LinkedList<ITerminalListener>(); this.selectionListeners = new LinkedList<ISelectionChangedListener>(); changeScreenSize(); this.terminalPainter = new TerminalPainter(this); this.terminalSelection = new TerminalSelection(this); addMouseListener(this.terminalSelection); addMouseMoveListener(this.terminalSelection); addPaintListener(this.terminalPainter); addListener(SWT.Resize, new Listener() { public void handleEvent(final Event event) { changeScreenSize(); } }); getVerticalBar().addSelectionListener(new SelectionAdapter() { int prevValue = -1; @Override public void widgetSelected(final SelectionEvent event) { if (prevValue != getVerticalBar().getSelection()) { prevValue = getVerticalBar().getSelection(); triggerRedraw(); } } }); addMouseListener(new MouseAdapter() { @Override public void mouseDown(final MouseEvent event) { if (event.button == 2) { paste(); } } }); addListener(SWT.KeyDown, new Listener() { public void handleEvent(final Event event) { // index 0 = keypad numeric mode, index 1 = keypad application mode // vt100 final byte[][] arrowUp = { { SWT.ESC, '[', 'A' }, { SWT.ESC, 'O', 'A' } }; final byte[][] arrowDown = { { SWT.ESC, '[', 'B' }, { SWT.ESC, 'O', 'B' } }; final byte[][] arrowRight = { { SWT.ESC, '[', 'C' }, { SWT.ESC, 'O', 'C' } }; final byte[][] arrowLeft = { { SWT.ESC, '[', 'D' }, { SWT.ESC, 'O', 'D' } }; // xterm final byte[] backspace = { SWT.DEL }; final byte[] home = { SWT.ESC, '[', 'H' }; final byte[] end = { SWT.ESC, '[', 'F' }; final byte[] insert = { SWT.ESC, '[', '2', '~' }; final byte[] delete = { SWT.ESC, '[', '3', '~' }; final byte[] pageUp = { SWT.ESC, '[', '5', '~' }; final byte[] pageDown = { SWT.ESC, '[', '6', '~' }; final byte[] F1 = { SWT.ESC, 'O', 'P' }; final byte[] F2 = { SWT.ESC, 'O', 'Q' }; final byte[] F3 = { SWT.ESC, 'O', 'R' }; final byte[] F4 = { SWT.ESC, 'O', 'S' }; final byte[] F5 = { SWT.ESC, '[', '1', '5', '~' }; final byte[] F6 = { SWT.ESC, '[', '1', '7', '~' }; final byte[] F7 = { SWT.ESC, '[', '1', '8', '~' }; final byte[] F8 = { SWT.ESC, '[', '1', '9', '~' }; final byte[] F9 = { SWT.ESC, '[', '2', '0', '~' }; final byte[] F10 = { SWT.ESC, '[', '2', '1', '~' }; final byte[] F11 = { SWT.ESC, '[', '2', '3', '~' }; final byte[] F12 = { SWT.ESC, '[', '2', '4', '~' }; final byte[] F13 = { SWT.ESC, '[', '2', '5', '~' }; final byte[] F14 = { SWT.ESC, '[', '2', '6', '~' }; final byte[] F15 = { SWT.ESC, '[', '2', '8', '~' }; final int[] unhandledKeycodes = { // sorted by keycode SWT.ALT, SWT.SHIFT, SWT.CTRL, SWT.CAPS_LOCK, SWT.NUM_LOCK, SWT.SCROLL_LOCK, SWT.PAUSE }; try { OutputStream out = Terminal.this.output; if (out != null) { if (event.keyCode == SWT.ARROW_UP) { out.write(arrowUp[Terminal.this.keyPadMode.getIndex()]); } else if (event.keyCode == SWT.ARROW_DOWN) { out.write(arrowDown[Terminal.this.keyPadMode.getIndex()]); } else if (event.keyCode == SWT.ARROW_RIGHT) { out.write(arrowRight[Terminal.this.keyPadMode.getIndex()]); } else if (event.keyCode == SWT.ARROW_LEFT) { out.write(arrowLeft[Terminal.this.keyPadMode.getIndex()]); } else if (event.keyCode == SWT.HOME) out.write(home); else if (event.keyCode == SWT.END) out.write(end); else if (event.keyCode == SWT.INSERT) out.write(insert); else if (event.character == SWT.DEL) out.write(delete); else if (event.character == SWT.BS) out.write(backspace); else if (event.keyCode == SWT.PAGE_UP) { if ((event.stateMask & SWT.SHIFT) != 0) scrollHistoryPageUp(); else out.write(pageUp); } else if (event.keyCode == SWT.PAGE_DOWN) { if ((event.stateMask & SWT.SHIFT) != 0) scrollHistoryPageDown(); else out.write(pageDown); } else if (event.keyCode == SWT.F1) out.write(F1); else if (event.keyCode == SWT.F2) out.write(F2); else if (event.keyCode == SWT.F3) out.write(F3); else if (event.keyCode == SWT.F4) out.write(F4); else if (event.keyCode == SWT.F5) out.write(F5); else if (event.keyCode == SWT.F6) out.write(F6); else if (event.keyCode == SWT.F7) out.write(F7); else if (event.keyCode == SWT.F8) out.write(F8); else if (event.keyCode == SWT.F9) out.write(F9); else if (event.keyCode == SWT.F10) out.write(F10); else if (event.keyCode == SWT.F11) out.write(F11); else if (event.keyCode == SWT.F12) out.write(F12); else if (event.keyCode == SWT.F13) out.write(F13); else if (event.keyCode == SWT.F14) out.write(F14); else if (event.keyCode == SWT.F15) out.write(F15); else if (event.character == SWT.CR) { out.write(SWT.CR); if (Terminal.this.newLineMode) out.write(SWT.LF); } else if (event.character != 0) out.write(event.character); else if (Arrays.binarySearch(unhandledKeycodes, event.keyCode) < 0) { Activator.logMessage(IStatus.WARNING, Messages.formatMessage("Terminal.unhandledKeycode", //$NON-NLS-1$ Integer.valueOf(event.keyCode), Integer.valueOf(event.character))); } out.flush(); } } catch (IOException ioException) { Activator.logException(ioException); } } }); reset(); } void scrollHistoryPageUp() { int oldPos = getScrollbarPosLine(); int newPos = oldPos - this.numLines; if (newPos < 0) newPos = 0; if (newPos != oldPos) { getVerticalBar().setSelection(newPos); triggerRedraw(); } } void scrollHistoryPageDown() { int oldPos = getScrollbarPosLine(); int newPos = oldPos + this.numLines; if (newPos > this.historySize) newPos = this.historySize; if (newPos != oldPos) { getVerticalBar().setSelection(newPos); triggerRedraw(); } } private void initMenu() { ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages(); Menu popUpMenu = new Menu(getShell(), SWT.POP_UP); final MenuItem copyItem = new MenuItem(popUpMenu, SWT.PUSH); copyItem.setText(Messages.getString("Terminal.copy")); //$NON-NLS-1$ // TODO disable menu if selection is empty ImageDescriptor copyImage = sharedImages.getImageDescriptor(ISharedImages.IMG_TOOL_COPY); copyItem.setImage(copyImage.createImage()); copyItem.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent event) { copy(); } }); MenuItem pasteItem = new MenuItem(popUpMenu, SWT.PUSH); pasteItem.setText(Messages.getString("Terminal.paste")); //$NON-NLS-1$ ImageDescriptor pasteImage = sharedImages.getImageDescriptor(ISharedImages.IMG_TOOL_PASTE); pasteItem.setImage(pasteImage.createImage()); pasteItem.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent event) { paste(); } }); popUpMenu.addMenuListener(new MenuAdapter() { @Override public void menuShown(final MenuEvent event) { copyItem.setEnabled(!getSelection().isEmpty()); } }); setMenu(popUpMenu); } /** * Triggers copy from the terminal into the clipboard. */ void copy() { String text = ((ITextSelection) getSelection()).getText(); TextTransfer plainTextTransfer = TextTransfer.getInstance(); this.clipboard.setContents(new String[] { text }, new Transfer[] { plainTextTransfer }); } private Object getClipboardContent(final int clipboardType) { TextTransfer plainTextTransfer = TextTransfer.getInstance(); return this.clipboard.getContents(plainTextTransfer, clipboardType); } /** * Triggers paste from the clipboard into the terminal. */ public void paste() { checkWidget(); String text = (String) getClipboardContent(DND.CLIPBOARD); if (text != null && text.length() > 0) { for (int i = 0; i < text.length(); i++) { Event event = new Event(); event.character = text.charAt(i); notifyListeners(SWT.KeyDown, event); } } } /** * Registers a terminal listener for tracking terminal size and title changes. * @param termListener the listener. */ public void addTerminalListener(final ITerminalListener termListener) { this.terminalListeners.add(termListener); } /** * Removes a terminal listener for tracking terminal size and title changes. * @param termListener the listener. */ public void removeTerminalListener(final ITerminalListener termListener) { this.terminalListeners.remove(termListener); } private void bell() { getDisplay().syncExec(new Runnable() { public void run() { getDisplay().beep(); } }); // TODO visible bell? } private void carriageReturn() { if (this.cursor.col != 0) { int oldCol = this.cursor.col; this.cursor.col = 0; triggerRedraw(oldCol, this.cursor.line, 1, 1); triggerRedraw(0, this.cursor.line, 1, 1); } } private void backspace() { if (this.cursor.col > 0) { this.cursor.col--; triggerRedraw(this.cursor.col + 1, this.cursor.line, 1, 1); triggerRedraw(this.cursor.col, this.cursor.line, 1, 1); } } private void startReaderThread() { Thread thread = new Thread(new Runnable() { public void run() { readerMainLoop(); } }, "Terminal"); //$NON-NLS-1$ thread.start(); } void readerMainLoop() { try { this.running = true; while (this.running) { int ch = read(); if (ch == BEL) bell(); else if (ch == SWT.CR) carriageReturn(); else if (ch == SWT.LF) nextLine(false); else if (ch == SI) this.cursor.charSet = this.charSetG[0]; else if (ch == SO) this.cursor.charSet = this.charSetG[1]; else if (ch == SWT.TAB) tabulator(); else if (ch == FF) eraseScreen(); else if (ch == SWT.BS) backspace(); else if (ch == SWT.ESC) parseEscSequence(); else if (ch == -1) continue; else if (ch < ' ') { Activator.logMessage(IStatus.WARNING, Messages.formatMessage("Terminal.unhandledCtrlChar", //$NON-NLS-1$ Character.valueOf((char) ch), Integer.valueOf(ch))); } else { this.input.unread(ch); readText(); } } } catch (IOException ioException) { Activator.logException(ioException); } for (ITerminalListener listener : this.terminalListeners) { listener.terminated(); } } private void tabulator() { int oldCol = this.cursor.col; Integer cursorCol = Integer.valueOf(this.cursor.col + 1); Integer[] tabArray = this.tabulatorPositons.toArray(new Integer[this.tabulatorPositons.size()]); int pos = Arrays.binarySearch(tabArray, cursorCol); if (pos < 0) { pos = (-pos) - 1; // get insertion point } if (tabArray.length > pos) { int nextTabStop = tabArray[pos].intValue(); if (nextTabStop < numColsInLine(this.cursor.line)) { this.cursor.col = nextTabStop; } else { Activator.logMessage(IStatus.WARNING, Messages.getString("Terminal.tabStopOutsideDisplay")); //$NON-NLS-1$ } } else { // next tabstop (tab width 8) this.cursor.col = ((this.cursor.col + TAB_WIDTH + 1) / TAB_WIDTH) * TAB_WIDTH; if (this.cursor.col >= numColsInLine(this.cursor.line)) { this.cursor.col = numColsInLine(this.cursor.line) - 1; } } triggerRedraw(oldCol, this.cursor.line, 1, 1); triggerRedraw(this.cursor.col, this.cursor.line, 1, 1); } private void readText() throws IOException { int ch = read(); int line = this.cursor.line; int startCol = this.cursor.col; int colsToUpdate = 0; while (ch >= ' ') { if (this.cursor.col >= numColsInLine(this.cursor.line)) { if (this.wraparound) { this.cursor.col = 0; triggerRedraw(startCol, line, colsToUpdate, 1); startCol = 0; colsToUpdate = 0; if (this.cursor.line < this.numLines - 1) { this.cursor.line++; line = this.cursor.line; } else { // triggerRedraw( numColsInLine( this.cursor.line ) - 1, this.cursor.line, 1, 1 ); scrollUp(); } } else { this.cursor.col = numColsInLine(this.cursor.line) - 1; } } Char scrCh = this.screenBuffer[this.cursor.line + this.historySize][this.cursor.col]; scrCh.ch = (char) ch; scrCh.setToCursorFormat(this.cursor); this.cursor.col++; colsToUpdate++; if (this.input.available() == 0) { if (colsToUpdate != 0) { triggerRedraw(startCol, line, colsToUpdate, 1); triggerRedraw(this.cursor.col, this.cursor.line, 1, 1); startCol += colsToUpdate; colsToUpdate = 0; } } ch = read(); } triggerRedraw(startCol, line, colsToUpdate, 1); triggerRedraw(this.cursor.col, this.cursor.line, 1, 1); this.input.unread(ch); } private int numColsInLine(final int line) { int numColsInLine = this.numCols; if (this.lineWidthMode[line] == LineWidthMode.DOUBLE) { numColsInLine /= 2; } return numColsInLine; } void triggerRedraw(final int col, final int line, final int cols, final int lines) { int fontWidthMult = 1; for (int i = line; i < line + lines && i + Terminal.this.historySize < this.lineWidthMode.length; i++) { if (this.lineWidthMode[i + Terminal.this.historySize] == LineWidthMode.DOUBLE) { fontWidthMult = 2; break; } } final int finalFontWidthMult = fontWidthMult; getDisplay().syncExec(new Runnable() { public void run() { if (!isDisposed()) { redraw(col * getFontWidth() * finalFontWidthMult, (line + Terminal.this.historySize - getScrollbarPosLine()) * getFontHeigth(), cols * getFontWidth() * finalFontWidthMult, lines * getFontHeigth(), false); } } }); } void triggerRedraw() { getDisplay().syncExec(new Runnable() { public void run() { redraw(); } }); } private void parseEscSequence() throws IOException { int ch = read(); List<Integer> params = new LinkedList<Integer>(); switch (ch) { case '[': do { params.add(Integer.valueOf(readNumber())); ch = read(); } while (ch == ';'); executeControlSequence(listToArray(params), ch); break; case ']': executeOperatingSystemCommand(); break; case '#': executeDecCommand(); break; case '(': // SCS - Select Character Set (G0) selectCharacterSet(0); break; case ')': // SCS - Select Character Set (G1) selectCharacterSet(1); break; case '*': // SCS - Select Character Set (G2) selectCharacterSet(2); break; case '+': // SCS - Select Character Set (G3) selectCharacterSet(3); break; case 'Z': // DECID - Identify Terminal deviceAttributes(new int[0]); break; case '=': // DECKPAM - Keypad Application Mode this.keyPadMode = KeyPadMode.APPLICATION; break; case '>': // DECKPNM - Keypad Numeric Mode this.keyPadMode = KeyPadMode.NUMERIC; break; case '7': // DECSC - Save Cursor saveCursor(); break; case '8': // DECRC - Restore Cursor restoreCursor(); break; case 'H': // HTS - Horizontal Tabulation Set horizontalTabulationSet(); break; case 'D': // IND - Index index(); break; case 'E': // NEL - Next Line nextLine(true); break; case 'M': // RI - Reverse Index reverseIndex(); break; case 'c': // RIS - Reset To Initial State; reset(); break; // ------------------ VT52 commands ------------------ // TODO implement a VT52 mode /* case 'A': // Cursor Up Activator.logMessage( IStatus.WARNING, "Cursor Up not implemented" ); break; case 'B': // Cursor Down Activator.logMessage( IStatus.WARNING, "Cursor Down not implemented" ); break; case 'C': // Cursor Right Activator.logMessage( IStatus.WARNING, "Cursor Right not implemented" ); break; //case 'D': // Cursor Left //Activator.logMessage( IStatus.WARNING, "Cursor Left not implemented" ); //break; case 'F': // Enter Graphics Mode Activator.logMessage( IStatus.WARNING, "Enter Graphics Mode not implemented" ); break; case 'G': // Exit Graphics Mode Activator.logMessage( IStatus.WARNING, "Exit Graphics Mode not implemented" ); break; //case 'H': // Cursor to Home //Activator.logMessage( IStatus.WARNING, "Cursor to Home not implemented" ); //break; case 'I': // Reverse Line Feed Activator.logMessage( IStatus.WARNING, "Reverse Line Feed not implemented" ); break; case 'J': // Erase to End of Screen Activator.logMessage( IStatus.WARNING, "Erase to End of Screen not implemented" ); break; case 'K': // Erase to End of Line Activator.logMessage( IStatus.WARNING, "Erase to End of Line not implemented" ); break; case 'Y': // Direct Cursor Address Activator.logMessage( IStatus.WARNING, "Direct Cursor Address not implemented" ); break; case '<': // Enter ANSI Mode Activator.logMessage( IStatus.WARNING, "Enter ANSI Mode not implemented" ); break;*/ // ---------------- end VT52 commands ---------------- default: Activator.logMessage(IStatus.WARNING, Messages.formatMessage("Terminal.unknownEscSeq", //$NON-NLS-1$ Character.valueOf((char) ch))); break; } } private void horizontalTabulationSet() { Integer cursorCol = Integer.valueOf(this.cursor.col); if (!this.tabulatorPositons.contains(cursorCol)) { this.tabulatorPositons.add(cursorCol); } } private void selectCharacterSet(final int charSetNr) throws IOException { int ch = read(); CharSet charSet = CharSet.USASCII; switch (ch) { case 'A': Activator.logMessage(IStatus.WARNING, Messages.getString("Terminal.ukCharSetNotSupported")); //$NON-NLS-1$ break; case 'B': charSet = CharSet.USASCII; break; case '0': charSet = CharSet.SPECIAL; break; case '1': Activator.logMessage(IStatus.WARNING, Messages.getString("Terminal.alternateStdCharSetNotSupported")); //$NON-NLS-1$ break; case '2': Activator.logMessage(IStatus.WARNING, Messages.getString("Terminal.alternateSpecialCharSetNotSupported")); //$NON-NLS-1$ break; default: Activator.logMessage(IStatus.WARNING, Messages.getString("Terminal.unknownCharSet")); //$NON-NLS-1$ break; } this.charSetG[charSetNr] = charSet; } private void nextLine(final boolean returnToColZero) { int oldCol = this.cursor.col; if (returnToColZero) this.cursor.col = 0; triggerRedraw(oldCol, this.cursor.line, 1, 1); index(); } private void index() { if (this.cursor.line < this.bottomMargin) { int oldLine = this.cursor.line; this.cursor.line++; triggerRedraw(this.cursor.col, oldLine, 1, 1); triggerRedraw(this.cursor.col, this.cursor.line, 1, 1); } else scrollUp(); } private void reverseIndex() { if (this.cursor.line > this.topMargin) { int oldLine = this.cursor.line; this.cursor.line--; triggerRedraw(this.cursor.col, oldLine, 1, 1); triggerRedraw(this.cursor.col, this.cursor.line, 1, 1); } else scrollDown(); } private void saveCursor() { this.savedCursor = new Cursor(this.cursor); } private void restoreCursor() { int oldCol = this.cursor.col; int oldLine = this.cursor.line; this.cursor = this.savedCursor; triggerRedraw(oldCol, oldLine, 1, 1); triggerRedraw(this.cursor.col, this.cursor.line, 1, 1); } private static int[] listToArray(final List<Integer> list) { int[] array = new int[list.size()]; for (int i = 0; i < list.size(); i++) { array[i] = list.get(i).intValue(); } return array; } private void executeDecCommand() throws IOException { int ch = read(); switch (ch) { case '3': // DECDHL - Double Height Line (Top Half) doubleHeightLine(LineHeightMode.DOUBLE_TOP); break; case '4': // DECDHL - Double Height Line (Bottom Half) doubleHeightLine(LineHeightMode.DOUBLE_BOTTOM); break; case '5': // DECSWL - Single-width Line doubleWidthLine(LineWidthMode.NORMAL); break; case '6': // DECDWL - Double-Width Line doubleWidthLine(LineWidthMode.DOUBLE); break; case '8': // DECALN - Screen Alignment Display screenAlignmentDisplay(); break; default: Activator.logMessage(IStatus.WARNING, Messages.formatMessage("Terminal.unknownDecCommand", //$NON-NLS-1$ Character.valueOf((char) ch))); } } private void doubleWidthLine(final LineWidthMode mode) { this.lineHeightMode[this.cursor.line + this.historySize] = LineHeightMode.NORMAL; this.lineWidthMode[this.cursor.line + this.historySize] = mode; if (mode == LineWidthMode.DOUBLE && this.cursor.col > this.numCols / 2) { this.cursor.col = this.numCols / 2; } triggerRedraw(0, this.cursor.line, this.numCols, 1); } private void doubleHeightLine(final LineHeightMode mode) { if (mode == LineHeightMode.DOUBLE_TOP || mode == LineHeightMode.DOUBLE_BOTTOM) { this.lineWidthMode[this.cursor.line + this.historySize] = LineWidthMode.DOUBLE; } this.lineHeightMode[this.cursor.line + this.historySize] = mode; triggerRedraw(0, this.cursor.line, this.numCols, 1); } private void screenAlignmentDisplay() { for (int i = this.historySize; i < this.numLines + this.historySize; i++) { this.lineHeightMode[i] = LineHeightMode.NORMAL; this.lineWidthMode[i] = LineWidthMode.NORMAL; } for (int i = this.historySize; i < this.numLines + this.historySize; i++) { for (Char character : this.screenBuffer[i]) { character.ch = 'E'; } } triggerRedraw(); } private void executeOperatingSystemCommand() throws IOException { int commandNr = readNumber(); int colorNr; Color color; if (read() != ';') { Activator.logMessage(IStatus.WARNING, Messages.getString("Terminal.invalidOsCommand")); //$NON-NLS-1$ } else switch (commandNr) { case 0: // Set Window Title this.windowTitle = readString(); for (ITerminalListener listener : this.terminalListeners) { listener.windowTitleChanged(this.windowTitle); } break; case 4: colorNr = readNumber(); if (read() != ';') { Activator.logMessage(IStatus.WARNING, Messages.getString("Terminal.invalidOsCommand")); //$NON-NLS-1$ } else { color = parseColorString(readString()); this.systemColors[colorNr] = color; } break; default: Activator.logMessage(IStatus.WARNING, Messages.formatMessage("Terminal.unknownOsCommand", //$NON-NLS-1$ Integer.valueOf(commandNr))); break; } } private Color parseColorString(final String colorString) { Color color = null; if (colorString.startsWith("rgb:")) { //$NON-NLS-1$ StringTokenizer tokenizer = new StringTokenizer(colorString.substring(4), "/"); //$NON-NLS-1$ if (tokenizer.countTokens() == 3) { int r = Integer.parseInt(tokenizer.nextToken(), 16); int g = Integer.parseInt(tokenizer.nextToken(), 16); int b = Integer.parseInt(tokenizer.nextToken(), 16); color = new Color(getDisplay(), r, g, b); } } if (color == null) { color = this.defaultFgColor; Activator.logMessage(IStatus.WARNING, Messages.getString("Terminal.unknownColorString")); //$NON-NLS-1$ } return color; } private String readString( /*final char[] terminatingChars*/ ) throws IOException { final char[] terminatingChars = new char[] { BEL, SWT.ESC }; // sorted array StringBuilder buffer = new StringBuilder(); char ch = (char) read(); while (Arrays.binarySearch(terminatingChars, ch) < 0) { buffer.append(ch); ch = (char) read(); } if (ch == SWT.ESC) { if (read() != '\\') { Activator.logMessage(IStatus.WARNING, Messages.getString("Terminal.stringTerminatorExpected")); //$NON-NLS-1$ } } return buffer.toString(); } private void executeControlSequence(final int[] params, final int command) throws IOException { switch (command) { case 'D': // CUB - Cursor Backward cursorBackward(params); break; case 'B': // CUD - Cursor Down cursorDown(params); break; case 'C': // CUF - Cursor Forward cursorForward(params); break; case 'H': // CUP - Cursor Position case 'f': // HVP - Horizontal and Vertical Position cursorPosition(params); break; case 'A': // CUU - Cursor Up cursorUp(params); break; case 'c': // DA - Device Attributes deviceAttributes(params); break; case 'q': // DECLL - Load LEDS loadLeds(params); break; case 'x': // DECREPTPARM - Report Terminal Parameters / DECREQTPARM - Request Terminal Parameters requestTerminalParameters(params); break; case 'r': // DECSTBM - Set Top and Bottom Margins setTopAndBottomMargins(params, false); break; case 'y': // DECTST - Invoke Confidence Test Activator.logMessage(IStatus.WARNING, Messages.getString("Terminal.invokeConfidenceTestNotImplemented")); //$NON-NLS-1$ break; case 'n': // DSR - Device Status Report deviceStatusReport(params); break; case 'J': // ED - Erase In Display eraseInDisplay(params); break; case 'K': // EL - Erase In Line eraseInLine(params); break; case 'l': // RM - Reset Mode resetMode(params); break; case 'm': // SGR - Select Graphic Rendition selectGraphicRendition(params); break; case 'h': // SM - Set Mode setMode(params); break; case 'g': // TBC - Tabulation Clear tabulationClear(params); break; // Xterm (and some VTs >100) case 'd': // VPA - Vertical Line Position Absolute verticalLinePositionAbsolute(params); break; case 'X': // ECH - Erase Character eraseCharacter(params); break; case 'P': // DCH - Delete Character deleteCharacter(params); break; case 'G': // CHA - Cursor Horizontal Absolute cursorHorizontalAbsolute(params); break; default: Activator.logMessage(IStatus.WARNING, Messages.formatMessage("Terminal.unknownCtrlSeq", //$NON-NLS-1$ Character.valueOf((char) command))); break; } } private void deviceStatusReport(final int[] params) throws IOException { final byte[] response = { SWT.ESC, '[', '0', 'n' }; // terminal ok int val = 0; if (params.length != 0) val = params[0]; switch (val) { case 5: this.output.write(response); this.output.flush(); break; case 6: reportCursorPosition(); break; default: Activator.logMessage(IStatus.WARNING, Messages.getString("Terminal.invalidDeviceStatusReportParam")); //$NON-NLS-1$ break; } } private void reportCursorPosition() throws IOException { this.output.write(SWT.ESC); this.output.write('['); this.output.write(Integer.toString(this.cursor.line + 1).getBytes("ASCII")); //$NON-NLS-1$ this.output.write(';'); this.output.write(Integer.toString(this.cursor.col + 1).getBytes("ASCII")); //$NON-NLS-1$ this.output.write('R'); this.output.flush(); } private void requestTerminalParameters(final int[] params) throws IOException { // Parity none, 8 bits, xmitspeed 38400, recvspeed 38400, // clock multiplier 1, STP option flags 0 byte[] response = { SWT.ESC, '[', '2', ';', '1', ';', '1', ';', '1', '2', '8', ';', '1', '2', '8', ';', '1', ';', '0', 'x' }; if (params.length > 0 && params[0] != 0 && params[0] != 1) { Activator.logMessage(IStatus.WARNING, Messages.getString("Terminal.unknownTermParamReq")); //$NON-NLS-1$ } else { if (params[0] == 1) response[2] = '3'; this.output.write(response); this.output.flush(); } } private void eraseCharacter(final int[] params) { int val = 1; int colsInLine = numColsInLine(this.cursor.line); if (params.length != 0) val = params[0]; if (val == 0) val = 1; for (int i = this.cursor.col; (i < (this.cursor.col + val)) && (i < colsInLine); i++) { this.screenBuffer[this.cursor.line + this.historySize][i].erase(this.defaultFgColor, this.defaultBgColor); this.screenBuffer[this.cursor.line + this.historySize][i].bgColor = this.cursor.bgColor; } triggerRedraw(this.cursor.col, this.cursor.line, val, 1); } private void deleteCharacter(final int[] params) { int val = 1; int colsInLine = numColsInLine(this.cursor.line); if (params.length != 0) val = params[0]; if (val == 0) val = 1; if (val > (colsInLine - this.cursor.col)) val = colsInLine - this.cursor.col; int colsToMove = colsInLine - this.cursor.col - val; for (int i = this.cursor.col; i < (this.cursor.col + colsToMove); i++) { this.screenBuffer[this.cursor.line + this.historySize][i] = this.screenBuffer[this.cursor.line + this.historySize][i + val]; } for (int i = (this.cursor.col + colsToMove); i < colsInLine; i++) { this.screenBuffer[this.cursor.line + this.historySize][i] = new Char(this.defaultFgColor, this.defaultBgColor); } triggerRedraw(this.cursor.col, this.cursor.line, colsInLine - this.cursor.col, 1); } private void setMode(final int[] params) { for (int param : params) { switch (param) { case 1: // Application Cursor Key Mode this.cursorKeyMode = CursorKeyMode.APPLICATION; break; case 3: // 132 Column Mode this.cursor.reset(this.defaultFgColor, this.defaultBgColor); this.tabulatorPositons.clear(); eraseScreen(); getDisplay().syncExec(new Runnable() { public void run() { setSize(132 * getFontWidth() + getVerticalBar().getSize().x, 24 * getFontHeigth()); } }); break; case 4: // Smooth Scrolling Mode // TODO implement smooth scrolling this.scrollMode = ScrollMode.SMOOTH; break; case 5: // Reverse Screen Mode this.reverseScreenMode = true; triggerRedraw(); break; case 6: // Relative Origin Mode setOriginMode(OriginMode.RELATIVE); break; case 7: // Wraparound On this.wraparound = true; break; case 8: // Auto repeat On // XXX check if auto repeat keyboard events in SWT are possible? Activator.logMessage(IStatus.WARNING, Messages.getString("Terminal.autoRepeatOnNotSupported")); //$NON-NLS-1$ break; case 9: // Interlace On Activator.logMessage(IStatus.WARNING, Messages.getString("Terminal.interlaceOnNotSupported")); //$NON-NLS-1$ break; case 20: // New Line Mode this.newLineMode = true; break; case 45: // Reverse Wraparound On this.reverseWraparound = true; break; default: Activator.logMessage(IStatus.WARNING, Messages.formatMessage("Terminal.unknownSetModeParam", //$NON-NLS-1$ Integer.valueOf(param))); break; } } } private void resetMode(final int[] params) { for (int param : params) { switch (param) { case 1: // Cursor Cursor Key Mode this.cursorKeyMode = CursorKeyMode.CURSOR; break; case 2: // XXX VT52 Mode not supported Activator.logMessage(IStatus.WARNING, Messages.getString("Terminal.vt52ModeNotSupported")); //$NON-NLS-1$ break; case 3: // 80 Column Mode getDisplay().syncExec(new Runnable() { public void run() { setSize(80 * getFontWidth() + getVerticalBar().getSize().x, 24 * getFontHeigth()); } }); this.cursor.reset(this.defaultFgColor, this.defaultBgColor); this.tabulatorPositons.clear(); eraseScreen(); break; case 4: this.scrollMode = ScrollMode.JUMP; break; case 5: // Normal Screen Mode this.reverseScreenMode = false; triggerRedraw(); break; case 6: // Absolute Origin Mode setOriginMode(OriginMode.ABSOLUTE); break; case 7: // Wraparound Off this.wraparound = false; break; case 8: // Auto repeat Off // XXX check if auto repeat keyboard events in SWT are possible? Activator.logMessage(IStatus.WARNING, Messages.getString("Terminal.autoRepeatOffNotSupported")); //$NON-NLS-1$ break; case 9: // Interlace Off Activator.logMessage(IStatus.WARNING, Messages.getString("Terminal.interlaceOffNotSupported")); //$NON-NLS-1$ break; case 20: // Line Feed Mode this.newLineMode = false; break; case 45: // Reverse Wraparound Off this.reverseWraparound = false; break; default: Activator.logMessage(IStatus.WARNING, Messages.formatMessage("Terminal.unknownResetModeParam", //$NON-NLS-1$ Integer.valueOf(param))); break; } } } private void tabulationClear(final int[] params) { Integer cursorCol = Integer.valueOf(this.cursor.col); int val = 0; if (params.length > 0) val = params[0]; switch (val) { case 0: this.tabulatorPositons.remove(cursorCol); break; case 3: this.tabulatorPositons.clear(); break; default: Activator.logMessage(IStatus.WARNING, Messages.formatMessage("Terminal.unknownTabClearParam", //$NON-NLS-1$ Integer.valueOf(val))); break; } } private void reset() { this.cursor.reset(this.defaultFgColor, this.defaultBgColor); this.tabulatorPositons.clear(); this.wraparound = true; // <-- XXX is this really the right value for VT100? this.reverseWraparound = false; this.reverseScreenMode = false; this.newLineMode = false; this.keyPadMode = KeyPadMode.NUMERIC; for (int i = 0; i < this.charSetG.length; i++) this.charSetG[i] = CharSet.USASCII; this.topMargin = 0; this.bottomMargin = this.numLines - 1; for (int i = 0; i < this.leds.length; i++) this.leds[i] = false; setOriginMode(OriginMode.ABSOLUTE); eraseScreen(); } private void setOriginMode(final OriginMode mode) { int oldLine = this.cursor.line; int oldCol = this.cursor.col; if (mode == OriginMode.ABSOLUTE) { this.cursor.col = 0; this.cursor.line = 0; } else { this.cursor.col = 0; this.cursor.line = this.topMargin; } this.originMode = mode; triggerRedraw(oldCol, oldLine, 1, 1); triggerRedraw(this.cursor.col, this.cursor.line, 1, 1); } void scrollUpCopy() { Char[] tmp; if (this.topMargin == 0) { tmp = scrollHistory(); } else { tmp = this.screenBuffer[this.topMargin + this.historySize]; } for (int i = this.topMargin; i < this.bottomMargin; i++) { this.lineHeightMode[i + this.historySize] = this.lineHeightMode[i + this.historySize + 1]; this.lineWidthMode[i + this.historySize] = this.lineWidthMode[i + this.historySize + 1]; this.screenBuffer[i + this.historySize] = this.screenBuffer[i + this.historySize + 1]; } this.screenBuffer[this.bottomMargin + this.historySize] = tmp; eraseLine(this.bottomMargin); this.lineWidthMode[this.bottomMargin + this.historySize] = LineWidthMode.NORMAL; this.lineHeightMode[this.bottomMargin + this.historySize] = LineHeightMode.NORMAL; } private void scrollUp() { getDisplay().syncExec(new Runnable() { public void run() { scrollUpCopy(); Terminal.this.terminalPainter.scrollUp(Terminal.this.topMargin, Terminal.this.bottomMargin); } }); triggerRedraw(0, this.bottomMargin, this.numCols, 1); // repaint old cursor position triggerRedraw(this.cursor.col, this.cursor.line - 1, 1, 1); } private Char[] scrollHistory() { Char[] tmp = this.screenBuffer[0]; for (int i = 0; i < this.historySize; i++) { this.lineHeightMode[i] = this.lineHeightMode[i + 1]; this.lineWidthMode[i] = this.lineWidthMode[i + 1]; this.screenBuffer[i] = this.screenBuffer[i + 1]; } return tmp; } void scrollDownCopy() { Char[] tmp = this.screenBuffer[this.bottomMargin + this.historySize]; for (int i = this.bottomMargin; i > this.topMargin; i--) { this.lineHeightMode[i + this.historySize] = this.lineHeightMode[i + this.historySize - 1]; this.lineWidthMode[i + this.historySize] = this.lineWidthMode[i + this.historySize - 1]; this.screenBuffer[i + this.historySize] = this.screenBuffer[i + this.historySize - 1]; } this.screenBuffer[this.topMargin + this.historySize] = tmp; eraseLine(this.topMargin); this.lineWidthMode[this.topMargin + this.historySize] = LineWidthMode.NORMAL; this.lineHeightMode[this.topMargin + this.historySize] = LineHeightMode.NORMAL; } private void scrollDown() { getDisplay().syncExec(new Runnable() { public void run() { scrollDownCopy(); Terminal.this.terminalPainter.scrollDown(Terminal.this.topMargin, Terminal.this.bottomMargin); } }); triggerRedraw(0, this.topMargin, this.numCols, 1); // repaint old cursor position triggerRedraw(this.cursor.col, this.cursor.line + 1, 1, 1); } private void setTopAndBottomMargins(final int[] params, final boolean skipCursorReset) { int top = 0; int bottom = this.numLines; if (params.length > 0) top = params[0]; if (params.length > 1) bottom = params[1]; if (top > 0) top--; if (bottom > 0) bottom--; if (top >= bottom) { Activator.logMessage(IStatus.WARNING, Messages.getString("Terminal.topLargerEqualBottom")); //$NON-NLS-1$ } else if (bottom >= this.numLines) { Activator.logMessage(IStatus.WARNING, Messages.formatMessage("Terminal.bottomOutOfScreen", //$NON-NLS-1$ Integer.valueOf(bottom), Integer.valueOf(this.numLines))); } else { this.topMargin = top; this.bottomMargin = bottom; if (!skipCursorReset) cursorPosition(new int[0]); } } private void selectGraphicRendition(final int[] params) { int val = 0; int idx = 0; do { if (params.length > idx) val = params[idx]; if (val >= 30 && val <= 37) { // foreground colors // TODO change color when bold is changed if (this.cursor.bold) this.cursor.fgColor = this.systemColors[val - 30 + 8]; else this.cursor.fgColor = this.systemColors[val - 30]; } else if (val >= 40 && val <= 47) { // background colors this.cursor.bgColor = this.systemColors[val - 40]; } else switch (val) { // VT100 case 0: this.cursor.bold = false; this.cursor.underscore = false; this.cursor.blink = false; this.cursor.negative = false; this.cursor.italics = false; this.cursor.strikethrough = false; this.cursor.fgColor = this.defaultFgColor; this.cursor.bgColor = this.defaultBgColor; break; case 1: this.cursor.bold = true; break; case 4: this.cursor.underscore = true; break; case 5: this.cursor.blink = true; break; case 7: this.cursor.negative = true; break; // ANSI case 3: this.cursor.italics = true; break; case 9: this.cursor.strikethrough = true; break; case 22: this.cursor.bold = false; break; case 23: this.cursor.italics = false; break; case 24: this.cursor.underscore = false; break; case 27: this.cursor.negative = false; break; case 29: this.cursor.strikethrough = false; break; case 38: if (params.length >= idx + 3 && params[idx + 1] == 5 && params[idx + 2] < 256) { this.cursor.fgColor = this.systemColors[params[idx + 2]]; idx += 2; } else { Activator.logMessage(IStatus.WARNING, Messages.getString("Terminal.invalidColorParams")); //$NON-NLS-1$ } break; case 39: this.cursor.fgColor = this.defaultFgColor; break; case 48: if (params.length >= idx + 3 && params[idx + 1] == 5 && params[idx + 2] < 256) { this.cursor.bgColor = this.systemColors[params[idx + 2]]; idx += 2; } else { Activator.logMessage(IStatus.WARNING, Messages.getString("Terminal.invalidColorParams")); //$NON-NLS-1$ } break; case 49: this.cursor.bgColor = this.defaultBgColor; break; default: Activator.logMessage(IStatus.WARNING, Messages.formatMessage("Terminal.unknownGraphRendParam", //$NON-NLS-1$ Integer.valueOf(val))); break; } idx++; } while (idx < params.length); } private void eraseScreen() { final int[] eraseScreen = { 2 }; eraseInDisplay(eraseScreen); resetLineModes(); } private void resetLineModes() { for (int i = 0; i < this.lineHeightMode.length; i++) { this.lineHeightMode[i] = LineHeightMode.NORMAL; this.lineWidthMode[i] = LineWidthMode.NORMAL; } } private void eraseInDisplay(final int[] params) { int val = 0; if (params.length > 0) val = params[0]; switch (val) { case 0: // From cursor to end of screen eraseInLine(params); for (int i = this.cursor.line + 1; i < this.numLines; i++) { eraseLine(i); } triggerRedraw(0, this.cursor.line + 1, this.numCols, this.numLines - this.cursor.line - 1); break; case 1: // From beginning of screen to cursor eraseInLine(params); for (int i = 0; i < this.cursor.line; i++) { eraseLine(i); } triggerRedraw(0, 0, this.numCols, this.cursor.line); break; case 2: // Entire screen for (int i = 0; i < this.numLines; i++) { eraseLine(i); } triggerRedraw(); break; default: Activator.logMessage(IStatus.WARNING, Messages.formatMessage("Terminal.unknownEraseInDispParam", //$NON-NLS-1$ Integer.valueOf(val))); break; } } private void eraseInLine(final int[] params) { int val = 0; if (params.length > 0) val = params[0]; switch (val) { case 0: // From cursor to end of line for (int i = this.cursor.col; i < this.screenBuffer[this.cursor.line + this.historySize].length; i++) { this.screenBuffer[this.cursor.line + this.historySize][i].erase(this.defaultFgColor, this.defaultBgColor); } triggerRedraw(this.cursor.col, this.cursor.line, this.numCols - this.cursor.col, 1); break; case 1: // From beginning of line to cursor for (int i = 0; i <= this.cursor.col; i++) { this.screenBuffer[this.cursor.line + this.historySize][i].erase(this.defaultFgColor, this.defaultBgColor); } triggerRedraw(0, this.cursor.line, this.cursor.col + 1, 1); break; case 2: // Entire line containing cursor eraseLine(this.cursor.line); triggerRedraw(0, this.cursor.line, this.numCols, 1); break; default: Activator.logMessage(IStatus.WARNING, Messages.formatMessage("Terminal.unknownEraseInLineParam", //$NON-NLS-1$ Integer.valueOf(val))); break; } } private void eraseLine(final int lineNr) { for (Char character : this.screenBuffer[lineNr + this.historySize]) { character.erase(this.defaultFgColor, this.defaultBgColor); } } private void loadLeds(final int[] params) { int val = 0; if (params.length > 0) val = params[0]; if (val == 0) { for (int i = 0; i < this.leds.length; i++) this.leds[i] = false; } else if (val < this.leds.length) { this.leds[val - 1] = true; } else { Activator.logMessage(IStatus.WARNING, Messages.formatMessage("Terminal.invalidLoadLedsParam", //$NON-NLS-1$ Integer.valueOf(val))); } } private void cursorBackward(final int[] params) { int val = 1; int oldCol = this.cursor.col; int oldLine = this.cursor.line; if (params.length != 0) val = params[0]; if (val == 0) val = 1; if (this.reverseWraparound /*|| cursor.col == 0*/ ) { // XXX I'm not so sure if this is correct behavior while (this.cursor.col - val < 0) { if (this.cursor.line != 0) { val -= this.cursor.col + 1; this.cursor.line--; this.cursor.col = numColsInLine(this.cursor.line) - 1; } else { this.cursor.col = 0; val = 0; break; } } this.cursor.col -= val; } else { if (this.cursor.col - val < 0) this.cursor.col = 0; else this.cursor.col -= val; } triggerRedraw(oldCol, oldLine, 1, 1); triggerRedraw(this.cursor.col, this.cursor.line, 1, 1); } private void cursorDown(final int[] params) { int val = 1; int oldLine = this.cursor.line; if (params.length != 0) val = params[0]; if (val == 0) val = 1; if (this.cursor.line + val >= this.bottomMargin) this.cursor.line = this.bottomMargin - 1; else this.cursor.line += val; triggerRedraw(this.cursor.col, oldLine, 1, 1); triggerRedraw(this.cursor.col, this.cursor.line, 1, 1); } private void cursorForward(final int[] params) { int val = 1; int oldCol = this.cursor.col; if (params.length != 0) val = params[0]; if (val == 0) val = 1; if (this.cursor.col + val >= this.numCols) this.cursor.col = this.numCols - 1; else this.cursor.col += val; triggerRedraw(oldCol, this.cursor.line, 1, 1); triggerRedraw(this.cursor.col, this.cursor.line, 1, 1); } private void cursorUp(final int[] params) { int val = 1; int oldLine = this.cursor.line; if (params.length != 0) val = params[0]; if (val == 0) val = 1; if (this.cursor.line - val < this.topMargin) this.cursor.line = this.topMargin; else this.cursor.line -= val; triggerRedraw(this.cursor.col, oldLine, 1, 1); triggerRedraw(this.cursor.col, this.cursor.line, 1, 1); } private void verticalLinePositionAbsolute(final int[] params) { int val = 1; int oldLine = this.cursor.line; if (params.length != 0) val = params[0]; if (val == 0) val = 1; if (val >= this.numLines) val = this.numLines - 1; this.cursor.line = val; triggerRedraw(this.cursor.col, oldLine, 1, 1); triggerRedraw(this.cursor.col, this.cursor.line, 1, 1); } private void cursorHorizontalAbsolute(final int[] params) { int val = 1; int oldCol = this.cursor.col; if (params.length != 0) val = params[0]; if (val == 0) val = 1; if (val >= this.numCols) val = this.numCols - 1; this.cursor.col = val; triggerRedraw(oldCol, this.cursor.col, 1, 1); triggerRedraw(this.cursor.col, this.cursor.line, 1, 1); } private void cursorPosition(final int[] params) { int col = 0; int line = 0; int lines = this.numLines; if (params.length > 0) line = params[0]; if (params.length > 1) col = params[1]; if (line > 0) line--; if (col > 0) col--; if (this.originMode == OriginMode.RELATIVE) { line += this.topMargin; lines = this.bottomMargin; } if (line >= lines) { line = lines - 1; Activator.logMessage(IStatus.WARNING, Messages.getString("Terminal.cursorPosOutOfRangeLine")); //$NON-NLS-1$ } if (col >= this.numCols) { col = this.numCols - 1; Activator.logMessage(IStatus.WARNING, Messages.getString("Terminal.cursorPosOutOfRangeCol")); //$NON-NLS-1$ } int oldLine = this.cursor.line; int oldCol = this.cursor.col; this.cursor.line = line; this.cursor.col = col; if (oldLine < this.numLines && oldCol < this.numCols) { triggerRedraw(oldCol, oldLine, 1, 1); } triggerRedraw(this.cursor.col, this.cursor.line, 1, 1); } private void deviceAttributes(final int[] params) throws IOException { final byte[] response = { SWT.ESC, '[', '?', '1', ';', '2', 'c' }; // 2 = VT100 with AVO if (params.length > 0 && params[0] != 0) { Activator.logMessage(IStatus.WARNING, Messages.getString("Terminal.unknownDevAttribReq")); //$NON-NLS-1$ } else { this.output.write(response); this.output.flush(); } } private int readNumber() throws IOException { int ch = read(); int val = 0; while ((ch >= '0' && ch <= '9') || ch == '?') { if (ch != '?') { // XXX meaning of '?' character ?!? val *= 10; val += ch - '0'; } ch = read(); } this.input.unread(ch); return val; } private int read() throws IOException { int val = this.input.read(); if (val == -1) this.running = false; this.terminalSelection.clearSelection(); return val; } private void initSystemColorTable() { Display dpy = getDisplay(); this.defaultBgColor = dpy.getSystemColor(SWT.COLOR_BLACK); this.defaultFgColor = dpy.getSystemColor(SWT.COLOR_GRAY); this.systemColors = new Color[256]; this.systemColors[0] = dpy.getSystemColor(SWT.COLOR_BLACK); this.systemColors[1] = dpy.getSystemColor(SWT.COLOR_DARK_RED); this.systemColors[2] = dpy.getSystemColor(SWT.COLOR_DARK_GREEN); this.systemColors[3] = dpy.getSystemColor(SWT.COLOR_DARK_YELLOW); this.systemColors[4] = dpy.getSystemColor(SWT.COLOR_DARK_BLUE); this.systemColors[5] = dpy.getSystemColor(SWT.COLOR_DARK_MAGENTA); this.systemColors[6] = dpy.getSystemColor(SWT.COLOR_DARK_CYAN); this.systemColors[7] = dpy.getSystemColor(SWT.COLOR_GRAY); this.systemColors[8] = dpy.getSystemColor(SWT.COLOR_DARK_GRAY); this.systemColors[9] = dpy.getSystemColor(SWT.COLOR_RED); this.systemColors[10] = dpy.getSystemColor(SWT.COLOR_GREEN); this.systemColors[11] = dpy.getSystemColor(SWT.COLOR_YELLOW); this.systemColors[12] = dpy.getSystemColor(SWT.COLOR_BLUE); this.systemColors[13] = dpy.getSystemColor(SWT.COLOR_MAGENTA); this.systemColors[14] = dpy.getSystemColor(SWT.COLOR_CYAN); this.systemColors[15] = dpy.getSystemColor(SWT.COLOR_WHITE); for (int i = 16; i < 256; i++) { this.systemColors[i] = this.defaultBgColor; } } void changeScreenSize() { Char[][] newScreenBuffer; LineHeightMode[] newLineHeightMode; LineWidthMode[] newLineWidthMode; Font font = getFont(); Point widgetSize = getSize(); GC gc = new GC(this); gc.setFont(font); this.fontWidth = gc.getFontMetrics().getAverageCharWidth(); this.fontHeight = gc.getFontMetrics().getHeight(); gc.dispose(); this.numCols = (widgetSize.x - getVerticalBar().getSize().x) / this.fontWidth; this.numLines = widgetSize.y / this.fontHeight; if (this.numCols < 2) this.numCols = 80; if (this.numLines < 2) this.numLines = 24; newScreenBuffer = new Char[this.numLines + this.historySize][]; newLineHeightMode = new LineHeightMode[this.numLines + this.historySize]; newLineWidthMode = new LineWidthMode[this.numLines + this.historySize]; int linesDiff = newScreenBuffer.length - this.screenBuffer.length; if (linesDiff < 0 && this.cursor.line + this.historySize >= newScreenBuffer.length) { linesDiff = newScreenBuffer.length - (this.cursor.line + this.historySize) - 1; } else if (linesDiff < 0) { linesDiff = 0; } for (int i = 0; i < newScreenBuffer.length; i++) { newScreenBuffer[i] = new Char[this.numCols]; // copy contents of old screenbuffer int oldIndex = i - linesDiff; if (oldIndex >= 0 && oldIndex < this.screenBuffer.length) { int len = newScreenBuffer[i].length; if (this.screenBuffer[oldIndex].length < len) len = this.screenBuffer[oldIndex].length; for (int j = 0; j < len; j++) newScreenBuffer[i][j] = this.screenBuffer[oldIndex][j]; newLineHeightMode[i] = this.lineHeightMode[oldIndex]; newLineWidthMode[i] = this.lineWidthMode[oldIndex]; } for (int j = 0; j < newScreenBuffer[i].length; j++) { if (newScreenBuffer[i][j] == null) { newScreenBuffer[i][j] = new Char(this.defaultFgColor, this.defaultBgColor); } } if (newLineHeightMode[i] == null) newLineHeightMode[i] = LineHeightMode.NORMAL; if (newLineWidthMode[i] == null) newLineWidthMode[i] = LineWidthMode.NORMAL; } this.screenBuffer = newScreenBuffer; this.lineHeightMode = newLineHeightMode; this.lineWidthMode = newLineWidthMode; setTopAndBottomMargins(new int[0], true); this.cursor.line += linesDiff; if (this.cursor.col >= this.numCols) this.cursor.col = this.numCols - 1; if (this.numCols != 0 && this.numLines != 0) { for (ITerminalListener listener : this.terminalListeners) { listener.windowSizeChanged(this.numCols, this.numLines, this.numCols * this.fontWidth, this.numLines * this.fontHeight); } } this.getVerticalBar().setValues(this.historySize, 0, this.screenBuffer.length, this.numLines, 1, this.numLines); } /** * Sets the InputStream from which the data to display should be read. * @param in stream to display. */ public void setInputStream(final InputStream in) { this.input = new PushbackInputStream(in); startReaderThread(); } /** * Sets the OuputStream to which the terminal input sould be sent to. * @param out stream to send input to. */ public void setOutputStream(final OutputStream out) { this.output = out; } @Override public void setFont(final Font font) { this.terminalPainter.setFont(font); super.setFont(font); } @Override public Color getForeground() { return this.defaultFgColor; } @Override public Color getBackground() { return this.defaultBgColor; } boolean isInReverseScreenMode() { return this.reverseScreenMode; } int getFontHeigth() { return this.fontHeight; } int getFontWidth() { return this.fontWidth; } Char[][] getScreenBuffer() { return this.screenBuffer; } LineHeightMode[] getLineHeightMode() { return this.lineHeightMode; } LineWidthMode[] getLineWidthMode() { return this.lineWidthMode; } int getCursorLine() { return this.cursor.line; } int getCursorCol() { return this.cursor.col; } int getScrollbarPosLine() { return getVerticalBar().getSelection(); } int getNumLines() { return this.numLines; } int getNumCols() { return this.numCols; } int getHistorySize() { return this.historySize; } public void addSelectionChangedListener(final ISelectionChangedListener listener) { this.selectionListeners.add(listener); } public ISelection getSelection() { return this.terminalSelection; } public void removeSelectionChangedListener(final ISelectionChangedListener listener) { this.selectionListeners.remove(listener); } public void setSelection(final ISelection selection) { // TODO Auto-generated method stub } void fireSelectionChanged() { SelectionChangedEvent event = new SelectionChangedEvent(this, this.terminalSelection); for (ISelectionChangedListener listener : this.selectionListeners) { listener.selectionChanged(event); } } }