diff --git a/releng/org.eclipse.rse.build/maps/terminal.map b/releng/org.eclipse.rse.build/maps/terminal.map index 5387ed28960..ed16fe8ae6a 100644 --- a/releng/org.eclipse.rse.build/maps/terminal.map +++ b/releng/org.eclipse.rse.build/maps/terminal.map @@ -1,9 +1,9 @@ -feature@org.eclipse.tm.terminal=v20070913,:pserver:anonymous:none@dev.eclipse.org:/cvsroot/dsdp,,org.eclipse.tm.core/terminal/org.eclipse.tm.terminal-feature +feature@org.eclipse.tm.terminal=v20070918,:pserver:anonymous:none@dev.eclipse.org:/cvsroot/dsdp,,org.eclipse.tm.core/terminal/org.eclipse.tm.terminal-feature feature@org.eclipse.tm.terminal.sdk=v20070808,:pserver:anonymous:none@dev.eclipse.org:/cvsroot/dsdp,,org.eclipse.tm.core/terminal/org.eclipse.tm.terminal.sdk-feature feature@org.eclipse.tm.terminal.serial=v20070609,:pserver:anonymous:none@dev.eclipse.org:/cvsroot/dsdp,,org.eclipse.tm.core/terminal/org.eclipse.tm.terminal.serial-feature feature@org.eclipse.tm.terminal.ssh=v20070808,:pserver:anonymous:none@dev.eclipse.org:/cvsroot/dsdp,,org.eclipse.tm.core/terminal/org.eclipse.tm.terminal.ssh-feature feature@org.eclipse.tm.terminal.telnet=v20070609,:pserver:anonymous:none@dev.eclipse.org:/cvsroot/dsdp,,org.eclipse.tm.core/terminal/org.eclipse.tm.terminal.telnet-feature -feature@org.eclipse.tm.terminal.view=v20070913,:pserver:anonymous:none@dev.eclipse.org:/cvsroot/dsdp,,org.eclipse.tm.core/terminal/org.eclipse.tm.terminal.view-feature +feature@org.eclipse.tm.terminal.view=v20070918,:pserver:anonymous:none@dev.eclipse.org:/cvsroot/dsdp,,org.eclipse.tm.core/terminal/org.eclipse.tm.terminal.view-feature plugin@org.eclipse.tm.terminal=v20070918,:pserver:anonymous:none@dev.eclipse.org:/cvsroot/dsdp,,org.eclipse.tm.core/terminal/org.eclipse.tm.terminal plugin@org.eclipse.tm.terminal.serial=v20070605,:pserver:anonymous:none@dev.eclipse.org:/cvsroot/dsdp,,org.eclipse.tm.core/terminal/org.eclipse.tm.terminal.serial plugin@org.eclipse.tm.terminal.ssh=v20070909,:pserver:anonymous:none@dev.eclipse.org:/cvsroot/dsdp,,org.eclipse.tm.core/terminal/org.eclipse.tm.terminal.ssh diff --git a/terminal/org.eclipse.tm.terminal-feature/feature.xml b/terminal/org.eclipse.tm.terminal-feature/feature.xml index 8bb182759d1..dec05659730 100644 --- a/terminal/org.eclipse.tm.terminal-feature/feature.xml +++ b/terminal/org.eclipse.tm.terminal-feature/feature.xml @@ -2,7 +2,7 @@ diff --git a/terminal/org.eclipse.tm.terminal.view-feature/feature.xml b/terminal/org.eclipse.tm.terminal.view-feature/feature.xml index 6e3316b926c..3a7af44dfeb 100644 --- a/terminal/org.eclipse.tm.terminal.view-feature/feature.xml +++ b/terminal/org.eclipse.tm.terminal.view-feature/feature.xml @@ -2,7 +2,7 @@ diff --git a/terminal/org.eclipse.tm.terminal.view/META-INF/MANIFEST.MF b/terminal/org.eclipse.tm.terminal.view/META-INF/MANIFEST.MF index 15a4f49a770..6ac93261e5b 100644 --- a/terminal/org.eclipse.tm.terminal.view/META-INF/MANIFEST.MF +++ b/terminal/org.eclipse.tm.terminal.view/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.tm.terminal.view;singleton:=true -Bundle-Version: 1.0.0.qualifier +Bundle-Version: 1.0.1.qualifier Bundle-Activator: org.eclipse.tm.internal.terminal.view.TerminalViewPlugin Bundle-Localization: plugin Require-Bundle: org.eclipse.ui, diff --git a/terminal/org.eclipse.tm.terminal.view/src/org/eclipse/tm/internal/terminal/view/TerminalView.java b/terminal/org.eclipse.tm.terminal.view/src/org/eclipse/tm/internal/terminal/view/TerminalView.java index 45d8b564f2b..c6d29d91e33 100644 --- a/terminal/org.eclipse.tm.terminal.view/src/org/eclipse/tm/internal/terminal/view/TerminalView.java +++ b/terminal/org.eclipse.tm.terminal.view/src/org/eclipse/tm/internal/terminal/view/TerminalView.java @@ -28,11 +28,11 @@ import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.window.ApplicationWindow; import org.eclipse.jface.window.Window; -import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.dnd.TextTransfer; import org.eclipse.swt.events.MenuEvent; import org.eclipse.swt.events.MenuListener; import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Menu; import org.eclipse.tm.internal.terminal.actions.TerminalAction; @@ -522,12 +522,12 @@ public class TerminalView extends ViewPart implements ITerminalView, ITerminalLi } protected void setupContextMenus() { - StyledText ctlText; + Control ctlText; MenuManager menuMgr; Menu menu; TerminalContextMenuHandler contextMenuHandler; - ctlText = fCtlTerminal.getCtlText(); + ctlText = fCtlTerminal.getControl(); menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$ menu = menuMgr.createContextMenu(ctlText); contextMenuHandler = new TerminalContextMenuHandler(); @@ -675,6 +675,7 @@ public class TerminalView extends ViewPart implements ITerminalView, ITerminalLi fMenuAboutToShow = true; updateEditCopy(); updateEditCut(); + updateEditSelectAll(); updateEditPaste(); updateEditClearAll(); diff --git a/terminal/org.eclipse.tm.terminal/.options b/terminal/org.eclipse.tm.terminal/.options index 5335bdd53c3..7e0cf7ac0ec 100644 --- a/terminal/org.eclipse.tm.terminal/.options +++ b/terminal/org.eclipse.tm.terminal/.options @@ -1,8 +1,8 @@ -org.eclipse.tm.terminal/debug = true -org.eclipse.tm.terminal/debug/flag = true -org.eclipse.tm.terminal/debug/filter = * +org.eclipse.tm.terminal/debug/log/directory = /tmp/ org.eclipse.tm.terminal/debug/log = true org.eclipse.tm.terminal/debug/log/error = true org.eclipse.tm.terminal/debug/log/info = false org.eclipse.tm.terminal/debug/log/char = false org.eclipse.tm.terminal/debug/log/buffer/size = false +org.eclipse.tm.terminal/debug/log/VT100Backend = false +org.eclipse.tm.terminal/debug/use_old_implementation = false \ No newline at end of file diff --git a/terminal/org.eclipse.tm.terminal/.settings/org.eclipse.jdt.core.prefs b/terminal/org.eclipse.tm.terminal/.settings/org.eclipse.jdt.core.prefs index d141d33956c..e06a0de226b 100644 --- a/terminal/org.eclipse.tm.terminal/.settings/org.eclipse.jdt.core.prefs +++ b/terminal/org.eclipse.tm.terminal/.settings/org.eclipse.jdt.core.prefs @@ -1,7 +1,8 @@ -#Tue Jan 30 22:33:44 CET 2007 +#Thu Aug 09 03:12:08 CEST 2007 eclipse.preferences.version=1 +instance/org.eclipse.core.net/org.eclipse.core.net.hasMigrated=true org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.2 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.4 org.eclipse.jdt.core.compiler.compliance=1.4 org.eclipse.jdt.core.compiler.doc.comment.support=enabled org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning @@ -67,4 +68,4 @@ org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=di org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning -org.eclipse.jdt.core.compiler.source=1.3 +org.eclipse.jdt.core.compiler.source=1.4 diff --git a/terminal/org.eclipse.tm.terminal/.settings/org.eclipse.jdt.ui.prefs b/terminal/org.eclipse.tm.terminal/.settings/org.eclipse.jdt.ui.prefs deleted file mode 100644 index 9a20fca52a4..00000000000 --- a/terminal/org.eclipse.tm.terminal/.settings/org.eclipse.jdt.ui.prefs +++ /dev/null @@ -1,3 +0,0 @@ -#Mon Jul 31 14:55:17 CEST 2006 -eclipse.preferences.version=1 -internal.default.compliance=user diff --git a/terminal/org.eclipse.tm.terminal/META-INF/MANIFEST.MF b/terminal/org.eclipse.tm.terminal/META-INF/MANIFEST.MF index a1ddfdd674d..28ebaa8b016 100644 --- a/terminal/org.eclipse.tm.terminal/META-INF/MANIFEST.MF +++ b/terminal/org.eclipse.tm.terminal/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.tm.terminal; singleton:=true -Bundle-Version: 1.0.0.qualifier +Bundle-Version: 1.0.1.qualifier Bundle-Activator: org.eclipse.tm.internal.terminal.control.impl.TerminalPlugin Bundle-Vendor: %providerName Bundle-Localization: plugin @@ -13,4 +13,8 @@ Bundle-RequiredExecutionEnvironment: J2SE-1.4 Bundle-ClassPath: . Export-Package: org.eclipse.tm.internal.terminal.control;x-friends:="org.eclipse.tm.terminal.view", org.eclipse.tm.internal.terminal.control.impl;x-internal:=true, - org.eclipse.tm.internal.terminal.provisional.api;x-friends:="org.eclipse.tm.terminal.serial,org.eclipse.tm.terminal.ssh,org.eclipse.tm.terminal.telnet,org.eclipse.tm.terminal.view" + org.eclipse.tm.internal.terminal.emulator;x-friends:="org.eclipse.tm.terminal.test", + org.eclipse.tm.internal.terminal.model;x-friends:="org.eclipse.tm.terminal.test", + org.eclipse.tm.internal.terminal.provisional.api;x-friends:="org.eclipse.tm.terminal.serial,org.eclipse.tm.terminal.ssh,org.eclipse.tm.terminal.telnet,org.eclipse.tm.terminal.view", + org.eclipse.tm.internal.terminal.textcanvas;x-friends:="org.eclipse.tm.terminal.test", + org.eclipse.tm.terminal.model diff --git a/terminal/org.eclipse.tm.terminal/build.properties b/terminal/org.eclipse.tm.terminal/build.properties index 50dc00bf907..f3c88941ea4 100644 --- a/terminal/org.eclipse.tm.terminal/build.properties +++ b/terminal/org.eclipse.tm.terminal/build.properties @@ -28,4 +28,6 @@ output.. = bin/ src.includes = schema/,\ README.txt,\ about.html +javacSource=1.4 +javacTarget=1.4 \ No newline at end of file diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/control/CommandInputFieldWithHistory.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/control/CommandInputFieldWithHistory.java index f117fc5cd2b..ca9fbe77789 100644 --- a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/control/CommandInputFieldWithHistory.java +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/control/CommandInputFieldWithHistory.java @@ -149,8 +149,8 @@ public class CommandInputFieldWithHistory implements ICommandInputField { } public void createControl(Composite parent,final ITerminalViewControl terminal) { fInputField=new Text(parent, SWT.SINGLE|SWT.BORDER); - fInputField.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); - fInputField.setFont(terminal.getCtlText().getFont()); + fInputField.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + fInputField.setFont(terminal.getFont()); fInputField.addKeyListener(new KeyListener(){ public void keyPressed(KeyEvent e) { if(e.keyCode=='\n' || e.keyCode=='\r') { diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/control/ITerminalViewControl.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/control/ITerminalViewControl.java index d9cfb5cbfb4..d498d97ac64 100644 --- a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/control/ITerminalViewControl.java +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/control/ITerminalViewControl.java @@ -11,9 +11,9 @@ *******************************************************************************/ package org.eclipse.tm.internal.terminal.control; -import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.dnd.Clipboard; import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.widgets.Control; import org.eclipse.tm.internal.terminal.provisional.api.ITerminalConnectorInfo; import org.eclipse.tm.internal.terminal.provisional.api.TerminalState; @@ -24,7 +24,8 @@ import org.eclipse.tm.internal.terminal.provisional.api.TerminalState; public interface ITerminalViewControl { boolean isEmpty(); void setFont(Font font); - StyledText getCtlText(); + Font getFont(); + Control getControl(); boolean isDisposed(); void selectAll(); void clearTerminal(); @@ -72,5 +73,4 @@ public interface ITerminalViewControl { * in the terminal view. -1 means unlimited. */ public void setBufferLineLimit(int bufferLineLimit); - } \ No newline at end of file diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/control/TerminalViewControlFactory.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/control/TerminalViewControlFactory.java index a5453057aac..bdd9b89b8b3 100644 --- a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/control/TerminalViewControlFactory.java +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/control/TerminalViewControlFactory.java @@ -13,10 +13,15 @@ package org.eclipse.tm.internal.terminal.control; import org.eclipse.swt.widgets.Composite; import org.eclipse.tm.internal.terminal.control.impl.TerminalControl; +import org.eclipse.tm.internal.terminal.control.impl.TerminalPlugin; +import org.eclipse.tm.internal.terminal.emulator.VT100TerminalControl; import org.eclipse.tm.internal.terminal.provisional.api.ITerminalConnectorInfo; public class TerminalViewControlFactory { public static ITerminalViewControl makeControl(ITerminalListener target, Composite wndParent, ITerminalConnectorInfo[] connectors) { - return new TerminalControl(target, wndParent, connectors); + if(TerminalPlugin.isOptionEnabled("org.eclipse.tm.terminal/debug/use_old_implementation")) //$NON-NLS-1$ + return new TerminalControl(target, wndParent, connectors); + else + return new VT100TerminalControl(target, wndParent, connectors); } } diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/control/impl/TerminalControl.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/control/impl/TerminalControl.java index 44e95db74d6..f528280bd86 100644 --- a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/control/impl/TerminalControl.java +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/control/impl/TerminalControl.java @@ -406,7 +406,12 @@ public class TerminalControl implements ITerminalControlForText, ITerminalContro getTerminalText().fontChanged(); } - + public Font getFont() { + return getCtlText().getFont(); + } + public Control getControl() { + return fCtlText; + } protected void setupControls(Composite parent) { // The Terminal view now aims to be an ANSI-conforming terminal emulator, so it // can't have a horizontal scroll bar (but a vertical one is ok). Also, do @@ -423,8 +428,8 @@ public class TerminalControl implements ITerminalControlForText, ITerminalContro fWndParent.setLayout(layout); setCtlText(new StyledText(fWndParent, SWT.V_SCROLL)); fCtlText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + //fCtlText.setWordWrap(false); - fDisplay = getCtlText().getDisplay(); fClipboard = new Clipboard(fDisplay); // fViewer.setDocument(new TerminalDocument()); @@ -515,7 +520,7 @@ public class TerminalControl implements ITerminalControlForText, ITerminalContro /* (non-Javadoc) * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#getCtlText() */ - public StyledText getCtlText() { + protected StyledText getCtlText() { return fCtlText; } @@ -856,4 +861,5 @@ public class TerminalControl implements ITerminalControlForText, ITerminalContro getTerminalText().setBufferLineLimit(bufferLineLimit); } + } diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/IVT100EmulatorBackend.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/IVT100EmulatorBackend.java new file mode 100644 index 00000000000..362aa26d237 --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/IVT100EmulatorBackend.java @@ -0,0 +1,172 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. and others. + * 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 + * + * Contributors: + * Michael Scharf (Wind River) - initial API and implementation + *******************************************************************************/ +package org.eclipse.tm.internal.terminal.emulator; + +import org.eclipse.tm.terminal.model.Style; + +public interface IVT100EmulatorBackend { + + /** + * This method erases all text from the Terminal view. Including the history + */ + void clearAll(); + + /** + * Sets the Dimensions of the addressable scroll space of the screen.... + * Keeps the cursor position relative to the bottom of the screen! + * @param lines + * @param cols + */ + void setDimensions(int lines, int cols); + + /** + * This method makes room for N characters on the current line at the cursor + * position. Text under the cursor moves right without wrapping at the end + * of the line. + * 01234 + * 0 123 + */ + void insertCharacters(int charactersToInsert); + + /** + * Erases from cursor to end of screen, including cursor position. Cursor does not move. + */ + void eraseToEndOfScreen(); + + /** + * Erases from beginning of screen to cursor, including cursor position. Cursor does not move. + */ + void eraseToCursor(); + + /** + * Erases complete display. All lines are erased and changed to single-width. Cursor does not move. + */ + void eraseAll(); + + /** + * Erases complete line. + */ + void eraseLine(); + + /** + * Erases from cursor to end of line, including cursor position. + */ + void eraseLineToEnd(); + + /** + * Erases from beginning of line to cursor, including cursor position. + */ + void eraseLineToCursor(); + + /** + * Inserts n lines at line with cursor. Lines displayed below cursor move down. + * Lines moved past the bottom margin are lost. This sequence is ignored when + * cursor is outside scrolling region. + * @param n the number of lines to insert + */ + void insertLines(int n); + + /** + * Deletes n characters, starting with the character at cursor position. + * When a character is deleted, all characters to the right of cursor move + * left. This creates a space character at right margin. This character + * has same character attribute as the last character moved left. + * @param n + * 012345 + * 0145xx + */ + void deleteCharacters(int n); + + /** + * Deletes n lines, starting at line with cursor. As lines are deleted, + * lines displayed below cursor move up. Lines added to bottom of screen + * have spaces with same character attributes as last line moved up. This + * sequence is ignored when cursor is outside scrolling region. + * @param n the number of lines to delete + */ + void deleteLines(int n); + + Style getDefaultStyle(); + + void setDefaultStyle(Style defaultStyle); + + Style getStyle(); + + /** + * Sets the style to be used from now on + * @param style + */ + void setStyle(Style style); + + /** + * This method displays a subset of the newly-received text in the Terminal + * view, wrapping text at the right edge of the screen and overwriting text + * when the cursor is not at the very end of the screen's text. + *

+ * + * There are never any ANSI control characters or escape sequences in the + * text being displayed by this method (this includes newlines, carriage + * returns, and tabs). + *

+ */ + void appendString(String buffer); + + /** + * Process a newline (Control-J) character. A newline (NL) character just + * moves the cursor to the same column on the next line, creating new lines + * when the cursor reaches the bottom edge of the terminal. This is + * counter-intuitive, especially to UNIX programmers who are taught that + * writing a single NL to a terminal is sufficient to move the cursor to the + * first column of the next line, as if a carriage return (CR) and a NL were + * written. + *

+ * + * UNIX terminals typically display a NL character as a CR followed by a NL + * because the terminal device typically has the ONLCR attribute bit set + * (see the termios(4) man page for details), which causes the terminal + * device driver to translate NL to CR + NL on output. The terminal itself + * (i.e., a hardware terminal or a terminal emulator, like xterm or this + * code) _always_ interprets a CR to mean "move the cursor to the beginning + * of the current line" and a NL to mean "move the cursor to the same column + * on the next line". + *

+ */ + void processNewline(); + + /** + * This method returns the relative line number of the line containing the + * cursor. The returned line number is relative to the topmost visible line, + * which has relative line number 0. + * + * @return The relative line number of the line containing the cursor. + */ + int getCursorLine(); + + int getCursorColumn(); + + /** + * This method moves the cursor to the specified line and column. Parameter + * targetLine is the line number of a screen line, so it has a + * minimum value of 0 (the topmost screen line) and a maximum value of + * heightInLines - 1 (the bottommost screen line). A line does not have to + * contain any text to move the cursor to any column in that line. + */ + void setCursor(int targetLine, int targetColumn); + + void setCursorColumn(int targetColumn); + + void setCursorLine(int targetLine); + + int getLines(); + + int getColumns(); + +} \ No newline at end of file diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100BackendTraceDecorator.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100BackendTraceDecorator.java new file mode 100644 index 00000000000..c4d57ab98bc --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100BackendTraceDecorator.java @@ -0,0 +1,144 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. and others. + * 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 + * + * Contributors: + * Michael Scharf (Wind River) - initial API and implementation + *******************************************************************************/ +package org.eclipse.tm.internal.terminal.emulator; + +import java.io.PrintStream; + +import org.eclipse.tm.terminal.model.Style; + +public class VT100BackendTraceDecorator implements IVT100EmulatorBackend { + final IVT100EmulatorBackend fBackend; + final PrintStream fWriter; + public VT100BackendTraceDecorator(IVT100EmulatorBackend backend, PrintStream out) { + fBackend = backend; + fWriter=out; + } + + public void appendString(String buffer) { + fWriter.println("appendString(\""+buffer+"\")"); //$NON-NLS-1$ //$NON-NLS-2$ + fBackend.appendString(buffer); + } + + public void clearAll() { + fWriter.println("clearAll()"); //$NON-NLS-1$ + fBackend.clearAll(); + } + + public void deleteCharacters(int n) { + fWriter.println("deleteCharacters("+n+")"); //$NON-NLS-1$ //$NON-NLS-2$ + fBackend.deleteCharacters(n); + } + + public void deleteLines(int n) { + fWriter.println("deleteLines("+n+")"); //$NON-NLS-1$ //$NON-NLS-2$ + fBackend.deleteLines(n); + } + + public void eraseAll() { + fWriter.println("eraseAll()"); //$NON-NLS-1$ + fBackend.eraseAll(); + } + + public void eraseLine() { + fWriter.println("eraseLine()"); //$NON-NLS-1$ + fBackend.eraseLine(); + } + + public void eraseLineToCursor() { + fWriter.println("eraseLineToCursor()"); //$NON-NLS-1$ + fBackend.eraseLineToCursor(); + } + + public void eraseLineToEnd() { + fWriter.println("eraseLineToEnd()"); //$NON-NLS-1$ + fBackend.eraseLineToEnd(); + } + + public void eraseToCursor() { + fWriter.println("eraseToCursor()"); //$NON-NLS-1$ + fBackend.eraseToCursor(); + } + + public void eraseToEndOfScreen() { + fWriter.println("eraseToEndOfScreen()"); //$NON-NLS-1$ + fBackend.eraseToEndOfScreen(); + } + + public int getColumns() { + return fBackend.getColumns(); + } + + public int getCursorColumn() { + return fBackend.getCursorColumn(); + } + + public int getCursorLine() { + return fBackend.getCursorLine(); + } + + public Style getDefaultStyle() { + return fBackend.getDefaultStyle(); + } + + public int getLines() { + return fBackend.getLines(); + } + + public Style getStyle() { + return fBackend.getStyle(); + } + + public void insertCharacters(int charactersToInsert) { + fWriter.println("insertCharacters("+charactersToInsert+")"); //$NON-NLS-1$ //$NON-NLS-2$ + fBackend.insertCharacters(charactersToInsert); + } + + public void insertLines(int n) { + fWriter.println("insertLines("+n+")"); //$NON-NLS-1$ //$NON-NLS-2$ + fBackend.insertLines(n); + } + + public void processNewline() { + fWriter.println("processNewline()"); //$NON-NLS-1$ + fBackend.processNewline(); + } + + public void setCursor(int targetLine, int targetColumn) { + fWriter.println("setCursor("+targetLine+", "+targetColumn+")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + fBackend.setCursor(targetLine, targetColumn); + } + + public void setCursorColumn(int targetColumn) { + fWriter.println("setCursorColumn("+targetColumn+")"); //$NON-NLS-1$ //$NON-NLS-2$ + fBackend.setCursorColumn(targetColumn); + } + + public void setCursorLine(int targetLine) { + fWriter.println("setCursorLine("+targetLine+")"); //$NON-NLS-1$ //$NON-NLS-2$ + fBackend.setCursorLine(targetLine); + } + + public void setDefaultStyle(Style defaultStyle) { + fWriter.println("setDefaultStyle("+defaultStyle+")"); //$NON-NLS-1$ //$NON-NLS-2$ + fBackend.setDefaultStyle(defaultStyle); + } + + public void setDimensions(int lines, int cols) { + fWriter.println("setDimensions("+lines+","+cols+")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + fBackend.setDimensions(lines, cols); + } + + public void setStyle(Style style) { + fWriter.println("setStyle("+style+")"); //$NON-NLS-1$ //$NON-NLS-2$ + fBackend.setStyle(style); + } + +} diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100Emulator.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100Emulator.java new file mode 100644 index 00000000000..0caa98ef586 --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100Emulator.java @@ -0,0 +1,1171 @@ +/******************************************************************************* + * Copyright (c) 2003, 2007 Wind River Systems, Inc. and others. + * 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 Contributors: + * The following Wind River employees contributed to the Terminal component + * that contains this file: Chris Thew, Fran Litterio, Stephen Lamb, + * Helmut Haigermoser and Ted Williams. + * + * Contributors: + * Michael Scharf (Wind River) - split into core, view and connector plugins + * Martin Oberhuber (Wind River) - fixed copyright headers and beautified + *******************************************************************************/ +package org.eclipse.tm.internal.terminal.emulator; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; + +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.ControlListener; +import org.eclipse.tm.internal.terminal.control.impl.ITerminalControlForText; +import org.eclipse.tm.internal.terminal.control.impl.TerminalControl; +import org.eclipse.tm.internal.terminal.control.impl.TerminalPlugin; +import org.eclipse.tm.internal.terminal.provisional.api.ITerminalConnector; +import org.eclipse.tm.internal.terminal.provisional.api.Logger; +import org.eclipse.tm.internal.terminal.provisional.api.TerminalState; +import org.eclipse.tm.terminal.model.ITerminalTextData; +import org.eclipse.tm.terminal.model.Style; + +/** + * This class processes character data received from the remote host and + * displays it to the user using the Terminal view's StyledText widget. This + * class processes ANSI control characters, including NUL, backspace, carriage + * return, linefeed, and a subset of ANSI escape sequences sufficient to allow + * use of screen-oriented applications, such as vi, Emacs, and any GNU + * readline-enabled application (Bash, bc, ncftp, etc.). + *

+ * + * @author Fran Litterio + * @author Chris Thew + */ +public class VT100Emulator implements ControlListener { + /** This is a character processing state: Initial state. */ + private static final int ANSISTATE_INITIAL = 0; + + /** This is a character processing state: We've seen an escape character. */ + private static final int ANSISTATE_ESCAPE = 1; + + /** + * This is a character processing state: We've seen a '[' after an escape + * character. Expecting a parameter character or a command character next. + */ + private static final int ANSISTATE_EXPECTING_PARAMETER_OR_COMMAND = 2; + + /** + * This is a character processing state: We've seen a ']' after an escape + * character. We are now expecting an operating system command that + * reprograms an intelligent terminal. + */ + private static final int ANSISTATE_EXPECTING_OS_COMMAND = 3; + + /** + * This field holds the current state of the Finite TerminalState Automaton (FSA) + * that recognizes ANSI escape sequences. + * + * @see #processNewText() + */ + private int ansiState = ANSISTATE_INITIAL; + + /** + * This field holds a reference to the {@link TerminalControl} object that + * instantiates this class. + */ + private ITerminalControlForText terminal; + + /** + * This field holds a reference to the StyledText widget that is used to + * display text to the user. + */ + final private IVT100EmulatorBackend text; + /** + * This field hold the saved absolute line number of the cursor when + * processing the "ESC 7" and "ESC 8" command sequences. + */ + private int savedCursorLine = 0; + + /** + * This field hold the saved column number of the cursor when processing the + * "ESC 7" and "ESC 8" command sequences. + */ + private int savedCursorColumn = 0; + + /** + * This field holds an array of StringBuffer objects, each of which is one + * parameter from the current ANSI escape sequence. For example, when + * parsing the escape sequence "\e[20;10H", this array holds the strings + * "20" and "10". + */ + private StringBuffer[] ansiParameters = new StringBuffer[16]; + + /** + * This field holds the OS-specific command found in an escape sequence of + * the form "\e]...\u0007". + */ + private StringBuffer ansiOsCommand = new StringBuffer(128); + + /** + * This field holds the index of the next unused element of the array stored + * in field {@link #ansiParameters}. + */ + private int nextAnsiParameter = 0; + + final Reader fReader; + + boolean fCrAfterNewLine; + /** + * The constructor. + */ + public VT100Emulator(ITerminalTextData data,ITerminalControlForText terminal,InputStream input) { + super(); + + Logger.log("entered"); //$NON-NLS-1$ + + this.terminal = terminal; + + for (int i = 0; i < ansiParameters.length; ++i) { + ansiParameters[i] = new StringBuffer(); + } + Reader reader=null; + try { + // TODO convert byte to char using "ISO-8859-1" + reader=new InputStreamReader(input,"ISO-8859-1"); //$NON-NLS-1$ + } catch (UnsupportedEncodingException e) { + // should never happen! + e.printStackTrace(); + } + fReader=reader; + if(TerminalPlugin.isOptionEnabled("org.eclipse.tm.terminal/debug/log/VT100Backend")) //$NON-NLS-1$ + text=new VT100BackendTraceDecorator(new VT100EmulatorBackend(data),System.out); + else + text=new VT100EmulatorBackend(data); + + text.setDimensions(24, 80); + Style style=Style.getStyle("BLACK", "WHITE"); //$NON-NLS-1$ //$NON-NLS-2$ + text.setDefaultStyle(style); + text.setStyle(style); + } + public void setDimensions(int lines,int cols) { + // TODO allow to set the dimension in the UI and or prefs + lines=Math.max(3, lines); + cols=Math.max(10, cols); + text.setDimensions(lines, cols); + ITerminalConnector telnetConnection = getConnector(); + if (telnetConnection != null) { + telnetConnection.setTerminalSize(text.getColumns(), text.getLines()); + } + + } + + /** + * This method performs clean up when this VT100Emulator object is no longer + * needed. After calling this method, no other method on this object should + * be called. + */ + public void dispose() { + } + + /** + * This method is required by interface ControlListener. It allows us to + * know when the StyledText widget is moved. + */ + public void controlMoved(ControlEvent event) { + Logger.log("entered"); //$NON-NLS-1$ + // Empty. + } + + /** + * This method is required by interface ControlListener. It allows us to + * know when the StyledText widget is resized. + */ + public void controlResized(ControlEvent event) { + Logger.log("entered"); //$NON-NLS-1$ + adjustTerminalDimensions(); + } + + /** + * This method erases all text from the Terminal view. + */ + public void clearTerminal() { + Logger.log("entered"); //$NON-NLS-1$ + text.clearAll(); + } + + /** + * This method is called when the user changes the Terminal view's font. We + * attempt to recompute the pixel width of the new font's characters and fix + * the terminal's dimensions. + */ + public void fontChanged() { + Logger.log("entered"); //$NON-NLS-1$ + + if (text != null) + adjustTerminalDimensions(); + } +// /** +// * This method executes in the Display thread to process data received from +// * the remote host by class {@link org.eclipse.tm.internal.terminal.telnet.TelnetConnection} and +// * other implementors of {@link ITerminalConnector}, like the +// * SerialPortHandler. +// *

+// * These connectors write text to the terminal's buffer through +// * {@link TerminalControl#writeToTerminal(String)} and then have +// * this run method executed in the display thread. This method +// * must not execute at the same time as methods +// * {@link #setNewText(StringBuffer)} and {@link #clearTerminal()}. +// *

+// * IMPORTANT: This method must be called in strict alternation with method +// * {@link #setNewText(StringBuffer)}. +// *

+// */ + public void processText() { + try { + // If the status bar is showing "OPENED", change it to "CONNECTED". + + if (terminal.getState()==TerminalState.OPENED) { + // TODO Why???? + terminal.setState(TerminalState.CONNECTED); + } + + // Find the width and height of the terminal, and resize it to display an + // integral number of lines and columns. + + adjustTerminalDimensions(); + + // Restore the caret offset, process and display the new text, then save + // the caret offset. See the documentation for field caretOffset for + // details. + + // ISSUE: Is this causing the scroll-to-bottom-on-output behavior? + + try { + processNewText(); + } catch (IOException e) { + Logger.logException(e); + } + + } catch (Exception ex) { + Logger.logException(ex); + } + } + /** + * This method scans the newly received text, processing ANSI control + * characters and escape sequences and displaying normal text. + * @throws IOException + */ + private void processNewText() throws IOException { + Logger.log("entered"); //$NON-NLS-1$ + + + // Scan the newly received text. + + while (hasNextChar()) { + char character = getNextChar(); + + switch (ansiState) { + case ANSISTATE_INITIAL: + switch (character) { + case '\u0000': + break; // NUL character. Ignore it. + + case '\u0007': + processBEL(); // BEL (Control-G) + break; + + case '\b': + processBackspace(); // Backspace + break; + + case '\t': + processTab(); // Tab. + break; + + case '\n': + processNewline(); // Newline (Control-J) + if(fCrAfterNewLine) + processCarriageReturn(); // Carriage Return (Control-M) + break; + + case '\r': + processCarriageReturn(); // Carriage Return (Control-M) + break; + + case '\u001b': + ansiState = ANSISTATE_ESCAPE; // Escape. + break; + + default: + processNonControlCharacters(character); + break; + } + break; + + case ANSISTATE_ESCAPE: + // We've seen an escape character. Here, we process the character + // immediately following the escape. + + switch (character) { + case '[': + ansiState = ANSISTATE_EXPECTING_PARAMETER_OR_COMMAND; + nextAnsiParameter = 0; + + // Erase the parameter strings in preparation for optional + // parameter characters. + + for (int i = 0; i < ansiParameters.length; ++i) { + ansiParameters[i].delete(0, ansiParameters[i].length()); + } + break; + + case ']': + ansiState = ANSISTATE_EXPECTING_OS_COMMAND; + ansiOsCommand.delete(0, ansiOsCommand.length()); + break; + + case '7': + // Save cursor position and character attributes + + ansiState = ANSISTATE_INITIAL; + savedCursorLine = relativeCursorLine(); + savedCursorColumn = getCursorColumn(); + break; + + case '8': + // Restore cursor and attributes to previously saved + // position + + ansiState = ANSISTATE_INITIAL; + moveCursor(savedCursorLine, savedCursorColumn); + break; + + case 'c': + // Reset the terminal + ansiState = ANSISTATE_INITIAL; + resetTerminal(); + break; + + default: + Logger + .log("Unsupported escape sequence: escape '" + character + "'"); //$NON-NLS-1$ //$NON-NLS-2$ + ansiState = ANSISTATE_INITIAL; + break; + } + break; + + case ANSISTATE_EXPECTING_PARAMETER_OR_COMMAND: + // Parameters can appear after the '[' in an escape sequence, but they + // are optional. + + if (character == '@' || (character >= 'A' && character <= 'Z') + || (character >= 'a' && character <= 'z')) { + ansiState = ANSISTATE_INITIAL; + processAnsiCommandCharacter(character); + } else { + processAnsiParameterCharacter(character); + } + break; + + case ANSISTATE_EXPECTING_OS_COMMAND: + // A BEL (\u0007) character marks the end of the OSC sequence. + + if (character == '\u0007') { + ansiState = ANSISTATE_INITIAL; + processAnsiOsCommand(); + } else { + ansiOsCommand.append(character); + } + break; + + default: + // This should never happen! If it does happen, it means there is a + // bug in the FSA. For robustness, we return to the initial + // state. + + Logger.log("INVALID ANSI FSA STATE: " + ansiState); //$NON-NLS-1$ + ansiState = ANSISTATE_INITIAL; + break; + } + } + } + private void resetTerminal() { + text.eraseAll(); + text.setCursor(0, 0); + text.setStyle(text.getDefaultStyle()); + } + /** + * This method is called when we have parsed an OS Command escape sequence. + * The only one we support is "\e]0;...\u0007", which sets the terminal + * title. + */ + private void processAnsiOsCommand() { + if (ansiOsCommand.charAt(0) != '0' || ansiOsCommand.charAt(1) != ';') { + Logger + .log("Ignoring unsupported ANSI OSC sequence: '" + ansiOsCommand + "'"); //$NON-NLS-1$ //$NON-NLS-2$ + return; + } + terminal.setTerminalTitle(ansiOsCommand.substring(2)); + } + + /** + * This method dispatches control to various processing methods based on the + * command character found in the most recently received ANSI escape + * sequence. This method only handles command characters that follow the + * ANSI standard Control Sequence Introducer (CSI), which is "\e[...", where + * "..." is an optional ';'-separated sequence of numeric parameters. + *

+ */ + private void processAnsiCommandCharacter(char ansiCommandCharacter) { + // If the width or height of the terminal is ridiculously small (one line or + // column or less), don't even try to process the escape sequence. This avoids + // throwing an exception (SPR 107450). The display will be messed up, but what + // did you user expect by making the terminal so small? + + switch (ansiCommandCharacter) { + case '@': + // Insert character(s). + processAnsiCommand_atsign(); + break; + + case 'A': + // Move cursor up N lines (default 1). + processAnsiCommand_A(); + break; + + case 'B': + // Move cursor down N lines (default 1). + processAnsiCommand_B(); + break; + + case 'C': + // Move cursor forward N columns (default 1). + processAnsiCommand_C(); + break; + + case 'D': + // Move cursor backward N columns (default 1). + processAnsiCommand_D(); + break; + + case 'E': + // Move cursor to first column of Nth next line (default 1). + processAnsiCommand_E(); + break; + + case 'F': + // Move cursor to first column of Nth previous line (default 1). + processAnsiCommand_F(); + break; + + case 'G': + // Move to column N of current line (default 1). + processAnsiCommand_G(); + break; + + case 'H': + // Set cursor Position. + processAnsiCommand_H(); + break; + + case 'J': + // Erase part or all of display. Cursor does not move. + processAnsiCommand_J(); + break; + + case 'K': + // Erase in line (cursor does not move). + processAnsiCommand_K(); + break; + + case 'L': + // Insert line(s) (current line moves down). + processAnsiCommand_L(); + break; + + case 'M': + // Delete line(s). + processAnsiCommand_M(); + break; + + case 'm': + // Set Graphics Rendition (SGR). + processAnsiCommand_m(); + break; + + case 'n': + // Device Status Report (DSR). + processAnsiCommand_n(); + break; + + case 'P': + // Delete character(s). + processAnsiCommand_P(); + break; + + case 'S': + // Scroll up. + // Emacs, vi, and GNU readline don't seem to use this command, so we ignore + // it for now. + break; + + case 'T': + // Scroll down. + // Emacs, vi, and GNU readline don't seem to use this command, so we ignore + // it for now. + break; + + case 'X': + // Erase character. + // Emacs, vi, and GNU readline don't seem to use this command, so we ignore + // it for now. + break; + + case 'Z': + // Cursor back tab. + // Emacs, vi, and GNU readline don't seem to use this command, so we ignore + // it for now. + break; + + default: + Logger.log("Ignoring unsupported ANSI command character: '" + //$NON-NLS-1$ + ansiCommandCharacter + "'"); //$NON-NLS-1$ + break; + } + } + + /** + * This method makes room for N characters on the current line at the cursor + * position. Text under the cursor moves right without wrapping at the end + * of the line. + */ + private void processAnsiCommand_atsign() { + int charactersToInsert = getAnsiParameter(0); + text.insertCharacters(charactersToInsert); + } + + /** + * This method moves the cursor up by the number of lines specified by the + * escape sequence parameter (default 1). + */ + private void processAnsiCommand_A() { + moveCursorUp(getAnsiParameter(0)); + } + + /** + * This method moves the cursor down by the number of lines specified by the + * escape sequence parameter (default 1). + */ + private void processAnsiCommand_B() { + moveCursorDown(getAnsiParameter(0)); + } + + /** + * This method moves the cursor forward by the number of columns specified + * by the escape sequence parameter (default 1). + */ + private void processAnsiCommand_C() { + moveCursorForward(getAnsiParameter(0)); + } + + /** + * This method moves the cursor backward by the number of columns specified + * by the escape sequence parameter (default 1). + */ + private void processAnsiCommand_D() { + moveCursorBackward(getAnsiParameter(0)); + } + + /** + * This method moves the cursor to the first column of the Nth next line, + * where N is specified by the ANSI parameter (default 1). + */ + private void processAnsiCommand_E() { + int linesToMove = getAnsiParameter(0); + + moveCursor(relativeCursorLine() + linesToMove, 0); + } + + /** + * This method moves the cursor to the first column of the Nth previous + * line, where N is specified by the ANSI parameter (default 1). + */ + private void processAnsiCommand_F() { + int linesToMove = getAnsiParameter(0); + + moveCursor(relativeCursorLine() - linesToMove, 0); + } + + /** + * This method moves the cursor within the current line to the column + * specified by the ANSI parameter (default is column 1). + */ + private void processAnsiCommand_G() { + int targetColumn = 1; + + if (ansiParameters[0].length() > 0) + targetColumn = getAnsiParameter(0) - 1; + + moveCursor(relativeCursorLine(), targetColumn); + } + + /** + * This method sets the cursor to a position specified by the escape + * sequence parameters (default is the upper left corner of the screen). + */ + private void processAnsiCommand_H() { + moveCursor(getAnsiParameter(0) - 1, getAnsiParameter(1) - 1); + } + + /** + * This method deletes some (or all) of the text on the screen without + * moving the cursor. + */ + private void processAnsiCommand_J() { + int ansiParameter; + + if (ansiParameters[0].length() == 0) + ansiParameter = 0; + else + ansiParameter = getAnsiParameter(0); + + switch (ansiParameter) { + case 0: + text.eraseToEndOfScreen(); + break; + + case 1: + // Erase from beginning to current position (inclusive). + text.eraseToCursor(); + break; + + case 2: + // Erase entire display. + + text.eraseAll(); + break; + + default: + Logger.log("Unexpected J-command parameter: " + ansiParameter); //$NON-NLS-1$ + break; + } + } + + /** + * This method deletes some (or all) of the text in the current line without + * moving the cursor. + */ + private void processAnsiCommand_K() { + int ansiParameter = getAnsiParameter(0); + + switch (ansiParameter) { + case 0: + // Erase from beginning to current position (inclusive). + text.eraseLineToCursor(); + break; + + case 1: + // Erase from current position to end (inclusive). + text.eraseLineToEnd(); + break; + + case 2: + // Erase entire line. + text.eraseLine(); + break; + + default: + Logger.log("Unexpected K-command parameter: " + ansiParameter); //$NON-NLS-1$ + break; + } + } + + /** + * Insert one or more blank lines. The current line of text moves down. Text + * that falls off the bottom of the screen is deleted. + */ + private void processAnsiCommand_L() { + text.insertLines(getAnsiParameter(0)); + } + + /** + * Delete one or more lines of text. Any lines below the deleted lines move + * up, which we implement by appending newlines to the end of the text. + */ + private void processAnsiCommand_M() { + text.deleteLines(getAnsiParameter(0)); + } + + /** + * This method sets a new graphics rendition mode, such as + * foreground/background color, bold/normal text, and reverse video. + */ + private void processAnsiCommand_m() { + if (ansiParameters[0].length() == 0) { + // This a special case: when no ANSI parameter is specified, act like a + // single parameter equal to 0 was specified. + + ansiParameters[0].append('0'); + } + Style style=text.getStyle(); + // There are a non-zero number of ANSI parameters. Process each one in + // order. + + int totalParameters = ansiParameters.length; + int parameterIndex = 0; + + while (parameterIndex < totalParameters + && ansiParameters[parameterIndex].length() > 0) { + int ansiParameter = getAnsiParameter(parameterIndex); + + switch (ansiParameter) { + case 0: + // Reset all graphics modes. + text.setStyle(text.getDefaultStyle()); + break; + + case 1: + text.setStyle(style.setBold(true)); + break; + + case 7: + text.setStyle(style.setReverse(true)); + break; + + case 10: // Set primary font. Ignored. + break; + +// case 22: +// // TODO +// //currentFontStyle = SWT.NORMAL; // Cancel bold or dim attributes +// // only. +// break; + + case 27: + text.setStyle(style.setReverse(false)); + break; + + case 30: + text.setStyle(style.setForground("BLACK")); //$NON-NLS-1$ + break; + + case 31: + text.setStyle(style.setForground("RED")); //$NON-NLS-1$ + break; + + case 32: + text.setStyle(style.setForground("GREEN")); //$NON-NLS-1$ + break; + + case 33: + text.setStyle(style.setForground("YELLOW")); //$NON-NLS-1$ + break; + + case 34: + text.setStyle(style.setForground("BLUE")); //$NON-NLS-1$ + break; + + case 35: + text.setStyle(style.setForground("MAGENTA")); //$NON-NLS-1$ + break; + + case 36: + text.setStyle(style.setForground("CYAN")); //$NON-NLS-1$ + break; + + case 37: + text.setStyle(style.setForground("WHITE")); //$NON-NLS-1$ + break; + + case 40: + text.setStyle(style.setBackground("BLACK")); //$NON-NLS-1$ + break; + + case 41: + text.setStyle(style.setBackground("RED")); //$NON-NLS-1$ + break; + + case 42: + text.setStyle(style.setBackground("GREEN")); //$NON-NLS-1$ + break; + + case 43: + text.setStyle(style.setBackground("YELLOW")); //$NON-NLS-1$ + break; + + case 44: + text.setStyle(style.setBackground("BLUE")); //$NON-NLS-1$ + break; + + case 45: + text.setStyle(style.setBackground("MAGENTA")); //$NON-NLS-1$ + break; + + case 46: + text.setStyle(style.setBackground("CYAN")); //$NON-NLS-1$ + break; + + case 47: + text.setStyle(style.setBackground("WHITE")); //$NON-NLS-1$ + break; + + default: + Logger + .log("Unsupported graphics rendition parameter: " + ansiParameter); //$NON-NLS-1$ + break; + } + + ++parameterIndex; + } + } + + /** + * This method responds to an ANSI Device Status Report (DSR) command from + * the remote endpoint requesting the cursor position. Requests for other + * kinds of status are ignored. + */ + private void processAnsiCommand_n() { + // Do nothing if the numeric parameter was not 6 (which means report cursor + // position). + + if (getAnsiParameter(0) != 6) + return; + + // Send the ANSI cursor position (which is 1-based) to the remote endpoint. + + String positionReport = "\u001b[" + (relativeCursorLine() + 1) + ";" + //$NON-NLS-1$ //$NON-NLS-2$ + (getCursorColumn() + 1) + "R"; //$NON-NLS-1$ + + OutputStreamWriter streamWriter = new OutputStreamWriter(terminal + .getOutputStream(), Charset.forName("ISO-8859-1")); //$NON-NLS-1$ + + try { + streamWriter.write(positionReport, 0, positionReport.length()); + streamWriter.flush(); + } catch (IOException ex) { + Logger.log("Caught IOException!"); //$NON-NLS-1$ + } + } + + /** + * Deletes one or more characters starting at the current cursor position. + * Characters on the same line and to the right of the deleted characters + * move left. If there are no characters on the current line at or to the + * right of the cursor column, no text is deleted. + */ + private void processAnsiCommand_P() { + text.deleteCharacters(getAnsiParameter(0)); + } + + /** + * This method returns one of the numeric ANSI parameters received in the + * most recent escape sequence. + * + * @return The parameterIndexth numeric ANSI parameter or -1 if the + * index is out of range. + */ + private int getAnsiParameter(int parameterIndex) { + if (parameterIndex < 0 || parameterIndex >= ansiParameters.length) { + // This should never happen. + return -1; + } + + String parameter = ansiParameters[parameterIndex].toString(); + + if (parameter.length() == 0) + return 1; + + int parameterValue = 1; + + // Don't trust the remote endpoint to send well formed numeric + // parameters. + + try { + parameterValue = Integer.parseInt(parameter); + } catch (NumberFormatException ex) { + parameterValue = 1; + } + + return parameterValue; + } + + /** + * This method processes a single parameter character in an ANSI escape + * sequence. Parameters are the (optional) characters between the leading + * "\e[" and the command character in an escape sequence (e.g., in the + * escape sequence "\e[20;10H", the parameter characters are "20;10"). + * Parameters are integers separated by one or more ';'s. + */ + private void processAnsiParameterCharacter(char ch) { + if (ch == ';') { + ++nextAnsiParameter; + } else { + if (nextAnsiParameter < ansiParameters.length) + ansiParameters[nextAnsiParameter].append(ch); + } + } + /** + * This method processes a contiguous sequence of non-control characters. + * This is a performance optimization, so that we don't have to insert or + * append each non-control character individually to the StyledText widget. + * A non-control character is any character that passes the condition in the + * below while loop. + * @throws IOException + */ + private void processNonControlCharacters(char character) throws IOException { + StringBuffer buffer=new StringBuffer(); + buffer.append(character); + // Identify a contiguous sequence of non-control characters, starting at + // firstNonControlCharacterIndex in newText. + while(hasNextChar()) { + character=getNextChar(); + if(character == '\u0000' || character == '\b' || character == '\t' + || character == '\u0007' || character == '\n' + || character == '\r' || character == '\u001b') { + pushBackChar(character); + break; + } + buffer.append(character); + } + + // Now insert the sequence of non-control characters in the StyledText widget + // at the location of the cursor. + + displayNewText(buffer.toString()); + } + + /** + * This method displays a subset of the newly-received text in the Terminal + * view, wrapping text at the right edge of the screen and overwriting text + * when the cursor is not at the very end of the screen's text. + *

+ * + * There are never any ANSI control characters or escape sequences in the + * text being displayed by this method (this includes newlines, carriage + * returns, and tabs). + *

+ */ + private void displayNewText(String buffer) { + text.appendString(buffer); + } + + + /** + * Process a BEL (Control-G) character. + */ + private void processBEL() { + // TODO + //Display.getDefault().beep(); + } + + /** + * Process a backspace (Control-H) character. + */ + private void processBackspace() { + moveCursorBackward(1); + } + + /** + * Process a tab (Control-I) character. We don't insert a tab character into + * the StyledText widget. Instead, we move the cursor forward to the next + * tab stop, without altering any of the text. Tab stops are every 8 + * columns. The cursor will never move past the rightmost column. + */ + private void processTab() { + moveCursorForward(8 - (getCursorColumn() % 8)); + } + + /** + * Process a newline (Control-J) character. A newline (NL) character just + * moves the cursor to the same column on the next line, creating new lines + * when the cursor reaches the bottom edge of the terminal. This is + * counter-intuitive, especially to UNIX programmers who are taught that + * writing a single NL to a terminal is sufficient to move the cursor to the + * first column of the next line, as if a carriage return (CR) and a NL were + * written. + *

+ * + * UNIX terminals typically display a NL character as a CR followed by a NL + * because the terminal device typically has the ONLCR attribute bit set + * (see the termios(4) man page for details), which causes the terminal + * device driver to translate NL to CR + NL on output. The terminal itself + * (i.e., a hardware terminal or a terminal emulator, like xterm or this + * code) _always_ interprets a CR to mean "move the cursor to the beginning + * of the current line" and a NL to mean "move the cursor to the same column + * on the next line". + *

+ */ + private void processNewline() { + text.processNewline(); + } + + /** + * Process a Carriage Return (Control-M). + */ + private void processCarriageReturn() { + text.setCursorColumn(0); + } + + /** + * This method computes the width of the terminal in columns and its height + * in lines, then adjusts the width and height of the view's StyledText + * widget so that it displays an integral number of lines and columns of + * text. The adjustment is always to shrink the widget vertically or + * horizontally, because if the control were to grow, it would be clipped by + * the edges of the view window (i.e., the view window does not become + * larger to accommodate its contents becoming larger). + *

+ * + * This method must be called immediately before each time text is written + * to the terminal so that we can properly line wrap text. Because it is + * called so frequently, it must be fast when there is no resizing to be + * done. + *

+ */ + private void adjustTerminalDimensions() { + // Compute how many pixels we need to shrink the StyledText control vertically + // to make it display an integral number of lines of text. + + // TODO +// if(text.getColumns()!=80 && text.getLines()!=80) +// text.setDimensions(24, 80); + // If we are in a TELNET connection and we know the dimensions of the terminal, + // we give the size information to the TELNET connection object so it can + // communicate it to the TELNET server. If we are in a serial connection, + // there is nothing we can do to tell the remote host about the size of the + // terminal. + ITerminalConnector telnetConnection = getConnector(); + // TODO MSA: send only if dimensions have really changed! + if (telnetConnection != null) { + telnetConnection.setTerminalSize(text.getColumns(), text.getLines()); + } + + } + + private ITerminalConnector getConnector() { + if(terminal.getTerminalConnectorInfo()!=null) + return terminal.getTerminalConnectorInfo().getConnector(); + return null; + } + + /** + * This method returns the relative line number of the line containing the + * cursor. The returned line number is relative to the topmost visible line, + * which has relative line number 0. + * + * @return The relative line number of the line containing the cursor. + */ + private int relativeCursorLine() { + return text.getCursorLine(); + } + + /** + * This method moves the cursor to the specified line and column. Parameter + * targetLine is the line number of a screen line, so it has a + * minimum value of 0 (the topmost screen line) and a maximum value of + * heightInLines - 1 (the bottommost screen line). A line does not have to + * contain any text to move the cursor to any column in that line. + */ + private void moveCursor(int targetLine, int targetColumn) { + text.setCursor(targetLine,targetColumn); + } + + /** + * This method moves the cursor down lines lines, but won't move the + * cursor past the bottom of the screen. This method does not cause any + * scrolling. + */ + private void moveCursorDown(int lines) { + moveCursor(relativeCursorLine() + lines, getCursorColumn()); + } + + /** + * This method moves the cursor up lines lines, but won't move the + * cursor past the top of the screen. This method does not cause any + * scrolling. + */ + private void moveCursorUp(int lines) { + moveCursor(relativeCursorLine() - lines, getCursorColumn()); + } + + /** + * This method moves the cursor forward columns columns, but won't + * move the cursor past the right edge of the screen, nor will it move the + * cursor onto the next line. This method does not cause any scrolling. + */ + private void moveCursorForward(int columnsToMove) { + moveCursor(relativeCursorLine(), getCursorColumn() + columnsToMove); + } + + /** + * This method moves the cursor backward columnsToMove columns, but + * won't move the cursor past the left edge of the screen, nor will it move + * the cursor onto the previous line. This method does not cause any + * scrolling. + */ + private void moveCursorBackward(int columnsToMove) { + moveCursor(relativeCursorLine(), getCursorColumn() - columnsToMove); + } + /** + * Resets the state of the terminal text (foreground color, background color, + * font style and other internal state). It essentially makes it ready for new input. + */ + public void resetState() { + ansiState=ANSISTATE_INITIAL; + text.setStyle(text.getDefaultStyle()); + } + +// public OutputStream getOutputStream() { +// return fTerminalInputStream.getOutputStream(); +// } + + /** + * Buffer for {@link #pushBackChar(char)}. + */ + private int fNextChar=-1; + private char getNextChar() throws IOException { + int c=-1; + if(fNextChar!=-1) { + c= fNextChar; + fNextChar=-1; + } else { + c = fReader.read(); + } + // TODO: better end of file handling + if(c==-1) + c=0; + return (char)c; + } + + private boolean hasNextChar() throws IOException { + if(fNextChar>=0) + return true; + return fReader.ready(); + } + + /** + * Put back one character to the stream. This method can push + * back exactly one character. The character is the next character + * returned by {@link #getNextChar} + * @param c the character to be pushed back. + */ + void pushBackChar(char c) { + //assert fNextChar!=-1: "Already a character waiting:"+fNextChar; //$NON-NLS-1$ + fNextChar=c; + } + private int getCursorColumn() { + return text.getCursorColumn(); + } + public boolean isCrAfterNewLine() { + return fCrAfterNewLine; + } + public void setCrAfterNewLine(boolean crAfterNewLine) { + fCrAfterNewLine = crAfterNewLine; + } +} diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100EmulatorBackend.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100EmulatorBackend.java new file mode 100644 index 00000000000..cda57c62c3a --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100EmulatorBackend.java @@ -0,0 +1,387 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. and others. + * 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 + * + * Contributors: + * Michael Scharf (Wind River) - initial API and implementation + *******************************************************************************/ +package org.eclipse.tm.internal.terminal.emulator; + +import org.eclipse.tm.terminal.model.ITerminalTextData; +import org.eclipse.tm.terminal.model.Style; + +/** + * + */ +public class VT100EmulatorBackend implements IVT100EmulatorBackend { + + /** + * This field holds the number of the column in which the cursor is + * logically positioned. The leftmost column on the screen is column 0, and + * column numbers increase to the right. The maximum value of this field is + * {@link #widthInColumns} - 1. We track the cursor column using this field + * to avoid having to recompute it repeatly using StyledText method calls. + *

+ * + * The StyledText widget that displays text has a vertical bar (called the + * "caret") that appears _between_ character cells, but ANSI terminals have + * the concept of a cursor that appears _in_ a character cell, so we need a + * convention for which character cell the cursor logically occupies when + * the caret is physically between two cells. The convention used in this + * class is that the cursor is logically in column N when the caret is + * physically positioned immediately to the _left_ of column N. + *

+ * + * When fCursorColumn is N, the next character output to the terminal appears + * in column N. When a character is output to the rightmost column on a + * given line (column widthInColumns - 1), the cursor moves to column 0 on + * the next line after the character is drawn (this is how line wrapping is + * implemented). If the cursor is in the bottommost line when line wrapping + * occurs, the topmost visible line is scrolled off the top edge of the + * screen. + *

+ */ + private int fCursorColumn; + private int fCursorLine; + private Style fDefaultStyle; + private Style fStyle; + int fLines; + int fColumns; + final private ITerminalTextData fTerminal; + public VT100EmulatorBackend(ITerminalTextData terminal) { + fTerminal=terminal; + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#clearAll() + */ + public void clearAll() { + synchronized (fTerminal) { + // clear the history + int n=fTerminal.getHeight(); + for (int line = 0; line < n; line++) { + fTerminal.cleanLine(line); + } + fTerminal.setDimensions(fLines, fTerminal.getWidth()); + setStyle(getDefaultStyle()); + setCursor(0, 0); + } + } + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#setDimensions(int, int) + */ + public void setDimensions(int lines, int cols) { + synchronized (fTerminal) { + if(lines==fLines && cols==fColumns) + return; // nothing to do + // cursor line from the bottom + int cl=fLines-getCursorLine(); + int cc=getCursorColumn(); + + fLines=lines; + fColumns=cols; + // make the terminal at least as high as we need lines + fTerminal.setDimensions(Math.max(fLines,fTerminal.getHeight()), fColumns); + setCursor(fLines-cl, cc); + } + } + + int toAbsoluteLine(int line) { + synchronized (fTerminal) { + return fTerminal.getHeight()-fLines+line; + } + } + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#insertCharacters(int) + */ + public void insertCharacters(int charactersToInsert) { + synchronized (fTerminal) { + int line=toAbsoluteLine(fCursorLine); + int n=charactersToInsert; + for (int col = fColumns-1; col >=fCursorColumn+n; col--) { + char c=fTerminal.getChar(line, col-n); + Style style=fTerminal.getStyle(line, col-n); + fTerminal.setChar(line, col,c, style); + } + int last=Math.min(fCursorColumn+n, fColumns); + for (int col = fCursorColumn; col 0; + int line=toAbsoluteLine(fCursorLine); + int nLines=fTerminal.getHeight()-line; + fTerminal.scroll(line, nLines, n); + } + } + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#deleteCharacters(int) + */ + public void deleteCharacters(int n) { + synchronized (fTerminal) { + int line=toAbsoluteLine(fCursorLine); + for (int col = fCursorColumn+n; col < fColumns; col++) { + char c=fTerminal.getChar(line, col); + Style style=fTerminal.getStyle(line, col); + fTerminal.setChar(line, col-n,c, style); + } + int first=Math.max(fCursorColumn, fColumns-n); + for (int col = first; col 0; + int line=toAbsoluteLine(fCursorLine); + int nLines=fTerminal.getHeight()-line; + fTerminal.scroll(line, nLines, -n); + } + } + private boolean isCusorInScrollingRegion() { + // TODO Auto-generated method stub + return true; + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#getDefaultStyle() + */ + public Style getDefaultStyle() { + synchronized (fTerminal) { + return fDefaultStyle; + } + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#setDefaultStyle(org.eclipse.tm.terminal.model.Style) + */ + public void setDefaultStyle(Style defaultStyle) { + synchronized (fTerminal) { + fDefaultStyle = defaultStyle; + } + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#getStyle() + */ + public Style getStyle() { + synchronized (fTerminal) { + if(fStyle==null) + return fDefaultStyle; + return fStyle; + } + } + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#setStyle(org.eclipse.tm.terminal.model.Style) + */ + public void setStyle(Style style) { + synchronized (fTerminal) { + fStyle=style; + } + } + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#appendString(java.lang.String) + */ + public void appendString(String buffer) { + synchronized (fTerminal) { + char[] chars=buffer.toCharArray(); + int line=toAbsoluteLine(fCursorLine); + int i=0; + while (i < chars.length) { + int n=Math.min(fColumns-fCursorColumn,chars.length-i); + fTerminal.setChars(line, fCursorColumn, chars, i, n, fStyle); + int col=fCursorColumn+n; + i+=n; + // wrap needed? + if(col>=fColumns) { + doNewline(); + line=toAbsoluteLine(fCursorLine); + setCursorColumn(0); + } else { + setCursorColumn(col); + } + } + } + } + + /** + * MUST be called from a synchronized block! + */ + private void doNewline() { + if(fCursorLine+1>=fLines) { + int h=fTerminal.getHeight(); + fTerminal.addLine(); + if(h!=fTerminal.getHeight()) + setCursorLine(fCursorLine+1); + } else { + setCursorLine(fCursorLine+1); + } + } + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#processNewline() + */ + public void processNewline() { + synchronized (fTerminal) { + doNewline(); + } + } + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#getCursorLine() + */ + public int getCursorLine() { + synchronized (fTerminal) { + return fCursorLine; + } + } + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#getCursorColumn() + */ + public int getCursorColumn() { + synchronized (fTerminal) { + return fCursorColumn; + } + } + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#setCursor(int, int) + */ + public void setCursor(int targetLine, int targetColumn) { + synchronized (fTerminal) { + setCursorLine(targetLine); + setCursorColumn(targetColumn); + } + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#setCursorColumn(int) + */ + public void setCursorColumn(int targetColumn) { + synchronized (fTerminal) { + if(targetColumn<0) + targetColumn=0; + else if(targetColumn>=fColumns) + targetColumn=fColumns-1; + fCursorColumn=targetColumn; + // We make the assumption that nobody is changing the + // terminal cursor except this class! + // This assumption gives a huge performance improvement + fTerminal.setCursorColumn(targetColumn); + } + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#setCursorLine(int) + */ + public void setCursorLine(int targetLine) { + synchronized (fTerminal) { + if(targetLine<0) + targetLine=0; + else if(targetLine>=fLines) + targetLine=fLines-1; + fCursorLine=targetLine; + // We make the assumption that nobody is changing the + // terminal cursor except this class! + // This assumption gives a huge performance improvement + fTerminal.setCursorLine(toAbsoluteLine(targetLine)); + } + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#getLines() + */ + public int getLines() { + synchronized (fTerminal) { + return fLines; + } + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.emulator.IVT100EmulatorBackend#getColumns() + */ + public int getColumns() { + synchronized (fTerminal) { + return fColumns; + } + } +} diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100TerminalControl.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100TerminalControl.java new file mode 100644 index 00000000000..441d0c98071 --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/emulator/VT100TerminalControl.java @@ -0,0 +1,906 @@ +/******************************************************************************* + * Copyright (c) 2003, 2007 Wind River Systems, Inc. and others. + * 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 Contributors: + * The following Wind River employees contributed to the Terminal component + * that contains this file: Chris Thew, Fran Litterio, Stephen Lamb, + * Helmut Haigermoser and Ted Williams. + * + * Contributors: + * Michael Scharf (Wind River) - split into core, view and connector plugins + * Martin Oberhuber (Wind River) - fixed copyright headers and beautified + *******************************************************************************/ +package org.eclipse.tm.internal.terminal.emulator; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.SocketException; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.dnd.Clipboard; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.tm.internal.terminal.control.ICommandInputField; +import org.eclipse.tm.internal.terminal.control.ITerminalListener; +import org.eclipse.tm.internal.terminal.control.ITerminalViewControl; +import org.eclipse.tm.internal.terminal.control.impl.ITerminalControlForText; +import org.eclipse.tm.internal.terminal.control.impl.TerminalMessages; +import org.eclipse.tm.internal.terminal.control.impl.TerminalPlugin; +import org.eclipse.tm.internal.terminal.provisional.api.ITerminalConnector; +import org.eclipse.tm.internal.terminal.provisional.api.ITerminalConnectorInfo; +import org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl; +import org.eclipse.tm.internal.terminal.provisional.api.Logger; +import org.eclipse.tm.internal.terminal.provisional.api.TerminalState; +import org.eclipse.tm.internal.terminal.textcanvas.ITextCanvasModel; +import org.eclipse.tm.internal.terminal.textcanvas.PipedInputStream; +import org.eclipse.tm.internal.terminal.textcanvas.PollingTextCanvasModel; +import org.eclipse.tm.internal.terminal.textcanvas.TextCanvas; +import org.eclipse.tm.internal.terminal.textcanvas.TextLineRenderer; +import org.eclipse.tm.terminal.model.ITerminalTextData; +import org.eclipse.tm.terminal.model.ITerminalTextDataSnapshot; +import org.eclipse.tm.terminal.model.TerminalTextDataFactory; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.contexts.IContextActivation; +import org.eclipse.ui.contexts.IContextService; +import org.eclipse.ui.keys.IBindingService; + +/** + * + * This class was originally written to use nested classes, which unfortunately makes + * this source file larger and more complex than it needs to be. In particular, the + * methods in the nested classes directly access the fields of the enclosing class. + * One day we should pull the nested classes out into their own source files (but still + * in this package). + * + * @author Chris Thew + */ +public class VT100TerminalControl implements ITerminalControlForText, ITerminalControl, ITerminalViewControl +{ + protected final static String[] LINE_DELIMITERS = { "\n" }; //$NON-NLS-1$ + + /** + * This field holds a reference to a TerminalText object that performs all ANSI + * text processing on data received from the remote host and controls how text is + * displayed using the view's StyledText widget. + */ + private VT100Emulator fTerminalText; + private Display fDisplay; + private TextCanvas fCtlText; + private Composite fWndParent; + private Clipboard fClipboard; + private KeyListener fKeyHandler; + private ITerminalListener fTerminalListener; + private String fMsg = ""; //$NON-NLS-1$ + private FocusListener fFocusListener; + private ITerminalConnectorInfo fConnectorInfo; + private final ITerminalConnectorInfo[] fConnectors; + PipedInputStream fInputStream; + + private ICommandInputField fCommandInputField; + + private volatile TerminalState fState; + + private ITerminalTextData fTerminalModel; + + volatile private Job fJob; + + public VT100TerminalControl(ITerminalListener target, Composite wndParent, ITerminalConnectorInfo[] connectors) { + fConnectors=connectors; + fTerminalListener=target; + fTerminalModel=TerminalTextDataFactory.makeTerminalTextData(); + fTerminalModel.setDimensions(24, 80); + fTerminalModel.setMaxHeight(1000); + fInputStream=new PipedInputStream(8*1024); + fTerminalText=new VT100Emulator(fTerminalModel,this,fInputStream); + + setupTerminal(wndParent); + } + + public ITerminalConnectorInfo[] getConnectors() { + return fConnectors; + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#copy() + */ + public void copy() { + getCtlText().copy(); + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#paste() + */ + public void paste() { + TextTransfer textTransfer = TextTransfer.getInstance(); + String strText = (String) fClipboard.getContents(textTransfer); + pasteString(strText); +// TODO paste in another thread.... to avoid blocking +// new Thread() { +// public void run() { +// for (int i = 0; i < strText.length(); i++) { +// sendChar(strText.charAt(i), false); +// } +// +// } +// }.start(); + } + + /** + * @param strText + */ + public boolean pasteString(String strText) { + if(!isConnected()) + return false; + if (strText == null) + return false; + for (int i = 0; i < strText.length(); i++) { + sendChar(strText.charAt(i), false); + } + return true; + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#selectAll() + */ + public void selectAll() { + getCtlText().selectAll(); + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#sendKey(char) + */ + public void sendKey(char character) { + Event event; + KeyEvent keyEvent; + + event = new Event(); + event.widget = getCtlText(); + event.character = character; + event.keyCode = 0; + event.stateMask = 0; + event.doit = true; + keyEvent = new KeyEvent(event); + + fKeyHandler.keyPressed(keyEvent); + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#clearTerminal() + */ + public void clearTerminal() { + // The TerminalText object does all text manipulation. + + getTerminalText().clearTerminal(); + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#getClipboard() + */ + public Clipboard getClipboard() { + return fClipboard; + } + + /** + * @return non null selection + */ + public String getSelection() { + String txt= fCtlText.getSelectionText(); + if(txt==null) + txt=""; //$NON-NLS-1$ + return txt; + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#setFocus() + */ + public void setFocus() { + getCtlText().setFocus(); + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#isEmpty() + */ + public boolean isEmpty() { + return getCtlText().isEmpty(); + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#isDisposed() + */ + public boolean isDisposed() { + return getCtlText().isDisposed(); + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#isConnected() + */ + public boolean isConnected() { + return fState==TerminalState.CONNECTED; + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#disposeTerminal() + */ + public void disposeTerminal() { + Logger.log("entered."); //$NON-NLS-1$ + disconnectTerminal(); + fClipboard.dispose(); + getTerminalText().dispose(); + } + + public void connectTerminal() { + Logger.log("entered."); //$NON-NLS-1$ + if(getTerminalConnector()==null) + return; + fTerminalText.resetState(); + if(fConnectorInfo.getInitializationErrorMessage()!=null) { + showErrorMessage(NLS.bind( + TerminalMessages.CannotConnectTo, + fConnectorInfo.getName(), + fConnectorInfo.getInitializationErrorMessage())); + // we cannot connect because the connector was not initialized + return; + } + getTerminalConnector().connect(this); + // clean the error message + setMsg(""); //$NON-NLS-1$ + waitForConnect(); + } + + private ITerminalConnector getTerminalConnector() { + if(fConnectorInfo==null) + return null; + return fConnectorInfo.getConnector(); + } + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#disconnectTerminal() + */ + public void disconnectTerminal() { + Logger.log("entered."); //$NON-NLS-1$ + + if (getState()==TerminalState.CLOSED) { + return; + } + if(getTerminalConnector()!=null) { + getTerminalConnector().disconnect(); + } + } + + // TODO + private void waitForConnect() { + Logger.log("entered."); //$NON-NLS-1$ + // TODO + // Eliminate this code + while (getState()==TerminalState.CONNECTING) { + if (fDisplay.readAndDispatch()) + continue; + + fDisplay.sleep(); + } + if (!getMsg().equals("")) //$NON-NLS-1$ + { + showErrorMessage(getMsg()); + + disconnectTerminal(); + return; + } + getCtlText().setFocus(); + startReaderJob(); + + } + + private void startReaderJob() { + if(fJob==null) { + fJob=new Job("Terminal data reader") { //$NON-NLS-1$ + protected IStatus run(IProgressMonitor monitor) { + IStatus status=Status.OK_STATUS; + while(true) { + while(fInputStream.available()==0 && !monitor.isCanceled()) { + try { + fInputStream.waitForAvailable(500); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + if(monitor.isCanceled()) { + disconnectTerminal(); + status=Status.CANCEL_STATUS; + break; + } + try { + // TODO: should block when no text is available! + fTerminalText.processText(); + + } catch (Exception e) { + disconnectTerminal(); + status=new Status(IStatus.ERROR,TerminalPlugin.PLUGIN_ID,e.getLocalizedMessage(),e); + break; + } + } + // clean the job: start a new one when the connection getst restarted + fJob=null; + return status; + } + + }; + fJob.setSystem(true); + fJob.schedule(); + } + } + + private void showErrorMessage(String message) { + String strTitle = TerminalMessages.TerminalError; + MessageDialog.openError( getShell(), strTitle, message); + } + + protected void sendString(String string) { + try { + // Send the string after converting it to an array of bytes using the + // platform's default character encoding. + // + // TODO: Find a way to force this to use the ISO Latin-1 encoding. + + getOutputStream().write(string.getBytes()); + getOutputStream().flush(); + } catch (SocketException socketException) { + displayTextInTerminal(socketException.getMessage()); + + String strMsg = TerminalMessages.SocketError + + "!\n" + socketException.getMessage(); //$NON-NLS-1$ + showErrorMessage(strMsg); + + Logger.logException(socketException); + + disconnectTerminal(); + } catch (IOException ioException) { + showErrorMessage(TerminalMessages.IOError + "!\n" + ioException.getMessage());//$NON-NLS-1$ + + Logger.logException(ioException); + + disconnectTerminal(); + } + } + + public Shell getShell() { + return getCtlText().getShell(); + } + + protected void sendChar(char chKey, boolean altKeyPressed) { + try { + int byteToSend = chKey; + + if (altKeyPressed) { + // When the ALT key is pressed at the same time that a character is + // typed, translate it into an ESCAPE followed by the character. The + // alternative in this case is to set the high bit of the character + // being transmitted, but that will cause input such as ALT-f to be + // seen as the ISO Latin-1 character '�', which can be confusing to + // European users running Emacs, for whom Alt-f should move forward a + // word instead of inserting the '�' character. + // + // TODO: Make the ESCAPE-vs-highbit behavior user configurable. + + Logger.log("sending ESC + '" + byteToSend + "'"); //$NON-NLS-1$ //$NON-NLS-2$ + getOutputStream().write('\u001b'); + getOutputStream().write(byteToSend); + } else { + Logger.log("sending '" + byteToSend + "'"); //$NON-NLS-1$ //$NON-NLS-2$ + getOutputStream().write(byteToSend); + } + + getOutputStream().flush(); + } catch (SocketException socketException) { + Logger.logException(socketException); + + displayTextInTerminal(socketException.getMessage()); + + String strMsg = TerminalMessages.SocketError + + "!\n" + socketException.getMessage(); //$NON-NLS-1$ + + showErrorMessage(strMsg); + Logger.logException(socketException); + + disconnectTerminal(); + } catch (IOException ioException) { + Logger.logException(ioException); + + displayTextInTerminal(ioException.getMessage()); + + String strMsg = TerminalMessages.IOError + "!\n" + ioException.getMessage(); //$NON-NLS-1$ + + showErrorMessage(strMsg); + Logger.logException(ioException); + + disconnectTerminal(); + } + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#setupTerminal() + */ + public void setupTerminal(Composite parent) { + fState=TerminalState.CLOSED; + setupControls(parent); + setupListeners(); + setupHelp(fWndParent, TerminalPlugin.HELP_VIEW); + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#onFontChanged() + */ + public void setFont(Font font) { + getCtlText().setFont(font); + if(fCommandInputField!=null) { + fCommandInputField.setFont(font); + } + + // Tell the TerminalControl singleton that the font has changed. + + getTerminalText().fontChanged(); + } + public Font getFont() { + return getCtlText().getFont(); + } + public Control getControl() { + return fCtlText; + } + protected void setupControls(Composite parent) { + // The Terminal view now aims to be an ANSI-conforming terminal emulator, so it + // can't have a horizontal scroll bar (but a vertical one is ok). Also, do + // _not_ make the TextViewer read-only, because that prevents it from seeing a + // TAB character when the user presses TAB (instead, the TAB causes focus to + // switch to another Workbench control). We prevent local keyboard input from + // modifying the text in method TerminalVerifyKeyListener.verifyKey(). + + fWndParent=new Composite(parent,SWT.NONE); + GridLayout layout=new GridLayout(); + layout.marginWidth=0; + layout.marginHeight=0; + + fWndParent.setLayout(layout); + + ITerminalTextDataSnapshot snapshot=fTerminalModel.makeSnapshot(); + // TODO how to get the initial size correctly! + snapshot.updateSnapshot(false); + ITextCanvasModel canvasModel=new PollingTextCanvasModel(snapshot); + fCtlText=new TextCanvas(fWndParent,canvasModel,SWT.NONE); + fCtlText.setCellRenderer(new TextLineRenderer(fCtlText,canvasModel)); + + fCtlText.setLayoutData(new GridData(GridData.FILL_BOTH)); + fCtlText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + fCtlText.addListener(SWT.Resize, new Listener() { + public void handleEvent(Event e) { + Rectangle bonds=fCtlText.getClientArea(); + int lines=bonds.height/fCtlText.getCellHeight(); + int columns=bonds.width/fCtlText.getCellWidth(); + fTerminalText.setDimensions(lines, columns); + } + }); + + + fDisplay = getCtlText().getDisplay(); + fClipboard = new Clipboard(fDisplay); +// fViewer.setDocument(new TerminalDocument()); + setFont(JFaceResources.getTextFont()); + } + + protected void setupListeners() { + fKeyHandler = new TerminalKeyHandler(); + fFocusListener = new TerminalFocusListener(); + + getCtlText().addKeyListener(fKeyHandler); + getCtlText().addFocusListener(fFocusListener); + + } + + /** + * Setup all the help contexts for the controls. + */ + protected void setupHelp(Composite parent, String id) { + Control[] children = parent.getChildren(); + + for (int nIndex = 0; nIndex < children.length; nIndex++) { + if (children[nIndex] instanceof Composite) { + setupHelp((Composite) children[nIndex], id); + } + } + + PlatformUI.getWorkbench().getHelpSystem().setHelp(parent, id); + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#displayTextInTerminal(java.lang.String) + */ + public void displayTextInTerminal(String text) { + writeToTerminal("\r\n"+text+"\r\n"); //$NON-NLS-1$ //$NON-NLS-2$ + } + private void writeToTerminal(String text) { + try { + getRemoteToTerminalOutputStream().write(text.getBytes("ISO-8859-1")); //$NON-NLS-1$ + } catch (UnsupportedEncodingException e) { + // should never happen! + e.printStackTrace(); + } catch (IOException e) { + // should never happen! + e.printStackTrace(); + } + + } + + public OutputStream getRemoteToTerminalOutputStream() { + return fInputStream.getOutputStream(); + } + protected boolean isLogCharEnabled() { + return TerminalPlugin.isOptionEnabled(Logger.TRACE_DEBUG_LOG_CHAR); + } + protected boolean isLogBufferSizeEnabled() { + return TerminalPlugin + .isOptionEnabled(Logger.TRACE_DEBUG_LOG_BUFFER_SIZE); + } + + + public OutputStream getOutputStream() { + if(getTerminalConnector()!=null) + return getTerminalConnector().getOutputStream(); + return null; + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#setMsg(java.lang.String) + */ + public void setMsg(String msg) { + fMsg = msg; + } + + public String getMsg() { + return fMsg; + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#getCtlText() + */ + protected TextCanvas getCtlText() { + return fCtlText; + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#getTerminalText() + */ + public VT100Emulator getTerminalText() { + return fTerminalText; + } + + /** + */ + public ITerminalConnectorInfo getTerminalConnectorInfo() { + return fConnectorInfo; + } + + protected class TerminalFocusListener implements FocusListener { + private IContextActivation contextActivation = null; + + protected TerminalFocusListener() { + super(); + } + + public void focusGained(FocusEvent event) { + // Disable all keyboard accelerators (e.g., Control-B) so the Terminal view + // can see every keystroke. Without this, Emacs, vi, and Bash are unusable + // in the Terminal view. + + IBindingService bindingService = (IBindingService) PlatformUI + .getWorkbench().getAdapter(IBindingService.class); + bindingService.setKeyFilterEnabled(false); + + // The above code fails to cause Eclipse to disable menu-activation + // accelerators (e.g., Alt-F for the File menu), so we set the command + // context to be the Terminal view's command context. This enables us to + // override menu-activation accelerators with no-op commands in our + // plugin.xml file, which enables the Terminal view to see absolutly _all_ + // key-presses. + + IContextService contextService = (IContextService) PlatformUI + .getWorkbench().getAdapter(IContextService.class); + contextActivation = contextService + .activateContext("org.eclipse.tm.terminal.TerminalContext"); //$NON-NLS-1$ + } + + public void focusLost(FocusEvent event) { + // Enable all keybindings. + + IBindingService bindingService = (IBindingService) PlatformUI + .getWorkbench().getAdapter(IBindingService.class); + bindingService.setKeyFilterEnabled(true); + + // Restore the command context to its previous value. + + IContextService contextService = (IContextService) PlatformUI + .getWorkbench().getAdapter(IContextService.class); + contextService.deactivateContext(contextActivation); + } + } + + protected class TerminalKeyHandler extends KeyAdapter { + public void keyPressed(KeyEvent event) { + if (getState()==TerminalState.CONNECTING) + return; + + // We set the event.doit to false to prevent any further processing of this + // key event. The only reason this is here is because I was seeing the F10 + // key both send an escape sequence (due to this method) and switch focus + // to the Workbench File menu (forcing the user to click in the Terminal + // view again to continue entering text). This fixes that. + + event.doit = false; + + char character = event.character; + + if (!isConnected()) { + // Pressing ENTER while not connected causes us to connect. + if (character == '\r') { + connectTerminal(); + return; + } + + // Ignore all other keyboard input when not connected. + return; + } + + // If the event character is NUL ('\u0000'), then a special key was pressed + // (e.g., PageUp, PageDown, an arrow key, a function key, Shift, Alt, + // Control, etc.). The one exception is when the user presses Control-@, + // which sends a NUL character, in which case we must send the NUL to the + // remote endpoint. This is necessary so that Emacs will work correctly, + // because Control-@ (i.e., NUL) invokes Emacs' set-mark-command when Emacs + // is running on a terminal. When the user presses Control-@, the keyCode + // is 50. + + if (character == '\u0000' && event.keyCode != 50) { + // A special key was pressed. Figure out which one it was and send the + // appropriate ANSI escape sequence. + // + // IMPORTANT: Control will not enter this method for these special keys + // unless certain tags are present in the plugin.xml file + // for the Terminal view. Do not delete those tags. + + switch (event.keyCode) { + case 0x1000001: // Up arrow. + sendString("\u001b[A"); //$NON-NLS-1$ + break; + + case 0x1000002: // Down arrow. + sendString("\u001b[B"); //$NON-NLS-1$ + break; + + case 0x1000003: // Left arrow. + sendString("\u001b[D"); //$NON-NLS-1$ + break; + + case 0x1000004: // Right arrow. + sendString("\u001b[C"); //$NON-NLS-1$ + break; + + case 0x1000005: // PgUp key. + sendString("\u001b[I"); //$NON-NLS-1$ + break; + + case 0x1000006: // PgDn key. + sendString("\u001b[G"); //$NON-NLS-1$ + break; + + case 0x1000007: // Home key. + sendString("\u001b[H"); //$NON-NLS-1$ + break; + + case 0x1000008: // End key. + sendString("\u001b[F"); //$NON-NLS-1$ + break; + + case 0x100000a: // F1 key. + sendString("\u001b[M"); //$NON-NLS-1$ + break; + + case 0x100000b: // F2 key. + sendString("\u001b[N"); //$NON-NLS-1$ + break; + + case 0x100000c: // F3 key. + sendString("\u001b[O"); //$NON-NLS-1$ + break; + + case 0x100000d: // F4 key. + sendString("\u001b[P"); //$NON-NLS-1$ + break; + + case 0x100000e: // F5 key. + sendString("\u001b[Q"); //$NON-NLS-1$ + break; + + case 0x100000f: // F6 key. + sendString("\u001b[R"); //$NON-NLS-1$ + break; + + case 0x1000010: // F7 key. + sendString("\u001b[S"); //$NON-NLS-1$ + break; + + case 0x1000011: // F8 key. + sendString("\u001b[T"); //$NON-NLS-1$ + break; + + case 0x1000012: // F9 key. + sendString("\u001b[U"); //$NON-NLS-1$ + break; + + case 0x1000013: // F10 key. + sendString("\u001b[V"); //$NON-NLS-1$ + break; + + case 0x1000014: // F11 key. + sendString("\u001b[W"); //$NON-NLS-1$ + break; + + case 0x1000015: // F12 key. + sendString("\u001b[X"); //$NON-NLS-1$ + break; + + default: + // Ignore other special keys. Control flows through this case when + // the user presses SHIFT, CONTROL, ALT, and any other key not + // handled by the above cases. + break; + } + + // It's ok to return here, because we never locally echo special keys. + + return; + } + + // To fix SPR 110341, we consider the Alt key to be pressed only when the + // Control key is _not_ also pressed. This works around a bug in SWT where, + // on European keyboards, the AltGr key being pressed appears to us as Control + // + Alt being pressed simultaneously. + + Logger.log("stateMask = " + event.stateMask); //$NON-NLS-1$ + + boolean altKeyPressed = (((event.stateMask & SWT.ALT) != 0) && ((event.stateMask & SWT.CTRL) == 0)); + + if (!altKeyPressed && (event.stateMask & SWT.CTRL) != 0 + && character == ' ') { + // Send a NUL character -- many terminal emulators send NUL when + // Control-Space is pressed. This is used to set the mark in Emacs. + + character = '\u0000'; + } + + sendChar(character, altKeyPressed); + + // Special case: When we are in a TCP connection and echoing characters + // locally, send a LF after sending a CR. + // ISSUE: Is this absolutely required? + + if (character == '\r' && getTerminalConnectorInfo() != null + && isConnected() + && getTerminalConnectorInfo().getConnector().isLocalEcho()) { + sendChar('\n', false); + } + + // Now decide if we should locally echo the character we just sent. We do + // _not_ locally echo the character if any of these conditions are true: + // + // o This is a serial connection. + // + // o This is a TCP connection (i.e., m_telnetConnection is not null) and + // the remote endpoint is not a TELNET server. + // + // o The ALT (or META) key is pressed. + // + // o The character is any of the first 32 ISO Latin-1 characters except + // Control-I or Control-M. + // + // o The character is the DELETE character. + + if (getTerminalConnectorInfo() == null + || getTerminalConnectorInfo().getConnector().isLocalEcho() == false || altKeyPressed + || (character >= '\u0001' && character < '\t') + || (character > '\t' && character < '\r') + || (character > '\r' && character <= '\u001f') + || character == '\u007f') { + // No local echoing. + return; + } + + // Locally echo the character. + + StringBuffer charBuffer = new StringBuffer(); + charBuffer.append(character); + + // If the character is a carriage return, we locally echo it as a CR + LF + // combination. + + if (character == '\r') + charBuffer.append('\n'); + + writeToTerminal(charBuffer.toString()); + } + + } + + public void setTerminalTitle(String title) { + fTerminalListener.setTerminalTitle(title); + } + + + public TerminalState getState() { + return fState; + } + + + public void setState(TerminalState state) { + fState=state; + fTerminalListener.setState(state); + } + + public String getSettingsSummary() { + if(getTerminalConnector()!=null) + return getTerminalConnector().getSettingsSummary(); + return ""; //$NON-NLS-1$ + } + + public void setConnector(ITerminalConnectorInfo connector) { + fConnectorInfo=connector; + + } + public ICommandInputField getCommandInputField() { + return fCommandInputField; + } + + public void setCommandInputField(ICommandInputField inputField) { + if(fCommandInputField!=null) + fCommandInputField.dispose(); + fCommandInputField=inputField; + if(fCommandInputField!=null) + fCommandInputField.createControl(fWndParent, this); + fWndParent.layout(true); + } + + public int getBufferLineLimit() { + return fTerminalModel.getMaxHeight(); + } + + public void setBufferLineLimit(int bufferLineLimit) { + if(bufferLineLimit<=0) + return; + synchronized (fTerminalModel) { + if(fTerminalModel.getHeight()>bufferLineLimit) + fTerminalModel.setDimensions(bufferLineLimit, fTerminalModel.getWidth()); + fTerminalModel.setMaxHeight(bufferLineLimit); + } + } +} diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/model/ISnapshotChanges.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/model/ISnapshotChanges.java new file mode 100644 index 00000000000..15d7d9bf1f6 --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/model/ISnapshotChanges.java @@ -0,0 +1,80 @@ +package org.eclipse.tm.internal.terminal.model; + +import org.eclipse.tm.terminal.model.ITerminalTextData; + +public interface ISnapshotChanges { + + /** + * @param line might bigger than the number of lines.... + */ + void markLineChanged(int line); + + /** + * Marks all lines in the range as changed + * @param line >=0 + * @param n might be out of range + */ + void markLinesChanged(int line, int n); + + /** + * Marks all lines within the scrolling region + * changed and resets the scrolling information + */ + void convertScrollingIntoChanges(); + + /** + * @return true if something has changed + */ + boolean hasChanged(); + + /** + * @param startLine + * @param size + * @param shift + */ + void scroll(int startLine, int size, int shift); + + /** + * Mark all lines changed + * @param height if no window is set this is the number of + * lines that are marked as changed + */ + void setAllChanged(int height); + + int getFirstChangedLine(); + + int getLastChangedLine(); + + int getScrollWindowStartLine(); + + int getScrollWindowSize(); + + int getScrollWindowShift(); + + boolean hasLineChanged(int line); + + void markDimensionsChanged(); + boolean hasDimensionsChanged(); + void markCursorChanged(); + + /** + * @return true if the terminal data has changed + */ + boolean hasTerminalChanged(); + /** + * mark the terminal as changed + */ + void setTerminalChanged(); + + + void copyChangedLines(ITerminalTextData dest, ITerminalTextData source); + + /** + * @param startLine -1 means follow the end of the data + * @param size number of lines to follow + */ + void setInterestWindow(int startLine, int size); + int getInterestWindowStartLine(); + int getInterestWindowSize(); + +} \ No newline at end of file diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/model/SnapshotChanges.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/model/SnapshotChanges.java new file mode 100644 index 00000000000..91b72481a93 --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/model/SnapshotChanges.java @@ -0,0 +1,381 @@ +package org.eclipse.tm.internal.terminal.model; + +import org.eclipse.tm.terminal.model.ITerminalTextData; + + +/** + * Collects the changes of the {@link ITerminalTextData} + * + */ +public class SnapshotChanges implements ISnapshotChanges { + /** + * The first line changed + */ + private int fFirstChangedLine; + /** + * The last line changed + */ + private int fLastChangedLine; + private int fScrollWindowStartLine; + private int fScrollWindowSize; + private int fScrollWindowShift; + /** + * true, if scrolling should not tracked anymore + */ + private boolean fScrollDontTrack; + /** + * The lines that need to be copied + * into the snapshot (lines that have + * not changed don't have to be copied) + */ + private boolean[] fChangedLines; + + private int fInterestWindowSize; + private int fInterestWindowStartLine; + private boolean fDimensionsChanged; + private boolean fTerminalHasChanged; + private boolean fCursorHasChanged; + + public SnapshotChanges(int nLines) { + setChangedLinesLength(nLines); + fFirstChangedLine=Integer.MAX_VALUE; + fLastChangedLine=-1; + } + public SnapshotChanges(int windowStart, int windowSize) { + setChangedLinesLength(windowStart+windowSize); + fFirstChangedLine=Integer.MAX_VALUE; + fLastChangedLine=-1; + fInterestWindowStartLine=windowStart; + fInterestWindowSize=windowSize; + + } + /** + * This is used in asserts to throw an {@link RuntimeException}. + * This is useful for tests. + * @return never -- throws an exception + */ + private boolean throwRuntimeException() { + throw new RuntimeException(); + } + /** + * @param line + * @param size + * @return true if the range overlaps with the interest window + */ + boolean isInInterestWindow(int line, int size) { + if(fInterestWindowSize<=0) + return true; + if(line+size<=fInterestWindowStartLine || line>=fInterestWindowStartLine+fInterestWindowSize) + return false; + return true; + } + /** + * @param line + * @return true if the line is within the interest window + */ + boolean isInInterestWindow(int line) { + if(fInterestWindowSize<=0) + return true; + if(line=fInterestWindowStartLine+fInterestWindowSize) + return false; + return true; + } + /** + * @param line + * @return the line within the window + */ + int fitLineToWindow(int line) { + if(fInterestWindowSize<=0) + return line; + if(linebefore {@link #fitLineToWindow(int)} has been called! + * @param size + * @return the adjusted size. + *

Note:

{@link #fitLineToWindow(int)} has to be called on the line to + * move the window correctly! + */ + int fitSizeToWindow(int line, int size) { + if(fInterestWindowSize<=0) + return size; + if(linefInterestWindowStartLine+fInterestWindowSize) + size=fInterestWindowStartLine+fInterestWindowSize-line; + return size; + } + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.model.ISnapshotChanges#markLineChanged(int) + */ + public void markLineChanged(int line) { + if(!isInInterestWindow(line)) + return; + line=fitLineToWindow(line); + if(linefLastChangedLine) + fLastChangedLine=line; + // in case the terminal got resized we expand + // don't remember the changed line because + // there is nothing to copy + if(line0 || fScrollWindowShift!=0 ||fDimensionsChanged || fCursorHasChanged) + return true; + return false; + } + public void markDimensionsChanged() { + fDimensionsChanged=true; + } + public boolean hasDimensionsChanged() { + return fDimensionsChanged; + } + public boolean hasTerminalChanged() { + return fTerminalHasChanged; + } + public void setTerminalChanged() { + fTerminalHasChanged=true; + } + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.model.ISnapshotChanges#scroll(int, int, int) + */ + public void scroll(int startLine, int size, int shift) { + size=fitSizeToWindow(startLine, size); + startLine=fitLineToWindow(startLine); + // let's track only negative shifts + if(fScrollDontTrack) { + // we are in a state where we cannot track scrolling + // so let's simply mark the scrolled lines as changed + markLinesChanged(startLine, size); + } else if(shift>=0) { + // we cannot handle positive scroll + // forget about clever caching of scroll events + doNotTrackScrollingAnymore(); + // mark all lines inside the scroll region as changed + markLinesChanged(startLine, size); + } else { + // we have already scrolled + if(fScrollWindowShift<0) { + // we have already scrolled + if(fScrollWindowStartLine==startLine && fScrollWindowSize==size) { + // we are scrolling the same region again? + fScrollWindowShift+=shift; + scrollChangesLinesWithNegativeShift(startLine,size,shift); + } else { + // mark all lines in the old scroll region as changed + doNotTrackScrollingAnymore(); + // mark all lines changed, because + markLinesChanged(startLine, size); + } + } else { + // first scroll in this change -- we just notify it + fScrollWindowStartLine=startLine; + fScrollWindowSize=size; + fScrollWindowShift=shift; + scrollChangesLinesWithNegativeShift(startLine,size,shift); + } + } + } + /** + * Some incompatible scrolling occurred. We cannot do the + * scroll optimization anymore... + */ + private void doNotTrackScrollingAnymore() { + if(fScrollWindowSize>0) { + // convert the current scrolling into changes + markLinesChanged(fScrollWindowStartLine, fScrollWindowSize); + fScrollWindowStartLine=0; + fScrollWindowSize=0; + fScrollWindowShift=0; + } + // don't be clever on scrolling anymore + fScrollDontTrack=true; + } + /** + * Scrolls the changed lines data + * + * @param line + * @param n + * @param shift must be negative! + */ + private void scrollChangesLinesWithNegativeShift(int line, int n, int shift) { + assert shift <0 || throwRuntimeException(); + // scroll the region + // don't run out of bounds! + int m=Math.min(line+n+shift,getChangedLineLength()+shift); + for (int i = line; i < m; i++) { + setChangedLine(i, hasLineChanged(i-shift)); + // move the first changed line up. + // We don't have to move the maximum down, + // because with a shift scroll, the max is moved + // my the next loop in this method + if(i0) { + int shift=oldStartLine-startLine; + if(shift==0) { + if(size>oldSize) { + // add lines to the end + markLinesChanged(oldStartLine+oldSize, size-oldSize); + } + // else no lines within the window have changed + + } else if(Math.abs(shift)oldHeight) { + //the line was appended + sendLinesChangedToSnapshot(oldHeight, 1); + int width=getWidth(); + sendDimensionsChanged(oldHeight, width, newHeight, width); + + } else { + // the line was scrolled + sendScrolledToSnapshots(0, oldHeight, -1); + } + + } + + public void copy(ITerminalTextData source) { + fData.copy(source); + fCursorLine=source.getCursorLine(); + fCursorColumn=source.getCursorColumn(); + } + + public void copyLine(ITerminalTextData source, int sourceLine, int destLine) { + fData.copyLine(source, sourceLine, destLine); + } + public void copyRange(ITerminalTextData source, int sourceStartLine, int destStartLine, int length) { + fData.copyRange(source, sourceStartLine, destStartLine, length); + } + public char[] getChars(int line) { + return fData.getChars(line); + } + public Style[] getStyles(int line) { + return fData.getStyles(line); + } + public int getMaxHeight() { + return fData.getMaxHeight(); + } + public void setMaxHeight(int height) { + fData.setMaxHeight(height); + } + public void cleanLine(int line) { + fData.cleanLine(line); + sendLineChangedToSnapshots(line); + } + public int getCursorColumn() { + return fCursorColumn; + } + public int getCursorLine() { + return fCursorLine; + } + public void setCursorColumn(int column) { + fCursorColumn=column; + sendCursorChanged(); + } + public void setCursorLine(int line) { + fCursorLine=line; + sendCursorChanged(); + } +} diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/model/TerminalTextDataFastScroll.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/model/TerminalTextDataFastScroll.java new file mode 100644 index 00000000000..6038241ca48 --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/model/TerminalTextDataFastScroll.java @@ -0,0 +1,250 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. and others. + * 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 + * + * Contributors: + * Michael Scharf (Wind River) - initial API and implementation + *******************************************************************************/ +package org.eclipse.tm.internal.terminal.model; + +import org.eclipse.tm.terminal.model.ITerminalTextData; +import org.eclipse.tm.terminal.model.ITerminalTextDataSnapshot; +import org.eclipse.tm.terminal.model.LineSegment; +import org.eclipse.tm.terminal.model.Style; + +/** + * This class is optimized for scrolling the entire {@link #getHeight()}. + * The scrolling is done by moving an offset into the data and using + * the modulo operator. + * + */ +public class TerminalTextDataFastScroll implements ITerminalTextData { + + final ITerminalTextData fData; + private int fHeight; + private int fMaxHeight; + /** + * The offset into the array. + */ + int fOffset; + public TerminalTextDataFastScroll(ITerminalTextData data,int maxHeight) { + fMaxHeight=maxHeight; + fData=data; + fData.setDimensions(maxHeight, fData.getWidth()); + if(maxHeight>2) + assert shiftOffset(-2) || throwRuntimeException(); + } + public TerminalTextDataFastScroll(int maxHeight) { + this(new TerminalTextDataStore(),maxHeight); + } + public TerminalTextDataFastScroll() { + this(new TerminalTextDataStore(),1); + } + /** + * This is used in asserts to throw an {@link RuntimeException}. + * This is useful for tests. + * @return never -- throws an exception + */ + private boolean throwRuntimeException() { + throw new RuntimeException(); + } + /** + * + * @param line + * @return the actual line number in {@link #fData} + */ + int getPositionOfLine(int line) { + return (line+fOffset)%fMaxHeight; + } + /** + * Moves offset by delta. This does not move the data! + * @param delta + */ + void moveOffset(int delta) { + assert Math.abs(delta)=0 && destStartLine+length<=fHeight) || throwRuntimeException(); + for (int i = 0; i < length; i++) { + fData.copyLine(source, i+sourceStartLine, getPositionOfLine(i+destStartLine)); + } + } + + public char getChar(int line, int column) { + assert (line>=0 && line=0 && line=0 && line=0 && line=0 && line=0 && startLine+size<=fHeight) || throwRuntimeException(); + if(shift>=fMaxHeight || -shift>=fMaxHeight) { + cleanLines(startLine, fMaxHeight-startLine); + return; + } + if(size==fHeight) { + // This is the case this class is optimized for! + moveOffset(-shift); + // we only have to clean the lines that appear by the move + if(shift<0) { + cleanLines(Math.max(startLine, startLine+size+shift),Math.min(-shift, getHeight()-startLine)); + } else { + cleanLines(startLine, Math.min(shift, getHeight()-startLine)); + } + } else { + // we have to copy the lines. + if(shift<0) { + // move the region up + // shift is negative!! + for (int i = startLine; i < startLine+size+shift; i++) { + fData.copyLine(fData, getPositionOfLine(i-shift), getPositionOfLine(i)); + } + // then clean the opened lines + cleanLines(Math.max(0, startLine+size+shift),Math.min(-shift, getHeight()-startLine)); + } else { + for (int i = startLine+size-1; i >=startLine && i-shift>=0; i--) { + fData.copyLine(fData, getPositionOfLine(i-shift), getPositionOfLine(i)); + } + cleanLines(startLine, Math.min(shift, getHeight()-startLine)); + } + } + } + + public void setChar(int line, int column, char c, Style style) { + assert (line>=0 && line=0 && line=0 && line=0 || throwRuntimeException(); + assert width>=0 || throwRuntimeException(); + if(height > fMaxHeight) + setMaxHeight(height); + fHeight=height; + if(width!=fData.getWidth()) + fData.setDimensions(fMaxHeight, width); + } + + public void setMaxHeight(int maxHeight) { + assert maxHeight>=fHeight || throwRuntimeException(); + // move everything to offset0 + int start=getPositionOfLine(0); + if(start!=0) { + // invent a more efficient algorithm.... + ITerminalTextData buffer=new TerminalTextDataStore(); + // create a buffer with the expected height + buffer.setDimensions(maxHeight, getWidth()); + int n=Math.min(fMaxHeight-start,maxHeight); + // copy the first part + buffer.copyRange(fData, start, 0, n); + // copy the second part + if(nnot threadsafe! + */ +class TerminalTextDataSnapshot implements ITerminalTextDataSnapshot { + /** + * The changes of the current snapshot relative to the + * previous snapshot + */ + volatile ISnapshotChanges fCurrentChanges; + /** + * Keeps track of changes that happened since the current + * snapshot has been made. + */ + ISnapshotChanges fFutureChanges; + /** + * Is used as lock and is the reference to the terminal we take snapshots from. + */ + final TerminalTextData fTerminal; + /** + * A snapshot copy of of fTerminal + */ + // snapshot does not need internal synchronisation + final TerminalTextDataWindow fSnapshot; + // this variable is synchronized on fTerminal! + private SnapshotOutOfDateListener[] fListener=new SnapshotOutOfDateListener[0]; + // this variable is synchronized on fTerminal! + private boolean fListenersNeedNotify; + private int fInterestWindowSize; + private int fInterestWindowStartLine; + + TerminalTextDataSnapshot(TerminalTextData terminal) { + fSnapshot = new TerminalTextDataWindow(); + fTerminal = terminal; + fCurrentChanges = new SnapshotChanges(fTerminal.getHeight()); + fCurrentChanges.setTerminalChanged(); + fFutureChanges = new SnapshotChanges(fTerminal.getHeight()); + fFutureChanges.markLinesChanged(0, fTerminal.getHeight()); + fListenersNeedNotify=true; + fInterestWindowSize=-1; + } + /** + * This is used in asserts to throw an {@link RuntimeException}. + * This is useful for tests. + * @return never -- throws an exception + */ + private boolean throwRuntimeException() { + throw new RuntimeException(); + } + + public void detach() { + fTerminal.removeSnapshot(this); + } + + public boolean isOutOfDate() { + // this is called from fTerminal, therefore we lock on fTerminal + synchronized (fTerminal) { + return fFutureChanges.hasChanged(); + } + } + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.model.ITerminalTextDataSnapshot#snapshot() + */ + public void updateSnapshot(boolean detectScrolling) { + // make sure terminal does not change while we make the snapshot + synchronized (fTerminal) { + // let's make the future changes current + fCurrentChanges=fFutureChanges; + fFutureChanges=new SnapshotChanges(fTerminal.getHeight()); + fFutureChanges.setInterestWindow(fInterestWindowStartLine, fInterestWindowSize); + // and update the snapshot + if(fSnapshot.getHeight()!=fTerminal.getHeight()||fSnapshot.getWidth()!=fTerminal.getWidth()) { + if(fInterestWindowSize==-1) + fSnapshot.setWindow(0, fTerminal.getHeight()); + // if the dimensions have changed, we need a full copy + fSnapshot.copy(fTerminal); + // and we mark all lines as changed + fCurrentChanges.setAllChanged(fTerminal.getHeight()); + } else { + // first we do the scroll on the copy + int start=fCurrentChanges.getScrollWindowStartLine(); + int lines=Math.min(fCurrentChanges.getScrollWindowSize(), fSnapshot.getHeight()-start); + fSnapshot.scroll(start, lines, fCurrentChanges.getScrollWindowShift()); + // and then create the snapshot of the changed lines + fCurrentChanges.copyChangedLines(fSnapshot, fTerminal); + } + fListenersNeedNotify=true; + fSnapshot.setCursorLine(fTerminal.getCursorLine()); + fSnapshot.setCursorColumn(fTerminal.getCursorColumn()); + } + if(!detectScrolling) { + // let's pretend there was no scrolling and + // convert the scrolling into line changes + fCurrentChanges.convertScrollingIntoChanges(); + } + } + + public char getChar(int line, int column) { + return fSnapshot.getChar(line, column); + } + + public int getHeight() { + return fSnapshot.getHeight(); + } + + public LineSegment[] getLineSegments(int line, int column, int len) { + return fSnapshot.getLineSegments(line, column, len); + } + + public Style getStyle(int line, int column) { + return fSnapshot.getStyle(line, column); + } + + public int getWidth() { + return fSnapshot.getWidth(); + } + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.model.ITerminalTextDataSnapshot#getFirstChangedLine() + */ + public int getFirstChangedLine() { + return fCurrentChanges.getFirstChangedLine(); + } + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.model.ITerminalTextDataSnapshot#getLastChangedLine() + */ + public int getLastChangedLine() { + return fCurrentChanges.getLastChangedLine(); + } + + public boolean hasLineChanged(int line) { + return fCurrentChanges.hasLineChanged(line); + } + public boolean hasDimensionsChanged() { + return fCurrentChanges.hasDimensionsChanged(); + } + public boolean hasTerminalChanged() { + return fCurrentChanges.hasTerminalChanged(); + } + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.model.ITerminalTextDataSnapshot#getScrollChangeY() + */ + public int getScrollWindowStartLine() { + return fCurrentChanges.getScrollWindowStartLine(); + } + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.model.ITerminalTextDataSnapshot#getScrollChangeN() + */ + public int getScrollWindowSize() { + return fCurrentChanges.getScrollWindowSize(); + } + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.model.ITerminalTextDataSnapshot#getScrollChangeShift() + */ + public int getScrollWindowShift() { + return fCurrentChanges.getScrollWindowShift(); + } + + /** + * Announces a change in line line + * @param line + */ + void markLineChanged(int line) { + // threading + fFutureChanges.markLineChanged(line); + fFutureChanges.setTerminalChanged(); + notifyListers(); + } + /** + * Announces a change of n lines beginning with line line + * @param line + * @param n + */ + void markLinesChanged(int line,int n) { + fFutureChanges.markLinesChanged(line,n); + fFutureChanges.setTerminalChanged(); + notifyListers(); + } + + void markDimensionsChanged() { + fFutureChanges.markDimensionsChanged(); + fFutureChanges.setTerminalChanged(); + notifyListers(); + } + void markCursorChanged() { + fFutureChanges.markCursorChanged(); + fFutureChanges.setTerminalChanged(); + notifyListers(); + } + + /** + * @param startLine + * @param size + * @param shift + */ + void scroll(int startLine, int size, int shift) { + fFutureChanges.scroll(startLine,size,shift); + fFutureChanges.setTerminalChanged(); + notifyListers(); + } + /** + * Notifies listeners about the change + */ + private void notifyListers() { + // this code has to be called from a block synchronized on fTerminal + synchronized (fTerminal) { + if(fListenersNeedNotify) { + for (int i = 0; i < fListener.length; i++) { + fListener[i].snapshotOutOfDate(this); + } + fListenersNeedNotify=false; + } + } + } + public ITerminalTextDataSnapshot makeSnapshot() { + return fSnapshot.makeSnapshot(); + } + + synchronized public void addListener(SnapshotOutOfDateListener listener) { + List list=new ArrayList(); + list.addAll(Arrays.asList(fListener)); + list.add(listener); + fListener=(SnapshotOutOfDateListener[]) list.toArray(new SnapshotOutOfDateListener[list.size()]); + } + + synchronized public void removeListener(SnapshotOutOfDateListener listener) { + List list=new ArrayList(); + list.addAll(Arrays.asList(fListener)); + list.remove(listener); + fListener=(SnapshotOutOfDateListener[]) list.toArray(new SnapshotOutOfDateListener[list.size()]); + } + public String toString() { + return fSnapshot.toString(); + } + + + public int getInterestWindowSize() { + return fInterestWindowSize; + } + + + public int getInterestWindowStartLine() { + return fInterestWindowStartLine; + } + + public void setInterestWindow(int startLine, int size) { + assert startLine>=0 || throwRuntimeException(); + assert size>=0 || throwRuntimeException(); + fInterestWindowStartLine=startLine; + fInterestWindowSize=size; + fSnapshot.setWindow(startLine, size); + fFutureChanges.setInterestWindow(startLine, size); + notifyListers(); + } + + + public char[] getChars(int line) { + return fSnapshot.getChars(line); + } + + + public Style[] getStyles(int line) { + return fSnapshot.getStyles(line); + } + public int getCursorColumn() { + return fSnapshot.getCursorColumn(); + } + public int getCursorLine() { + return fSnapshot.getCursorLine(); + } + public ITerminalTextData getTerminalTextData() { + return fTerminal; + } +} + + diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/model/TerminalTextDataStore.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/model/TerminalTextDataStore.java new file mode 100644 index 00000000000..fd85cc35e3d --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/model/TerminalTextDataStore.java @@ -0,0 +1,333 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. and others. + * 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 + * + * Contributors: + * Michael Scharf (Wind River) - initial API and implementation + *******************************************************************************/ +package org.eclipse.tm.internal.terminal.model; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.tm.terminal.model.ITerminalTextData; +import org.eclipse.tm.terminal.model.ITerminalTextDataSnapshot; +import org.eclipse.tm.terminal.model.LineSegment; +import org.eclipse.tm.terminal.model.Style; + +/** + * This class is thread safe. + * + */ +public class TerminalTextDataStore implements ITerminalTextData { + private char[][] fChars; + private Style[][] fStyle; + private int fWidth; + private int fHeight; + private int fMaxHeight; + private int fCursorColumn; + private int fCursorLine; + public TerminalTextDataStore() { + fChars=new char[0][]; + fStyle=new Style[0][]; + fWidth=0; + } + /** + * This is used in asserts to throw an {@link RuntimeException}. + * This is useful for tests. + * @return never -- throws an exception + */ + private boolean throwRuntimeException() { + throw new RuntimeException(); + } + + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.text.ITerminalTextData#getWidth() + */ + public int getWidth() { + return fWidth; + } + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.text.ITerminalTextData#getHeight() + */ + public int getHeight() { + return fHeight; + } + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.text.ITerminalTextData#setDimensions(int, int) + */ + public void setDimensions(int height, int width) { + assert height>=0 || throwRuntimeException(); + assert width>=0 || throwRuntimeException(); + // just extend the region + if(height>fChars.length) { + int h=4*height/3; + if(fMaxHeight>0 && h>fMaxHeight) + h=fMaxHeight; + fStyle=(Style[][]) resizeArray(fStyle, height); + fChars=(char[][]) resizeArray(fChars, height); + } + // clean the new lines + if(height>fHeight) { + for (int i = fHeight; i < height; i++) { + fStyle[i]=null; + fChars[i]=null; + } + } + // set dimensions after successful resize! + fWidth=width; + fHeight=height; + } + /** + * Reallocates an array with a new size, and copies the contents of the old + * array to the new array. + * + * @param origArray the old array, to be reallocated. + * @param newSize the new array size. + * @return A new array with the same contents (chopped off if needed or filled with 0 or null). + */ + private Object resizeArray(Object origArray, int newSize) { + int oldSize = Array.getLength(origArray); + if(oldSize==newSize) + return origArray; + Class elementType = origArray.getClass().getComponentType(); + Object newArray = Array.newInstance(elementType, newSize); + int preserveLength = Math.min(oldSize, newSize); + if (preserveLength > 0) + System.arraycopy(origArray, 0, newArray, 0, preserveLength); + return newArray; + } + + + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.text.ITerminalTextData#getLineSegments(int, int, int) + */ + public LineSegment[] getLineSegments(int line, int column, int len) { + // get the styles and chars for this line + Style[] styles=fStyle[line]; + char[] chars=fChars[line]; + int col=column; + int n=column+len; + + // expand the line if needed.... + if(styles==null) + styles=new Style[n]; + else if(styles.length=fChars[line].length) + return 0; + return fChars[line][column]; + } + /* (non-Javadoc) + * @see org.eclipse.tm.internal.terminal.text.ITerminalTextData#getStyle(int, int) + */ + public Style getStyle(int line, int column) { + assert column=fStyle[line].length) + return null; + return fStyle[line][column]; + } + + void ensureLineLength(int iLine, int length) { + if(length>fWidth) + throw new RuntimeException(); + if(fChars[iLine]==null) { + fChars[iLine]=new char[length]; + } else if(fChars[iLine].length=startLine && i-shift>=0; i--) { + fChars[i]=fChars[i-shift]; + fStyle[i]=fStyle[i-shift]; + } + cleanLines(startLine, Math.min(shift, getHeight()-startLine)); + } + } + /** + * Replaces the lines with new empty data + * @param line + * @param len + */ + private void cleanLines(int line, int len) { + for (int i = line; i < line+len; i++) { + fChars[i]=null; + fStyle[i]=null; + } + } + + /* + * @return a text representation of the object. + * Lines are separated by '\n'. No style information is returned. + */ + public String toString() { + StringBuffer buff=new StringBuffer(); + for (int line = 0; line < getHeight(); line++) { + if(line>0) + buff.append("\n"); //$NON-NLS-1$ + for (int column = 0; column < fWidth; column++) { + buff.append(getChar(line, column)); + } + } + return buff.toString(); + } + + + public ITerminalTextDataSnapshot makeSnapshot() { + throw new UnsupportedOperationException(); + } + + public void addLine() { + if(fMaxHeight>0 && getHeight()char=='\000' and style=null. + * + */ +public class TerminalTextDataWindow implements ITerminalTextData { + final ITerminalTextData fData; + int fWindowStartLine; + int fWindowSize; + int fHeight; + int fMaxHeight; + public TerminalTextDataWindow(ITerminalTextData data) { + fData=data; + } + public TerminalTextDataWindow() { + this(new TerminalTextDataStore()); + } + /** + * This is used in asserts to throw an {@link RuntimeException}. + * This is useful for tests. + * @return never -- throws an exception + */ + private boolean throwRuntimeException() { + throw new RuntimeException(); + } + /** + * @param line + * @return true if the line is within the window + */ + boolean isInWindow(int line) { + return line>=fWindowStartLine && line0 && getHeight()0) + fData.copyRange(source, fWindowStartLine, 0, n); + } + public void copyRange(ITerminalTextData source, int sourceStartLine, int destStartLine, int length) { + int n=length; + int dStart=destStartLine-fWindowStartLine; + int sStart=sourceStartLine; + // if start outside our range, cut the length to copy + if(dStart<0) { + n+=dStart; + sStart-=dStart; + dStart=0; + } + // do not exceed the window size + n=Math.min(n,fWindowSize); + if(n>0) + fData.copyRange(source, sStart, dStart, n); + + } + public void copyLine(ITerminalTextData source, int sourceLine, int destLine) { + if(isInWindow(destLine)) + fData.copyLine(source, sourceLine, destLine-fWindowStartLine); + } + public void scroll(int startLine, int size, int shift) { + assert (startLine>=0 && startLine+size<=fHeight) || throwRuntimeException(); + int n=size; + int start=startLine-fWindowStartLine; + // if start outside our range, cut the length to copy + if(start<0) { + n+=start; + start=0; + } + n=Math.min(n,fWindowSize-start); + // do not exceed the window size + if(n>0) + fData.scroll(start, n, shift); + } + public void setChar(int line, int column, char c, Style style) { + if(!isInWindow(line)) + return; + fData.setChar(line-fWindowStartLine, column, c, style); + } + public void setChars(int line, int column, char[] chars, int start, int len, Style style) { + if(!isInWindow(line)) + return; + fData.setChars(line-fWindowStartLine, column, chars, start, len, style); + } + public void setChars(int line, int column, char[] chars, Style style) { + if(!isInWindow(line)) + return; + fData.setChars(line-fWindowStartLine, column, chars, style); + } + public void setDimensions(int height, int width) { + assert height>=0 || throwRuntimeException(); + fData.setDimensions(fWindowSize, width); + fHeight=height; + } + public void setMaxHeight(int height) { + fMaxHeight=height; + } + public void setWindow(int startLine, int size) { +// assert startLine+size<=getHeight()||throwRuntimeException(); + fWindowStartLine=startLine; + fWindowSize=size; + fData.setDimensions(fWindowSize, getWidth()); + } + public int getWindowStartLine() { + return fWindowStartLine; + } + public int getWindowSize() { + return fWindowSize; + } + public void setHeight(int height) { + fHeight = height; + } + public void cleanLine(int line) { + if(isInWindow(line)) + fData.cleanLine(line-fWindowStartLine); + } + public int getCursorColumn() { + return fData.getCursorColumn(); + } + public int getCursorLine() { + return fData.getCursorLine(); + } + public void setCursorColumn(int column) { + fData.setCursorColumn(column); + } + public void setCursorLine(int line) { + fData.setCursorLine(line); + } +} diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/provisional/api/Logger.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/provisional/api/Logger.java index ee986b9599a..a45b7fa61d5 100644 --- a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/provisional/api/Logger.java +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/provisional/api/Logger.java @@ -168,7 +168,8 @@ public final class Logger { */ public static final void logException(Exception ex) { // log in eclipse error log - TerminalPlugin.getDefault().getLog().log(new Status(IStatus.ERROR, TerminalPlugin.PLUGIN_ID, IStatus.OK, ex.getMessage(), ex)); + if(TerminalPlugin.getDefault()!=null) + TerminalPlugin.getDefault().getLog().log(new Status(IStatus.ERROR, TerminalPlugin.PLUGIN_ID, IStatus.OK, ex.getMessage(), ex)); // Read my own stack to get the class name, method name, and line number // of // where this method was called. @@ -189,6 +190,8 @@ public final class Logger { + "." + methodName + ":" + lineNumber + ": " + //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ "Caught exception: " + ex); //$NON-NLS-1$ ex.printStackTrace(tmpStream); + } else { + ex.printStackTrace(); } } } diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/AbstractTextCanvasModel.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/AbstractTextCanvasModel.java new file mode 100644 index 00000000000..f5491f7cf04 --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/AbstractTextCanvasModel.java @@ -0,0 +1,289 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. and others. + * 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 + * + * Contributors: + * Michael Scharf (Wind River) - initial API and implementation + *******************************************************************************/ +package org.eclipse.tm.internal.terminal.textcanvas; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.swt.graphics.Point; +import org.eclipse.tm.terminal.model.ITerminalTextDataReadOnly; +import org.eclipse.tm.terminal.model.ITerminalTextDataSnapshot; + +abstract public class AbstractTextCanvasModel implements ITextCanvasModel { + protected List fListeners = new ArrayList(); + private int fCursorLine; + private int fCursorColumn; + private boolean fShowCursor; + private long fCursorTime; + private boolean fCursorIsEnabled; + private final ITerminalTextDataSnapshot fSnapshot; + private int fLines; + + private int fSelectionStartLine; + private int fSeletionEndLine; + private int fSelectionStartCoumn; + private int fSelectionEndColumn; + private ITerminalTextDataSnapshot fSelectionSnapshot; + + public AbstractTextCanvasModel(ITerminalTextDataSnapshot snapshot) { + fSnapshot=snapshot; + fLines=fSnapshot.getHeight(); + } + public void addCellCanvasModelListener(ITextCanvasModelListener listener) { + fListeners.add(listener); + } + + public void removeCellCanvasModelListener(ITextCanvasModelListener listener) { + fListeners.remove(listener); + } + + protected void fireCellRangeChanged(int x, int y, int width, int height) { + for (Iterator iter = fListeners.iterator(); iter.hasNext();) { + ITextCanvasModelListener listener = (ITextCanvasModelListener) iter.next(); + listener.rangeChanged(x, y, width, height); + } + } + protected void fireDimensionsChanged( int width,int height) { + for (Iterator iter = fListeners.iterator(); iter.hasNext();) { + ITextCanvasModelListener listener = (ITextCanvasModelListener) iter.next(); + listener.dimensionsChanged(width,height); + } + + } + protected void fireTerminalDataChanged() { + for (Iterator iter = fListeners.iterator(); iter.hasNext();) { + ITextCanvasModelListener listener = (ITextCanvasModelListener) iter.next(); + listener.terminalDataChanged(); + } + + } + public ITerminalTextDataReadOnly getTerminalText() { + return fSnapshot; + } + protected ITerminalTextDataSnapshot getSnapshot() { + return fSnapshot; + } + protected void updateSnapshot() { + if(fSnapshot.isOutOfDate()) { + fSnapshot.updateSnapshot(false); + if(fSnapshot.hasTerminalChanged()) + fireTerminalDataChanged(); + // TODO why does hasDimensionsChanged not work?????? + // if(fSnapshot.hasDimensionsChanged()) + // fireDimensionsChanged(); + if(fLines!=fSnapshot.getHeight()) { + fireDimensionsChanged(fSnapshot.getWidth(),fSnapshot.getHeight()); + fLines=fSnapshot.getHeight(); + } + int y=fSnapshot.getFirstChangedLine(); + // has any line changed? + if(y=getSnapshot().getHeight()) { + cursorLine=getSnapshot().getHeight()-1; + cursorColumn=getSnapshot().getWidth()-1; + } + // has the cursor moved? + if(fCursorLine!=cursorLine || fCursorColumn!=cursorColumn) { + // hide the old cursor! + fShowCursor=false; + // clean the previous cursor + fireCellRangeChanged(fCursorColumn, fCursorLine, 1, 1); + // the cursor is shown when it moves! + fShowCursor=true; + fCursorTime=System.currentTimeMillis(); + fCursorLine=cursorLine; + fCursorColumn=cursorColumn; + // and draw the new cursor + fireCellRangeChanged(fCursorColumn, fCursorLine, 1, 1); + } else { + long t=System.currentTimeMillis(); + // TODO make the cursor blink time customisable + if(t-fCursorTime>500) { + fShowCursor=!fShowCursor; + fCursorTime=t; + fireCellRangeChanged(fCursorColumn, fCursorLine, 1, 1); + } + } + } + public void setVisibleRectangle(int startLine, int startCol, int height, int width) { + fSnapshot.setInterestWindow(Math.max(0,startLine), Math.max(1,Math.min(fSnapshot.getHeight(),height))); + update(); + } + protected void showCursor(boolean show) { + fShowCursor=true; + } + public void setCursorEnabled(boolean visible) { + fCursorTime=System.currentTimeMillis(); + fShowCursor=visible; + fCursorIsEnabled=visible; + fireCellRangeChanged(fCursorColumn, fCursorLine, 1, 1); + } + public boolean isCursorEnabled() { + return fCursorIsEnabled; + } + + public Point getSelectionEnd() { + if(fSelectionStartLine<0) + return null; + else + return new Point(fSelectionEndColumn, fSeletionEndLine); + } + + public Point getSelectionStart() { + if (fSelectionStartLine < 0) + return null; + else + return new Point(fSelectionStartCoumn,fSelectionStartLine); + } + + public void setSelection(int startLine, int endLine, int startColumn, int endColumn) { + assert(startLine<0 || startLine<=endLine); + if(startLine>=0) { + if(fSelectionSnapshot==null) { + fSelectionSnapshot=fSnapshot.getTerminalTextData().makeSnapshot(); + fSelectionSnapshot.updateSnapshot(true); + } + } else if(fSelectionSnapshot!=null) { + fSelectionSnapshot.detach(); + fSelectionSnapshot=null; + } + int oldStart=fSelectionStartLine; + int oldEnd=fSeletionEndLine; + fSelectionStartLine = startLine; + fSeletionEndLine = endLine; + fSelectionStartCoumn = startColumn; + fSelectionEndColumn = endColumn; + if(fSelectionSnapshot!=null) { + fSelectionSnapshot.setInterestWindow(0, fSeletionEndLine); + } + int changedStart; + int changedEnd; + if(oldStart<0) { + changedStart=fSelectionStartLine; + changedEnd=fSeletionEndLine; + } else if(fSelectionStartLine<0) { + changedStart=oldStart; + changedEnd=oldEnd; + } else { + changedStart=Math.min(oldStart, fSelectionStartLine); + changedEnd=Math.max(oldEnd, fSeletionEndLine); + } + if(changedStart>=0) { + fireCellRangeChanged(0, changedStart, fSnapshot.getWidth(), changedEnd-changedStart+1); + } + } + + public boolean hasLineSelection(int line) { + if (fSelectionStartLine < 0) + return false; + else + return line >= fSelectionStartLine && line <= fSeletionEndLine; + } + + public String getSelectedText() { + if(fSelectionStartLine<0 || fSelectionSnapshot==null) + return ""; //$NON-NLS-1$ + if(fSelectionStartLine<0 || fSelectionSnapshot==null) + return ""; //$NON-NLS-1$ + StringBuffer buffer=new StringBuffer(); + for (int line = fSelectionStartLine; line <= fSeletionEndLine; line++) { + String text; + char[] chars=fSelectionSnapshot.getChars(line); + if(chars!=null) { + text=new String(chars); + if(line==fSeletionEndLine) + text=text.substring(0, Math.min(fSelectionEndColumn,text.length())); + if(line==fSelectionStartLine) + text=text.substring(Math.min(fSelectionStartCoumn,text.length())); + // get rid of the empty space at the end of the lines + text=text.replaceAll("\000+$",""); //$NON-NLS-1$//$NON-NLS-2$ + // null means space + text=text.replace('\000', ' '); + } else { + text=""; //$NON-NLS-1$ + } + buffer.append(text); + if(line < fSeletionEndLine) + buffer.append('\n'); + } + return buffer.toString(); + } + private void updateSelection() { + if (fSelectionSnapshot != null && fSelectionSnapshot.isOutOfDate()) { + // let's see if the selection text has changed since the last snapshot + String oldSelection = getSelectedText(); + fSelectionSnapshot.updateSnapshot(true); + // has the selection moved? + if (fSelectionSnapshot != null && fSelectionStartLine >= 0 && fSelectionSnapshot.getScrollWindowSize() > 0) { + int start = fSelectionStartLine + fSelectionSnapshot.getScrollWindowShift(); + int end = fSeletionEndLine + fSelectionSnapshot.getScrollWindowShift(); + if (start < 0) + if (end >= 0) + start = 0; + else + start = -1; + setSelection(start, end, fSelectionStartCoumn, fSelectionEndColumn); + } + // have lines inside the selection changed? + if (fSelectionSnapshot != null && fSelectionSnapshot.getFirstChangedLine() <= fSeletionEndLine && + fSelectionSnapshot.getLastChangedLine() >= fSelectionStartLine) { + // has the selected text changed? + String newSelection = getSelectedText(); + if (!oldSelection.equals(newSelection)) + setSelection(-1, -1, -1, -1); + } + // update the observed window... + if (fSelectionSnapshot != null) + // todo make -1 to work! + fSelectionSnapshot.setInterestWindow(0, fSeletionEndLine); + } + } + +} \ No newline at end of file diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/GridCanvas.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/GridCanvas.java new file mode 100644 index 00000000000..60dd821c621 --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/GridCanvas.java @@ -0,0 +1,183 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. and others. + * 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 + * + * Contributors: + * Michael Scharf (Wind River) - initial API and implementation + *******************************************************************************/ +package org.eclipse.tm.internal.terminal.textcanvas; + + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; + +/** + * A Grid based Canvas. The canvas has rows and columns. + * CellPainting is done with the abstract method drawCell + */ +abstract public class GridCanvas extends VirtualCanvas { + /** width of a cell */ + private int fCellWidth; + /** height of a cell */ + private int fCellHeight; + + public GridCanvas(Composite parent, int style) { + super(parent, style); + addListener(SWT.MouseWheel, new Listener() { + public void handleEvent(Event event) { + if(getVerticalBar().isVisible()) { + int delta=-fCellHeight; + if(event.count<0) + delta=-delta; + scrollYDelta(delta); + } + event.doit=false; + } + }); + + } + + /** template method paint. + * iterates over all cells in the clipping rectangle and paints them. + */ + protected void paint(GC gc) { + Rectangle clipping=gc.getClipping(); + if(clipping.width==0 || clipping.height==0) + return; + Rectangle clientArea= getScreenRectInVirtualSpace(); + // Beginning coordinates + int xOffset=clientArea.x; + int yOffset=clientArea.y; + int colFirst=virtualXToCell(xOffset+clipping.x); + if(colFirst>getCols()) + colFirst=getCols(); + int rowFirst=virtualYToCell(yOffset+clipping.y); + // End coordinates + int colLast=virtualXToCell(xOffset+clipping.x+clipping.width+fCellWidth); + if(colLast>getCols()) + colLast=getCols(); + int rowLast=virtualYToCell(yOffset+clipping.y+clipping.height+fCellHeight); + if(rowLast>getRows()) + rowLast=getRows(); + // System.out.println(rowFirst+"->"+rowLast+" "+System.currentTimeMillis()); + // draw the cells + for(int row=rowFirst;row<=rowLast;row++) { + int cx=colFirst*fCellWidth-xOffset; + int cy=row*fCellHeight-yOffset; + drawLine(gc,row,cx,cy,colFirst,colLast); + } + paintUnoccupiedSpace(gc,clipping); + } + /** + * @param gc + * @param row the line to draw + * @param x coordinate on screen + * @param y coordinate on screen + * @param colFirst first column to draw + * @param colLast last column to draw + */ + abstract void drawLine(GC gc, int row, int x, int y, int colFirst, int colLast); + + abstract protected int getRows(); + abstract protected int getCols(); + + protected void setCellWidth(int cellWidth) { + fCellWidth = cellWidth; + getHorizontalBar().setIncrement(fCellWidth); + } + + public int getCellWidth() { + return fCellWidth; + } + + protected void setCellHeight(int cellHeight) { + fCellHeight = cellHeight; + getVerticalBar().setIncrement(fCellHeight); + } + + public int getCellHeight() { + return fCellHeight; + } + + int virtualXToCell(int x) { + return x/fCellWidth; + } + + int virtualYToCell(int y) { + return y/fCellHeight; + } + + protected Point screenPointToCell(int x, int y) { + x=screenXtoVirtual(x)/fCellWidth; + y=screenYtoVirtual(y)/fCellHeight; + return new Point(x,y); + } + + Point screenPointToCell(Point point) { + return screenPointToCell(point.x,point.y); + } + + protected Point cellToOriginOnScreen(int x, int y) { + x=virtualXtoScreen(fCellWidth*x); + y=virtualYtoScreen(fCellHeight*y); + return new Point(x,y); + } + + Point cellToOriginOnScreen(Point cell) { + return cellToOriginOnScreen(cell.x,cell.y); + } + + Rectangle getCellScreenRect(Point cell) { + return getCellScreenRect(cell.x,cell.y); + } + + Rectangle getCellScreenRect(int x, int y) { + x=fCellWidth*virtualXtoScreen(x); + y=fCellHeight*virtualYtoScreen(y); + return new Rectangle(x,y,fCellWidth,fCellHeight); + } + + protected Rectangle getCellVirtualRect(Point cell) { + return getCellVirtualRect(cell.x,cell.y); + } + + Rectangle getCellVirtualRect(int x, int y) { + x=fCellWidth*x; + y=fCellHeight*y; + return new Rectangle(x,y,fCellWidth,fCellHeight); + } + protected void viewRectangleChanged(int x, int y, int width, int height) { + int cellX=virtualXToCell(x); + int cellY=virtualYToCell(y); + // End coordinates + int xE=virtualXToCell(x+fCellWidth+width-1); + if(xE>getCols()) + xE=getCols(); + int yE=virtualYToCell(y+fCellHeight+height-1); + if(yE>getRows()) + yE=getRows(); + visibleCellRectangleChanged(cellX,cellY,xE-cellX,yE-cellY); + } + + /** + * Called when the viewed part has changed. + * Override when you need this information.... + * Is only called if the values change (well, almost) + * @param x origin of visible cells + * @param y + * @param width number of cells visible in x direction + * @param height + */ + protected void visibleCellRectangleChanged(int x, int y, int width, int height) { + } + +} + diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/ILinelRenderer.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/ILinelRenderer.java new file mode 100644 index 00000000000..d217186a996 --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/ILinelRenderer.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. and others. + * 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 + * + * Contributors: + * Michael Scharf (Wind River) - initial API and implementation + *******************************************************************************/ +package org.eclipse.tm.internal.terminal.textcanvas; + +import org.eclipse.swt.graphics.GC; + +/** + * + */ +public interface ILinelRenderer { + int getCellWidth(); + int getCellHeight(); + void drawLine(ITextCanvasModel model, GC gc, int line, int x, int y, int colFirst, int colLast); +} diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/ITextCanvasModel.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/ITextCanvasModel.java new file mode 100644 index 00000000000..d802b996840 --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/ITextCanvasModel.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. and others. + * 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 + * + * Contributors: + * Michael Scharf (Wind River) - initial API and implementation + *******************************************************************************/ +package org.eclipse.tm.internal.terminal.textcanvas; + +import org.eclipse.swt.graphics.Point; +import org.eclipse.tm.terminal.model.ITerminalTextDataReadOnly; + +public interface ITextCanvasModel { + void addCellCanvasModelListener(ITextCanvasModelListener listener); + void removeCellCanvasModelListener(ITextCanvasModelListener listener); + + ITerminalTextDataReadOnly getTerminalText(); + /** + * This is is + * @param startLine + * @param startCol + * @param height + * @param width + */ + void setVisibleRectangle(int startLine, int startCol, int height, int width); + + /** + * @return true when the cursor is shown (used for blinking cursors) + */ + boolean isCursorOn(); + /** + * Show/Hide the cursor. + * @param visible + */ + void setCursorEnabled(boolean visible); + + /** + * @return true if the cursor is shown. + */ + boolean isCursorEnabled(); + + /** + * @return the line of the cursor + */ + int getCursorLine(); + /** + * @return the column of the cursor + */ + int getCursorColumn(); + + /** + * @return the start of the selection or null if nothing is selected + * {@link Point#x} is the column and {@link Point#y} is the line. + */ + Point getSelectionStart(); + /** + * @return the end of the selection or null if nothing is selected + * {@link Point#x} is the column and {@link Point#y} is the line. + */ + Point getSelectionEnd(); + /** + * @param startLine + * @param endLine + * @param startColumn + * @param endColumn + */ + void setSelection(int startLine, int endLine, int startColumn, int endColumn); + + /** + * @param line + * @return true if line is part of the selection + */ + boolean hasLineSelection(int line); + + String getSelectedText(); +} \ No newline at end of file diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/ITextCanvasModelListener.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/ITextCanvasModelListener.java new file mode 100644 index 00000000000..cb52c8cf529 --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/ITextCanvasModelListener.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. and others. + * 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 + * + * Contributors: + * Michael Scharf (Wind River) - initial API and implementation + *******************************************************************************/ +package org.eclipse.tm.internal.terminal.textcanvas; + +/** + */ +public interface ITextCanvasModelListener { + void cellSizeChanged(); + void rangeChanged(int col, int line, int width, int height); + void dimensionsChanged(int cols, int rows); + /** + * Called when any text change happened. Used to scroll to the + * end of text in auto scroll mode. This does not get fired + * when the window of interest has changed! + */ + void terminalDataChanged(); +} diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/PipedInputStream.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/PipedInputStream.java new file mode 100644 index 00000000000..c522626f246 --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/PipedInputStream.java @@ -0,0 +1,305 @@ +/******************************************************************************* + * Copyright (c) 1996, 2007 Wind River Systems, Inc. and others. + * 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 + * + * Contributors: + * Michael Scharf (Wind River) - initial API and implementation + * Douglas Lea (Addison Wesley) - [cq:1552] BoundedBufferWithStateTracking adapted to BoundedByteBuffer + * Martin Oberhuber (Wind River) - the waitForAvailable method + *******************************************************************************/ + +package org.eclipse.tm.internal.terminal.textcanvas; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * The main purpose of this class is to start a runnable in the + * display thread when data is available and to pretend no data + * is available after a given amount of time the runnable is running. + * + */ +public class PipedInputStream extends InputStream { + /** + * The output stream used by the terminal backend to write to the terminal + */ + protected final OutputStream fOutputStream; + /** + * A blocking byte queue. + */ + private final BoundedByteBuffer fQueue; + + /** + * A byte bounded buffer used to synchronize the input and the output stream. + *

+ * Adapted from BoundedBufferWithStateTracking + * http://gee.cs.oswego.edu/dl/cpj/allcode.java + * http://gee.cs.oswego.edu/dl/cpj/ + *

+ * BoundedBufferWithStateTracking is part of the examples for the book + * Concurrent Programming in Java: Design Principles and Patterns by + * Doug Lea (ISBN 0-201-31009-0). Second edition published by + * Addison-Wesley, November 1999. The code is + * Copyright(c) Douglas Lea 1996, 1999 and released to the public domain + * and may be used for any purposes whatsoever. + *

+ * For some reasons a solution based on + * PipedOutputStream/PipedIntputStream + * does work *very* slowly: + * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4404700 + *

+ * + */ + private class BoundedByteBuffer { + protected final byte[] fBuffer; // the elements + protected int fPutPos = 0; // circular indices + protected int fTakePos = 0; + protected int fUsedSlots = 0; // the count + private boolean fClosed; + public BoundedByteBuffer(int capacity) throws IllegalArgumentException { + // make sure we don't deadlock on too small capacity + if (capacity <= 0) + throw new IllegalArgumentException(); + fBuffer = new byte[capacity]; + } + /** + * @return the bytes available for {@link #read()} + * Must be called with a lock on this! + */ + public int available() { + return fUsedSlots; + } + /** + * Writes a single byte to the buffer. Blocks if the buffer is full. + * @param b + * @throws InterruptedException + * Must be called with a lock on this! + */ + public void write(byte b) throws InterruptedException { + while (fUsedSlots == fBuffer.length) + // wait until not full + wait(); + + fBuffer[fPutPos] = b; + fPutPos = (fPutPos + 1) % fBuffer.length; // cyclically increment + + if (fUsedSlots++ == 0) // signal if was empty + notifyAll(); + } + public int getFreeSlots() { + return fBuffer.length - fUsedSlots; + } + public void write(byte[] b, int off, int len) throws InterruptedException { + assert len<=getFreeSlots(); + while (fUsedSlots == fBuffer.length) + // wait until not full + wait(); + int n = Math.min(len, fBuffer.length - fPutPos); + System.arraycopy(b, off, fBuffer, fPutPos, n); + if (fPutPos + len > n) + System.arraycopy(b, off + n, fBuffer, 0, len - n); + fPutPos = (fPutPos + len) % fBuffer.length; // cyclically increment + boolean wasEmpty = fUsedSlots == 0; + fUsedSlots += len; + if (wasEmpty) // signal if was empty + notifyAll(); + } + /** + * Read a single byte. Blocks until a byte is available. + * @return a byte from the buffer + * @throws InterruptedException + * Must be called with a lock on this! + */ + public int read() throws InterruptedException { + while (fUsedSlots == 0) { + if(fClosed) + return -1; + // wait until not empty + wait(); + } + byte b = fBuffer[fTakePos]; + fTakePos = (fTakePos + 1) % fBuffer.length; + + if (fUsedSlots-- == fBuffer.length) // signal if was full + notifyAll(); + return b; + } + public int read(byte[] cbuf, int off, int len) throws InterruptedException { + assert len<=available(); + while (fUsedSlots == 0) { + if(fClosed) + return 0; + // wait until not empty + wait(); + } + int n = Math.min(len, fBuffer.length - fTakePos); + System.arraycopy(fBuffer, fTakePos, cbuf, off, n); + if (fTakePos + len > n) + System.arraycopy(fBuffer, 0, cbuf, off + n, len - n); + fTakePos = (fTakePos + len) % fBuffer.length; + boolean wasFull = fUsedSlots == fBuffer.length; + fUsedSlots -= len; + if(wasFull) + notifyAll(); + + return len; + } + public void close() { + fClosed=true; + notifyAll(); + } + public boolean isClosed() { + return fClosed; + } + } + + /** + * An output stream that calls {@link PipedInputStream#textAvailable} + * every time data is written to the stream. The data is written to + * {@link PipedInputStream#fQueue}. + * + */ + class PipedOutputStream extends OutputStream { + public void write(byte[] b, int off, int len) throws IOException { + try { + synchronized (fQueue) { + if(fQueue.isClosed()) + throw new IOException("Stream is closed!"); //$NON-NLS-1$ + int written=0; + while(writtenPipedInputStream has no effect. The methods in + * this class can be called after the stream has been closed without + * generating an IOException. + *

+ */ + public void close() throws IOException { + } + + public int read(byte[] cbuf, int off, int len) throws IOException { + int n=0; + if(len==0) + return 0; + // read as much as we can using a single synchronized statement + try { + synchronized (fQueue) { + // if nothing available, block and read one byte + if (fQueue.available() == 0) { + // block now until at least one byte is available + int c = fQueue.read(); + // are we at the end of stream + if (c == -1) + return -1; + cbuf[off] = (byte) c; + n++; + } + // is there more data available? + if (n < len && fQueue.available() > 0) { + // read at most available() + int nn = Math.min(fQueue.available(), len - n); + // are we at the end of the stream? + if (nn == 0 && fQueue.isClosed()) { + // if no byte was read, return -1 to indicate end of stream + // else return the bytes we read up to now + if (n == 0) + n = -1; + return n; + } + fQueue.read(cbuf, off + n, nn); + n += nn; + } + + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return n; + } +} diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/PollingTextCanvasModel.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/PollingTextCanvasModel.java new file mode 100644 index 00000000000..9ca030403be --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/PollingTextCanvasModel.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. and others. + * 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 + * + * Contributors: + * Michael Scharf (Wind River) - initial API and implementation + *******************************************************************************/ +package org.eclipse.tm.internal.terminal.textcanvas; +import org.eclipse.swt.widgets.Display; +import org.eclipse.tm.terminal.model.ITerminalTextDataSnapshot; + +/** + * @author Michael.Scharf@scharf-software.com + * + */ +public class PollingTextCanvasModel extends AbstractTextCanvasModel { + int fPollInterval=50; + /** + * + */ + public PollingTextCanvasModel(ITerminalTextDataSnapshot snapshot) { + super(snapshot); + Display.getDefault().timerExec(fPollInterval,new Runnable(){ + public void run() { + update(); + Display.getDefault().timerExec(fPollInterval,this); + }}); + } + public void setUpdateInterval(int t) { + fPollInterval=t; + } +} diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/StyleMap.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/StyleMap.java new file mode 100644 index 00000000000..1311cf3fe89 --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/StyleMap.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. and others. + * 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 + * + * Contributors: + * Michael Scharf (Wind River) - initial API and implementation + *******************************************************************************/ +package org.eclipse.tm.internal.terminal.textcanvas; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jface.resource.JFaceResources; +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.Display; +import org.eclipse.tm.terminal.model.Style; +import org.eclipse.tm.terminal.model.StyleColor; + +public class StyleMap { + String fFontName=JFaceResources.TEXT_FONT; + Map fColorMap=new HashMap(); + Map fFontMap=new HashMap(); + private Point fCharSize; + private Style fDefaultStyle; + StyleMap() { + Display display=Display.getCurrent(); + fColorMap.put(StyleColor.getStyleColor("white"), new Color(display,255,255,255)); //$NON-NLS-1$ + fColorMap.put(StyleColor.getStyleColor("black"), new Color(display,0,0,0)); //$NON-NLS-1$ + fColorMap.put(StyleColor.getStyleColor("red"), new Color(display,255,128,128)); //$NON-NLS-1$ + fColorMap.put(StyleColor.getStyleColor("green"), new Color(display,128,255,128)); //$NON-NLS-1$ + fColorMap.put(StyleColor.getStyleColor("blue"), new Color(display,128,128,255)); //$NON-NLS-1$ + fColorMap.put(StyleColor.getStyleColor("yellow"), new Color(display,255,255,0)); //$NON-NLS-1$ + fColorMap.put(StyleColor.getStyleColor("cyan"), new Color(display,0,255,255)); //$NON-NLS-1$ + fColorMap.put(StyleColor.getStyleColor("magenta"), new Color(display,255,255,0)); //$NON-NLS-1$ + fColorMap.put(StyleColor.getStyleColor("gray"), new Color(display,128,128,128)); //$NON-NLS-1$ + fColorMap.put(StyleColor.getStyleColor("WHITE"), new Color(display,255,255,255)); //$NON-NLS-1$ + fColorMap.put(StyleColor.getStyleColor("BLACK"), new Color(display,0,0,0)); //$NON-NLS-1$ + fColorMap.put(StyleColor.getStyleColor("RED"), new Color(display,255,128,128)); //$NON-NLS-1$ + fColorMap.put(StyleColor.getStyleColor("GREEN"), new Color(display,128,255,128)); //$NON-NLS-1$ + fColorMap.put(StyleColor.getStyleColor("BLUE"), new Color(display,128,128,255)); //$NON-NLS-1$ + fColorMap.put(StyleColor.getStyleColor("YELLOW"), new Color(display,255,255,0)); //$NON-NLS-1$ + fColorMap.put(StyleColor.getStyleColor("CYAN"), new Color(display,0,255,255)); //$NON-NLS-1$ + fColorMap.put(StyleColor.getStyleColor("MAGENTA"), new Color(display,255,255,0)); //$NON-NLS-1$ + fColorMap.put(StyleColor.getStyleColor("GRAY"), new Color(display,128,128,128)); //$NON-NLS-1$ + fDefaultStyle=Style.getStyle(StyleColor.getStyleColor("black"),StyleColor.getStyleColor("white")); //$NON-NLS-1$ //$NON-NLS-2$ + GC gc = new GC (display); + gc.setFont(getFont()); + fCharSize = gc.textExtent ("W"); //$NON-NLS-1$ + gc.dispose (); + + } + public Color getColor(StyleColor colorName) { + return (Color) fColorMap.get(colorName); + } + public Color getForegrondColor(Style style) { + style = defaultIfNull(style); + if(style.isReverse()) + return getColor(style.getBackground()); + else + return getColor(style.getForground()); + } + private Style defaultIfNull(Style style) { + if(style==null) + style=fDefaultStyle; + return style; + } + public Color getBackgroundColor(Style style) { + style = defaultIfNull(style); + if(style.isReverse()) + return getColor(style.getForground()); + else + return getColor(style.getBackground()); + } +// static Font getBoldFont(Font font) { +// FontData fontDatas[] = font.getFontData(); +// FontData data = fontDatas[0]; +// return new Font(Display.getCurrent(), data.getName(), data.getHeight(), data.getStyle()|SWT.BOLD); +// } + + public Font getFont(Style style) { + style = defaultIfNull(style); + if(style.isBold()) { + return JFaceResources.getFontRegistry().getBold(fFontName); + } else if(style.isUnderline()) { + return JFaceResources.getFontRegistry().getItalic(fFontName); + + } + return JFaceResources.getFontRegistry().get(fFontName); + } + + public Font getFont() { + return JFaceResources.getFontRegistry().get(fFontName); + + } + public int getFontWidth() { + return fCharSize.x; + } + public int getFontHeight() { + return fCharSize.y; + } +} diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/TextCanvas.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/TextCanvas.java new file mode 100644 index 00000000000..1decdda14a9 --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/TextCanvas.java @@ -0,0 +1,213 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. and others. + * 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 + * + * Contributors: + * Michael Scharf (Wind River) - initial API and implementation + *******************************************************************************/ +package org.eclipse.tm.internal.terminal.textcanvas; + + +import org.eclipse.swt.SWT; +import org.eclipse.swt.dnd.Clipboard; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; + +/** + * A cell oriented Canvas. Maintains a list of "cells". + * It can either be vertically or horizontally scrolled. + * The CellRenderer is responsible for painting the cell. + */ +public class TextCanvas extends GridCanvas { + protected final ITextCanvasModel fCellCanvasModel; + /** Renders the cells */ + private ILinelRenderer fCellRenderer; + private boolean fScrollLock; + private Point fDraggingStart; + private Point fDraggingEnd; + /** + * Create a new CellCanvas with the given SWT style bits. + * (SWT.H_SCROLL and SWT.V_SCROLL are automatically added). + */ + public TextCanvas(Composite parent, ITextCanvasModel model, int style) { + super(parent, style | SWT.H_SCROLL | SWT.V_SCROLL); + fCellCanvasModel=model; + fCellCanvasModel.addCellCanvasModelListener(new ITextCanvasModelListener(){ + public void cellSizeChanged() { + setCellWidth(fCellRenderer.getCellWidth()); + setCellHeight(fCellRenderer.getCellHeight()); + + calculateGrid(); + + } + public void rangeChanged(int col, int line, int width, int height) { + repaintRange(col,line,width,height); + } + public void dimensionsChanged(int cols, int rows) { + calculateGrid(); + } + public void terminalDataChanged() { + if(isDisposed()) + return; + scrollToEnd(); + } + }); + addListener(SWT.Resize, new Listener() { + public void handleEvent(Event e) { + calculateGrid(); + } + }); + addFocusListener(new FocusListener(){ + public void focusGained(FocusEvent e) { + fCellCanvasModel.setCursorEnabled(true); + } + public void focusLost(FocusEvent e) { + fCellCanvasModel.setCursorEnabled(false); + }}); + addMouseListener(new MouseListener(){ + public void mouseDoubleClick(MouseEvent e) { + } + public void mouseDown(MouseEvent e) { + if(e.button==1) { // left button + fDraggingStart=screenPointToCell(e.x, e.y); + fDraggingEnd=null; + } + } + public void mouseUp(MouseEvent e) { + if(e.button==1) { // left button + setSelection(screenPointToCell(e.x, e.y)); + fDraggingStart=null; + } + } + }); + addMouseMoveListener(new MouseMoveListener() { + + public void mouseMove(MouseEvent e) { + if (fDraggingStart != null) { + setSelection(screenPointToCell(e.x, e.y)); + } + } + }); + } + + void setSelection(Point p) { + if (!p.equals(fDraggingEnd)) { + fDraggingEnd = p; + if (compare(p, fDraggingStart) < 0) { + fCellCanvasModel.setSelection(p.y, fDraggingStart.y, p.x, fDraggingStart.x); + } else { + fCellCanvasModel.setSelection(fDraggingStart.y, p.y, fDraggingStart.x, p.x); + + } + } + } + + int compare(Point p1, Point p2) { + if (p1.equals(p2)) + return 0; + if (p1.y == p2.y) { + if (p1.x > p2.x) + return 1; + else + return -1; + } + if (p1.y > p2.y) { + return 1; + } else { + return -1; + } + } + public void setCellRenderer(ILinelRenderer cellRenderer) { + fCellRenderer = cellRenderer; + setCellWidth(fCellRenderer.getCellWidth()); + setCellHeight(fCellRenderer.getCellHeight()); + } + public ILinelRenderer getCellRenderer() { + return fCellRenderer; + } + private void calculateGrid() { + setVirtualExtend(getCols()*getCellWidth(),getRows()*getCellHeight()); + // scroll to end + scrollToEnd(); + // make sure the scroll area is correct: + scrollY(getVerticalBar()); + scrollX(getHorizontalBar()); + + updateViewRectangle(); + getParent().layout(); + redraw(); + } + void scrollToEnd() { + if(!fScrollLock) { + int y=-(getRows()*getCellHeight()-getClientArea().height); + Rectangle v=getViewRectangle(); + if(v.y!=y) { + setVirtualOrigin(0,y); + } + } + } + /** + * + * @return true if the cursor should be shown on output.... + */ + public boolean isScrollLock() { + return fScrollLock; + } + /** + * If set then if the size changes + * @param scrollLock + */ + public void setScrollLock(boolean scrollLock) { + fScrollLock=scrollLock; + } + protected void repaintRange(int col, int line, int width, int height) { + Point origin=cellToOriginOnScreen(col,line); + Rectangle r=new Rectangle(origin.x,origin.y,width*getCellWidth(),height*getCellHeight()); + repaint(r); + } + protected void drawLine(GC gc, int line, int x, int y, int colFirst, int colLast) { + fCellRenderer.drawLine(fCellCanvasModel, gc,line,x,y,colFirst, colLast); + + } + protected void visibleCellRectangleChanged(int x, int y, int width, int height) { + fCellCanvasModel.setVisibleRectangle(y,x,height,width); + update(); + } + protected int getCols() { + return fCellCanvasModel.getTerminalText().getWidth(); + } + protected int getRows() { + return fCellCanvasModel.getTerminalText().getHeight(); + } + public String getSelectionText() { + // TODO -- create a hasSelectionMethod! + return fCellCanvasModel.getSelectedText(); + } + public void copy() { + Clipboard clipboard = new Clipboard(getDisplay()); + clipboard.setContents(new Object[] { getSelectionText() }, new Transfer[] { TextTransfer.getInstance() }); + clipboard.dispose(); + } + public void selectAll() { + fCellCanvasModel.setSelection(0, fCellCanvasModel.getTerminalText().getHeight(), 0, fCellCanvasModel.getTerminalText().getWidth()); + + } + public boolean isEmpty() { + return false; + } +} + diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/TextLineRenderer.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/TextLineRenderer.java new file mode 100644 index 00000000000..54180496f8f --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/internal/terminal/textcanvas/TextLineRenderer.java @@ -0,0 +1,141 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. and others. + * 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 + * + * Contributors: + * Michael Scharf (Wind River) - initial API and implementation + *******************************************************************************/ +package org.eclipse.tm.internal.terminal.textcanvas; + +import org.eclipse.swt.SWT; +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.Display; +import org.eclipse.tm.terminal.model.ITerminalTextDataReadOnly; +import org.eclipse.tm.terminal.model.LineSegment; +import org.eclipse.tm.terminal.model.Style; + +/** + * + */ +public class TextLineRenderer implements ILinelRenderer { + TextCanvas fCanvas; + private final ITextCanvasModel fModel; + StyleMap fStyleMap=new StyleMap(); + Color fBackgroundColor; + public TextLineRenderer(TextCanvas c, ITextCanvasModel model) { + fCanvas=c; + fModel=model; + fBackgroundColor=c.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND); + } + /* (non-Javadoc) + * @see com.imagicus.thumbs.view.ICellRenderer#getCellWidth() + */ + public int getCellWidth() { + return fStyleMap.getFontWidth(); + } + /* (non-Javadoc) + * @see com.imagicus.thumbs.view.ICellRenderer#getCellHeight() + */ + public int getCellHeight() { + return fStyleMap.getFontHeight(); + } + public void drawLine(ITextCanvasModel model, GC gc, int line, int x, int y, int colFirst, int colLast) { + if(line<0 || line>=getTerminalText().getHeight() || colFirst>=getTerminalText().getWidth() || colFirst-colLast==0) { + fillBackground(gc, x, y, getCellWidth()*(colFirst-colLast), getCellHeight()); + } else { + colLast=Math.min(colLast, getTerminalText().getWidth()); + LineSegment[] segments=getTerminalText().getLineSegments(line, colFirst, colLast-colFirst); + for (int i = 0; i < segments.length; i++) { + LineSegment segment=segments[i]; + Style style=segment.getStyle(); + setupGC(gc, style); + String text=segment.getText(); + drawText(gc, x, y, colFirst, segment.getColumn(), text); + drawCursor(model, gc, line, x, y, colFirst); + } + if(fModel.hasLineSelection(line)) { + gc.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT)); + gc.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_LIST_SELECTION)); + Point start=model.getSelectionStart(); + Point end=model.getSelectionEnd(); + char[] chars=model.getTerminalText().getChars(line); + if(chars==null) + return; + int offset=0; + if(start.y==line) + offset=start.x; + offset=Math.max(offset, colFirst); + int len; + if(end.y==line) + len=end.x-offset+1; + else + len=chars.length-offset+1; + len=Math.min(len,chars.length-offset); + if(len>0) { + String text=new String(chars,offset,len); + drawText(gc, x, y, colFirst, offset, text); + } + } + } + } + + private void fillBackground(GC gc, int x, int y, int width, int height) { + Color bg=gc.getBackground(); + gc.setBackground(getBackgroundColor()); + gc.fillRectangle (x,y,width,height); + gc.setBackground(bg); + + } + + private Color getBackgroundColor() { + return fBackgroundColor; + } + private void drawCursor(ITextCanvasModel model, GC gc, int row, int x, int y, int colFirst) { + if(!model.isCursorOn()) + return; + int cursorLine=model.getCursorLine(); + + if(row==cursorLine) { + int cursorColumn=model.getCursorColumn(); + if(cursorColumnCanvas showing a virtual object. + * Virtual: the extent of the total canvas. + * Screen: the visible client area in the screen. + */ +public abstract class VirtualCanvas extends Canvas { + + private Rectangle fVirtualBounds = new Rectangle(0,0,0,0); + private Rectangle fClientArea; + private GC fPaintGC=null; + /** + * prevent infinite loop in {@link #updateScrollbars()} + */ + private boolean fInUpdateScrollbars; + + public VirtualCanvas(Composite parent, int style) { + super(parent, style|SWT.NO_BACKGROUND|SWT.NO_REDRAW_RESIZE); + fPaintGC= new GC(this); + fClientArea=getClientArea(); + addListener(SWT.Paint, new Listener() { + public void handleEvent(Event event) { + paint(event.gc); + } + }); + addListener(SWT.Resize, new Listener() { + public void handleEvent(Event event) { + fClientArea=getClientArea(); + updateViewRectangle(); + } + }); + getVerticalBar().addListener(SWT.Selection, new Listener() { + public void handleEvent(Event e) { + scrollY((ScrollBar)e.widget); + postScrollEventHandling(e); + + } + + }); + getHorizontalBar().addListener(SWT.Selection, new Listener() { + public void handleEvent(Event e) { + scrollX((ScrollBar)e.widget); + postScrollEventHandling(e); + + } + }); + addDisposeListener(new DisposeListener(){ + public void widgetDisposed(DisposeEvent e) { + if(fPaintGC!=null){ + fPaintGC.dispose(); + fPaintGC=null; + } + } + + }); + } + /** HACK: run an event loop if the scrollbar is dragged...*/ + private void postScrollEventHandling(Event e) { + if(true&&e.detail==SWT.DRAG) { + // TODO check if this is always ok??? + // used to process runnables while scrolling + // This fixes the update problems when scrolling! + // see: https://bugs.eclipse.org/bugs/show_bug.cgi?id=47582#5 + // TODO investigate: + // The alternative is to call redraw on the new visible area + // redraw(expose.x, expose.y, expose.width, expose.height, true); + + while (!getDisplay().isDisposed() && getDisplay().readAndDispatch()) { + // do nothing here... + } + } + } + + protected void scrollX(ScrollBar hBar) { + int hSelection = hBar.getSelection (); + int destX = -hSelection - fVirtualBounds.x; + fVirtualBounds.x = -hSelection; + scrollSmart(destX, 0); + updateViewRectangle(); + } + protected void scrollXDelta(int delta) { + getHorizontalBar().setSelection(-fVirtualBounds.x+delta); + scrollX(getHorizontalBar()); + } + + protected void scrollY(ScrollBar vBar) { + int vSelection = vBar.getSelection (); + int destY = -vSelection - fVirtualBounds.y; + if(destY!=0) { + fVirtualBounds.y = -vSelection; + scrollSmart(0,destY); + updateViewRectangle(); + } + + } + protected void scrollYDelta(int delta) { + getVerticalBar().setSelection(-fVirtualBounds.y+delta); + scrollY(getVerticalBar()); + } + + + private void scrollSmart(int deltaX, int deltaY) { + Rectangle rect = getBounds(); + scroll (deltaX, deltaY, 0, 0, rect.width, rect.height, false); + } + + /** + * @param rect in virtual space + */ + protected void revealRect(Rectangle rect) { + Rectangle visibleRect=getScreenRectInVirtualSpace(); + // scroll the X part + int deltaX=0; + if(rect.x0||marginHeight>0){ + Color bg=getBackground(); + gc.setBackground(getBackgroundColor()); + if (marginWidth > 0) { + gc.fillRectangle (width, clipping.y, marginWidth, clipping.height); + } + if (marginHeight > 0) { + gc.fillRectangle (clipping.x, height, clipping.width, marginHeight); + } + gc.setBackground(bg); + } + } + /** + * @private + */ + protected boolean inClipping(Rectangle clipping, Rectangle r) { + // TODO check if this is OK in all cases (the <=!) + // + if(r.x+r.width<=clipping.x) + return false; + if(clipping.x+clipping.width<=r.x) + return false; + if(r.y+r.height<=clipping.y) + return false; + if(clipping.y+clipping.height<=r.y) + return false; + + return true; + } + /** + * @return the screen rect in virtual space (starting with (0,0)) + * of the visible screen. (x,y>=0) + */ + protected Rectangle getScreenRectInVirtualSpace() { + Rectangle r= new Rectangle(fClientArea.x-fVirtualBounds.x,fClientArea.y-fVirtualBounds.y,fClientArea.width,fClientArea.height); + return r; + } + /** + * @return the rect in virtual space (starting with (0,0)) + * of the visible screen. (x,y>=0) + */ + protected Rectangle getRectInVirtualSpace(Rectangle r) { + return new Rectangle(r.x-fVirtualBounds.x,r.y-fVirtualBounds.y,r.width,r.height); + } + + /** + * Sets the extend of the virtual dieplay ares + * @param width + * @param height + */ + protected void setVirtualExtend(int width, int height) { + fVirtualBounds.width=width; + fVirtualBounds.height=height; + updateScrollbars(); + updateViewRectangle(); + } + /** + * sets the scrolling origin. Also sets the scrollbars. + * Does NOT redraw! + * Use negative values (move the virtual origin to the top left + * to see something in the screen (which is located at (0,0)) + * @param x + * @param y + */ + protected void setVirtualOrigin(int x, int y) { + fVirtualBounds.x=x; + fVirtualBounds.y=y; + getHorizontalBar().setSelection(-x); + getVerticalBar().setSelection(-y); + updateViewRectangle(); + } + protected Rectangle getVirtualBounds() { + return cloneRectangle(fVirtualBounds); + } + /** + * @param x + * @return the virtual coordinate in scree space + */ + protected int virtualXtoScreen(int x) { + return x+fVirtualBounds.x; + } + protected int virtualYtoScreen(int y) { + return y+fVirtualBounds.y; + } + protected int screenXtoVirtual(int x) { + return x-fVirtualBounds.x; + } + protected int screenYtoVirtual(int y) { + return y-fVirtualBounds.y; + } + /** called when the viewed part is changing */ + private Rectangle fViewRectangle=new Rectangle(0,0,0,0); + void updateViewRectangle() { + if( + fViewRectangle.x==-fVirtualBounds.x + && fViewRectangle.y==-fVirtualBounds.y + && fViewRectangle.width==fClientArea.width + && fViewRectangle.height==fClientArea.height + ) + return; + fViewRectangle.x=-fVirtualBounds.x; + fViewRectangle.y=-fVirtualBounds.y; + fViewRectangle.width=fClientArea.width; + fViewRectangle.height=fClientArea.height; + viewRectangleChanged(fViewRectangle.x,fViewRectangle.y,fViewRectangle.width,fViewRectangle.height); + } + protected Rectangle getViewRectangle() { + return cloneRectangle(fViewRectangle); + } + private Rectangle cloneRectangle(Rectangle r) { + return new Rectangle(r.x,r.y,r.width,r.height); + } + /** + * Called when the viewed part has changed. + * Override when you need this information.... + * Is only called if the values change! + * @param x visible in virtual space + * @param y visible in virtual space + * @param width + * @param height + */ + protected void viewRectangleChanged(int x, int y, int width, int height) { + } + /** + * @private + */ + private void updateScrollbars() { + // don't get into infinite loops.... + if(!fInUpdateScrollbars) { + fInUpdateScrollbars=true; + try { + doUpdateScrollbar(); + } finally { + fInUpdateScrollbars=false; + } + } + } + private void doUpdateScrollbar() { + Point size= getSize(); + Rectangle clientArea= getClientArea(); + + ScrollBar horizontal= getHorizontalBar(); + if (fVirtualBounds.width <= clientArea.width) { + // TODO IMPORTANT in ScrollBar.setVisible comment out the line + // that checks 'isvisible' and returns (at the beginning) + horizontal.setVisible(false); + horizontal.setSelection(0); + } else { + horizontal.setPageIncrement(clientArea.width - horizontal.getIncrement()); + int max= fVirtualBounds.width + (size.x - clientArea.width); + horizontal.setMaximum(max); + horizontal.setThumb(size.x > max ? max : size.x); + horizontal.setVisible(true); + } + + ScrollBar vertical= getVerticalBar(); + if (fVirtualBounds.height <= clientArea.height) { + vertical.setVisible(false); + vertical.setSelection(0); + } else { + vertical.setPageIncrement(clientArea.height - vertical.getIncrement()); + int max= fVirtualBounds.height + (size.y - clientArea.height); + vertical.setMaximum(max); + vertical.setThumb(size.y > max ? max : size.y); + vertical.setVisible(true); + } + } +} + diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/model/ITerminalTextData.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/model/ITerminalTextData.java new file mode 100644 index 00000000000..f3359e10fb4 --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/model/ITerminalTextData.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. and others. + * 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 + * + * Contributors: + * Michael Scharf (Wind River) - initial API and implementation + *******************************************************************************/ +package org.eclipse.tm.terminal.model; + + +/** + * A writable matrix of characters and {@link Style}. This is intended to be the + * low level representation of the text of a Terminal. Higher layers are responsible + * to fill the text and styles into this representation. + * + *

Note: Implementations of this interface has to be thread safe. + *

Note: This interface is not intended to be implemented by clients. + */ +public interface ITerminalTextData extends ITerminalTextDataReadOnly { + + /** + * Sets the dimensions of the data. If the dimensions are smaller than the current + * dimensions, the lines will be chopped. If the dimensions are bigger, then + * the new elements will be filled with 0 chars and null Style. + * @param height + * @param width + */ + void setDimensions(int height, int width); + + void setMaxHeight(int height); + int getMaxHeight(); + + /** + * Set a single character and the associated {@link Style}. + * @param line line must be >=0 and < height + * @param column column must be >=0 and < width + * @param c the new character at this position + * @param style the style or null + */ + void setChar(int line, int column, char c, Style style); + + /** + * Set an array of characters showing in the same {@link Style}. + * @param line line must be >=0 and < height + * @param column column must be >=0 and < width + * @param chars the new characters at this position + * @param style the style or null + */ + void setChars(int line, int column, char[] chars, Style style); + + /** + * Set a subrange of an array of characters showing in the same {@link Style}. + * @param line line must be >=0 and < height + * @param column column must be >=0 and < width + * @param chars the new characters at this position + * @param start the start index in the chars array + * @param len the number of characters to insert. Characters beyond width are not inserted. + * @param style the style or null + */ + void setChars(int line, int column, char[] chars, int start, int len, Style style); + + + /** + * Cleans the entire line. + * @param line + */ + void cleanLine(int line); +// /** +// * @param line +// * @return true if this line belongs to the previous line but is simply +// * wrapped. +// */ +// boolean isWrappedLine(int line); +// +// /** +// * Makes this line an extension to the previous line. Wrapped lines get folded back +// * when the width of the terminal changes +// * @param line +// * @param extendsPreviousLine +// */ +// void setWrappedLine(int line, boolean extendsPreviousLine); + + /** + * Shifts some lines up or down. The "empty" space is filled with '\000' chars + * and null {@link Style} + *

To illustrate shift, here is some sample data: + *

+	 * 0 aaaa
+	 * 1 bbbb
+	 * 2 cccc
+	 * 3 dddd
+	 * 4 eeee
+	 * 
+ * + * Shift a region of 3 lines up by one line shift(1,3,-1) + *
+	 * 0 aaaa
+	 * 1 cccc
+	 * 2 dddd
+	 * 3 
+	 * 4 eeee
+	 * 
+ * + * + * Shift a region of 3 lines down by one line shift(1,3,1) + *
+	 * 0 aaaa
+	 * 1 
+	 * 2 bbbb
+	 * 3 cccc
+	 * 4 eeee
+	 * 
+ * @param startLine the start line of the shift + * @param size the number of lines to shift + * @param shift how much scrolling is done. New scrolled area is filled with '\000'. + * Negative number means scroll down, positive scroll up (see example above). + */ + void scroll(int startLine, int size, int shift); + + /**Adds a new line to the terminal. If maxHeigth is reached, the entire terminal + * will be scrolled. Else a line will be added. + */ + void addLine(); + /** + * Copies the entire source into this and changes the size accordingly + * @param source + */ + void copy(ITerminalTextData source); + /** + * Copy a sourceLine from source to this at destLine. + * @param source + * @param sourceLine + * @param destLine + */ + void copyLine(ITerminalTextData source,int sourceLine, int destLine); + /** + * Copy length lines from source starting at sourceLine into this starting at + * destLine. + * @param source + * @param sourceStartLine + * @param destStartLine + * @param length + */ + void copyRange(ITerminalTextData source, int sourceStartLine, int destStartLine,int length); + + void setCursorLine(int line); + void setCursorColumn(int column); +} \ No newline at end of file diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/model/ITerminalTextDataReadOnly.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/model/ITerminalTextDataReadOnly.java new file mode 100644 index 00000000000..183446c191d --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/model/ITerminalTextDataReadOnly.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. and others. + * 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 + * + * Contributors: + * Michael Scharf (Wind River) - initial API and implementation + *******************************************************************************/ +package org.eclipse.tm.terminal.model; + + +public interface ITerminalTextDataReadOnly { + + /** + * @return the width of the terminal + */ + int getWidth(); + + /** + * @return the height of the terminal + */ + int getHeight(); + + /** + * @param line be >=0 and < height + * @param startCol must be >=0 and < width + * @param numberOfCols must be > 0 + * @return a the line segments of the specified range + */ + LineSegment[] getLineSegments(int line, int startCol, int numberOfCols); + + /** + * @param line must be >=0 and < height + * @param column must be >=0 and < width + * @return the character at column,line + */ + char getChar(int line, int column); + + /** + * @param line must be >=0 and < height + * @param column must be >=0 and < width + * @return style at column,line or null + */ + Style getStyle(int line, int column); + + /** + * Creates a new instance of {@link ITerminalTextDataSnapshot} that + * can be used to track changes. Make sure to call {@link ITerminalTextDataSnapshot#detach()} + * if you don't need the snapshots anymore. + *

Note: A new snapshot is empty and needs a call to {@link ITerminalTextDataSnapshot#updateSnapshot(boolean)} to + * get its initial values. You might want to setup the snapshot to your needs by calling + * {@link ITerminalTextDataSnapshot#setInterestWindow(int, int)}. + *

+ * @return a new instance of {@link ITerminalTextDataSnapshot} that "listens" to changes of + * this. + */ + public ITerminalTextDataSnapshot makeSnapshot(); + + char[] getChars(int line); + Style[] getStyles(int line); + + /** + * @return the line in which the cursor is at the moment + */ + int getCursorLine(); + /** + * @return the column at which the cursor is at the moment + */ + int getCursorColumn(); +} \ No newline at end of file diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/model/ITerminalTextDataSnapshot.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/model/ITerminalTextDataSnapshot.java new file mode 100644 index 00000000000..7d4065e7581 --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/model/ITerminalTextDataSnapshot.java @@ -0,0 +1,212 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. and others. + * 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 + * + * Contributors: + * Michael Scharf (Wind River) - initial API and implementation + *******************************************************************************/ +package org.eclipse.tm.terminal.model; + +/** + * This class maintains a snapshot of an instance of {@link ITerminalTextData}. + * While the {@link ITerminalTextData} continues changing, the snapshot remains + * unchanged until the next snapshot is taken by calling {@link #updateSnapshot(boolean)}. + * This is important, because the {@link ITerminalTextData} might get + * modified by another thread. Suppose you would want to draw the content of + * the {@link ITerminalTextData} using the following loop: + *
+ * for(int line=0;line<term.getHeight();line++) 
+ *     for(int column=0; column<term.getWidth();column++)
+ *         drawCharacter(column,line,term.getChar(column,line),term.getStyle(column,line));
+ * 
+ * This might fail because the background thread could change the dimensions of the + * {@link ITerminalTextData} while you iterate the loop. One solution would be to + * put a synchronized(term){} statement around the code. This has + * two problems: 1. you would have to know about the internals of the synchronisation + * of {@link ITerminalTextData}. 2. The other thread that changes {@link ITerminalTextData} + * is blocked while the potentially slow drawing is done. + *

+ * Solution: Take a snapshot of the terminal and use the snapshot to draw + * the content. There is no danger that the data structure get changed while + * you draw. There are also methods to find out what has changed to minimize + * the number of lines that get redrawn.

+ * + *

Drawing optimization: To optimize redrawing of changed lines, this class keeps + * track of lines that have changed since the previous snapshot.

+ * + *
+ * // iterate over the potentially changed lines
+ * for(int line=snap.getFirstChangedLine();line<=snap.getLastChangedLine();line++) 
+ *     // redraw only if the line has changed
+ *     if(snap.hasLineChanged(line))
+ *         for(int column=0; column<snap.getWidth();column++)
+ *            drawCharacter(column,line,snap.getChar(column,line),snap.getStyle(column,line));
+ * 
+ * + *

Scroll optimization: Often new lines are appended at the bottom of the + * terminal and the rest of the lines are scrolled up. In this case all lines would be + * marked as changed. To optimize for this + * case, {@link #updateSnapshot(boolean)} can be called with true for + * the detectScrolling parameter. The object will keep track of scrolling. + * The UI must first handle the scrolling and then use the {@link #hasLineChanged(int)} + * method to determine scrolling: + *

+ * // scroll the visible region of the UI before drawing the changed lines.
+ * doUIScrolling(snap.getScrollChangeY(),snap.getScrollChangeN(),snap.getScrollChangeShift());
+ * // iterate over the potentially changed lines
+ * for(int line=snap.getFirstChangedLine();line<=snap.getFirstChangedLine();line++) 
+ *     // redraw only if the line has changed
+ *     if(snap.hasLineChanged(line))
+ *         for(int column=0; column<snap.getWidth();column++)
+ *            drawCharacter(column,line,snap.getChar(column,line),snap.getStyle(column,line));
+ * 
+ *

+ * + *

Note: This interface is not intended to be implemented by clients.

+ *

Threading Note: This class is not thread save! All methods have to be called by + * the a same thread, that created the instance by calling + * {@link ITerminalTextDataReadOnly#makeSnapshot()}.

+ */ +public interface ITerminalTextDataSnapshot extends ITerminalTextDataReadOnly { + /** + * This listener gets called when the current snapshot + * is out of date. Calling {@link ITerminalTextDataSnapshot#updateSnapshot(boolean)} + * will have an effect. Once the {@link #snapshotOutOfDate(ITerminalTextDataSnapshot)} method is called, + * it will not be called until {@link ITerminalTextDataSnapshot#updateSnapshot(boolean)} + * is called and a new snapshot needs to be updated again. + *

+ * A typical terminal view would not update the snapshot immediately + * after the {@link #snapshotOutOfDate(ITerminalTextDataSnapshot)} has been called. It would introduce a + * delay to update the UI (and the snapshot} 10 or 20 times per second. + * + *

Make sure you don't spend too much time in this method. + */ + interface SnapshotOutOfDateListener { + /** + * Gets called when the snapshot is out of date. To get the snapshot up to date, + * call {@link ITerminalTextDataSnapshot#updateSnapshot(boolean)}. + * @param snapshot The snapshot that is out of date + */ + void snapshotOutOfDate(ITerminalTextDataSnapshot snapshot); + } + void addListener(SnapshotOutOfDateListener listener); + void removeListener(SnapshotOutOfDateListener listener); + + /** + * Ends the listening to the {@link ITerminalTextData}. After this + * has been called no new snapshot data is collected. + */ + void detach(); + /** + * @return true if the data has changed since the previous snapshot. + */ + boolean isOutOfDate(); + + /** + * The window of interest is the region the snapshot should track. + * Changes outside this region are ignored. The change takes effect after + * an update! + * @param startLine -1 means track the end of the data + * @param size number of lines to track. A size of -1 means track all. + */ + void setInterestWindow(int startLine, int size); + int getInterestWindowStartLine(); + int getInterestWindowSize(); + + /** + * Create a new snapshot of the {@link ITerminalTextData}. It will efficiently + * copy the data of the {@link ITerminalTextData} into an internal representation. + * The snapshot also keeps track of the changes since the previous snapshot. + *

With the methods {@link #getFirstChangedLine()}, {@link #getLastChangedLine()} and + * {@link #hasLineChanged(int)} + * you can find out what has changed in the current snapshot since the previous snapshot. + * @param detectScrolling if true the snapshot tries to identify scroll + * changes since the last snapshot. In this case the information about scrolling + * can be retrieved using the following methods: + * {@link #getScrollWindowStartLine()}, {@link #getScrollWindowSize()} and {@link #getScrollWindowShift()} + *
Note: The method {@link #hasLineChanged(int)} returns changes after the + * scrolling has been applied. + */ + void updateSnapshot(boolean detectScrolling); + + /** + * @return The first line changed in this snapshot compared + * to the previous snapshot. + * + *

Note: If no line has changed, this + * returns {@link Integer#MAX_VALUE} + * + *

Note: if {@link #updateSnapshot(boolean)} has been called with true, + * then this does not include lines that only have been scrolled. This is the + * first line that has changed after the scroll has been applied. + */ + int getFirstChangedLine(); + + /** + * @return The last line changed in this snapshot compared + * to the previous snapshot. If the height has changed since the + * last update of the snapshot, then the returned value is within + * the new dimensions. + * + *

Note: If no line has changed, this returns -1 + * + *

Note: if {@link #updateSnapshot(boolean)} has been called with true, + * then this does not include lines that only have been scrolled. This is the + * last line that has changed after the scroll has been applied. + * + *

A typical for loop using this method would look like this (note the <= in the for loop): + *

+	 * for(int line=snap.{@link #getFirstChangedLine()}; line <= snap.getLastChangedLine(); line++)
+	 *    if(snap.{@link #hasLineChanged(int) hasLineChanged(line)})
+	 *       doSomething(line);
+	 * 
+ */ + int getLastChangedLine(); + + /** + * @param line + * @return true if the line has changed since the previous snapshot + */ + boolean hasLineChanged(int line); + + boolean hasDimensionsChanged(); + + /** + * @return true if the terminal has changed (and not just the + * window of interest) + */ + boolean hasTerminalChanged(); + /** + * If {@link #updateSnapshot(boolean)} was called with true, then this method + * returns the top of the scroll region. + * @return The first line scrolled in this snapshot compared + * to the previous snapshot. See also {@link ITerminalTextData#scroll(int, int, int)}. + */ + int getScrollWindowStartLine(); + + /** + * If {@link #updateSnapshot(boolean)} was called with true, then this method + * returns the size of the scroll region. + * @return The number of lines scrolled in this snapshot compared + * to the previous snapshot. See also {@link ITerminalTextData#scroll(int, int, int)} + * If nothing has changed, 0 is returned. + */ + int getScrollWindowSize(); + + /** + * If {@link #updateSnapshot(boolean)} was called with true, then this method + * returns number of lines moved by the scroll region. + * @return The the scroll shift of this snapshot compared + * to the previous snapshot. See also {@link ITerminalTextData#scroll(int, int, int)} + */ + int getScrollWindowShift(); + + /** + * @return The {@link ITerminalTextData} on that this instance is observing. + */ + ITerminalTextData getTerminalTextData(); + +} \ No newline at end of file diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/model/LineSegment.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/model/LineSegment.java new file mode 100644 index 00000000000..9c8c38935ff --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/model/LineSegment.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. and others. + * 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 + * + * Contributors: + * Michael Scharf (Wind River) - initial API and implementation + *******************************************************************************/ +package org.eclipse.tm.terminal.model; + + +public class LineSegment { + private final String fText; + private final int fCol; + private final Style fStyle; + public LineSegment(int col, String text, Style style) { + fCol = col; + fText = text; + fStyle = style; + } + public Style getStyle() { + return fStyle; + } + public String getText() { + return fText; + } + public int getColumn() { + return fCol; + } + public String toString() { + return "LineSegment("+fCol+", \""+fText+"\","+fStyle+")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + } +} \ No newline at end of file diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/model/Style.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/model/Style.java new file mode 100644 index 00000000000..9e755143ad1 --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/model/Style.java @@ -0,0 +1,154 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. and others. + * 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 + * + * Contributors: + * Michael Scharf (Wind River) - initial API and implementation + *******************************************************************************/ +package org.eclipse.tm.terminal.model; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author scharf + * Flyweight + * Threadsafe. + * + */ +// TODO add an Object for user data, use weak map to keep track of styles with associated +// user data +public class Style { + private final StyleColor fForground; + private final StyleColor fBackground; + private final boolean fBold; + private final boolean fBlink; + private final boolean fUnderline; + private final boolean fReverse; + private final static Map fgStyles=new HashMap(); + private Style(StyleColor forground, StyleColor background, boolean bold, boolean blink, boolean underline, boolean reverse) { + fForground = forground; + fBackground = background; + fBold = bold; + fBlink = blink; + fUnderline = underline; + fReverse = reverse; + } + public static Style getStyle(StyleColor forground, StyleColor background, boolean bold, boolean blink, boolean underline, boolean reverse) { + Style style = new Style(forground,background, bold, blink,underline,reverse); + Style cached; + synchronized (fgStyles) { + cached=(Style) fgStyles.get(style); + if(cached==null) { + cached=style; + fgStyles.put(cached, cached); + } + } + return cached; + } + public static Style getStyle(String forground, String background) { + return getStyle(StyleColor.getStyleColor(forground), StyleColor.getStyleColor(background),false,false,false,false); + } + public static Style getStyle(StyleColor forground, StyleColor background) { + return getStyle(forground, background,false,false,false,false); + } + public Style setForground(StyleColor forground) { + return getStyle(forground,fBackground,fBold,fBlink,fUnderline,fReverse); + } + public Style setBackground(StyleColor background) { + return getStyle(fForground,background,fBold,fBlink,fUnderline,fReverse); + } + public Style setForground(String colorName) { + return getStyle(StyleColor.getStyleColor(colorName),fBackground,fBold,fBlink,fUnderline,fReverse); + } + public Style setBackground(String colorName) { + return getStyle(fForground,StyleColor.getStyleColor(colorName),fBold,fBlink,fUnderline,fReverse); + } + public Style setBold(boolean bold) { + return getStyle(fForground,fBackground,bold,fBlink,fUnderline,fReverse); + } + public Style setBlink(boolean blink) { + return getStyle(fForground,fBackground,fBold,blink,fUnderline,fReverse); + } + public Style setUnderline(boolean underline) { + return getStyle(fForground,fBackground,fBold,fBlink,underline,fReverse); + } + public Style setReverse(boolean reverse) { + return getStyle(fForground,fBackground,fBold,fBlink,fUnderline,reverse); + } + public StyleColor getBackground() { + return fBackground; + } + public boolean isBlink() { + return fBlink; + } + public boolean isBold() { + return fBold; + } + public StyleColor getForground() { + return fForground; + } + public boolean isReverse() { + return fReverse; + } + public boolean isUnderline() { + return fUnderline; + } + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((fBackground == null) ? 0 : fBackground.hashCode()); + result = prime * result + (fBlink ? 1231 : 1237); + result = prime * result + (fBold ? 1231 : 1237); + result = prime * result + ((fForground == null) ? 0 : fForground.hashCode()); + result = prime * result + (fReverse ? 1231 : 1237); + result = prime * result + (fUnderline ? 1231 : 1237); + return result; + } + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final Style other = (Style) obj; + // background == is the same as equals + if (fBackground != other.fBackground) + return false; + if (fBlink != other.fBlink) + return false; + if (fBold != other.fBold) + return false; + if (fForground != other.fForground) + return false; + if (fReverse != other.fReverse) + return false; + if (fUnderline != other.fUnderline) + return false; + return true; + } + public String toString() { + StringBuffer result=new StringBuffer(); + result.append("Style(foreground="); //$NON-NLS-1$ + result.append(fForground); + result.append(", background="); //$NON-NLS-1$ + result.append(fBackground); + if(fBlink) + result.append(", blink"); //$NON-NLS-1$ + if(fBold) + result.append(", bold"); //$NON-NLS-1$ + if(fBlink) + result.append(", blink"); //$NON-NLS-1$ + if(fReverse) + result.append(", reverse"); //$NON-NLS-1$ + if(fUnderline) + result.append(", underline"); //$NON-NLS-1$ + result.append(")"); //$NON-NLS-1$ + return result.toString(); + } + +} \ No newline at end of file diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/model/StyleColor.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/model/StyleColor.java new file mode 100644 index 00000000000..39dbb6629d2 --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/model/StyleColor.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. and others. + * 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 + * + * Contributors: + * Michael Scharf (Wind River) - initial API and implementation + *******************************************************************************/ +package org.eclipse.tm.terminal.model; + +import java.util.HashMap; +import java.util.Map; + +/** + * + * Flyweight + * Threadsafe. + */ +public class StyleColor { + private final static Map fgStyleColors=new HashMap(); + final String fName; + + /** + * @param name the name of the color. It is up to the UI to associate a + * named color with a visual representation + * @return a StyleColor + */ + public static StyleColor getStyleColor(String name) { + StyleColor result; + synchronized (fgStyleColors) { + result=(StyleColor) fgStyleColors.get(name); + if(result==null) { + result=new StyleColor(name); + fgStyleColors.put(name, result); + } + } + return result; + } + // nobody except the factory method is allowed to instantiate this class! + private StyleColor(String name) { + fName = name; + } + + public String getName() { + return fName; + } + + public String toString() { + return fName; + } + // no need to override equals and hashCode, because Object uses object identity +} \ No newline at end of file diff --git a/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/model/TerminalTextDataFactory.java b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/model/TerminalTextDataFactory.java new file mode 100644 index 00000000000..bb7545dedc5 --- /dev/null +++ b/terminal/org.eclipse.tm.terminal/src/org/eclipse/tm/terminal/model/TerminalTextDataFactory.java @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2007 Wind River Systems, Inc. and others. + * 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 + * + * Contributors: + * Michael Scharf (Wind River) - initial API and implementation + *******************************************************************************/ +package org.eclipse.tm.terminal.model; + +import org.eclipse.tm.internal.terminal.model.SynchronizedTerminalTextData; +import org.eclipse.tm.internal.terminal.model.TerminalTextData; + +public class TerminalTextDataFactory { + static public ITerminalTextData makeTerminalTextData() { + return new SynchronizedTerminalTextData(new TerminalTextData()); + } +}