p2izing the editor
[taxeditor.git] / eclipseprojects / eu.etaxonomy.taxeditor / src / eu / etaxonomy / taxeditor / editor / name / RulerWithIcon.java
1 /**
2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9
10 package eu.etaxonomy.taxeditor.editor.name;
11
12 import java.util.Iterator;
13
14 import org.apache.log4j.Logger;
15 import org.eclipse.jface.text.BadLocationException;
16 import org.eclipse.jface.text.IDocument;
17 import org.eclipse.jface.text.IRegion;
18 import org.eclipse.jface.text.ITextListener;
19 import org.eclipse.jface.text.ITextViewer;
20 import org.eclipse.jface.text.ITextViewerExtension5;
21 import org.eclipse.jface.text.IViewportListener;
22 import org.eclipse.jface.text.JFaceTextUtil;
23 import org.eclipse.jface.text.Position;
24 import org.eclipse.jface.text.Region;
25 import org.eclipse.jface.text.TextEvent;
26 import org.eclipse.jface.text.source.Annotation;
27 import org.eclipse.jface.text.source.IAnnotationAccess;
28 import org.eclipse.jface.text.source.IAnnotationAccessExtension;
29 import org.eclipse.jface.text.source.IAnnotationModel;
30 import org.eclipse.jface.text.source.IAnnotationModelListener;
31 import org.eclipse.jface.text.source.IAnnotationPresentation;
32 import org.eclipse.jface.text.source.IVerticalRuler;
33 import org.eclipse.jface.text.source.IVerticalRulerExtension;
34 import org.eclipse.swt.SWT;
35 import org.eclipse.swt.custom.StyledText;
36 import org.eclipse.swt.events.DisposeEvent;
37 import org.eclipse.swt.events.DisposeListener;
38 import org.eclipse.swt.events.MouseEvent;
39 import org.eclipse.swt.events.MouseListener;
40 import org.eclipse.swt.events.PaintEvent;
41 import org.eclipse.swt.events.PaintListener;
42 import org.eclipse.swt.graphics.Font;
43 import org.eclipse.swt.graphics.GC;
44 import org.eclipse.swt.graphics.Image;
45 import org.eclipse.swt.graphics.Point;
46 import org.eclipse.swt.graphics.Rectangle;
47 import org.eclipse.swt.widgets.Canvas;
48 import org.eclipse.swt.widgets.Composite;
49 import org.eclipse.swt.widgets.Control;
50 import org.eclipse.swt.widgets.Display;
51 import org.eclipse.ui.forms.widgets.TableWrapData;
52
53 /**
54 * 99% of the code in this class was copied from org.eclipse.jface.text.source.VerticalRuler,
55 * which allows neither access to its paint methods nor subclassing to do same.
56 * <p>
57 * Changes made in method doubleBufferPaint(doubleBufferPaint(GC dest)).
58 *
59 * @see org.eclipse.jface.text.source.VerticalRuler
60 *
61 * @author p.ciardelli
62 * @created 27.01.2009
63 * @version 1.0
64 */
65 public class RulerWithIcon implements IVerticalRuler, IVerticalRulerExtension {
66 private static final Logger logger = Logger.getLogger(RulerWithIcon.class);
67
68 /**
69 * Internal listener class.
70 */
71 class InternalListener implements IViewportListener, IAnnotationModelListener, ITextListener {
72
73 /*
74 * @see IViewportListener#viewportChanged(int)
75 */
76 public void viewportChanged(int verticalPosition) {
77 if (verticalPosition != fScrollPos)
78 redraw();
79 }
80
81 /*
82 * @see IAnnotationModelListener#modelChanged(IAnnotationModel)
83 */
84 public void modelChanged(IAnnotationModel model) {
85 update();
86 }
87
88 /*
89 * @see ITextListener#textChanged(TextEvent)
90 */
91 public void textChanged(TextEvent e) {
92 if (fTextViewer != null && e.getViewerRedrawState())
93 redraw();
94 }
95 }
96
97 /** The vertical ruler's text viewer */
98 private ITextViewer fTextViewer;
99 /** The ruler's canvas */
100 private Canvas fCanvas;
101 /** The vertical ruler's model */
102 private IAnnotationModel fModel;
103 /** Cache for the actual scroll position in pixels */
104 private int fScrollPos;
105 /** The buffer for double buffering */
106 private Image fBuffer;
107 /** The line of the last mouse button activity */
108 private int fLastMouseButtonActivityLine= -1;
109 /** The internal listener */
110 private InternalListener fInternalListener= new InternalListener();
111 /** The width of this vertical ruler */
112 private int fWidth;
113 /**
114 * The annotation access of this vertical ruler
115 * @since 3.0
116 */
117 private IAnnotationAccess fAnnotationAccess;
118 private Image icon;
119
120 /**
121 * Constructs a vertical ruler with the given width.
122 *
123 * @param width the width of the vertical ruler
124 */
125 public RulerWithIcon(int width) {
126 this(width, null);
127 }
128
129 /**
130 * Constructs a vertical ruler with the given width and the given annotation
131 * access.
132 *
133 * @param width the width of the vertical ruler
134 * @param annotationAcccess the annotation access
135 * @since 3.0
136 */
137 public RulerWithIcon(int width, IAnnotationAccess annotationAcccess) {
138 fWidth= width;
139 fAnnotationAccess= annotationAcccess;
140 }
141
142 /*
143 * @see IVerticalRuler#getControl()
144 */
145 public Control getControl() {
146 return fCanvas;
147 }
148
149 /*
150 * @see IVerticalRuler#createControl(Composite, ITextViewer)
151 */
152 public Control createControl(Composite parent, ITextViewer textViewer) {
153
154 fTextViewer= textViewer;
155
156 fCanvas= new Canvas(parent, SWT.NO_BACKGROUND);
157
158 TableWrapData layout = new TableWrapData(TableWrapData.LEFT);
159 layout.heightHint = 20;
160 layout.maxWidth = fWidth;
161 fCanvas.setLayoutData(layout);
162
163 fCanvas.addPaintListener(new PaintListener() {
164 public void paintControl(PaintEvent event) {
165 if (fTextViewer != null)
166 doubleBufferPaint(event.gc);
167 }
168 });
169
170 fCanvas.addDisposeListener(new DisposeListener() {
171 public void widgetDisposed(DisposeEvent e) {
172 handleDispose();
173 fTextViewer= null;
174 }
175 });
176
177 fCanvas.addMouseListener(new MouseListener() {
178 public void mouseUp(MouseEvent event) {
179 }
180
181 public void mouseDown(MouseEvent event) {
182 fLastMouseButtonActivityLine= toDocumentLineNumber(event.y);
183 }
184
185 public void mouseDoubleClick(MouseEvent event) {
186 fLastMouseButtonActivityLine= toDocumentLineNumber(event.y);
187 }
188 });
189
190 if (fTextViewer != null) {
191 fTextViewer.addViewportListener(fInternalListener);
192 fTextViewer.addTextListener(fInternalListener);
193 }
194
195 return fCanvas;
196 }
197
198 /**
199 * Disposes the ruler's resources.
200 */
201 private void handleDispose() {
202
203 if (fTextViewer != null) {
204 fTextViewer.removeViewportListener(fInternalListener);
205 fTextViewer.removeTextListener(fInternalListener);
206 fTextViewer= null;
207 }
208
209 if (fModel != null)
210 fModel.removeAnnotationModelListener(fInternalListener);
211
212 if (fBuffer != null) {
213 fBuffer.dispose();
214 fBuffer= null;
215 }
216 }
217
218
219 /**
220 * Double buffer drawing.
221 *
222 * @param dest the GC to draw into
223 */
224 private void doubleBufferPaint(GC dest) {
225
226 Point size= fCanvas.getSize();
227
228 if (size.x <= 0 || size.y <= 0)
229 return;
230
231 if (fBuffer != null) {
232 Rectangle r= fBuffer.getBounds();
233 if (r.width != size.x || r.height != size.y) {
234 fBuffer.dispose();
235 fBuffer= null;
236 }
237 }
238 if (fBuffer == null)
239 fBuffer= new Image(fCanvas.getDisplay(), size.x, size.y);
240
241 GC gc= new GC(fBuffer);
242 gc.setFont(fTextViewer.getTextWidget().getFont());
243 try {
244 gc.setBackground(fCanvas.getBackground());
245 gc.fillRectangle(0, 0, size.x, size.y);
246
247 // Code added to VerticalRuler starts here
248 if (icon != null) {
249
250 Rectangle r = icon.getBounds();
251
252 if (r.width > size.x || r.height > size.y) {
253 logger.warn(r.width + "x" + r.height + " icon too big for " + size.x + "x" + size.y + " Canvas.");
254 } else {
255
256 // Set destination coordinates to center icon
257 int x = (size.x - r.width) / 2;
258 int y = (size.y - r.height) / 2;
259
260 gc.drawImage(icon, 0, 0, r.width, r.height, x, y, r.width, r.height);
261 }
262 }
263 // Code added to VerticalRuler ends here
264
265 if (fTextViewer instanceof ITextViewerExtension5)
266 doPaint1(gc);
267 else
268 doPaint(gc);
269
270 } finally {
271 gc.dispose();
272 }
273
274 dest.drawImage(fBuffer, 0, 0);
275 }
276
277 /**
278 * Returns the document offset of the upper left corner of the
279 * widgets view port, possibly including partially visible lines.
280 *
281 * @return the document offset of the upper left corner including partially visible lines
282 * @since 2.0
283 */
284 private int getInclusiveTopIndexStartOffset() {
285
286 StyledText textWidget= fTextViewer.getTextWidget();
287 if (textWidget != null && !textWidget.isDisposed()) {
288 int top= JFaceTextUtil.getPartialTopIndex(fTextViewer);
289 try {
290 IDocument document= fTextViewer.getDocument();
291 return document.getLineOffset(top);
292 } catch (BadLocationException x) {
293 }
294 }
295
296 return -1;
297 }
298
299
300
301 /**
302 * Draws the vertical ruler w/o drawing the Canvas background.
303 *
304 * @param gc the GC to draw into
305 */
306 protected void doPaint(GC gc) {
307
308 if (fModel == null || fTextViewer == null)
309 return;
310
311 IAnnotationAccessExtension annotationAccessExtension= null;
312 if (fAnnotationAccess instanceof IAnnotationAccessExtension)
313 annotationAccessExtension= (IAnnotationAccessExtension) fAnnotationAccess;
314
315 StyledText styledText= fTextViewer.getTextWidget();
316 IDocument doc= fTextViewer.getDocument();
317
318 int topLeft= getInclusiveTopIndexStartOffset();
319 int bottomRight= fTextViewer.getBottomIndexEndOffset();
320 int viewPort= bottomRight - topLeft;
321
322 Point d= fCanvas.getSize();
323 fScrollPos= styledText.getTopPixel();
324
325 int topLine= -1, bottomLine= -1;
326 try {
327 IRegion region= fTextViewer.getVisibleRegion();
328 topLine= doc.getLineOfOffset(region.getOffset());
329 bottomLine= doc.getLineOfOffset(region.getOffset() + region.getLength());
330 } catch (BadLocationException x) {
331 return;
332 }
333
334 // draw Annotations
335 Rectangle r= new Rectangle(0, 0, 0, 0);
336 int maxLayer= 1; // loop at least once though layers.
337
338 for (int layer= 0; layer < maxLayer; layer++) {
339 Iterator iter= fModel.getAnnotationIterator();
340 while (iter.hasNext()) {
341 IAnnotationPresentation annotationPresentation= null;
342 Annotation annotation= (Annotation) iter.next();
343
344 int lay= IAnnotationAccessExtension.DEFAULT_LAYER;
345 if (annotationAccessExtension != null)
346 lay= annotationAccessExtension.getLayer(annotation);
347 else if (annotation instanceof IAnnotationPresentation) {
348 annotationPresentation= (IAnnotationPresentation)annotation;
349 lay= annotationPresentation.getLayer();
350 }
351 maxLayer= Math.max(maxLayer, lay+1); // dynamically update layer maximum
352
353 if (lay != layer) // wrong layer: skip annotation
354 continue;
355
356 Position position= fModel.getPosition(annotation);
357 if (position == null)
358 continue;
359
360 /*
361 * Commented this out because of following sequence:
362 * 1) empty name composite w/ annotation, i.e. "no sec reference"
363 * 2) focus in this composite worked as expected, i.e. annotation drawn in li. 397 below
364 * 3) lose focus, however, and for some reason (adding empty text "Click to add name?"),
365 * composite failed this overlapsWith() test, and annotation not redrawn
366 */
367 // if (!position.overlapsWith(topLeft, viewPort))
368 // continue;
369
370 try {
371
372 int offset= position.getOffset();
373 int length= position.getLength();
374
375 int startLine= doc.getLineOfOffset(offset);
376 if (startLine < topLine)
377 startLine= topLine;
378
379 int endLine= startLine;
380 if (length > 0)
381 endLine= doc.getLineOfOffset(offset + length - 1);
382 if (endLine > bottomLine)
383 endLine= bottomLine;
384
385 startLine -= topLine;
386 endLine -= topLine;
387
388 r.x= 0;
389 r.y= JFaceTextUtil.computeLineHeight(styledText, 0, startLine, startLine) - fScrollPos;
390
391 r.width= d.x;
392 int lines= endLine - startLine;
393
394 r.height= JFaceTextUtil.computeLineHeight(styledText, startLine, endLine + 1, (lines+1));
395
396 if (r.y < d.y && annotationAccessExtension != null) // annotation within visible area
397 annotationAccessExtension.paint(annotation, gc, fCanvas, r);
398 else if (annotationPresentation != null)
399 annotationPresentation.paint(gc, fCanvas, r);
400
401 } catch (BadLocationException e) {
402 }
403 }
404 }
405 }
406
407 /**
408 * Draws the vertical ruler w/o drawing the Canvas background. Uses
409 * <code>ITextViewerExtension5</code> for its implementation. Will replace
410 * <code>doPaint(GC)</code>.
411 *
412 * @param gc the GC to draw into
413 */
414 protected void doPaint1(GC gc) {
415
416 if (fModel == null || fTextViewer == null)
417 return;
418
419 IAnnotationAccessExtension annotationAccessExtension= null;
420 if (fAnnotationAccess instanceof IAnnotationAccessExtension)
421 annotationAccessExtension= (IAnnotationAccessExtension) fAnnotationAccess;
422
423 ITextViewerExtension5 extension= (ITextViewerExtension5) fTextViewer;
424 StyledText textWidget= fTextViewer.getTextWidget();
425
426 fScrollPos= textWidget.getTopPixel();
427 Point dimension= fCanvas.getSize();
428
429 // draw Annotations
430 Rectangle r= new Rectangle(0, 0, 0, 0);
431 int maxLayer= 1; // loop at least once through layers.
432
433 for (int layer= 0; layer < maxLayer; layer++) {
434 Iterator iter= fModel.getAnnotationIterator();
435 while (iter.hasNext()) {
436 IAnnotationPresentation annotationPresentation= null;
437 Annotation annotation= (Annotation) iter.next();
438
439 int lay= IAnnotationAccessExtension.DEFAULT_LAYER;
440 if (annotationAccessExtension != null)
441 lay= annotationAccessExtension.getLayer(annotation);
442 else if (annotation instanceof IAnnotationPresentation) {
443 annotationPresentation= (IAnnotationPresentation)annotation;
444 lay= annotationPresentation.getLayer();
445 }
446 maxLayer= Math.max(maxLayer, lay+1); // dynamically update layer maximum
447 if (lay != layer) // wrong layer: skip annotation
448 continue;
449
450 Position position= fModel.getPosition(annotation);
451 if (position == null)
452 continue;
453
454 IRegion widgetRegion= extension.modelRange2WidgetRange(new Region(position.getOffset(), position.getLength()));
455 if (widgetRegion == null)
456 continue;
457
458 int startLine= extension.widgetLineOfWidgetOffset(widgetRegion.getOffset());
459 if (startLine == -1)
460 continue;
461
462 int endLine= extension.widgetLineOfWidgetOffset(widgetRegion.getOffset() + Math.max(widgetRegion.getLength() -1, 0));
463 if (endLine == -1)
464 continue;
465
466 r.x= 0;
467 r.y= JFaceTextUtil.computeLineHeight(textWidget, 0, startLine, startLine) - fScrollPos;
468
469 r.width= dimension.x;
470 int lines= endLine - startLine;
471
472 r.height= JFaceTextUtil.computeLineHeight(textWidget, startLine, endLine + 1, lines+1);
473
474 if (r.y < dimension.y && annotationAccessExtension != null) // annotation within visible area
475 annotationAccessExtension.paint(annotation, gc, fCanvas, r);
476 else if (annotationPresentation != null)
477 annotationPresentation.paint(gc, fCanvas, r);
478 }
479 }
480 }
481
482 /**
483 * Thread-safe implementation.
484 * Can be called from any thread.
485 */
486 /*
487 * @see IVerticalRuler#update()
488 */
489 public void update() {
490 if (fCanvas != null && !fCanvas.isDisposed()) {
491 Display d= fCanvas.getDisplay();
492 if (d != null) {
493 d.asyncExec(new Runnable() {
494 public void run() {
495 redraw();
496 }
497 });
498 }
499 }
500 }
501
502 /**
503 * Redraws the vertical ruler.
504 */
505 private void redraw() {
506 if (fCanvas != null && !fCanvas.isDisposed()) {
507 GC gc= new GC(fCanvas);
508 doubleBufferPaint(gc);
509 gc.dispose();
510 }
511 }
512
513 /*
514 * @see IVerticalRuler#setModel(IAnnotationModel)
515 */
516 public void setModel(IAnnotationModel model) {
517 if (model != fModel) {
518
519 if (fModel != null)
520 fModel.removeAnnotationModelListener(fInternalListener);
521
522 fModel= model;
523
524 if (fModel != null)
525 fModel.addAnnotationModelListener(fInternalListener);
526
527 update();
528 }
529 }
530
531 /*
532 * @see IVerticalRuler#getModel()
533 */
534 public IAnnotationModel getModel() {
535 return fModel;
536 }
537
538 /*
539 * @see IVerticalRulerInfo#getWidth()
540 */
541 public int getWidth() {
542 return fWidth;
543 }
544
545 /*
546 * @see IVerticalRulerInfo#getLineOfLastMouseButtonActivity()
547 */
548 public int getLineOfLastMouseButtonActivity() {
549 return fLastMouseButtonActivityLine;
550 }
551
552 /*
553 * @see IVerticalRulerInfo#toDocumentLineNumber(int)
554 */
555 public int toDocumentLineNumber(int y_coordinate) {
556 if (fTextViewer == null || y_coordinate == -1)
557 return -1;
558
559 StyledText text= fTextViewer.getTextWidget();
560 int line= text.getLineIndex(y_coordinate);
561
562 if (line == text.getLineCount() - 1) {
563 // check whether y_coordinate exceeds last line
564 if (y_coordinate > text.getLinePixel(line + 1))
565 return -1;
566 }
567
568 return widgetLine2ModelLine(fTextViewer, line);
569 }
570
571 /**
572 * Returns the line of the viewer's document that corresponds to the given widget line.
573 *
574 * @param viewer the viewer
575 * @param widgetLine the widget line
576 * @return the corresponding line of the viewer's document
577 * @since 2.1
578 */
579 protected final static int widgetLine2ModelLine(ITextViewer viewer, int widgetLine) {
580
581 if (viewer instanceof ITextViewerExtension5) {
582 ITextViewerExtension5 extension= (ITextViewerExtension5) viewer;
583 return extension.widgetLine2ModelLine(widgetLine);
584 }
585
586 try {
587 IRegion r= viewer.getVisibleRegion();
588 IDocument d= viewer.getDocument();
589 return widgetLine += d.getLineOfOffset(r.getOffset());
590 } catch (BadLocationException x) {
591 }
592 return widgetLine;
593 }
594
595 /*
596 * @see IVerticalRulerExtension#setFont(Font)
597 * @since 2.0
598 */
599 public void setFont(Font font) {
600 }
601
602 /*
603 * @see IVerticalRulerExtension#setLocationOfLastMouseButtonActivity(int, int)
604 * @since 2.0
605 */
606 public void setLocationOfLastMouseButtonActivity(int x, int y) {
607 fLastMouseButtonActivityLine= toDocumentLineNumber(y);
608 }
609
610 /**
611 * Adds the given mouse listener.
612 *
613 * @param listener the listener to be added
614 * @deprecated will be removed
615 * @since 2.0
616 */
617 public void addMouseListener(MouseListener listener) {
618 if (fCanvas != null && !fCanvas.isDisposed())
619 fCanvas.addMouseListener(listener);
620 }
621
622 /**
623 * Removes the given mouse listener.
624 *
625 * @param listener the listener to be removed
626 * @deprecated will be removed
627 * @since 2.0
628 */
629 public void removeMouseListener(MouseListener listener) {
630 if (fCanvas != null && !fCanvas.isDisposed())
631 fCanvas.removeMouseListener(listener);
632 }
633
634 public void setIcon(Image icon) {
635 this.icon = icon;
636 }
637 }
638