blob: 560f374f441946707472ceacdcdbb1ed7eb8eac6 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2011 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
* Michael Scharf (Wind River) - [240098] The cursor should not blink when the terminal is disconnected
* Uwe Stieber (Wind River) - [281328] The very first few characters might be missing in the terminal control if opened and connected programmatically
* Martin Oberhuber (Wind River) - [294327] After logging in, the remote prompt is hidden
* Anton Leherbauer (Wind River) - [294468] Fix scroller and text line rendering
* Uwe Stieber (Wind River) - [205486] Fix ScrollLock always moving to line 1
* Anton Leherbauer (Wind River) - [219589] Copy an entire line selection
* Anton Leherbauer (Wind River) - [196465] Resizing Terminal changes Scroller location
* Anton Leherbauer (Wind River) - [324608] Terminal has strange scrolling behaviour
*******************************************************************************/
package org.eclipse.tm.internal.terminal.textcanvas;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.Composite;
/**
* 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 final ILinelRenderer fCellRenderer;
private boolean fScrollLock;
private Point fDraggingStart;
private Point fDraggingEnd;
private boolean fHasSelection;
private ResizeListener fResizeListener;
// The minSize is meant to determine the minimum size of the backing store
// (grid) into which remote data is rendered. If the viewport is smaller
// than that minimum size, the backing store size remains at the minSize,
// and a scrollbar is shown instead. In reality, this has the following
// issues or effects today:
// (a) Bug 281328: For very early data coming in before the widget is
// realized, the minSize determines into what initial grid that is
// rendered. See also @link{#addResizeHandler(ResizeListener)}.
// (b) Bug 294468: Since we have redraw and size computation problems
// with horizontal scrollers, for now the minColumns must be small
// enough to avoid a horizontal scroller appearing in most cases.
// (b) Bug 294327: since we have problems with the vertical scroller
// showing the correct location, minLines must be small enough
// to avoid a vertical scroller or new data may be rendered off-screen.
// As a compromise, we have been working with a 20x4 since the Terminal
// inception, though many users would want a 80x24 minSize and backing
// store. Pros and cons of the small minsize:
// + consistent "remote size==viewport size", vi works as expected
// - dumb terminals which expect 80x24 render garbled on small viewport.
// If bug 294468 were resolved, an 80 wide minSize would be preferrable
// since it allows switching the terminal viewport small/large as needed,
// without destroying the backing store. For a complete solution,
// Bug 196462 tracks the request for a user-defined fixed-widow-size-mode.
private int fMinColumns=80;
private int fMinLines=4;
private boolean fCursorEnabled;
private boolean fResizing;
/**
* 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,ILinelRenderer cellRenderer) {
super(parent, style | SWT.H_SCROLL | SWT.V_SCROLL);
fCellRenderer=cellRenderer;
setCellWidth(fCellRenderer.getCellWidth());
setCellHeight(fCellRenderer.getCellHeight());
fCellCanvasModel=model;
fCellCanvasModel.addCellCanvasModelListener(new ITextCanvasModelListener(){
@Override
public void rangeChanged(int col, int line, int width, int height) {
repaintRange(col,line,width,height);
}
@Override
public void dimensionsChanged(int cols, int rows) {
calculateGrid();
}
@Override
public void terminalDataChanged() {
if(isDisposed()) {
return;
}
// scroll to end (unless scroll lock is active)
if (!fResizing) {
calculateGrid();
scrollToEnd();
}
}
});
// let the cursor blink if the text canvas gets the focus...
addFocusListener(new FocusListener(){
@Override
public void focusGained(FocusEvent e) {
fCellCanvasModel.setCursorEnabled(fCursorEnabled);
}
@Override
public void focusLost(FocusEvent e) {
fCellCanvasModel.setCursorEnabled(false);
}});
addMouseListener(new MouseListener(){
@Override
public void mouseDoubleClick(MouseEvent e) {
}
@Override
public void mouseDown(MouseEvent e) {
if(e.button==1) { // left button
fDraggingStart=screenPointToCell(e.x, e.y);
fHasSelection=false;
if((e.stateMask&SWT.SHIFT)!=0) {
Point anchor=fCellCanvasModel.getSelectionAnchor();
if(anchor!=null) {
fDraggingStart=anchor;
}
} else {
fCellCanvasModel.setSelectionAnchor(fDraggingStart);
}
fDraggingEnd=null;
}
}
@Override
public void mouseUp(MouseEvent e) {
if(e.button==1) { // left button
updateHasSelection(e);
if(fHasSelection) {
setSelection(screenPointToCell(e.x, e.y));
} else {
fCellCanvasModel.setSelection(-1,-1,-1,-1);
}
fDraggingStart=null;
}
}
});
addMouseMoveListener(new MouseMoveListener() {
@Override
public void mouseMove(MouseEvent e) {
if (fDraggingStart != null) {
updateHasSelection(e);
setSelection(screenPointToCell(e.x, e.y));
}
}
});
serVerticalBarVisible(true);
setHorizontalBarVisible(false);
}
/**
* The user has to drag the mouse to at least one character to make a selection.
* Once this is done, even a one char selection is OK.
*
* @param e
*/
private void updateHasSelection(MouseEvent e) {
if(fDraggingStart!=null) {
Point p=screenPointToCell(e.x, e.y);
if(fDraggingStart.x!=p.x||fDraggingStart.y!=p.y) {
fHasSelection=true;
}
}
}
void setSelection(Point p) {
if (fDraggingStart !=null && !p.equals(fDraggingEnd)) {
fDraggingEnd = p;
if (compare(p, fDraggingStart) < 0) {
// bug 219589 - make sure selection start coordinates are non-negative
int startColumn = Math.max(0, p.x);
int startRow = Math.max(p.y, 0);
fCellCanvasModel.setSelection(startRow, fDraggingStart.y, startColumn, 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 ILinelRenderer getCellRenderer() {
return fCellRenderer;
}
public int getMinColumns() {
return fMinColumns;
}
public void setMinColumns(int minColumns) {
fMinColumns = minColumns;
}
public int getMinLines() {
return fMinLines;
}
public void setMinLines(int minLines) {
fMinLines = minLines;
}
protected void onResize(boolean init) {
if(fResizeListener!=null) {
Rectangle bonds=getClientArea();
int cellHeight = getCellHeight();
int cellWidth = getCellWidth();
int lines=bonds.height/cellHeight;
int columns=bonds.width/cellWidth;
// when the view is minimised, its size is set to 0
// we don't sent this to the terminal!
if((lines>0 && columns>0) || init) {
if(columns<fMinColumns) {
if(!isHorizontalBarVisble()) {
setHorizontalBarVisible(true);
bonds=getClientArea();
lines=bonds.height/cellHeight;
}
columns=fMinColumns;
} else if(columns>=fMinColumns && isHorizontalBarVisble()) {
setHorizontalBarVisible(false);
bonds=getClientArea();
lines=bonds.height/cellHeight;
columns=bonds.width/cellWidth;
}
if(lines<fMinLines) {
lines=fMinLines;
}
fResizeListener.sizeChanged(lines, columns);
}
}
super.onResize();
calculateGrid();
}
@Override
protected void onResize() {
fResizing = true;
try {
onResize(false);
} finally {
fResizing = false;
}
}
private void calculateGrid() {
Rectangle virtualBounds = getVirtualBounds();
setRedraw(false);
try {
setVirtualExtend(getCols()*getCellWidth(),getRows()*getCellHeight());
getParent().layout();
if (fResizing) {
// scroll to end if view port was near last line
Rectangle viewRect = getViewRectangle();
if (virtualBounds.height - (viewRect.y + viewRect.height) < getCellHeight() * 2) {
scrollToEnd();
}
}
} finally {
setRedraw(true);
}
}
void scrollToEnd() {
if(!fScrollLock) {
int y=-(getRows()*getCellHeight()-getClientArea().height);
if (y > 0) {
y = 0;
}
Rectangle v=getViewRectangle();
if(v.y!=-y) {
setVirtualOrigin(v.x,y);
}
// make sure the scroll area is correct:
scrollY(getVerticalBar());
scrollX(getHorizontalBar());
}
}
/**
*
* @return true if the cursor should be shown on output....
*/
public boolean isScrollLock() {
return fScrollLock;
}
/**
* If set then if the size changes
*/
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);
}
@Override
protected void drawLine(GC gc, int line, int x, int y, int colFirst, int colLast) {
fCellRenderer.drawLine(fCellCanvasModel, gc,line,x,y,colFirst, colLast);
}
@Override
protected Color getTerminalBackgroundColor() {
return fCellRenderer.getDefaultBackgroundColor();
}
@Override
protected void visibleCellRectangleChanged(int x, int y, int width, int height) {
fCellCanvasModel.setVisibleRectangle(y,x,height,width);
update();
}
@Override
protected int getCols() {
return fCellCanvasModel.getTerminalText().getWidth();
}
@Override
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());
fCellCanvasModel.setSelectionAnchor(new Point(0,0));
}
public boolean isEmpty() {
return false;
}
/**
* Gets notified when the visible size of the terminal changes.
* This should update the model!
*
*/
public interface ResizeListener {
void sizeChanged(int lines, int columns);
}
/**
* @param listener this listener gets notified, when the size of
* the widget changed. It should change the dimensions of the underlying
* terminaldata
*/
public void addResizeHandler(ResizeListener listener) {
if(fResizeListener!=null)
{
throw new IllegalArgumentException("There can be at most one listener at the moment!"); //$NON-NLS-1$
}
fResizeListener=listener;
// Bug 281328: [terminal] The very first few characters might be missing in
// the terminal control if opened and connected programmatically
//
// In case the terminal had not been visible yet or is too small (less than one
// line visible), the terminal should have a minimum size to avoid RuntimeExceptions.
Rectangle bonds=getClientArea();
if (bonds.height<getCellHeight() || bonds.width<getCellWidth()) {
//Widget not realized yet, or minimized to < 1 item:
//Just tell the listener our min size
fResizeListener.sizeChanged(getMinLines(), getMinColumns());
} else {
//Widget realized: compute actual size and force telling the listener
onResize(true);
}
}
public void onFontChange() {
fCellRenderer.onFontChange();
setCellWidth(fCellRenderer.getCellWidth());
setCellHeight(fCellRenderer.getCellHeight());
calculateGrid();
}
public void setInvertedColors(boolean invert) {
fCellRenderer.setInvertedColors(invert);
redraw();
}
/**
* @return true if the cursor is enabled (blinking). By default the cursor is not enabled.
*/
public boolean isCursorEnabled() {
return fCursorEnabled;
}
/**
* @param enabled enabling means that the cursor blinks
*/
public void setCursorEnabled(boolean enabled) {
if(enabled!=fCursorEnabled) {
fCursorEnabled=enabled;
fCellCanvasModel.setCursorEnabled(fCursorEnabled);
}
}
public void setColors(RGB background, RGB foreground) {
fCellRenderer.setColors(background, foreground);
redraw();
}
@Override public void setFont(Font font) {
super.setFont(font);
fCellRenderer.setFont(font);
redraw();
}
public int getCursorLine() {
return fCellCanvasModel.getCursorLine();
}
public char[] getChars(int line) {
return fCellCanvasModel.getTerminalText().getChars(line);
}
}