| /******************************************************************************* |
| * Copyright (c) 2010, 2016 Ericsson |
| * |
| * 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: |
| * Patrick Tasse - Initial API and implementation |
| ******************************************************************************/ |
| |
| package org.eclipse.tracecompass.tmf.ui.widgets.rawviewer; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.jdt.annotation.NonNull; |
| import org.eclipse.jface.resource.ColorRegistry; |
| import org.eclipse.jface.resource.FontRegistry; |
| import org.eclipse.jface.util.IPropertyChangeListener; |
| import org.eclipse.jface.util.PropertyChangeEvent; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.CaretEvent; |
| import org.eclipse.swt.custom.CaretListener; |
| import org.eclipse.swt.custom.ScrolledComposite; |
| import org.eclipse.swt.custom.StyledText; |
| import org.eclipse.swt.events.ControlEvent; |
| import org.eclipse.swt.events.ControlListener; |
| import org.eclipse.swt.events.KeyEvent; |
| import org.eclipse.swt.events.KeyListener; |
| import org.eclipse.swt.events.MouseAdapter; |
| import org.eclipse.swt.events.MouseEvent; |
| import org.eclipse.swt.events.MouseListener; |
| import org.eclipse.swt.events.MouseMoveListener; |
| import org.eclipse.swt.events.MouseTrackListener; |
| import org.eclipse.swt.events.MouseWheelListener; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.events.SelectionListener; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.Font; |
| import org.eclipse.swt.graphics.Point; |
| 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.Menu; |
| import org.eclipse.swt.widgets.Slider; |
| import org.eclipse.tracecompass.tmf.core.event.ITmfEvent; |
| import org.eclipse.tracecompass.tmf.core.trace.ITmfContext; |
| import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; |
| import org.eclipse.tracecompass.tmf.core.trace.location.ITmfLocation; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.themes.IThemeManager; |
| |
| /** |
| * TmfRawEventViewer allows for the display of the raw data for an arbitrarily |
| * large number of TMF events. |
| * |
| * It is essentially a Composite of a StyledText area and a Slider, where the number |
| * of visible lines in the StyledText control is set to fill the viewer display area. |
| * An underlying data model is used to store a cache of event raw text line data. |
| * The slider is ratio-based. |
| * |
| * @version 1.0 |
| * @author Patrick Tasse |
| */ |
| public class TmfRawEventViewer extends Composite implements ControlListener, SelectionListener, MouseListener, |
| KeyListener, CaretListener, MouseMoveListener, MouseTrackListener, MouseWheelListener, IPropertyChangeListener { |
| |
| private static final Color COLOR_BACKGROUND_ODD = Display.getCurrent().getSystemColor(SWT.COLOR_WHITE); |
| private static final Color COLOR_BACKGROUND_EVEN = new Color(Display.getDefault(), 242, 242, 242); |
| private static final String FONT_DEFINITION_ID = "org.eclipse.tracecompass.tmf.ui.font.eventraw"; //$NON-NLS-1$ |
| private static final String HIGHLIGHT_COLOR_DEFINITION_ID = "org.eclipse.tracecompass.tmf.ui.color.eventraw.highlight"; //$NON-NLS-1$ |
| private static final String SELECTION_COLOR_DEFINITION_ID = "org.eclipse.tracecompass.tmf.ui.color.eventraw.selection"; //$NON-NLS-1$ |
| private static final int MAX_LINE_DATA_SIZE = 1000; |
| private static final int SLIDER_MAX = 1000000; |
| private static final String EMPTY_STRING = ""; //$NON-NLS-1$ |
| private static final String LF = "\n"; //$NON-NLS-1$ |
| private static final @NonNull String CR_LF = "\r?\n"; //$NON-NLS-1$ |
| |
| private ITmfTrace fTrace; |
| private ITmfContext fBottomContext; |
| |
| private ScrolledComposite fScrolledComposite; |
| private Composite fTextArea; |
| private StyledText fStyledText; |
| private Font fFixedFont; |
| private Color fHighlightColor; |
| private Color fSelectionColor; |
| private Slider fSlider; |
| private SliderThrottler fSliderThrottler; |
| |
| private final List<LineData> fLines = new ArrayList<>(); |
| private boolean fActualRanks = false; |
| private int fTopLineIndex; |
| private int fLastTopLineIndex; |
| private final CaretPosition[] fStoredCaretPosition = new CaretPosition[] |
| { new CaretPosition(0, 0), new CaretPosition(0,0)}; |
| private int fNumVisibleLines; |
| private ITmfLocation fSelectedLocation = null; |
| private long fHighlightedRank = Long.MIN_VALUE; |
| private int fCursorYCoordinate = -1; |
| private int fHoldSelection = 0; |
| |
| // ------------------------------------------------------------------------ |
| // Classes |
| // ------------------------------------------------------------------------ |
| |
| private static class LineData { |
| long rank; |
| ITmfLocation location; |
| String string; |
| public LineData(long rank, ITmfLocation location, String string) { |
| this.rank = rank; |
| this.location = location; |
| if (string.length() == 0) { |
| /* workaround for setLineBackground has no effect on empty line */ |
| this.string = " "; //$NON-NLS-1$ |
| } else { |
| this.string = string; |
| } |
| } |
| @Override |
| public String toString() { |
| return rank + " [" + location + "]: " + string; //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| |
| private static class CaretPosition { |
| int time; |
| int caretOffset; |
| public CaretPosition(int time, int caretOffset) { |
| this.time = time; |
| this.caretOffset = caretOffset; |
| } |
| } |
| |
| private class SliderThrottler extends Thread { |
| private static final long DELAY = 400L; |
| private static final long POLLING_INTERVAL = 10L; |
| |
| @Override |
| public void run() { |
| final long startTime = System.currentTimeMillis(); |
| while ((System.currentTimeMillis() - startTime) < DELAY) { |
| try { |
| Thread.sleep(POLLING_INTERVAL); |
| } catch (InterruptedException e) { |
| } |
| } |
| Display.getDefault().asyncExec(() -> { |
| if (fSliderThrottler != SliderThrottler.this) { |
| return; |
| } |
| fSliderThrottler = null; |
| if (SliderThrottler.this.isInterrupted() || fSlider.isDisposed()) { |
| return; |
| } |
| Event event = new Event(); |
| event.widget = TmfRawEventViewer.this; |
| event.detail = SWT.NONE; |
| widgetSelected(new SelectionEvent(event)); |
| }); |
| } |
| } |
| |
| // ------------------------------------------------------------------------ |
| // Constructor |
| // ------------------------------------------------------------------------ |
| |
| /** |
| * Constructor |
| * @param parent The parent composite |
| * @param style The style bits |
| */ |
| public TmfRawEventViewer(Composite parent, int style) { |
| super(parent, style & (~SWT.H_SCROLL) & (~SWT.V_SCROLL)); |
| |
| // Set the layout |
| GridLayout gridLayout = new GridLayout(); |
| gridLayout.numColumns = 2; |
| gridLayout.horizontalSpacing = 0; |
| gridLayout.verticalSpacing = 0; |
| gridLayout.marginWidth = 0; |
| gridLayout.marginHeight = 0; |
| setLayout(gridLayout); |
| |
| // Create the controls |
| createTextArea(style & SWT.H_SCROLL); |
| createSlider(style & SWT.V_SCROLL); |
| |
| // Prevent the slider from being traversed |
| setTabList(new Control[] { fScrolledComposite }); |
| |
| addDisposeListener((e) -> { |
| if (fBottomContext != null) { |
| fBottomContext.dispose(); |
| } |
| PlatformUI.getWorkbench().getThemeManager().removePropertyChangeListener(TmfRawEventViewer.this); |
| }); |
| } |
| |
| // ------------------------------------------------------------------------ |
| // Font and color handling |
| // ------------------------------------------------------------------------ |
| |
| /** |
| * Initialize the fonts. |
| * @since 1.0 |
| */ |
| protected void initializeFonts() { |
| FontRegistry fontRegistry = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme().getFontRegistry(); |
| fFixedFont = fontRegistry.get(FONT_DEFINITION_ID); |
| fStyledText.setFont(fFixedFont); |
| } |
| |
| /** |
| * Initialize the colors. |
| * @since 1.1 |
| */ |
| protected void initializeColors() { |
| ColorRegistry colorRegistry = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme().getColorRegistry(); |
| fHighlightColor = colorRegistry.get(HIGHLIGHT_COLOR_DEFINITION_ID); |
| fSelectionColor = colorRegistry.get(SELECTION_COLOR_DEFINITION_ID); |
| } |
| |
| /** |
| * @since 1.0 |
| */ |
| @Override |
| public void propertyChange(PropertyChangeEvent event) { |
| if ((IThemeManager.CHANGE_CURRENT_THEME.equals(event.getProperty())) || |
| (FONT_DEFINITION_ID.equals(event.getProperty()))) { |
| initializeFonts(); |
| refreshTextArea(); |
| } |
| if ((IThemeManager.CHANGE_CURRENT_THEME.equals(event.getProperty())) || |
| (HIGHLIGHT_COLOR_DEFINITION_ID.equals(event.getProperty())) || |
| (SELECTION_COLOR_DEFINITION_ID.equals(event.getProperty()))) { |
| initializeColors(); |
| refreshTextArea(); |
| } |
| } |
| |
| // ------------------------------------------------------------------------ |
| // Text area handling |
| // ------------------------------------------------------------------------ |
| |
| /** |
| * Create the text area and add listeners |
| */ |
| private void createTextArea(int style) { |
| fScrolledComposite = new ScrolledComposite(this, style); |
| fScrolledComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); |
| fTextArea = new Composite(fScrolledComposite, SWT.NONE); |
| fTextArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); |
| fScrolledComposite.setContent(fTextArea); |
| fScrolledComposite.setExpandHorizontal(true); |
| fScrolledComposite.setExpandVertical(true); |
| fScrolledComposite.setAlwaysShowScrollBars(true); |
| fScrolledComposite.setMinSize(fTextArea.computeSize(SWT.DEFAULT, SWT.DEFAULT)); |
| fScrolledComposite.addControlListener(this); |
| |
| GridLayout textAreaGridLayout = new GridLayout(); |
| textAreaGridLayout.marginHeight = 0; |
| textAreaGridLayout.marginWidth = 0; |
| fTextArea.setLayout(textAreaGridLayout); |
| |
| fStyledText = new StyledText(fTextArea, SWT.READ_ONLY); |
| fStyledText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); |
| |
| initializeFonts(); |
| initializeColors(); |
| PlatformUI.getWorkbench().getThemeManager().addPropertyChangeListener(this); |
| |
| fStyledText.addCaretListener(this); |
| fStyledText.addMouseMoveListener(this); |
| fStyledText.addMouseTrackListener(this); |
| fStyledText.addMouseWheelListener(this); |
| /* disable mouse scroll of horizontal scroll bar */ |
| fStyledText.addListener(SWT.MouseWheel, event -> event.doit = false); |
| fStyledText.addKeyListener(this); |
| |
| fTextArea.setBackground(fStyledText.getBackground()); |
| fTextArea.addMouseListener(new MouseAdapter() { |
| @Override |
| public void mouseDown(MouseEvent e) { |
| fTextArea.setFocus(); |
| } |
| }); |
| } |
| |
| // ------------------------------------------------------------------------ |
| // Slider handling |
| // ------------------------------------------------------------------------ |
| |
| private void createSlider(int style) { |
| fSlider = new Slider(this, SWT.VERTICAL); |
| fSlider.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true)); |
| fSlider.setValues(0, 0, SLIDER_MAX, SLIDER_MAX, 1, 1); |
| fSlider.addSelectionListener(this); |
| fSlider.addMouseListener(this); |
| if ((style & SWT.V_SCROLL) == 0) { |
| fSlider.setVisible(false); |
| } |
| } |
| |
| // ------------------------------------------------------------------------ |
| // Controls interactions |
| // ------------------------------------------------------------------------ |
| |
| @Override |
| public boolean setFocus() { |
| boolean isVisible = isVisible(); |
| if (isVisible) { |
| fTextArea.setFocus(); |
| } |
| return isVisible; |
| } |
| |
| @Override |
| public void setMenu(Menu menu) { |
| fStyledText.setMenu(menu); |
| } |
| |
| /** |
| * Sets the trace and updates the content |
| * @param trace The trace to set |
| */ |
| public void setTrace(ITmfTrace trace) { |
| fTrace = trace; |
| fTopLineIndex = 0; |
| fLines.clear(); |
| refreshEventCount(); |
| } |
| |
| /** |
| * Refreshes the event count, updates the slider thumb and loads display |
| */ |
| public void refreshEventCount() { |
| if (fTrace != null) { |
| if (fTrace.getNbEvents() > 0) { |
| fSlider.setThumb((int) Math.max(SLIDER_MAX / fTrace.getNbEvents(), 1)); |
| } else { |
| fSlider.setThumb(SLIDER_MAX); |
| } |
| |
| if (!isVisible()) { |
| return; |
| } |
| |
| if (fLines.isEmpty()) { |
| setTopRank(0); |
| } else if (fLines.size() < fNumVisibleLines) { |
| if (fBottomContext != null) { |
| fBottomContext.dispose(); |
| fBottomContext = null; |
| } |
| loadLineData(); |
| fillTextArea(); |
| fSlider.setSelection((int) (SLIDER_MAX * fTrace.getLocationRatio(fLines.get(fTopLineIndex).location))); |
| } |
| } else { |
| if (fBottomContext != null) { |
| fBottomContext.dispose(); |
| fBottomContext = null; |
| } |
| fillTextArea(); |
| fSlider.setThumb(SLIDER_MAX); |
| fSlider.setSelection(0); |
| } |
| } |
| |
| /** |
| * Selects the event of given rank and makes it visible. |
| * @param rank The rank of event |
| */ |
| public void selectAndReveal(long rank) { |
| if (fTrace == null || !isVisible()) { |
| return; |
| } |
| if (fActualRanks && fTopLineIndex < fLines.size() && rank >= fLines.get(fTopLineIndex).rank) { |
| int lastVisibleIndex = Math.min(fTopLineIndex + fNumVisibleLines, fLines.size()) - 1; |
| if (rank <= fLines.get(lastVisibleIndex).rank) { |
| for (int i = fTopLineIndex; i < fLines.size(); i++) { |
| if (fLines.get(i).rank == rank) { |
| fSelectedLocation = fLines.get(i).location; |
| break; |
| } |
| } |
| refreshLineBackgrounds(); |
| return; |
| } |
| } |
| setTopRank(rank); |
| if (!fLines.isEmpty() && fHoldSelection == 0) { |
| fSelectedLocation = fLines.get(0).location; |
| refreshLineBackgrounds(); |
| } |
| } |
| |
| /** |
| * Add a selection listener |
| * @param listener A listener to add |
| */ |
| public void addSelectionListener(Listener listener) { |
| checkWidget(); |
| if (listener == null) { |
| SWT.error (SWT.ERROR_NULL_ARGUMENT); |
| } |
| addListener (SWT.Selection, listener); |
| } |
| |
| /** |
| * Remove selection listener |
| * @param listener A listener to remove |
| */ |
| public void removeSelectionListener(Listener listener) { |
| checkWidget(); |
| if (listener == null) { |
| SWT.error (SWT.ERROR_NULL_ARGUMENT); |
| } |
| removeListener(SWT.Selection, listener); |
| } |
| |
| private void sendSelectionEvent(LineData lineData) { |
| Event event = new Event(); |
| if (fActualRanks) { |
| event.data = Long.valueOf(lineData.rank); |
| } else { |
| event.data = lineData.location; |
| } |
| notifyListeners(SWT.Selection, event); |
| } |
| |
| private void setTopRank(long rank) { |
| if (fBottomContext != null) { |
| fBottomContext.dispose(); |
| } |
| fBottomContext = fTrace.seekEvent(rank); |
| if (fBottomContext == null) { |
| return; |
| } |
| fLines.clear(); |
| fActualRanks = true; |
| fTopLineIndex = 0; |
| loadLineData(); |
| refreshTextArea(); |
| if (fLines.isEmpty()) { |
| fSlider.setSelection(0); |
| } else { |
| fSlider.setSelection((int) (SLIDER_MAX * fTrace.getLocationRatio(fLines.get(fTopLineIndex).location))); |
| } |
| } |
| |
| private void setTopPosition(double ratio) { |
| if (fBottomContext != null) { |
| fBottomContext.dispose(); |
| } |
| fBottomContext = fTrace.seekEvent(ratio); |
| if (fBottomContext == null) { |
| return; |
| } |
| fBottomContext.setRank(0); |
| fLines.clear(); |
| fActualRanks = false; |
| fTopLineIndex = 0; |
| loadLineData(); |
| refreshTextArea(); |
| } |
| |
| private void loadLineData() { |
| if (fTopLineIndex < 0) { |
| if (!fLines.isEmpty() && fTrace.getLocationRatio(fLines.get(0).location) > 0) { |
| double lastRatio = fTrace.getLocationRatio(fLines.get(fLines.size() - 1).location); |
| double firstRatio = fTrace.getLocationRatio(fLines.get(0).location); |
| double delta; |
| boolean singleEvent = false; |
| if (firstRatio != lastRatio) { |
| // approximate ratio of at least 20 items |
| delta = Math.max(20, fNumVisibleLines) * (lastRatio - firstRatio) / (fLines.size() - 1); |
| } else { |
| delta = Math.pow(10, -15); |
| singleEvent = true; |
| } |
| while (fTopLineIndex < 0) { |
| ITmfLocation endLocation = fLines.get(0).location; |
| firstRatio = Math.max(0, firstRatio - delta); |
| ITmfContext context = fTrace.seekEvent(firstRatio); |
| ITmfLocation location; |
| int index = 0; |
| long rank = 0; |
| while (!context.getLocation().equals(endLocation)) { |
| location = context.getLocation(); |
| ITmfEvent event = fTrace.getNext(context); |
| if (event == null) { |
| break; |
| } |
| if (event.getContent() != null && event.getContent().getValue() != null) { |
| String[] lines = event.getContent().getValue().toString().split(CR_LF); |
| for (int i = 0; i < lines.length; i++) { |
| String line = lines[i]; |
| LineData lineData = new LineData(rank, location, line); |
| fLines.add(index++, lineData); |
| fTopLineIndex++; |
| fLastTopLineIndex++; |
| } |
| } else { |
| LineData lineData = new LineData(rank, location, EMPTY_STRING); |
| fLines.add(index++, lineData); |
| fTopLineIndex++; |
| fLastTopLineIndex++; |
| } |
| rank++; |
| } |
| context.dispose(); |
| long rankOffset = fLines.get(index).rank - rank; |
| for (int i = 0; i < index; i++) { |
| fLines.get(i).rank += rankOffset; |
| } |
| if (firstRatio == 0) { |
| break; |
| } |
| if (singleEvent) { |
| delta = Math.min(delta * 10, 0.1); |
| } |
| } |
| } |
| if (fTopLineIndex < 0) { |
| fTopLineIndex = 0; |
| } |
| } |
| |
| while (fLines.size() - fTopLineIndex < fNumVisibleLines) { |
| if (fBottomContext == null) { |
| if (fLines.isEmpty()) { |
| fBottomContext = fTrace.seekEvent(0); |
| } else { |
| fBottomContext = fTrace.seekEvent(fLines.get(fLines.size() - 1).location); |
| fTrace.getNext(fBottomContext); |
| } |
| if (fBottomContext == null) { |
| break; |
| } |
| } |
| long rank = fBottomContext.getRank(); |
| ITmfLocation location = fBottomContext.getLocation() != null ? fBottomContext.getLocation() : null; |
| ITmfEvent event = fTrace.getNext(fBottomContext); |
| if (event == null) { |
| break; |
| } |
| if (event.getContent() != null && event.getContent().getValue() != null) { |
| for (String line : event.getContent().getValue().toString().split(CR_LF)) { |
| int crPos; |
| if ((crPos = line.indexOf('\r')) != -1) { |
| line = line.substring(0, crPos); |
| } |
| LineData lineData = new LineData(rank, location, line); |
| fLines.add(lineData); |
| } |
| } else { |
| LineData lineData = new LineData(rank, location, EMPTY_STRING); |
| fLines.add(lineData); |
| } |
| } |
| fTopLineIndex = Math.max(0, Math.min(fTopLineIndex, fLines.size() - 1)); |
| |
| if (fLines.size() > MAX_LINE_DATA_SIZE) { |
| if (fTopLineIndex < MAX_LINE_DATA_SIZE / 2) { |
| long rank = fLines.get(MAX_LINE_DATA_SIZE - 1).rank; |
| for (int i = MAX_LINE_DATA_SIZE; i < fLines.size(); i++) { |
| if (fLines.get(i).rank > rank) { |
| fLines.subList(i, fLines.size()).clear(); |
| if (fBottomContext != null) { |
| fBottomContext.dispose(); |
| fBottomContext = null; |
| } |
| break; |
| } |
| } |
| } else { |
| long rank = fLines.get(fLines.size() - MAX_LINE_DATA_SIZE).rank; |
| for (int i = fLines.size() - MAX_LINE_DATA_SIZE - 1; i >= 0; i--) { |
| if (fLines.get(i).rank < rank) { |
| fLines.subList(0, i + 1).clear(); |
| fTopLineIndex -= (i + 1); |
| fLastTopLineIndex -= (i + 1); |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| private void refreshTextArea() { |
| fStyledText.setText(EMPTY_STRING); |
| for (int i = 0; i < fLines.size() - fTopLineIndex && i < fNumVisibleLines; i++) { |
| if (i > 0) |
| { |
| fStyledText.append(LF); |
| } |
| LineData lineData = fLines.get(fTopLineIndex + i); |
| fStyledText.append(lineData.string); |
| setLineBackground(i, lineData); |
| } |
| fTextArea.layout(); |
| fScrolledComposite.setMinSize(fTextArea.computeSize(SWT.DEFAULT, SWT.DEFAULT)); |
| fLastTopLineIndex = fTopLineIndex; |
| } |
| |
| private void fillTextArea() { |
| int nextLine = fStyledText.getCharCount() == 0 ? 0 : fStyledText.getLineCount(); |
| for (int i = nextLine; i < fLines.size() - fTopLineIndex && i < fNumVisibleLines; i++) { |
| if (i > 0) |
| { |
| fStyledText.append(LF); |
| } |
| LineData lineData = fLines.get(fTopLineIndex + i); |
| fStyledText.append(lineData.string); |
| setLineBackground(i, lineData); |
| } |
| int endLine = Math.min(fNumVisibleLines, fLines.size()); |
| if (endLine < fStyledText.getLineCount()) { |
| int endOffset = fStyledText.getOffsetAtLine(endLine) - 1; |
| if (endOffset > fStyledText.getCharCount()) { |
| fHoldSelection++; |
| fStyledText.replaceTextRange(endOffset, fStyledText.getCharCount() - endOffset, EMPTY_STRING); |
| fHoldSelection--; |
| } |
| } |
| fTextArea.layout(); |
| fScrolledComposite.setMinSize(fTextArea.computeSize(SWT.DEFAULT, SWT.DEFAULT)); |
| } |
| |
| private void updateTextArea() { |
| if (fTopLineIndex < fLastTopLineIndex) { |
| StringBuilder insertedText = new StringBuilder(); |
| for (int i = fTopLineIndex; i < fLastTopLineIndex; i++) { |
| insertedText.append(fLines.get(i).string).append(LF); |
| } |
| fStyledText.replaceTextRange(0, 0, insertedText.toString()); |
| for (int i = 0; i < fLastTopLineIndex - fTopLineIndex; i++) { |
| LineData lineData = fLines.get(fTopLineIndex + i); |
| setLineBackground(i, lineData); |
| } |
| fLastTopLineIndex = fTopLineIndex; |
| } else if (fTopLineIndex > fLastTopLineIndex) { |
| int length = 0; |
| for (int i = 0; i < fTopLineIndex - fLastTopLineIndex && i < fNumVisibleLines; i++) { |
| length += fLines.get(i + fLastTopLineIndex).string.length(); |
| if (i < fStyledText.getLineCount()) { |
| length += 1; |
| } |
| } |
| fStyledText.replaceTextRange(0, length, EMPTY_STRING); |
| fLastTopLineIndex = fTopLineIndex; |
| fillTextArea(); |
| } |
| int endLine = Math.min(fNumVisibleLines, fLines.size()); |
| if (endLine < fStyledText.getLineCount()) { |
| int endOffset = fStyledText.getOffsetAtLine(endLine) - 1; |
| if (endOffset > fStyledText.getCharCount()) { |
| fStyledText.replaceTextRange(endOffset, fStyledText.getCharCount() - endOffset, EMPTY_STRING); |
| } |
| } |
| fTextArea.layout(); |
| fScrolledComposite.setMinSize(fTextArea.computeSize(SWT.DEFAULT, SWT.DEFAULT)); |
| } |
| |
| private void refreshLineBackgrounds() { |
| for (int i = 0; (i < fStyledText.getLineCount()) && (i < fNumVisibleLines) && (i < fLines.size() - fTopLineIndex); i++) { |
| LineData lineData = fLines.get(fTopLineIndex + i); |
| setLineBackground(i, lineData); |
| } |
| } |
| |
| private void setLineBackground(int index, LineData lineData) { |
| if (lineData.location.equals(fSelectedLocation)) { |
| fStyledText.setLineBackground(index, 1, fSelectionColor); |
| } else if (lineData.rank == fHighlightedRank) { |
| fStyledText.setLineBackground(index, 1, fHighlightColor); |
| } else if (lineData.rank % 2 == 0) { |
| fStyledText.setLineBackground(index, 1, COLOR_BACKGROUND_EVEN); |
| } else { |
| fStyledText.setLineBackground(index, 1, COLOR_BACKGROUND_ODD); |
| } |
| } |
| |
| private void storeCaretPosition(int time, int caretOffset) { |
| if (fStoredCaretPosition[0].time == time) { |
| fStoredCaretPosition[0].caretOffset = caretOffset; |
| } else { |
| fStoredCaretPosition[1] = fStoredCaretPosition[0]; |
| fStoredCaretPosition[0] = new CaretPosition(time, caretOffset); |
| } |
| } |
| |
| private int getPreviousCaretOffset(int time) { |
| if (fStoredCaretPosition[0].time == time) { |
| return fStoredCaretPosition[1].caretOffset; |
| } |
| return fStoredCaretPosition[0].caretOffset; |
| } |
| |
| private void updateHighlightedRank() { |
| if (fCursorYCoordinate < 0 || fCursorYCoordinate > fStyledText.getSize().y) { |
| if (fHighlightedRank != Long.MIN_VALUE) { |
| fHighlightedRank = Long.MIN_VALUE; |
| refreshLineBackgrounds(); |
| } |
| return; |
| } |
| int offset = fStyledText.getOffsetAtLocation(new Point(0, fCursorYCoordinate)); |
| int line = fStyledText.getLineAtOffset(offset); |
| if (line < fLines.size() - fTopLineIndex) { |
| LineData lineData = fLines.get(fTopLineIndex + line); |
| if (fHighlightedRank != lineData.rank) { |
| fHighlightedRank = lineData.rank; |
| refreshLineBackgrounds(); |
| } |
| } else { |
| if (fHighlightedRank != Long.MIN_VALUE) { |
| fHighlightedRank = Long.MIN_VALUE; |
| refreshLineBackgrounds(); |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------ |
| // ControlListener (ScrolledComposite) |
| // ------------------------------------------------------------------------ |
| |
| @Override |
| public void controlResized(ControlEvent event) { |
| int areaHeight = fScrolledComposite.getSize().y; |
| if (fScrolledComposite.getHorizontalBar() != null) { |
| areaHeight -= fScrolledComposite.getHorizontalBar().getSize().y; |
| } |
| int lineHeight = fStyledText.getLineHeight(); |
| fNumVisibleLines = Math.max((areaHeight + lineHeight - 1) / lineHeight, 1); |
| |
| if (fBottomContext != null) { |
| loadLineData(); |
| fillTextArea(); |
| } |
| } |
| |
| @Override |
| public void controlMoved(ControlEvent e) { |
| // Do nothing |
| } |
| |
| // ------------------------------------------------------------------------ |
| // SelectionListener (Slider) |
| // ------------------------------------------------------------------------ |
| |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| fTextArea.setFocus(); |
| if (fLines.isEmpty()) { |
| return; |
| } |
| fHoldSelection++; |
| switch (e.detail) { |
| case SWT.DRAG: |
| case SWT.NONE: { |
| if (e.widget == fSlider) { |
| /* |
| * While the slider thumb is being dragged, only perform the |
| * refresh periodically. The event detail during the drag is |
| * SWT.DRAG on Windows and SWT.NONE on Linux. |
| */ |
| if (fSliderThrottler == null) { |
| fSliderThrottler = new SliderThrottler(); |
| fSliderThrottler.start(); |
| } |
| fHoldSelection = 0; |
| return; |
| } |
| /* |
| * The selection event was sent by the viewer, refresh now. |
| */ |
| if (fSlider.getSelection() == 0 || fSlider.getThumb() == SLIDER_MAX) { |
| fLines.clear(); |
| setTopPosition(0.0); |
| break; |
| } |
| double ratio = (double) fSlider.getSelection() / (SLIDER_MAX - fSlider.getThumb()); |
| double delta = Math.pow(10, -15); |
| fLines.clear(); |
| while (fLines.isEmpty()) { |
| setTopPosition(ratio); |
| if (ratio == 0.0) { |
| break; |
| } |
| delta = Math.min(delta * 10, 0.1); |
| ratio = Math.max(ratio - delta, 0.0); |
| } |
| break; |
| } |
| case SWT.ARROW_DOWN: { |
| if (fTopLineIndex >= fLines.size()) { |
| break; |
| } |
| fTopLineIndex++; |
| loadLineData(); |
| updateTextArea(); |
| break; |
| } |
| case SWT.PAGE_DOWN: { |
| fTopLineIndex += Math.max(fNumVisibleLines - 1, 1); |
| loadLineData(); |
| updateTextArea(); |
| break; |
| } |
| case SWT.ARROW_UP: { |
| if (fLines.isEmpty()) { |
| break; |
| } |
| fTopLineIndex--; |
| loadLineData(); |
| updateTextArea(); |
| break; |
| } |
| case SWT.PAGE_UP: { |
| fTopLineIndex -= Math.max(fNumVisibleLines - 1, 1); |
| loadLineData(); |
| updateTextArea(); |
| break; |
| } |
| case SWT.HOME: { |
| setTopPosition(0.0); |
| break; |
| } |
| case SWT.END: { |
| double ratio = 1.0; |
| double delta = Math.pow(10, -15); |
| fLines.clear(); |
| while (fLines.isEmpty()) { |
| setTopPosition(ratio); |
| if (ratio == 0.0) { |
| break; |
| } |
| delta = Math.min(delta * 10, 0.1); |
| ratio = Math.max(ratio - delta, 0.0); |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| if (e.detail != SWT.NONE) { |
| fSlider.setSelection((int) (SLIDER_MAX * fTrace.getLocationRatio(fLines.get(fTopLineIndex).location))); |
| } |
| |
| fHoldSelection = 0; |
| } |
| |
| @Override |
| public void widgetDefaultSelected(SelectionEvent e) { |
| // Do nothing |
| } |
| |
| // ------------------------------------------------------------------------ |
| // MouseListener (Slider) |
| // ------------------------------------------------------------------------ |
| |
| /** |
| * @since 1.1 |
| */ |
| @Override |
| public void mouseDown(MouseEvent e) { |
| // Do nothing |
| } |
| |
| /** |
| * @since 1.1 |
| */ |
| @Override |
| public void mouseUp(MouseEvent e) { |
| if (e.button != 1) { |
| return; |
| } |
| /* |
| * When the mouse button is released, perform the refresh immediately |
| * and interrupt and discard the slider throttler. |
| */ |
| if (fSliderThrottler != null) { |
| fSliderThrottler.interrupt(); |
| fSliderThrottler = null; |
| } |
| Event event = new Event(); |
| event.widget = this; |
| event.detail = SWT.NONE; |
| widgetSelected(new SelectionEvent(event)); |
| } |
| |
| /** |
| * @since 1.1 |
| */ |
| @Override |
| public void mouseDoubleClick(MouseEvent e) { |
| // Do nothing |
| } |
| |
| // ------------------------------------------------------------------------ |
| // KeyListener (StyledText) |
| // ------------------------------------------------------------------------ |
| |
| @Override |
| public void keyPressed(KeyEvent e) { |
| if (fLines.isEmpty()) { |
| return; |
| } |
| int caretOffset = fStyledText.getCaretOffset(); |
| int previousCaretOffset = getPreviousCaretOffset(e.time); |
| int previousLineAtCaretPosition = fStyledText.getLineAtOffset(previousCaretOffset); |
| int previousColumnAtCaretPosition = getPreviousCaretOffset(e.time) - fStyledText.getOffsetAtLine(previousLineAtCaretPosition); |
| switch (e.keyCode) { |
| case SWT.ARROW_DOWN: { |
| if (previousLineAtCaretPosition < (fNumVisibleLines - 2)) { |
| break; |
| } |
| fHoldSelection++; |
| fTopLineIndex++; |
| loadLineData(); |
| updateTextArea(); |
| fHoldSelection--; |
| LineData lineData = fLines.get(fTopLineIndex + fStyledText.getLineAtOffset(fStyledText.getCaretOffset())); |
| if (!lineData.location.equals(fSelectedLocation)) { |
| fSelectedLocation = lineData.location; |
| refreshLineBackgrounds(); |
| sendSelectionEvent(lineData); |
| } |
| break; |
| } |
| case SWT.PAGE_DOWN: { |
| if (previousLineAtCaretPosition >= (fNumVisibleLines - 1)) { |
| fHoldSelection++; |
| if (fLines.get(fTopLineIndex + previousLineAtCaretPosition).rank % 2 == 0) { |
| fStyledText.setLineBackground(previousLineAtCaretPosition, 1, COLOR_BACKGROUND_EVEN); |
| } else { |
| fStyledText.setLineBackground(previousLineAtCaretPosition, 1, COLOR_BACKGROUND_ODD); |
| } |
| fSelectedLocation = null; |
| fTopLineIndex += Math.max(fNumVisibleLines - 1, 1); |
| loadLineData(); |
| updateTextArea(); |
| fHoldSelection--; |
| } |
| int line = Math.min(fNumVisibleLines - 1, fStyledText.getLineCount() - 1); |
| int offset = fStyledText.getOffsetAtLine(line); |
| fStyledText.setSelection(offset + Math.min(previousColumnAtCaretPosition, fLines.get(fTopLineIndex + line).string.length())); |
| break; |
| } |
| case SWT.ARROW_RIGHT: { |
| if (previousCaretOffset < fStyledText.getCharCount() || previousLineAtCaretPosition < (fNumVisibleLines - 2)) { |
| break; |
| } |
| fHoldSelection++; |
| fTopLineIndex++; |
| loadLineData(); |
| updateTextArea(); |
| fHoldSelection--; |
| fStyledText.setSelection(fStyledText.getCaretOffset() + 1); |
| break; |
| } |
| case SWT.ARROW_UP: { |
| if (previousLineAtCaretPosition > 0) { |
| break; |
| } |
| if (fLines.isEmpty()) { |
| break; |
| } |
| fHoldSelection++; |
| fTopLineIndex--; |
| loadLineData(); |
| updateTextArea(); |
| fHoldSelection--; |
| LineData lineData = fLines.get(fTopLineIndex); |
| if (!lineData.location.equals(fSelectedLocation)) { |
| fSelectedLocation = lineData.location; |
| refreshLineBackgrounds(); |
| sendSelectionEvent(lineData); |
| } |
| fStyledText.setSelection(caretOffset); |
| break; |
| } |
| case SWT.PAGE_UP: { |
| if (previousLineAtCaretPosition > 0) { |
| break; |
| } |
| fHoldSelection++; |
| fTopLineIndex -= Math.max(fNumVisibleLines - 1, 1); |
| loadLineData(); |
| updateTextArea(); |
| fHoldSelection--; |
| LineData lineData = fLines.get(fTopLineIndex); |
| if (!lineData.location.equals(fSelectedLocation)) { |
| fSelectedLocation = lineData.location; |
| refreshLineBackgrounds(); |
| sendSelectionEvent(lineData); |
| } |
| fStyledText.setSelection(caretOffset); |
| break; |
| } |
| case SWT.ARROW_LEFT: { |
| if (previousCaretOffset > 0) { |
| break; |
| } |
| if (fLines.isEmpty()) { |
| break; |
| } |
| long topRank = fLines.get(fTopLineIndex).rank; |
| fHoldSelection++; |
| fTopLineIndex--; |
| loadLineData(); |
| updateTextArea(); |
| fHoldSelection--; |
| LineData lineData = fLines.get(fTopLineIndex); |
| if (!lineData.location.equals(fSelectedLocation)) { |
| fSelectedLocation = lineData.location; |
| refreshLineBackgrounds(); |
| sendSelectionEvent(lineData); |
| } |
| if (topRank != fLines.get(fTopLineIndex).rank) { |
| fStyledText.setSelection(fLines.get(fTopLineIndex).string.length()); |
| } |
| break; |
| } |
| case SWT.HOME: { |
| if ((e.stateMask & SWT.CTRL) == 0) { |
| break; |
| } |
| setTopPosition(0.0); |
| LineData lineData = fLines.get(fTopLineIndex); |
| if (!lineData.location.equals(fSelectedLocation)) { |
| fSelectedLocation = lineData.location; |
| refreshLineBackgrounds(); |
| sendSelectionEvent(lineData); |
| } |
| break; |
| } |
| case SWT.END: { |
| if ((e.stateMask & SWT.CTRL) == 0) { |
| break; |
| } |
| double ratio = 1.0; |
| double delta = Math.pow(10, -15); |
| fLines.clear(); |
| while (fLines.isEmpty()) { |
| setTopPosition(ratio); |
| if (ratio == 0.0) { |
| break; |
| } |
| delta = Math.min(delta * 10, 0.1); |
| ratio = Math.max(ratio - delta, 0.0); |
| } |
| LineData lineData = fLines.get(fTopLineIndex); |
| if (!lineData.location.equals(fSelectedLocation)) { |
| fSelectedLocation = lineData.location; |
| refreshLineBackgrounds(); |
| sendSelectionEvent(lineData); |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| updateHighlightedRank(); |
| fSlider.setSelection((int) (SLIDER_MAX * fTrace.getLocationRatio(fLines.get(fTopLineIndex).location))); |
| } |
| |
| @Override |
| public void keyReleased(KeyEvent e) { |
| // Do nothing |
| } |
| |
| // ------------------------------------------------------------------------ |
| // CaretListener (StyledText) |
| // ------------------------------------------------------------------------ |
| |
| @Override |
| public void caretMoved(CaretEvent event) { |
| if (fHoldSelection == 0) { |
| int line = fStyledText.getLineAtOffset(event.caretOffset); |
| if (fTopLineIndex + line < fLines.size()) { |
| LineData lineData = fLines.get(fTopLineIndex + line); |
| if (!lineData.location.equals(fSelectedLocation)) { |
| fSelectedLocation = lineData.location; |
| refreshLineBackgrounds(); |
| sendSelectionEvent(lineData); |
| } |
| } |
| } |
| storeCaretPosition(event.time, event.caretOffset); |
| if (fHoldSelection == 0) { |
| Point caret = fStyledText.getLocationAtOffset(fStyledText.getCaretOffset()); |
| Point origin = fScrolledComposite.getOrigin(); |
| if (origin.x > caret.x) { |
| origin.x = caret.x; |
| } else if (caret.x - origin.x > fScrolledComposite.getSize().x) { |
| origin.x = caret.x - fScrolledComposite.getSize().x + 1; |
| } |
| fScrolledComposite.setOrigin(origin); |
| } |
| } |
| |
| // ------------------------------------------------------------------------ |
| // MouseMoveListener (StyledText) |
| // ------------------------------------------------------------------------ |
| |
| @Override |
| public void mouseMove(MouseEvent e) { |
| fCursorYCoordinate = e.y; |
| if (e.y < 0 || e.y > fStyledText.getSize().y) { |
| if (fHighlightedRank != Long.MIN_VALUE) { |
| fHighlightedRank = Long.MIN_VALUE; |
| refreshLineBackgrounds(); |
| } |
| return; |
| } |
| int offset = fStyledText.getOffsetAtLocation(new Point(0, e.y)); |
| int line = fStyledText.getLineAtOffset(offset); |
| if (line < fLines.size() - fTopLineIndex) { |
| LineData lineData = fLines.get(fTopLineIndex + line); |
| if (fHighlightedRank != lineData.rank) { |
| fHighlightedRank = lineData.rank; |
| refreshLineBackgrounds(); |
| } |
| } else { |
| if (fHighlightedRank != Long.MIN_VALUE) { |
| fHighlightedRank = Long.MIN_VALUE; |
| refreshLineBackgrounds(); |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------ |
| // MouseTrackListener (StyledText) |
| // ------------------------------------------------------------------------ |
| |
| @Override |
| public void mouseExit(MouseEvent e) { |
| fCursorYCoordinate = -1; |
| if (fHighlightedRank != Long.MIN_VALUE) { |
| fHighlightedRank = Long.MIN_VALUE; |
| refreshLineBackgrounds(); |
| } |
| } |
| |
| @Override |
| public void mouseEnter(MouseEvent e) { |
| fCursorYCoordinate = e.y; |
| } |
| |
| @Override |
| public void mouseHover(MouseEvent e) { |
| // Do nothing |
| } |
| |
| // ------------------------------------------------------------------------ |
| // MouseWheelListener (StyledText) |
| // ------------------------------------------------------------------------ |
| |
| @Override |
| public void mouseScrolled(MouseEvent e) { |
| if (fLines.isEmpty() || e.count == 0) { |
| return; |
| } |
| fHoldSelection++; |
| fTopLineIndex -= e.count; |
| loadLineData(); |
| updateTextArea(); |
| fHoldSelection = 0; |
| updateHighlightedRank(); |
| fSlider.setSelection((int) (SLIDER_MAX * fTrace.getLocationRatio(fLines.get(fTopLineIndex).location))); |
| } |
| |
| } |