1 /*******************************************************************************
2 * Copyright (c) 2010, 2015 Ericsson
4 * All rights reserved. This program and the accompanying materials are
5 * made available under the terms of the Eclipse Public License v1.0 which
6 * accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * Patrick Tasse - Initial API and implementation
11 ******************************************************************************/
13 package org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.rawviewer
;
15 import java
.util
.ArrayList
;
16 import java
.util
.List
;
18 import org
.eclipse
.jface
.resource
.ColorRegistry
;
19 import org
.eclipse
.jface
.resource
.FontRegistry
;
20 import org
.eclipse
.jface
.util
.IPropertyChangeListener
;
21 import org
.eclipse
.jface
.util
.PropertyChangeEvent
;
22 import org
.eclipse
.swt
.SWT
;
23 import org
.eclipse
.swt
.custom
.CaretEvent
;
24 import org
.eclipse
.swt
.custom
.CaretListener
;
25 import org
.eclipse
.swt
.custom
.ScrolledComposite
;
26 import org
.eclipse
.swt
.custom
.StyledText
;
27 import org
.eclipse
.swt
.events
.ControlEvent
;
28 import org
.eclipse
.swt
.events
.ControlListener
;
29 import org
.eclipse
.swt
.events
.KeyEvent
;
30 import org
.eclipse
.swt
.events
.KeyListener
;
31 import org
.eclipse
.swt
.events
.MouseAdapter
;
32 import org
.eclipse
.swt
.events
.MouseEvent
;
33 import org
.eclipse
.swt
.events
.MouseListener
;
34 import org
.eclipse
.swt
.events
.MouseMoveListener
;
35 import org
.eclipse
.swt
.events
.MouseTrackListener
;
36 import org
.eclipse
.swt
.events
.MouseWheelListener
;
37 import org
.eclipse
.swt
.events
.SelectionEvent
;
38 import org
.eclipse
.swt
.events
.SelectionListener
;
39 import org
.eclipse
.swt
.graphics
.Color
;
40 import org
.eclipse
.swt
.graphics
.Font
;
41 import org
.eclipse
.swt
.graphics
.Point
;
42 import org
.eclipse
.swt
.layout
.GridData
;
43 import org
.eclipse
.swt
.layout
.GridLayout
;
44 import org
.eclipse
.swt
.widgets
.Composite
;
45 import org
.eclipse
.swt
.widgets
.Control
;
46 import org
.eclipse
.swt
.widgets
.Display
;
47 import org
.eclipse
.swt
.widgets
.Event
;
48 import org
.eclipse
.swt
.widgets
.Listener
;
49 import org
.eclipse
.swt
.widgets
.Menu
;
50 import org
.eclipse
.swt
.widgets
.Slider
;
51 import org
.eclipse
.tracecompass
.tmf
.core
.event
.ITmfEvent
;
52 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.ITmfContext
;
53 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.ITmfTrace
;
54 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.location
.ITmfLocation
;
55 import org
.eclipse
.ui
.PlatformUI
;
56 import org
.eclipse
.ui
.themes
.IThemeManager
;
59 * TmfRawEventViewer allows for the display of the raw data for an arbitrarily
60 * large number of TMF events.
62 * It is essentially a Composite of a StyledText area and a Slider, where the number
63 * of visible lines in the StyledText control is set to fill the viewer display area.
64 * An underlying data model is used to store a cache of event raw text line data.
65 * The slider is ratio-based.
68 * @author Patrick Tasse
70 public class TmfRawEventViewer
extends Composite
implements ControlListener
, SelectionListener
, MouseListener
,
71 KeyListener
, CaretListener
, MouseMoveListener
, MouseTrackListener
, MouseWheelListener
, IPropertyChangeListener
{
73 private static final Color COLOR_BACKGROUND_ODD
= Display
.getCurrent().getSystemColor(SWT
.COLOR_WHITE
);
74 private static final Color COLOR_BACKGROUND_EVEN
= new Color(Display
.getDefault(), 242, 242, 242);
75 private static final String FONT_DEFINITION_ID
= "org.eclipse.tracecompass.tmf.ui.font.eventraw"; //$NON-NLS-1$
76 private static final String HIGHLIGHT_COLOR_DEFINITION_ID
= "org.eclipse.tracecompass.tmf.ui.color.eventraw.highlight"; //$NON-NLS-1$
77 private static final String SELECTION_COLOR_DEFINITION_ID
= "org.eclipse.tracecompass.tmf.ui.color.eventraw.selection"; //$NON-NLS-1$
78 private static final int MAX_LINE_DATA_SIZE
= 1000;
79 private static final int SLIDER_MAX
= 1000000;
80 private static final String EMPTY_STRING
= ""; //$NON-NLS-1$
81 private static final String LF
= "\n"; //$NON-NLS-1$
82 private static final String CR_LF
= "\r?\n"; //$NON-NLS-1$
84 private ITmfTrace fTrace
;
85 private ITmfContext fBottomContext
;
87 private ScrolledComposite fScrolledComposite
;
88 private Composite fTextArea
;
89 private StyledText fStyledText
;
90 private Font fFixedFont
;
91 private Color fHighlightColor
;
92 private Color fSelectionColor
;
93 private Slider fSlider
;
94 private SliderThrottler fSliderThrottler
;
96 private final List
<LineData
> fLines
= new ArrayList
<>();
97 private boolean fActualRanks
= false;
98 private int fTopLineIndex
;
99 private int fLastTopLineIndex
;
100 private final CaretPosition
[] fStoredCaretPosition
= new CaretPosition
[]
101 { new CaretPosition(0, 0), new CaretPosition(0,0)};
102 private int fNumVisibleLines
;
103 private ITmfLocation fSelectedLocation
= null;
104 private long fHighlightedRank
= Long
.MIN_VALUE
;
105 private int fCursorYCoordinate
= -1;
106 private int fHoldSelection
= 0;
108 // ------------------------------------------------------------------------
110 // ------------------------------------------------------------------------
112 private static class LineData
{
114 ITmfLocation location
;
116 public LineData(long rank
, ITmfLocation location
, String string
) {
118 this.location
= location
;
119 if (string
.length() == 0) {
120 /* workaround for setLineBackground has no effect on empty line */
121 this.string
= " "; //$NON-NLS-1$
123 this.string
= string
;
127 public String
toString() {
128 return rank
+ " [" + location
+ "]: " + string
; //$NON-NLS-1$ //$NON-NLS-2$
132 private static class CaretPosition
{
135 public CaretPosition(int time
, int caretOffset
) {
137 this.caretOffset
= caretOffset
;
141 private class SliderThrottler
extends Thread
{
142 private static final long DELAY
= 400L;
143 private static final long POLLING_INTERVAL
= 10L;
147 final long startTime
= System
.currentTimeMillis();
148 while ((System
.currentTimeMillis() - startTime
) < DELAY
) {
150 Thread
.sleep(POLLING_INTERVAL
);
151 } catch (InterruptedException e
) {
154 Display
.getDefault().asyncExec(new Runnable() {
157 if (fSliderThrottler
!= SliderThrottler
.this) {
160 fSliderThrottler
= null;
161 if (SliderThrottler
.this.isInterrupted() || fSlider
.isDisposed()) {
164 Event event
= new Event();
165 event
.widget
= TmfRawEventViewer
.this;
166 event
.detail
= SWT
.NONE
;
167 widgetSelected(new SelectionEvent(event
));
173 // ------------------------------------------------------------------------
175 // ------------------------------------------------------------------------
179 * @param parent The parent composite
180 * @param style The style bits
182 public TmfRawEventViewer(Composite parent
, int style
) {
183 super(parent
, style
& (~SWT
.H_SCROLL
) & (~SWT
.V_SCROLL
));
186 GridLayout gridLayout
= new GridLayout();
187 gridLayout
.numColumns
= 2;
188 gridLayout
.horizontalSpacing
= 0;
189 gridLayout
.verticalSpacing
= 0;
190 gridLayout
.marginWidth
= 0;
191 gridLayout
.marginHeight
= 0;
192 setLayout(gridLayout
);
194 // Create the controls
195 createTextArea(style
& SWT
.H_SCROLL
);
196 createSlider(style
& SWT
.V_SCROLL
);
198 // Prevent the slider from being traversed
199 setTabList(new Control
[] { fScrolledComposite
});
203 public void dispose() {
204 if (fBottomContext
!= null) {
205 fBottomContext
.dispose();
207 PlatformUI
.getWorkbench().getThemeManager().removePropertyChangeListener(this);
211 // ------------------------------------------------------------------------
212 // Font and color handling
213 // ------------------------------------------------------------------------
216 * Initialize the fonts.
219 protected void initializeFonts() {
220 FontRegistry fontRegistry
= PlatformUI
.getWorkbench().getThemeManager().getCurrentTheme().getFontRegistry();
221 fFixedFont
= fontRegistry
.get(FONT_DEFINITION_ID
);
222 fStyledText
.setFont(fFixedFont
);
226 * Initialize the colors.
229 protected void initializeColors() {
230 ColorRegistry colorRegistry
= PlatformUI
.getWorkbench().getThemeManager().getCurrentTheme().getColorRegistry();
231 fHighlightColor
= colorRegistry
.get(HIGHLIGHT_COLOR_DEFINITION_ID
);
232 fSelectionColor
= colorRegistry
.get(SELECTION_COLOR_DEFINITION_ID
);
239 public void propertyChange(PropertyChangeEvent event
) {
240 if ((IThemeManager
.CHANGE_CURRENT_THEME
.equals(event
.getProperty())) ||
241 (FONT_DEFINITION_ID
.equals(event
.getProperty()))) {
245 if ((IThemeManager
.CHANGE_CURRENT_THEME
.equals(event
.getProperty())) ||
246 (HIGHLIGHT_COLOR_DEFINITION_ID
.equals(event
.getProperty())) ||
247 (SELECTION_COLOR_DEFINITION_ID
.equals(event
.getProperty()))) {
253 // ------------------------------------------------------------------------
254 // Text area handling
255 // ------------------------------------------------------------------------
258 * Create the text area and add listeners
260 private void createTextArea(int style
) {
261 fScrolledComposite
= new ScrolledComposite(this, style
);
262 fScrolledComposite
.setLayoutData(new GridData(SWT
.FILL
, SWT
.FILL
, true, true));
263 fTextArea
= new Composite(fScrolledComposite
, SWT
.NONE
);
264 fTextArea
.setLayoutData(new GridData(SWT
.FILL
, SWT
.FILL
, true, true));
265 fScrolledComposite
.setContent(fTextArea
);
266 fScrolledComposite
.setExpandHorizontal(true);
267 fScrolledComposite
.setExpandVertical(true);
268 fScrolledComposite
.setAlwaysShowScrollBars(true);
269 fScrolledComposite
.setMinSize(fTextArea
.computeSize(SWT
.DEFAULT
, SWT
.DEFAULT
));
270 fScrolledComposite
.addControlListener(this);
272 GridLayout textAreaGridLayout
= new GridLayout();
273 textAreaGridLayout
.marginHeight
= 0;
274 textAreaGridLayout
.marginWidth
= 0;
275 fTextArea
.setLayout(textAreaGridLayout
);
277 fStyledText
= new StyledText(fTextArea
, SWT
.READ_ONLY
);
278 fStyledText
.setLayoutData(new GridData(SWT
.FILL
, SWT
.FILL
, true, false));
282 PlatformUI
.getWorkbench().getThemeManager().addPropertyChangeListener(this);
284 fStyledText
.addCaretListener(this);
285 fStyledText
.addMouseMoveListener(this);
286 fStyledText
.addMouseTrackListener(this);
287 fStyledText
.addMouseWheelListener(this);
288 /* disable mouse scroll of horizontal scroll bar */
289 fStyledText
.addListener(SWT
.MouseWheel
, new Listener() {
291 public void handleEvent(Event event
) {
295 fStyledText
.addKeyListener(this);
297 fTextArea
.setBackground(fStyledText
.getBackground());
298 fTextArea
.addMouseListener(new MouseAdapter() {
300 public void mouseDown(MouseEvent e
) {
301 fTextArea
.setFocus();
306 // ------------------------------------------------------------------------
308 // ------------------------------------------------------------------------
310 private void createSlider(int style
) {
311 fSlider
= new Slider(this, SWT
.VERTICAL
);
312 fSlider
.setLayoutData(new GridData(SWT
.FILL
, SWT
.FILL
, false, true));
313 fSlider
.setValues(0, 0, SLIDER_MAX
, SLIDER_MAX
, 1, 1);
314 fSlider
.addSelectionListener(this);
315 fSlider
.addMouseListener(this);
316 if ((style
& SWT
.V_SCROLL
) == 0) {
317 fSlider
.setVisible(false);
321 // ------------------------------------------------------------------------
322 // Controls interactions
323 // ------------------------------------------------------------------------
326 public boolean setFocus() {
327 boolean isVisible
= isVisible();
329 fTextArea
.setFocus();
335 public void setMenu(Menu menu
) {
336 fStyledText
.setMenu(menu
);
340 * Sets the trace and updates the content
341 * @param trace The trace to set
343 public void setTrace(ITmfTrace trace
) {
351 * Refreshes the event count, updates the slider thumb and loads display
353 public void refreshEventCount() {
354 if (fTrace
!= null) {
355 if (fTrace
.getNbEvents() > 0) {
356 fSlider
.setThumb((int) Math
.max(SLIDER_MAX
/ fTrace
.getNbEvents(), 1));
358 fSlider
.setThumb(SLIDER_MAX
);
365 if (fLines
.size() == 0) {
367 } else if (fLines
.size() < fNumVisibleLines
) {
368 if (fBottomContext
!= null) {
369 fBottomContext
.dispose();
370 fBottomContext
= null;
374 fSlider
.setSelection((int) (SLIDER_MAX
* fTrace
.getLocationRatio(fLines
.get(fTopLineIndex
).location
)));
377 if (fBottomContext
!= null) {
378 fBottomContext
.dispose();
379 fBottomContext
= null;
382 fSlider
.setThumb(SLIDER_MAX
);
383 fSlider
.setSelection(0);
388 * Selects the event of given rank and makes it visible.
389 * @param rank The rank of event
391 public void selectAndReveal(long rank
) {
392 if (fTrace
== null || !isVisible()) {
395 if (fActualRanks
&& fTopLineIndex
< fLines
.size() && rank
>= fLines
.get(fTopLineIndex
).rank
) {
396 int lastVisibleIndex
= Math
.min(fTopLineIndex
+ fNumVisibleLines
, fLines
.size()) - 1;
397 if (rank
<= fLines
.get(lastVisibleIndex
).rank
) {
398 for (int i
= fTopLineIndex
; i
< fLines
.size(); i
++) {
399 if (fLines
.get(i
).rank
== rank
) {
400 fSelectedLocation
= fLines
.get(i
).location
;
404 refreshLineBackgrounds();
409 if (fLines
.size() > 0 && fHoldSelection
== 0) {
410 fSelectedLocation
= fLines
.get(0).location
;
411 refreshLineBackgrounds();
416 * Add a selection listener
417 * @param listener A listener to add
419 public void addSelectionListener(Listener listener
) {
421 if (listener
== null) {
422 SWT
.error (SWT
.ERROR_NULL_ARGUMENT
);
424 addListener (SWT
.Selection
, listener
);
428 * Remove selection listener
429 * @param listener A listener to remove
431 public void removeSelectionListener(Listener listener
) {
433 if (listener
== null) {
434 SWT
.error (SWT
.ERROR_NULL_ARGUMENT
);
436 removeListener(SWT
.Selection
, listener
);
439 private void sendSelectionEvent(LineData lineData
) {
440 Event event
= new Event();
442 event
.data
= Long
.valueOf(lineData
.rank
);
444 event
.data
= lineData
.location
;
446 notifyListeners(SWT
.Selection
, event
);
449 private void setTopRank(long rank
) {
450 if (fBottomContext
!= null) {
451 fBottomContext
.dispose();
453 fBottomContext
= fTrace
.seekEvent(rank
);
454 if (fBottomContext
== null) {
462 if (fLines
.size() == 0) {
463 fSlider
.setSelection(0);
465 fSlider
.setSelection((int) (SLIDER_MAX
* fTrace
.getLocationRatio(fLines
.get(fTopLineIndex
).location
)));
469 private void setTopPosition(double ratio
) {
470 if (fBottomContext
!= null) {
471 fBottomContext
.dispose();
473 fBottomContext
= fTrace
.seekEvent(ratio
);
474 if (fBottomContext
== null) {
477 fBottomContext
.setRank(0);
479 fActualRanks
= false;
485 private void loadLineData() {
486 if (fTopLineIndex
< 0) {
487 if (fLines
.size() > 0 && fTrace
.getLocationRatio(fLines
.get(0).location
) > 0) {
488 double lastRatio
= fTrace
.getLocationRatio(fLines
.get(fLines
.size() - 1).location
);
489 double firstRatio
= fTrace
.getLocationRatio(fLines
.get(0).location
);
491 boolean singleEvent
= false;
492 if (firstRatio
!= lastRatio
) {
493 // approximate ratio of at least 20 items
494 delta
= Math
.max(20, fNumVisibleLines
) * (lastRatio
- firstRatio
) / (fLines
.size() - 1);
496 delta
= Math
.pow(10, -15);
499 while (fTopLineIndex
< 0) {
500 ITmfLocation endLocation
= fLines
.get(0).location
;
501 firstRatio
= Math
.max(0, firstRatio
- delta
);
502 ITmfContext context
= fTrace
.seekEvent(firstRatio
);
503 ITmfLocation location
;
506 while (!context
.getLocation().equals(endLocation
)) {
507 location
= context
.getLocation();
508 ITmfEvent event
= fTrace
.getNext(context
);
512 if (event
.getContent() != null && event
.getContent().getValue() != null) {
513 String
[] lines
= event
.getContent().getValue().toString().split(CR_LF
);
514 for (int i
= 0; i
< lines
.length
; i
++) {
515 String line
= lines
[i
];
516 LineData lineData
= new LineData(rank
, location
, line
);
517 fLines
.add(index
++, lineData
);
522 LineData lineData
= new LineData(rank
, location
, EMPTY_STRING
);
523 fLines
.add(index
++, lineData
);
530 long rankOffset
= fLines
.get(index
).rank
- rank
;
531 for (int i
= 0; i
< index
; i
++) {
532 fLines
.get(i
).rank
+= rankOffset
;
534 if (firstRatio
== 0) {
538 delta
= Math
.min(delta
* 10, 0.1);
542 if (fTopLineIndex
< 0) {
547 while (fLines
.size() - fTopLineIndex
< fNumVisibleLines
) {
548 if (fBottomContext
== null) {
549 if (fLines
.size() == 0) {
550 fBottomContext
= fTrace
.seekEvent(0);
552 fBottomContext
= fTrace
.seekEvent(fLines
.get(fLines
.size() - 1).location
);
553 fTrace
.getNext(fBottomContext
);
555 if (fBottomContext
== null) {
559 long rank
= fBottomContext
.getRank();
560 ITmfLocation location
= fBottomContext
.getLocation() != null ? fBottomContext
.getLocation() : null;
561 ITmfEvent event
= fTrace
.getNext(fBottomContext
);
565 if (event
.getContent() != null && event
.getContent().getValue() != null) {
566 for (String line
: event
.getContent().getValue().toString().split(CR_LF
)) {
568 if ((crPos
= line
.indexOf('\r')) != -1) {
569 line
= line
.substring(0, crPos
);
571 LineData lineData
= new LineData(rank
, location
, line
);
572 fLines
.add(lineData
);
575 LineData lineData
= new LineData(rank
, location
, EMPTY_STRING
);
576 fLines
.add(lineData
);
579 fTopLineIndex
= Math
.max(0, Math
.min(fTopLineIndex
, fLines
.size() - 1));
581 if (fLines
.size() > MAX_LINE_DATA_SIZE
) {
582 if (fTopLineIndex
< MAX_LINE_DATA_SIZE
/ 2) {
583 long rank
= fLines
.get(MAX_LINE_DATA_SIZE
- 1).rank
;
584 for (int i
= MAX_LINE_DATA_SIZE
; i
< fLines
.size(); i
++) {
585 if (fLines
.get(i
).rank
> rank
) {
586 fLines
.subList(i
, fLines
.size()).clear();
587 if (fBottomContext
!= null) {
588 fBottomContext
.dispose();
589 fBottomContext
= null;
595 long rank
= fLines
.get(fLines
.size() - MAX_LINE_DATA_SIZE
).rank
;
596 for (int i
= fLines
.size() - MAX_LINE_DATA_SIZE
- 1; i
>= 0; i
--) {
597 if (fLines
.get(i
).rank
< rank
) {
598 fLines
.subList(0, i
+ 1).clear();
599 fTopLineIndex
-= (i
+ 1);
600 fLastTopLineIndex
-= (i
+ 1);
608 private void refreshTextArea() {
609 fStyledText
.setText(EMPTY_STRING
);
610 for (int i
= 0; i
< fLines
.size() - fTopLineIndex
&& i
< fNumVisibleLines
; i
++) {
613 fStyledText
.append(LF
);
615 LineData lineData
= fLines
.get(fTopLineIndex
+ i
);
616 fStyledText
.append(lineData
.string
);
617 setLineBackground(i
, lineData
);
620 fScrolledComposite
.setMinSize(fTextArea
.computeSize(SWT
.DEFAULT
, SWT
.DEFAULT
));
621 fLastTopLineIndex
= fTopLineIndex
;
624 private void fillTextArea() {
625 int nextLine
= fStyledText
.getCharCount() == 0 ?
0 : fStyledText
.getLineCount();
626 for (int i
= nextLine
; i
< fLines
.size() - fTopLineIndex
&& i
< fNumVisibleLines
; i
++) {
629 fStyledText
.append(LF
);
631 LineData lineData
= fLines
.get(fTopLineIndex
+ i
);
632 fStyledText
.append(lineData
.string
);
633 setLineBackground(i
, lineData
);
635 int endLine
= Math
.min(fNumVisibleLines
, fLines
.size());
636 if (endLine
< fStyledText
.getLineCount()) {
637 int endOffset
= fStyledText
.getOffsetAtLine(endLine
) - 1;
638 if (endOffset
> fStyledText
.getCharCount()) {
640 fStyledText
.replaceTextRange(endOffset
, fStyledText
.getCharCount() - endOffset
, EMPTY_STRING
);
645 fScrolledComposite
.setMinSize(fTextArea
.computeSize(SWT
.DEFAULT
, SWT
.DEFAULT
));
648 private void updateTextArea() {
649 if (fTopLineIndex
< fLastTopLineIndex
) {
650 StringBuilder insertedText
= new StringBuilder();
651 for (int i
= fTopLineIndex
; i
< fLastTopLineIndex
; i
++) {
652 insertedText
.append(fLines
.get(i
).string
).append(LF
);
654 fStyledText
.replaceTextRange(0, 0, insertedText
.toString());
655 for (int i
= 0; i
< fLastTopLineIndex
- fTopLineIndex
; i
++) {
656 LineData lineData
= fLines
.get(fTopLineIndex
+ i
);
657 setLineBackground(i
, lineData
);
659 fLastTopLineIndex
= fTopLineIndex
;
660 } else if (fTopLineIndex
> fLastTopLineIndex
) {
662 for (int i
= 0; i
< fTopLineIndex
- fLastTopLineIndex
&& i
< fNumVisibleLines
; i
++) {
663 length
+= fLines
.get(i
+ fLastTopLineIndex
).string
.length();
664 if (i
< fStyledText
.getLineCount()) {
668 fStyledText
.replaceTextRange(0, length
, EMPTY_STRING
);
669 fLastTopLineIndex
= fTopLineIndex
;
672 int endLine
= Math
.min(fNumVisibleLines
, fLines
.size());
673 if (endLine
< fStyledText
.getLineCount()) {
674 int endOffset
= fStyledText
.getOffsetAtLine(endLine
) - 1;
675 if (endOffset
> fStyledText
.getCharCount()) {
676 fStyledText
.replaceTextRange(endOffset
, fStyledText
.getCharCount() - endOffset
, EMPTY_STRING
);
680 fScrolledComposite
.setMinSize(fTextArea
.computeSize(SWT
.DEFAULT
, SWT
.DEFAULT
));
683 private void refreshLineBackgrounds() {
684 for (int i
= 0; (i
< fStyledText
.getLineCount()) && (i
< fNumVisibleLines
) && (i
< fLines
.size() - fTopLineIndex
); i
++) {
685 LineData lineData
= fLines
.get(fTopLineIndex
+ i
);
686 setLineBackground(i
, lineData
);
690 private void setLineBackground(int index
, LineData lineData
) {
691 if (lineData
.location
.equals(fSelectedLocation
)) {
692 fStyledText
.setLineBackground(index
, 1, fSelectionColor
);
693 } else if (lineData
.rank
== fHighlightedRank
) {
694 fStyledText
.setLineBackground(index
, 1, fHighlightColor
);
695 } else if (lineData
.rank
% 2 == 0) {
696 fStyledText
.setLineBackground(index
, 1, COLOR_BACKGROUND_EVEN
);
698 fStyledText
.setLineBackground(index
, 1, COLOR_BACKGROUND_ODD
);
702 private void storeCaretPosition(int time
, int caretOffset
) {
703 if (fStoredCaretPosition
[0].time
== time
) {
704 fStoredCaretPosition
[0].caretOffset
= caretOffset
;
706 fStoredCaretPosition
[1] = fStoredCaretPosition
[0];
707 fStoredCaretPosition
[0] = new CaretPosition(time
, caretOffset
);
711 private int getPreviousCaretOffset(int time
) {
712 if (fStoredCaretPosition
[0].time
== time
) {
713 return fStoredCaretPosition
[1].caretOffset
;
715 return fStoredCaretPosition
[0].caretOffset
;
718 private void updateHighlightedRank() {
719 if (fCursorYCoordinate
< 0 || fCursorYCoordinate
> fStyledText
.getSize().y
) {
720 if (fHighlightedRank
!= Long
.MIN_VALUE
) {
721 fHighlightedRank
= Long
.MIN_VALUE
;
722 refreshLineBackgrounds();
726 int offset
= fStyledText
.getOffsetAtLocation(new Point(0, fCursorYCoordinate
));
727 int line
= fStyledText
.getLineAtOffset(offset
);
728 if (line
< fLines
.size() - fTopLineIndex
) {
729 LineData lineData
= fLines
.get(fTopLineIndex
+ line
);
730 if (fHighlightedRank
!= lineData
.rank
) {
731 fHighlightedRank
= lineData
.rank
;
732 refreshLineBackgrounds();
735 if (fHighlightedRank
!= Long
.MIN_VALUE
) {
736 fHighlightedRank
= Long
.MIN_VALUE
;
737 refreshLineBackgrounds();
742 // ------------------------------------------------------------------------
743 // ControlListener (ScrolledComposite)
744 // ------------------------------------------------------------------------
747 public void controlResized(ControlEvent event
) {
748 int areaHeight
= fScrolledComposite
.getSize().y
;
749 if (fScrolledComposite
.getHorizontalBar() != null) {
750 areaHeight
-= fScrolledComposite
.getHorizontalBar().getSize().y
;
752 int lineHeight
= fStyledText
.getLineHeight();
753 fNumVisibleLines
= Math
.max((areaHeight
+ lineHeight
- 1) / lineHeight
, 1);
755 if (fBottomContext
!= null) {
762 public void controlMoved(ControlEvent e
) {
765 // ------------------------------------------------------------------------
766 // SelectionListener (Slider)
767 // ------------------------------------------------------------------------
770 public void widgetSelected(SelectionEvent e
) {
771 fTextArea
.setFocus();
772 if (fLines
.size() == 0) {
779 if (e
.widget
== fSlider
) {
781 * While the slider thumb is being dragged, only perform the
782 * refresh periodically. The event detail during the drag is
783 * SWT.DRAG on Windows and SWT.NONE on Linux.
785 if (fSliderThrottler
== null) {
786 fSliderThrottler
= new SliderThrottler();
787 fSliderThrottler
.start();
793 * The selection event was sent by the viewer, refresh now.
795 if (fSlider
.getSelection() == 0 || fSlider
.getThumb() == SLIDER_MAX
) {
800 double ratio
= (double) fSlider
.getSelection() / (SLIDER_MAX
- fSlider
.getThumb());
801 double delta
= Math
.pow(10, -15);
803 while (fLines
.size() == 0) {
804 setTopPosition(ratio
);
808 delta
= Math
.min(delta
* 10, 0.1);
809 ratio
= Math
.max(ratio
- delta
, 0.0);
813 case SWT
.ARROW_DOWN
: {
814 if (fTopLineIndex
>= fLines
.size()) {
822 case SWT
.PAGE_DOWN
: {
823 fTopLineIndex
+= Math
.max(fNumVisibleLines
- 1, 1);
829 if (fLines
.size() == 0) {
838 fTopLineIndex
-= Math
.max(fNumVisibleLines
- 1, 1);
849 double delta
= Math
.pow(10, -15);
851 while (fLines
.size() == 0) {
852 setTopPosition(ratio
);
856 delta
= Math
.min(delta
* 10, 0.1);
857 ratio
= Math
.max(ratio
- delta
, 0.0);
864 if (e
.detail
!= SWT
.NONE
) {
865 fSlider
.setSelection((int) (SLIDER_MAX
* fTrace
.getLocationRatio(fLines
.get(fTopLineIndex
).location
)));
872 public void widgetDefaultSelected(SelectionEvent e
) {
875 // ------------------------------------------------------------------------
876 // MouseListener (Slider)
877 // ------------------------------------------------------------------------
883 public void mouseDown(MouseEvent e
) {
890 public void mouseUp(MouseEvent e
) {
895 * When the mouse button is released, perform the refresh immediately
896 * and interrupt and discard the slider throttler.
898 if (fSliderThrottler
!= null) {
899 fSliderThrottler
.interrupt();
900 fSliderThrottler
= null;
902 Event event
= new Event();
904 event
.detail
= SWT
.NONE
;
905 widgetSelected(new SelectionEvent(event
));
912 public void mouseDoubleClick(MouseEvent e
) {
915 // ------------------------------------------------------------------------
916 // KeyListener (StyledText)
917 // ------------------------------------------------------------------------
920 public void keyPressed(KeyEvent e
) {
921 if (fLines
.size() == 0) {
924 int caretOffset
= fStyledText
.getCaretOffset();
925 int previousCaretOffset
= getPreviousCaretOffset(e
.time
);
926 int previousLineAtCaretPosition
= fStyledText
.getLineAtOffset(previousCaretOffset
);
927 int previousColumnAtCaretPosition
= getPreviousCaretOffset(e
.time
) - fStyledText
.getOffsetAtLine(previousLineAtCaretPosition
);
929 case SWT
.ARROW_DOWN
: {
930 if (previousLineAtCaretPosition
< (fNumVisibleLines
- 2)) {
938 LineData lineData
= fLines
.get(fTopLineIndex
+ fStyledText
.getLineAtOffset(fStyledText
.getCaretOffset()));
939 if (!lineData
.location
.equals(fSelectedLocation
)) {
940 fSelectedLocation
= lineData
.location
;
941 refreshLineBackgrounds();
942 sendSelectionEvent(lineData
);
946 case SWT
.PAGE_DOWN
: {
947 if (previousLineAtCaretPosition
>= (fNumVisibleLines
- 1)) {
949 if (fLines
.get(fTopLineIndex
+ previousLineAtCaretPosition
).rank
% 2 == 0) {
950 fStyledText
.setLineBackground(previousLineAtCaretPosition
, 1, COLOR_BACKGROUND_EVEN
);
952 fStyledText
.setLineBackground(previousLineAtCaretPosition
, 1, COLOR_BACKGROUND_ODD
);
954 fSelectedLocation
= null;
955 fTopLineIndex
+= Math
.max(fNumVisibleLines
- 1, 1);
960 int line
= Math
.min(fNumVisibleLines
- 1, fStyledText
.getLineCount() - 1);
961 int offset
= fStyledText
.getOffsetAtLine(line
);
962 fStyledText
.setSelection(offset
+ Math
.min(previousColumnAtCaretPosition
, fLines
.get(fTopLineIndex
+ line
).string
.length()));
965 case SWT
.ARROW_RIGHT
: {
966 if (previousCaretOffset
< fStyledText
.getCharCount() || previousLineAtCaretPosition
< (fNumVisibleLines
- 2)) {
974 fStyledText
.setSelection(fStyledText
.getCaretOffset() + 1);
978 if (previousLineAtCaretPosition
> 0) {
981 if (fLines
.size() == 0) {
989 LineData lineData
= fLines
.get(fTopLineIndex
);
990 if (!lineData
.location
.equals(fSelectedLocation
)) {
991 fSelectedLocation
= lineData
.location
;
992 refreshLineBackgrounds();
993 sendSelectionEvent(lineData
);
995 fStyledText
.setSelection(caretOffset
);
999 if (previousLineAtCaretPosition
> 0) {
1003 fTopLineIndex
-= Math
.max(fNumVisibleLines
- 1, 1);
1007 LineData lineData
= fLines
.get(fTopLineIndex
);
1008 if (!lineData
.location
.equals(fSelectedLocation
)) {
1009 fSelectedLocation
= lineData
.location
;
1010 refreshLineBackgrounds();
1011 sendSelectionEvent(lineData
);
1013 fStyledText
.setSelection(caretOffset
);
1016 case SWT
.ARROW_LEFT
: {
1017 if (previousCaretOffset
> 0) {
1020 if (fLines
.size() == 0) {
1023 long topRank
= fLines
.get(fTopLineIndex
).rank
;
1029 LineData lineData
= fLines
.get(fTopLineIndex
);
1030 if (!lineData
.location
.equals(fSelectedLocation
)) {
1031 fSelectedLocation
= lineData
.location
;
1032 refreshLineBackgrounds();
1033 sendSelectionEvent(lineData
);
1035 if (topRank
!= fLines
.get(fTopLineIndex
).rank
) {
1036 fStyledText
.setSelection(fLines
.get(fTopLineIndex
).string
.length());
1041 if ((e
.stateMask
& SWT
.CTRL
) == 0) {
1044 setTopPosition(0.0);
1045 LineData lineData
= fLines
.get(fTopLineIndex
);
1046 if (!lineData
.location
.equals(fSelectedLocation
)) {
1047 fSelectedLocation
= lineData
.location
;
1048 refreshLineBackgrounds();
1049 sendSelectionEvent(lineData
);
1054 if ((e
.stateMask
& SWT
.CTRL
) == 0) {
1058 double delta
= Math
.pow(10, -15);
1060 while (fLines
.size() == 0) {
1061 setTopPosition(ratio
);
1065 delta
= Math
.min(delta
* 10, 0.1);
1066 ratio
= Math
.max(ratio
- delta
, 0.0);
1068 LineData lineData
= fLines
.get(fTopLineIndex
);
1069 if (!lineData
.location
.equals(fSelectedLocation
)) {
1070 fSelectedLocation
= lineData
.location
;
1071 refreshLineBackgrounds();
1072 sendSelectionEvent(lineData
);
1079 updateHighlightedRank();
1080 fSlider
.setSelection((int) (SLIDER_MAX
* fTrace
.getLocationRatio(fLines
.get(fTopLineIndex
).location
)));
1084 public void keyReleased(KeyEvent e
) {
1087 // ------------------------------------------------------------------------
1088 // CaretListener (StyledText)
1089 // ------------------------------------------------------------------------
1092 public void caretMoved(CaretEvent event
) {
1093 if (fHoldSelection
== 0) {
1094 int line
= fStyledText
.getLineAtOffset(event
.caretOffset
);
1095 if (fTopLineIndex
+ line
< fLines
.size()) {
1096 LineData lineData
= fLines
.get(fTopLineIndex
+ line
);
1097 if (!lineData
.location
.equals(fSelectedLocation
)) {
1098 fSelectedLocation
= lineData
.location
;
1099 refreshLineBackgrounds();
1100 sendSelectionEvent(lineData
);
1104 storeCaretPosition(event
.time
, event
.caretOffset
);
1105 if (fHoldSelection
== 0) {
1106 Point caret
= fStyledText
.getLocationAtOffset(fStyledText
.getCaretOffset());
1107 Point origin
= fScrolledComposite
.getOrigin();
1108 if (origin
.x
> caret
.x
) {
1110 } else if (caret
.x
- origin
.x
> fScrolledComposite
.getSize().x
) {
1111 origin
.x
= caret
.x
- fScrolledComposite
.getSize().x
+ 1;
1113 fScrolledComposite
.setOrigin(origin
);
1117 // ------------------------------------------------------------------------
1118 // MouseMoveListener (StyledText)
1119 // ------------------------------------------------------------------------
1122 public void mouseMove(MouseEvent e
) {
1123 fCursorYCoordinate
= e
.y
;
1124 if (e
.y
< 0 || e
.y
> fStyledText
.getSize().y
) {
1125 if (fHighlightedRank
!= Long
.MIN_VALUE
) {
1126 fHighlightedRank
= Long
.MIN_VALUE
;
1127 refreshLineBackgrounds();
1131 int offset
= fStyledText
.getOffsetAtLocation(new Point(0, e
.y
));
1132 int line
= fStyledText
.getLineAtOffset(offset
);
1133 if (line
< fLines
.size() - fTopLineIndex
) {
1134 LineData lineData
= fLines
.get(fTopLineIndex
+ line
);
1135 if (fHighlightedRank
!= lineData
.rank
) {
1136 fHighlightedRank
= lineData
.rank
;
1137 refreshLineBackgrounds();
1140 if (fHighlightedRank
!= Long
.MIN_VALUE
) {
1141 fHighlightedRank
= Long
.MIN_VALUE
;
1142 refreshLineBackgrounds();
1147 // ------------------------------------------------------------------------
1148 // MouseTrackListener (StyledText)
1149 // ------------------------------------------------------------------------
1152 public void mouseExit(MouseEvent e
) {
1153 fCursorYCoordinate
= -1;
1154 if (fHighlightedRank
!= Long
.MIN_VALUE
) {
1155 fHighlightedRank
= Long
.MIN_VALUE
;
1156 refreshLineBackgrounds();
1161 public void mouseEnter(MouseEvent e
) {
1162 fCursorYCoordinate
= e
.y
;
1166 public void mouseHover(MouseEvent e
) {
1169 // ------------------------------------------------------------------------
1170 // MouseWheelListener (StyledText)
1171 // ------------------------------------------------------------------------
1174 public void mouseScrolled(MouseEvent e
) {
1175 if (fLines
.size() == 0) {
1179 fTopLineIndex
-= e
.count
;
1183 updateHighlightedRank();
1184 fSlider
.setSelection((int) (SLIDER_MAX
* fTrace
.getLocationRatio(fLines
.get(fTopLineIndex
).location
)));