comparison dwtx/jface/text/source/AbstractRulerColumn.d @ 129:eb30df5ca28b

Added JFace Text sources
author Frank Benoit <benoit@tionex.de>
date Sat, 23 Aug 2008 19:10:48 +0200
parents
children c4fb132a086c
comparison
equal deleted inserted replaced
128:8df1d4193877 129:eb30df5ca28b
1 /*******************************************************************************
2 * Copyright (c) 2006, 2007 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 * Port to the D programming language:
11 * Frank Benoit <benoit@tionex.de>
12 *******************************************************************************/
13 module dwtx.jface.text.source.AbstractRulerColumn;
14
15 import dwt.dwthelper.utils;
16
17
18
19
20
21 import dwt.DWT;
22 import dwt.custom.StyledText;
23 import dwt.events.MouseEvent;
24 import dwt.events.MouseListener;
25 import dwt.events.MouseMoveListener;
26 import dwt.events.PaintEvent;
27 import dwt.events.PaintListener;
28 import dwt.graphics.Color;
29 import dwt.graphics.Font;
30 import dwt.graphics.GC;
31 import dwt.widgets.Canvas;
32 import dwt.widgets.Composite;
33 import dwt.widgets.Control;
34 import dwt.widgets.Display;
35 import dwtx.core.runtime.Assert;
36 import dwtx.jface.resource.JFaceResources;
37 import dwtx.jface.text.ITextListener;
38 import dwtx.jface.text.ITextViewer;
39 import dwtx.jface.text.IViewportListener;
40 import dwtx.jface.text.JFaceTextUtil;
41 import dwtx.jface.text.TextEvent;
42
43
44 /**
45 * Abstract implementation of a {@link IVerticalRulerColumn} that
46 * uses a {@link Canvas} to draw the ruler contents and which
47 * handles scrolling and mouse selection.
48 *
49 * <h3>Painting</h3>
50 * Subclasses can hook into the paint loop at three levels:
51 * <ul>
52 * <li>Override <strong>{@link #paint(GC, ILineRange)}</strong> to control the entire painting of
53 * the ruler.</li>
54 * <li>Override <strong>{@link #paintLine(GC, int, int, int, int)}</strong> to control the
55 * painting of a line.</li>
56 * <li>Leave the painting to the default implementation, but override <strong>{@link #computeBackground(int)}</strong>,
57 * <strong>{@link #computeForeground(int)}</strong> and <strong>{@link #computeText(int)}</strong>
58 * to specify the ruler appearance for a line.</li>
59 * </ul>
60 *
61 * <h3>Invalidation</h3>
62 * Subclasses may call {@link #redraw()} to mark the entire ruler as needing to be redrawn.
63 * Alternatively, use {@link #redraw(ILineRange)} to only invalidate a certain line range, for
64 * example due to changes to the display model.
65 *
66 * <h3>Configuration</h3>
67 * Subclasses can set the following properties. Setting them may trigger redrawing.
68 * <ul>
69 * <li>The {@link #setFont(Font) font} used to draw text in {@link #paintLine(GC, int, int, int, int)}.</li>
70 * <li>The horizontal {@link #setTextInset(int) text inset} for text drawn.</li>
71 * <li>The {@link #setDefaultBackground(Color) default background color} of the ruler.</li>
72 * <li>The {@link #setWidth(int) width} of the ruler.</li>
73 * </ul>
74 *
75 * @since 3.3
76 */
77 public abstract class AbstractRulerColumn : IVerticalRulerColumn, IVerticalRulerInfo, IVerticalRulerInfoExtension {
78 private static final int DEFAULT_WIDTH= 12;
79 private static final int DEFAULT_TEXT_INSET= 2;
80
81 /**
82 * Handles all the mouse interaction in this line number ruler column.
83 */
84 private final class MouseHandler : MouseListener, MouseMoveListener {
85
86 /*
87 * @see dwt.events.MouseListener#mouseUp(dwt.events.MouseEvent)
88 */
89 public void mouseUp(MouseEvent event) {
90 }
91
92 /*
93 * @see dwt.events.MouseListener#mouseDown(dwt.events.MouseEvent)
94 */
95 public void mouseDown(MouseEvent event) {
96 fParentRuler.setLocationOfLastMouseButtonActivity(event.x, event.y);
97 }
98
99 /*
100 * @see dwt.events.MouseListener#mouseDoubleClick(dwt.events.MouseEvent)
101 */
102 public void mouseDoubleClick(MouseEvent event) {
103 fParentRuler.setLocationOfLastMouseButtonActivity(event.x, event.y);
104 }
105
106 /*
107 * @see dwt.events.MouseMoveListener#mouseMove(dwt.events.MouseEvent)
108 */
109 public void mouseMove(MouseEvent event) {
110 fParentRuler.setLocationOfLastMouseButtonActivity(event.x, event.y);
111 }
112 }
113
114 /**
115 * Internal listener class that updates the ruler upon scrolling and text modifications.
116 */
117 private final class InternalListener : IViewportListener, ITextListener {
118
119 /*
120 * @see IViewportListener#viewportChanged(int)
121 */
122 public void viewportChanged(int topPixel) {
123 int delta= topPixel - fLastTopPixel;
124 if (scrollVertical(delta))
125 fCanvas.update(); // force update the invalidated regions
126 }
127
128 /*
129 * @see ITextListener#textChanged(TextEvent)
130 */
131 public void textChanged(TextEvent event) {
132 /*
133 * Redraw: - when the viewer is drawing, and any of the following - the widget was not
134 * full before the change - the widget is not full after the change - the document event
135 * was a visual modification (no document event attached) - for example when the
136 * projection changes.
137 */
138 if (!event.getViewerRedrawState())
139 return;
140
141 if (fWasShowingEntireContents || event.getDocumentEvent() is null || JFaceTextUtil.isShowingEntireContents(fStyledText))
142 redraw();
143 }
144 }
145
146 /* Listeners */
147
148 /** The viewport listener. */
149 private final InternalListener fInternalListener= new InternalListener();
150 /** The mouse handler. */
151 private final MouseHandler fMouseHandler= new MouseHandler();
152
153 /*
154 * Implementation and context of this ruler - created and set in createControl(), disposed of in
155 * columnRemoved().
156 */
157
158 /** The parent ruler, possibly <code>null</code>. */
159 private CompositeRuler fParentRuler;
160 /** The canvas, the only widget used to draw this ruler, possibly <code>null</code>. */
161 private Canvas fCanvas;
162 /** The text viewer, possibly <code>null</code>. */
163 private ITextViewer fTextViewer;
164 /** The text viewer's widget, possibly <code>null</code>. */
165 private StyledText fStyledText;
166
167 /* State when the canvas was last painted. */
168
169 /** The text widget's top pixel when the ruler was last painted. */
170 private int fLastTopPixel= -1;
171 /** Whether the text widget was showing the entire contents when the ruler was last painted. */
172 private bool fWasShowingEntireContents= false;
173
174 /* Configuration */
175
176 /** The width of this ruler. */
177 private int fWidth= DEFAULT_WIDTH;
178 /** The text inset. */
179 private int fTextInset= DEFAULT_TEXT_INSET;
180 /** The default background color, <code>null</code> to use the text viewer's background color. */
181 private Color fBackground;
182 /** The font, <code>null</code> to use the default font. */
183 private Font fFont;
184 /** The annotation model, possibly <code>null</code>. */
185 private IAnnotationModel fModel;
186 /** The annotation hover, possibly <code>null</code>. */
187 private IAnnotationHover fHover;
188
189 /**
190 * Creates a new ruler.
191 */
192 protected AbstractRulerColumn() {
193 }
194
195 /*
196 * @see dwtx.jface.text.source.IVerticalRulerColumn#createControl(dwtx.jface.text.source.CompositeRuler,
197 * dwt.widgets.Composite)
198 */
199 public Control createControl(CompositeRuler parentRuler, Composite parentControl) {
200 Assert.isLegal(parentControl !is null);
201 Assert.isLegal(parentRuler !is null);
202 Assert.isLegal(fParentRuler is null); // only call when not yet initialized!
203
204 fParentRuler= parentRuler;
205
206 fTextViewer= getParentRuler().getTextViewer();
207 fTextViewer.addViewportListener(fInternalListener);
208 fTextViewer.addTextListener(fInternalListener);
209
210 fStyledText= fTextViewer.getTextWidget();
211
212 fCanvas= new Canvas(parentControl, getCanvasStyle());
213
214 fCanvas.setBackground(getDefaultBackground());
215 fCanvas.setFont(getFont());
216
217 fCanvas.addPaintListener(new PaintListener() {
218 public void paintControl(PaintEvent event) {
219 AbstractRulerColumn.this.paintControl(event);
220 }
221 });
222
223 fCanvas.addMouseListener(fMouseHandler);
224 fCanvas.addMouseMoveListener(fMouseHandler);
225
226 return fCanvas;
227 }
228
229 /**
230 * Returns the DWT style bits used when creating the ruler canvas.
231 * <p>
232 * The default implementation returns <code>DWT.NO_BACKGROUND</code>.</p>
233 * <p>
234 * Clients may reimplement this method to create a canvas with their
235 * desired style bits.</p>
236 *
237 * @return the DWT style bits, or <code>DWT.NONE</code> if none
238 */
239 protected int getCanvasStyle() {
240 return DWT.NO_BACKGROUND;
241 }
242
243 /*
244 * @see dwtx.jface.text.source.IVerticalRulerColumn#getControl()
245 */
246 public final Control getControl() {
247 return fCanvas;
248 }
249
250 /**
251 * The new width in pixels. The <code>DEFAULT_WIDTH</code> constant
252 * specifies the default width.
253 *
254 * @param width the new width
255 */
256 protected final void setWidth(int width) {
257 Assert.isLegal(width >= 0);
258 if (fWidth !is width) {
259 fWidth= width;
260 CompositeRuler composite= getParentRuler();
261 if (composite !is null)
262 composite.relayout();
263 }
264 }
265
266 /*
267 * @see dwtx.jface.text.source.IVerticalRulerColumn#getWidth()
268 */
269 public final int getWidth() {
270 return fWidth;
271 }
272
273 /**
274 * Returns the parent ruler, <code>null</code> before
275 * {@link #createControl(CompositeRuler, Composite)} has been called.
276 *
277 * @return the parent ruler or <code>null</code>
278 */
279 protected final CompositeRuler getParentRuler() {
280 return fParentRuler;
281 }
282
283 /**
284 * {@inheritDoc}
285 *
286 * @param font the font or <code>null</code> to use the default font
287 */
288 public final void setFont(Font font) {
289 if (fFont !is font) {
290 fFont= font;
291 redraw();
292 }
293 }
294
295 /**
296 * Returns the current font. If a font has not been explicitly set, the widget's font is
297 * returned.
298 *
299 * @return the font used to render text on the ruler.
300 */
301 protected final Font getFont() {
302 if (fFont !is null)
303 return fFont;
304 if (fStyledText !is null && !fStyledText.isDisposed())
305 return fStyledText.getFont();
306 return JFaceResources.getTextFont();
307 }
308
309 /**
310 * Sets the text inset (padding) used to draw text in {@link #paintLine(GC, int, int, int, int)}.
311 *
312 * @param textInset the new text inset
313 */
314 protected final void setTextInset(int textInset) {
315 if (textInset !is fTextInset) {
316 fTextInset= textInset;
317 redraw();
318 }
319 }
320
321 /**
322 * Returns the text inset for text drawn by {@link #paintLine(GC, int, int, int, int)}. The
323 * <code>DEFAULT_TEXT_INSET</code> constant specifies the default inset in pixels.
324 *
325 * @return the text inset for text
326 */
327 protected final int getTextInset() {
328 return fTextInset;
329 }
330
331 /*
332 * @see dwtx.jface.text.source.IVerticalRulerColumn#setModel(dwtx.jface.text.source.IAnnotationModel)
333 */
334 public void setModel(IAnnotationModel model) {
335 if (fModel !is model) {
336 fModel= model;
337 redraw();
338 }
339 }
340
341 /*
342 * @see dwtx.jface.text.source.IVerticalRulerInfoExtension#getModel()
343 */
344 public final IAnnotationModel getModel() {
345 return fModel;
346 }
347
348 /**
349 * Sets the default background color for this column. The default background is used as default
350 * implementation of {@link #computeBackground(int)} and also to paint the area of the ruler
351 * that does not correspond to any lines (when the viewport is not entirely filled with lines).
352 *
353 * @param background the default background color, <code>null</code> to use the text widget's
354 * background
355 */
356 protected final void setDefaultBackground(Color background) {
357 if (fBackground !is background) {
358 fBackground= background;
359 if (fCanvas !is null && !fCanvas.isDisposed())
360 fCanvas.setBackground(getDefaultBackground());
361 redraw();
362 }
363 }
364
365 /**
366 * Returns the background color. May return <code>null</code> if the system is shutting down.
367 *
368 * @return the background color
369 */
370 protected final Color getDefaultBackground() {
371 if (fBackground !is null)
372 return fBackground;
373 if (fStyledText !is null && !fStyledText.isDisposed())
374 return fStyledText.getBackground();
375 Display display;
376 if (fCanvas !is null && !fCanvas.isDisposed())
377 display= fCanvas.getDisplay();
378 else
379 display= Display.getCurrent();
380 if (display !is null)
381 return display.getSystemColor(DWT.COLOR_LIST_BACKGROUND);
382 return null;
383 }
384
385 /**
386 * Sets the annotation hover.
387 *
388 * @param hover the annotation hover, <code>null</code> for no hover
389 */
390 protected final void setHover(IAnnotationHover hover) {
391 if (fHover !is hover)
392 fHover= hover;
393 }
394
395 /*
396 * @see dwtx.jface.text.source.IVerticalRulerInfoExtension#getHover()
397 */
398 public IAnnotationHover getHover() {
399 return fHover;
400 }
401
402 /**
403 * Disposes this ruler column.
404 * <p>
405 * Subclasses may extend this method.</p>
406 * <p>
407 * Clients who created this column are responsible to call this method
408 * once the column is no longer used.</p>
409 */
410 public void dispose() {
411 if (fTextViewer !is null) {
412 fTextViewer.removeViewportListener(fInternalListener);
413 fTextViewer.removeTextListener(fInternalListener);
414 fTextViewer= null;
415 }
416
417 if (fStyledText !is null)
418 fStyledText= null;
419
420 if (fCanvas !is null) {
421 fCanvas.dispose();
422 fCanvas= null;
423 }
424 }
425
426 /*
427 * @see dwtx.jface.text.source.IVerticalRulerColumn#redraw()
428 */
429 public final void redraw() {
430 if (fCanvas !is null && !fCanvas.isDisposed())
431 fCanvas.redraw();
432 }
433
434 /**
435 * Marks the region covered by <code>lines</code> as needing to be redrawn.
436 *
437 * @param lines the lines to be redrawn in document coordinates
438 */
439 protected final void redraw(ILineRange lines) {
440 if (fCanvas is null || fCanvas.isDisposed())
441 return;
442 int firstModelLine= lines.getStartLine();
443 int lastModelLine= firstModelLine + lines.getNumberOfLines();
444 int firstWidgetLine= JFaceTextUtil.modelLineToWidgetLine(fTextViewer, firstModelLine);
445 int lastWidgetLine= JFaceTextUtil.modelLineToWidgetLine(fTextViewer, lastModelLine);
446
447 int from= Math.max(0, fStyledText.getLinePixel(firstWidgetLine));
448 // getLinePixel will return the last pixel of the last line if line is lineCount
449 int to= Math.min(fCanvas.getSize().y, fStyledText.getLinePixel(lastWidgetLine + 1));
450 fCanvas.redraw(0, from, fWidth, to - from, false);
451 }
452
453 /**
454 * Paints the ruler column.
455 *
456 * @param event the paint event
457 */
458 private void paintControl(PaintEvent event) {
459 if (fTextViewer is null)
460 return;
461 fWasShowingEntireContents= JFaceTextUtil.isShowingEntireContents(fStyledText);
462 fLastTopPixel= fStyledText.getTopPixel();
463
464 ILineRange lines= computeDirtyWidgetLines(event);
465 GC gc= event.gc;
466 paint(gc, lines);
467
468 if ((fCanvas.getStyle() & DWT.NO_BACKGROUND) !is 0) {
469 // fill empty area below any lines
470 int firstEmpty= Math.max(event.y, fStyledText.getLinePixel(fStyledText.getLineCount()));
471 int lastEmpty= event.y + event.height;
472 if (lastEmpty > firstEmpty) {
473 gc.setBackground(getDefaultBackground());
474 gc.fillRectangle(0, firstEmpty, getWidth(), lastEmpty - firstEmpty);
475 }
476 }
477 }
478
479 /**
480 * Computes the widget lines that need repainting given the clipping region of a paint event.
481 *
482 * @param event the paint event
483 * @return the lines in widget coordinates that need repainting
484 */
485 private ILineRange computeDirtyWidgetLines(PaintEvent event) {
486 int firstLine= fStyledText.getLineIndex(event.y);
487 int lastLine= fStyledText.getLineIndex(event.y + event.height - 1);
488 return new LineRange(firstLine, lastLine - firstLine + 1);
489 }
490
491 /**
492 * Paints the ruler. Note that <code>lines</code> reference widget line indices, and that
493 * <code>lines</code> may not cover the entire viewport, but only the lines that need to be
494 * painted. The lines may not be entirely visible.
495 * <p>
496 * Subclasses may replace or extend. The default implementation calls
497 * {@link #paintLine(GC, int, int, int, int)} for every visible line.
498 * </p>
499 *
500 * @param gc the graphics context to paint on
501 * @param lines the lines to paint in widget coordinates
502 */
503 protected void paint(GC gc, ILineRange lines) {
504 final int firstLine= lines.getStartLine();
505 final int lastLine= firstLine + lines.getNumberOfLines();
506 for (int line= firstLine; line < lastLine; line++) {
507 int modelLine= JFaceTextUtil.widgetLine2ModelLine(fTextViewer, line);
508 if (modelLine is -1)
509 continue;
510 int linePixel= fStyledText.getLinePixel(line);
511 int lineHeight= fStyledText.getLineHeight(fStyledText.getOffsetAtLine(line));
512 paintLine(gc, modelLine, line, linePixel, lineHeight);
513 }
514 }
515
516 /**
517 * Paints the ruler representation of a single line.
518 * <p>
519 * Subclasses may replace or extend. The default implementation draws the text obtained by
520 * {@link #computeText(int)} in the {@link #computeForeground(int) foreground color} and fills
521 * the entire width using the {@link #computeBackground(int) background color}. The text is
522 * drawn {@link #getTextInset()} pixels to the right of the left border.
523 * </p>
524 *
525 * @param gc the graphics context to paint on
526 * @param modelLine the model line (based on document coordinates)
527 * @param widgetLine the line in the text widget corresponding to <code>modelLine</code>
528 * @param linePixel the first y-pixel of the widget line
529 * @param lineHeight the line height in pixels
530 */
531 protected void paintLine(GC gc, int modelLine, int widgetLine, int linePixel, int lineHeight) {
532 gc.setBackground(computeBackground(modelLine));
533 gc.fillRectangle(0, linePixel, getWidth(), lineHeight);
534 String text= computeText(modelLine);
535 if (text !is null) {
536 gc.setForeground(computeForeground(modelLine));
537 gc.drawString(text, getTextInset(), linePixel, true);
538 }
539 }
540
541 /**
542 * Returns the text to be drawn for a certain line by {@link #paintLine(GC, int, int, int, int)},
543 * <code>null</code> for no text. The default implementation returns <code>null</code>.
544 * <p>
545 * Subclasses may replace or extend.
546 * </p>
547 *
548 * @param line the document line number
549 * @return the text to be drawn for the given line, <code>null</code> for no text
550 */
551 protected String computeText(int line) {
552 return null;
553 }
554
555 /**
556 * Returns the background color drawn for a certain line by
557 * {@link #paintLine(GC, int, int, int, int)}. The default implementation returns
558 * {@link #getDefaultBackground()}.
559 * <p>
560 * Subclasses may replace or extend.
561 * </p>
562 *
563 * @param line the document line number
564 * @return the background color for drawn for the given line
565 */
566 protected Color computeBackground(int line) {
567 return getDefaultBackground();
568 }
569
570 /**
571 * Returns the foreground color drawn for a certain line by
572 * {@link #paintLine(GC, int, int, int, int)}. The default implementation returns a
573 * {@link DWT#COLOR_DARK_GRAY} color.
574 * <p>
575 * Subclasses may replace or extend.
576 * </p>
577 *
578 * @param line the document line number
579 * @return the foreground color for drawn for the given line
580 */
581 protected Color computeForeground(int line) {
582 return fStyledText.getDisplay().getSystemColor(DWT.COLOR_DARK_GRAY);
583 }
584
585 /*
586 * @see dwtx.jface.text.source.IVerticalRulerInfo#getLineOfLastMouseButtonActivity()
587 */
588 public final int getLineOfLastMouseButtonActivity() {
589 return getParentRuler().getLineOfLastMouseButtonActivity();
590 }
591
592 /*
593 * @see dwtx.jface.text.source.IVerticalRulerInfo#toDocumentLineNumber(int)
594 */
595 public final int toDocumentLineNumber(int y_coordinate) {
596 return getParentRuler().toDocumentLineNumber(y_coordinate);
597 }
598
599 /*
600 * @see dwtx.jface.text.source.IVerticalRulerInfoExtension#addVerticalRulerListener(dwtx.jface.text.source.IVerticalRulerListener)
601 */
602 public void addVerticalRulerListener(IVerticalRulerListener listener) {
603 throw new UnsupportedOperationException();
604 }
605
606 /*
607 * @see dwtx.jface.text.source.IVerticalRulerInfoExtension#removeVerticalRulerListener(dwtx.jface.text.source.IVerticalRulerListener)
608 */
609 public void removeVerticalRulerListener(IVerticalRulerListener listener) {
610 throw new UnsupportedOperationException();
611 }
612
613 /**
614 * Scrolls the canvas vertically (adapted from
615 * {@linkplain StyledText StyledText.scrollVertical()}).
616 *
617 * @param pixels the number of pixels to scroll (negative to scroll upwards)
618 * @return <code>true</code> if the widget was scrolled, <code>false</code> if the widget
619 * was not scrolled
620 */
621 private bool scrollVertical(int pixels) {
622 if (pixels is 0 || fCanvas is null || fCanvas.isDisposed())
623 return false;
624
625 final int width= getWidth();
626 final int clientAreaHeight= fStyledText.getClientArea().height;
627 final int topMargin= 0;
628 final int leftMargin= 0;
629 final int bottomMargin= 0;
630
631 if (pixels > 0) {
632 // downwards scrolling - content moves upwards
633 int sourceY= topMargin + pixels;
634 int scrollHeight= clientAreaHeight - sourceY - bottomMargin;
635 if (scrollHeight > 0)
636 // scroll recycled area
637 fCanvas.scroll(leftMargin, topMargin, leftMargin, sourceY, width, scrollHeight, true);
638 if (sourceY > scrollHeight) {
639 // redraw in-between area
640 int redrawY= Math.max(0, topMargin + scrollHeight);
641 int redrawHeight= Math.min(clientAreaHeight, pixels - scrollHeight);
642 fCanvas.redraw(leftMargin, redrawY, width, redrawHeight, true);
643 }
644 } else {
645 // upwards scrolling - content moves downwards
646 int destinationY= topMargin - pixels;
647 int scrollHeight= clientAreaHeight - destinationY - bottomMargin;
648 if (scrollHeight > 0)
649 // scroll recycled area
650 fCanvas.scroll(leftMargin, destinationY, leftMargin, topMargin, width, scrollHeight, true);
651 if (destinationY > scrollHeight) {
652 // redraw in-between area
653 int redrawY= Math.max(0, topMargin + scrollHeight);
654 int redrawHeight= Math.min(clientAreaHeight, -pixels - scrollHeight);
655 fCanvas.redraw(leftMargin, redrawY, width, redrawHeight, true);
656 }
657 }
658 return true;
659 }
660 }