blob: 198f5edbacbbe58392c62bd3e4cb285748f4b9c6 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2009 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
* Martin Oberhuber (Wind River) - [261486][api][cleanup] Mark @noimplement interfaces as @noextend
*******************************************************************************/
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:
*
* <pre>
* for (int line = 0; line &lt; term.getHeight(); line++)
* for (int column = 0; column &lt; term.getWidth(); column++)
* drawCharacter(column, line, term.getChar(column, line), term.getStyle(column, line));
* </pre>
*
* 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 <code>synchronized(term){}</code> 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.
* <p>
* <b>Solution:</b> 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.
* </p>
*
* <p>
* <b>Drawing optimization</b>: To optimize redrawing of changed lines, this
* class keeps track of lines that have changed since the previous snapshot.
* </p>
*
* <pre>
* // iterate over the potentially changed lines
* for (int line = snap.getFirstChangedLine(); line &lt;= snap.getLastChangedLine(); line++)
* // redraw only if the line has changed
* if (snap.hasLineChanged(line))
* for (int column = 0; column &lt; snap.getWidth(); column++)
* drawCharacter(column, line, snap.getChar(column, line), snap.getStyle(column, line));
* </pre>
*
* <p>
* <b>Scroll optimization:</b> 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 <code>true</code> for the
* <code>detectScrolling</code> parameter. The object will keep track of
* scrolling. The UI must <b>first</b> handle the scrolling and then use the
* {@link #hasLineChanged(int)} method to determine scrolling:
*
* <pre>
* // scroll the visible region of the UI &lt;b&gt;before&lt;/b&gt; drawing the changed lines.
* doUIScrolling(snap.getScrollChangeY(), snap.getScrollChangeN(), snap.getScrollChangeShift());
* // iterate over the potentially changed lines
* for (int line = snap.getFirstChangedLine(); line &lt;= snap.getFirstChangedLine(); line++)
* // redraw only if the line has changed
* if (snap.hasLineChanged(line))
* for (int column = 0; column &lt; snap.getWidth(); column++)
* drawCharacter(column, line, snap.getChar(column, line), snap.getStyle(column, line));
* </pre>
*
* </p>
* <p>
* <b>Threading Note</b>: This class is not thread safe! All methods have to be
* called by the a same thread, that created the instance by calling
* {@link ITerminalTextDataReadOnly#makeSnapshot()}.
* </p>
*
* @noimplement This interface is not intended to be implemented by clients.
* @noextend This interface is not intended to be extended by clients.
*/
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.
* <p>
* 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.
*
* <p>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.
* <p>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 <code>true</code> 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()}
* <br><b>Note:</b> The method {@link #hasLineChanged(int)} returns changes <b>after</b> the
* scrolling has been applied.
*/
void updateSnapshot(boolean detectScrolling);
/**
* @return The first line changed in this snapshot compared
* to the previous snapshot.
*
* <p><b>Note:</b> If no line has changed, this
* returns {@link Integer#MAX_VALUE}
*
* <p><b>Note:</b> if {@link #updateSnapshot(boolean)} has been called with <code>true</code>,
* then this does not include lines that only have been scrolled. This is the
* first line that has changed <b>after</b> 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.
*
* <p><b>Note:</b> If no line has changed, this returns <code>-1</code>
*
* <p><b>Note:</b> if {@link #updateSnapshot(boolean)} has been called with <code>true</code>,
* then this does not include lines that only have been scrolled. This is the
* last line that has changed <b>after</b> the scroll has been applied.
*
* <p>A typical for loop using this method would look like this (note the <code>&lt;=</code> in the for loop):
* <pre>
* for(int line=snap.{@link #getFirstChangedLine()}; line <b>&lt;=</b> snap.getLastChangedLine(); line++)
* if(snap.{@link #hasLineChanged(int) hasLineChanged(line)})
* doSomething(line);
* </pre>
*/
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 <code>true</code>, 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 <code>true</code>, 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 <code>true</code>, 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();
}