View Javadoc

1   /*
2    *  File: RelationRenderer.java 
3    *  Copyright (c) 2004-2008  Peter Kliem (Peter.Kliem@jaret.de)
4    *  A commercial license is available, see http://www.jaret.de.
5    *
6    *  This program is free software; you can redistribute it and/or modify
7    *  it under the terms of the GNU General Public License as published by
8    *  the Free Software Foundation; either version 2 of the License, or
9    *  (at your option) any later version.
10   *
11   *  This program is distributed in the hope that it will be useful,
12   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   *  GNU General Public License for more details.
15   *
16   *  You should have received a copy of the GNU General Public License
17   *  along with this program; if not, write to the Free Software
18   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19   */
20  package de.jaret.util.ui.timebars.swt.renderer;
21  
22  import java.util.ArrayList;
23  import java.util.List;
24  
25  import org.eclipse.swt.SWT;
26  import org.eclipse.swt.graphics.Color;
27  import org.eclipse.swt.graphics.GC;
28  import org.eclipse.swt.graphics.Point;
29  import org.eclipse.swt.graphics.RGB;
30  import org.eclipse.swt.graphics.Rectangle;
31  import org.eclipse.swt.printing.Printer;
32  
33  import de.jaret.util.date.Interval;
34  import de.jaret.util.ui.timebars.TimeBarViewerDelegate;
35  import de.jaret.util.ui.timebars.model.IIntervalRelation;
36  import de.jaret.util.ui.timebars.model.IIntervalRelation.Type;
37  import de.jaret.util.ui.timebars.model.IRelationalInterval;
38  import de.jaret.util.ui.timebars.model.TimeBarRow;
39  
40  /**
41   * Renderer rendering relations between intervals that implement the
42   * IRelationalInterval interface. Does not support vertical orientation.
43   * 
44   * @author kliem
45   * @version $Id: RelationRenderer.java 1073 2010-11-22 21:25:33Z kliem $
46   */
47  public class RelationRenderer extends RendererBase implements IRelationRenderer {
48  	/** default cache size. */
49  	private static final int DEFAULT_CACHE_SIZE = 200;
50  
51  	/** default rgb value for the lines. */
52  	private static final RGB DEFAULT_LINE_COLOR = new RGB(0, 0, 0);
53  	/** default rgb value for the selected lines. */
54  	private static final RGB DEFAULT_SELECTED_COLOR = new RGB(0, 0, 255);
55  	/** default line width for the connection lines. */
56  	private static final int DEFAULT_LINE_WIDTH = 1;
57  	/** default arrow size. */
58  	private static final int DEFAULT_ARROW_SIZE = 5;
59  
60  	/** cache holding the drawn lines. */
61  	private List<Line> _cache;
62  	/** color used for the lines. */
63  	protected Color _lineColor;
64  	/** color used for the lines when selected. */
65  	protected Color _selectedColor;
66  
67  	/** linewidth to use. */
68  	protected int _lineWidth = DEFAULT_LINE_WIDTH;
69  	/** arrow size. */
70  	protected int _arrowSize = DEFAULT_ARROW_SIZE;
71  
72  	/**
73  	 * Create the renderer for a printer.
74  	 * 
75  	 * @param printer
76  	 *            printer device
77  	 */
78  	public RelationRenderer(Printer printer) {
79  		super(printer);
80  	}
81  
82  	/**
83  	 * Create the renderer for use on displays.
84  	 */
85  	public RelationRenderer() {
86  		super(null);
87  	}
88  
89  	/**
90  	 * Iniitlize the used colors for the device of the GC.
91  	 * 
92  	 * @param gc
93  	 *            GC
94  	 */
95  	private void initColors(GC gc) {
96  		if (_lineColor == null) {
97  			_lineColor = new Color(gc.getDevice(), DEFAULT_LINE_COLOR);
98  			_selectedColor = new Color(gc.getDevice(), DEFAULT_SELECTED_COLOR);
99  		}
100 	}
101 
102 	/**
103 	 * {@inheritDoc} Processes all rows that are displayed.
104 	 */
105 	public void renderRelations(TimeBarViewerDelegate delegate, GC gc,
106 			boolean printing) {
107 
108 		if (_lineColor == null) {
109 			initColors(gc);
110 		}
111 		_cache = new ArrayList<Line>(DEFAULT_CACHE_SIZE);
112 
113 		int firstRow = delegate.getFirstRow();
114 
115 		// set the clipping to include only the area of the diagram rect
116 		Rectangle clipSave = gc.getClipping();
117 		Rectangle nc = new Rectangle(delegate.getDiagramRect().x, delegate
118 				.getDiagramRect().y, delegate.getDiagramRect().width, delegate
119 				.getDiagramRect().height);
120 		gc.setClipping(gc.getClipping().intersection(nc));
121 
122 		int upperYBound = delegate.getDiagramRect().y;
123 		int lowerYBound = upperYBound + delegate.getDiagramRect().height;
124 		// when using the clip bounds lines for non drawn relations will be lost
125 		// if (gc.isClipped()) {
126 		// upperYBound = gc.getClipping().y;
127 		// lowerYBound = upperYBound + gc.getClipping().height;
128 		// }
129 
130 		int rowsDisplayed = delegate.getRowsDisplayed();
131 		for (int r = firstRow; r <= firstRow + rowsDisplayed + 1
132 				&& r < delegate.getRowCount(); r++) {
133 			TimeBarRow row = delegate.getRow(r);
134 			int y = delegate.yForRow(row);
135 			int rowHeight = delegate.getTimeBarViewState().getRowHeight(row);
136 			if (y == -1) {
137 				// no coord -> is not displayed
138 				break;
139 			}
140 
141 			// row is drawn if either the beginning or the end is inside the
142 			// clipping rect
143 			// or if the upperBound is inside the row rect (clipping rect is
144 			// inside the row rect
145 			if ((y >= upperYBound && y <= lowerYBound)
146 					|| (y + rowHeight >= upperYBound && y + rowHeight <= lowerYBound)
147 					|| (upperYBound > y && upperYBound < y + rowHeight)) {
148 				drawRow(delegate, gc, delegate.getRow(r), y, printing);
149 			}
150 		}
151 		gc.setClipping(clipSave);
152 
153 	}
154 
155 	/**
156 	 * Proceses a single row.
157 	 * 
158 	 * @param delegate
159 	 *            the delegate
160 	 * @param gc
161 	 *            gc to draw on
162 	 * @param row
163 	 *            row to process
164 	 * @param y
165 	 *            y coordinate of the row
166 	 * @param printing
167 	 *            true when printing
168 	 */
169 	private void drawRow(TimeBarViewerDelegate delegate, GC gc, TimeBarRow row,
170 			int y, boolean printing) {
171 		for (Interval interval : row.getIntervals()) {
172 			// do not process filtered intervals
173 			if (delegate.getIntervalFilter() == null
174 					|| delegate.getIntervalFilter().isInResult(interval)) {
175 				if (interval instanceof IRelationalInterval) {
176 					IRelationalInterval rInterval = (IRelationalInterval) interval;
177 					for (IIntervalRelation relation : rInterval.getRelations()) {
178 						// double check both ends of the relation to be
179 						// unfiltered
180 						if (delegate.getIntervalFilter() == null
181 								|| (delegate.getIntervalFilter().isInResult(
182 										relation.getStartInterval()) && delegate
183 										.getIntervalFilter().isInResult(
184 												relation.getEndInterval()))) {
185 							if (!hasBeenDrawn(relation)) {
186 								drawDependency(delegate, gc, rInterval, y, row,
187 										relation);
188 							}
189 						}
190 					}
191 				}
192 			}
193 		}
194 
195 	}
196     int limitCoord(int coord) {
197     	if (coord<-100) {
198     		coord = -100;
199     	} else if (coord>15000) {
200     		coord = 15000;
201     	}
202     	return coord;
203     }
204     void limitCoord(int[] coords) {
205     	for (int i=0;i<coords.length;i++) {
206     		coords[i] = limitCoord(coords[i]);
207     	}
208     }
209     
210     Rectangle limitCoord(Rectangle rect) {
211     	if (rect.x<-100) {
212     		rect.width = rect.width+rect.x+100;
213     		rect.x= -100;
214     	}
215     	if (rect.width>15000) {
216     		rect.width = 15000;
217     	}
218     	return rect;
219     }
220 
221 	/**
222 	 * Draw a single relation.
223 	 * 
224 	 * @param delegate
225 	 *            the delegate
226 	 * @param gc
227 	 *            the gc to draw on
228 	 * @param rInterval
229 	 *            the interval from which we are coming
230 	 * @param y
231 	 *            y of the row
232 	 * @param row
233 	 *            the row the interval is on
234 	 * @param relation
235 	 *            the relation to draw
236 	 */
237 	private void drawDependency(TimeBarViewerDelegate delegate, GC gc,
238 			IRelationalInterval rInterval, int y, TimeBarRow row,
239 			IIntervalRelation relation) {
240 		int off = scaleX(_arrowSize + _arrowSize / 2);
241 
242 		boolean selected = delegate.getSelectionModel().isSelected(relation);
243 
244 		Color fg = gc.getForeground();
245 		int lineWidth = gc.getLineWidth();
246 		int linejoin = gc.getLineJoin();
247 		gc.setLineJoin(SWT.JOIN_ROUND);
248 
249 		Color color = null;
250 		if (selected) {
251 			color = _selectedColor;
252 		} else {
253 			color = _lineColor;
254 		}
255 		gc.setForeground(color);
256 		gc.setLineWidth(scaleX(_lineWidth));
257 
258 		IRelationalInterval beginTask;
259 		TimeBarRow beginRow;
260 		Point begin;
261 
262 		IRelationalInterval endTask;
263 		TimeBarRow endRow;
264 		Point end;
265 
266 		if (relation.getStartInterval().equals(rInterval)) {
267 			beginTask = rInterval;
268 			beginRow = row;
269 
270 			endTask = relation.getEndInterval();
271 			endRow = getRowForInterval(delegate, endTask);
272 		} else {
273 			endTask = rInterval;
274 			endRow = row;
275 
276 			beginTask = relation.getStartInterval();
277 			beginRow = getRowForInterval(delegate, beginTask);
278 		}
279 
280 		int rheight = delegate.getTimeBarViewState().getRowHeight(beginRow);
281 
282 		if (relation.getType() == IIntervalRelation.Type.END_BEGIN) {
283 			begin = getRightPoint(delegate, beginRow, beginTask);
284 			end = getLeftPoint(delegate, endRow, endTask);
285 			int ydir = end.y > begin.y ? 1 : -1;
286 
287 			int[] points = new int[] { begin.x, begin.y, begin.x + off,
288 					begin.y, begin.x + off, begin.y + (rheight / 2 * ydir),
289 					end.x - off, begin.y + (rheight / 2 * ydir), end.x - off,
290 					end.y, end.x, end.y };
291 			limitCoord(points);
292 			gc.drawPolyline(points);
293 
294 			gc.setForeground(color);
295 			registerLines(relation, points);
296 
297 			drawArrow(gc, begin, end, color, relation.getDirection(), relation
298 					.getType());
299 
300 		} else if (relation.getType() == IIntervalRelation.Type.BEGIN_BEGIN) {
301 			begin = getLeftPoint(delegate, beginRow, beginTask);
302 			end = getLeftPoint(delegate, endRow, endTask);
303 			int ydir = end.y > begin.y ? 1 : -1;
304 
305 			int[] points = new int[] { begin.x, begin.y, begin.x - off,
306 					begin.y, begin.x - off, begin.y + (rheight / 2 * ydir),
307 					end.x - off, begin.y + (rheight / 2 * ydir), end.x - off,
308 					end.y, end.x, end.y };
309 
310 			limitCoord(points);
311 			gc.drawPolyline(points);
312 			registerLines(relation, points);
313 			drawArrow(gc, begin, end, color, relation.getDirection(), relation
314 					.getType());
315 		} else if (relation.getType() == IIntervalRelation.Type.END_END) {
316 			begin = getRightPoint(delegate, beginRow, beginTask);
317 			end = getRightPoint(delegate, endRow, endTask);
318 			int ydir = end.y > begin.y ? 1 : -1;
319 
320 			int[] points = new int[] { begin.x, begin.y, begin.x + off,
321 					begin.y, begin.x + off, begin.y + (rheight / 2 * ydir),
322 					end.x + off, begin.y + (rheight / 2 * ydir), end.x + off,
323 					end.y, end.x, end.y };
324 			limitCoord(points);
325 			gc.drawPolyline(points);
326 			registerLines(relation, points);
327 			drawArrow(gc, begin, end, color, relation.getDirection(), relation
328 					.getType());
329 		} else if (relation.getType() == IIntervalRelation.Type.BEGIN_END) {
330 			begin = getLeftPoint(delegate, beginRow, beginTask);
331 			end = getRightPoint(delegate, endRow, endTask);
332 			int ydir = end.y > begin.y ? 1 : -1;
333 			// unused int xdir = end.x > begin.x ? 1 : -1;
334 
335 			int[] points = new int[] { begin.x, begin.y, begin.x - off,
336 					begin.y, begin.x - off, begin.y + (rheight / 2 * ydir),
337 					end.x + off, begin.y + (rheight / 2 * ydir), end.x + off,
338 					end.y, end.x, end.y };
339 			limitCoord(points);
340 			gc.drawPolyline(points);
341 			registerLines(relation, points);
342 			drawArrow(gc, begin, end, color, relation.getDirection(), relation
343 					.getType());
344 		}
345 		gc.setForeground(fg);
346 		gc.setLineWidth(lineWidth);
347 		gc.setLineJoin(linejoin);
348 	}
349 
350 	/**
351 	 * Calculate the point on the right side of an interval for connection.
352 	 * 
353 	 * @param delegate
354 	 *            the delegate
355 	 * @param row
356 	 *            the row
357 	 * @param interval
358 	 *            the interval
359 	 * @return the point on the right side of the interval to connect the draw
360 	 *         relation to
361 	 */
362 	private Point getRightPoint(TimeBarViewerDelegate delegate, TimeBarRow row,
363 			Interval interval) {
364 		java.awt.Rectangle rect = delegate.getIntervalBounds(row, interval);
365 		return new Point(rect.x + rect.width, rect.y + rect.height / 2);
366 	}
367 
368 	/**
369 	 * Calculate the point on the left side of an interval for connection.
370 	 * 
371 	 * @param delegate
372 	 *            the delegate
373 	 * @param row
374 	 *            the row
375 	 * @param interval
376 	 *            the interval
377 	 * @return the point on the left side of the interval to connect the draw
378 	 *         relation to
379 	 */
380 	private Point getLeftPoint(TimeBarViewerDelegate delegate, TimeBarRow row,
381 			Interval interval) {
382 		java.awt.Rectangle rect = delegate.getIntervalBounds(row, interval);
383 		return new Point(rect.x, rect.y + rect.height / 2);
384 	}
385 
386 	/**
387 	 * Wrapper supplying the row for a given interval.
388 	 * 
389 	 * @param delegate
390 	 *            the delegate
391 	 * @param interval
392 	 *            interval to search the
393 	 * @return row or <code>null</code>
394 	 */
395 	private TimeBarRow getRowForInterval(TimeBarViewerDelegate delegate,
396 			Interval interval) {
397 		return delegate.getModel().getRowForInterval(interval);
398 	}
399 
400 	/**
401 	 * Check whether a relation has already been drawn.
402 	 * 
403 	 * @param relation
404 	 *            relation to check
405 	 * @return <code>true</code> if the relation has already been drawn
406 	 */
407 	private boolean hasBeenDrawn(IIntervalRelation relation) {
408 		if (_cache != null) {
409 			for (Line l : _cache) {
410 				if (l.relation.equals(relation)) {
411 					return true;
412 				}
413 			}
414 		}
415 		return false;
416 	}
417 
418 	/**
419 	 * Register a line for later recognition.
420 	 * 
421 	 * @param relation
422 	 *            the relation
423 	 * @param coords
424 	 *            pairs of xy coords
425 	 */
426 	private void registerLines(IIntervalRelation relation, int[] coords) {
427 		for (int i = 0; i <= coords.length - 4; i += 2) {
428 			_cache.add(new Line(relation, coords[i], coords[i + 1],
429 					coords[i + 2], coords[i + 3]));
430 		}
431 	}
432 
433 	/**
434 	 * Draw begin and/or end arrows for a relation depending on the direction.
435 	 * 
436 	 * @param gc
437 	 *            GC to paint on
438 	 * @param begin
439 	 *            begin point
440 	 * @param end
441 	 *            end point
442 	 * @param color
443 	 *            color to use
444 	 * @param direction
445 	 *            direction
446 	 * @param type
447 	 *            type of the relation used to determine which arrows to draw
448 	 */
449 	private void drawArrow(GC gc, Point begin, Point end, Color color,
450 			IIntervalRelation.Direction direction, Type type) {
451 
452 		if (direction.equals(IIntervalRelation.Direction.BACK)
453 				|| direction.equals(IIntervalRelation.Direction.BI)) {
454 			if (type.equals(Type.END_BEGIN) || type.equals(Type.END_END)) {
455 				drawArrow(gc, begin, false, color);
456 			} else {
457 				drawArrow(gc, begin, true, color);
458 			}
459 		}
460 		if (direction.equals(IIntervalRelation.Direction.FORWARD)
461 				|| direction.equals(IIntervalRelation.Direction.BI)) {
462 			if (type.equals(Type.BEGIN_END) || type.equals(Type.END_END)) {
463 				drawArrow(gc, end, false, color);
464 			} else {
465 				drawArrow(gc, end, true, color);
466 			}
467 		}
468 	}
469 
470 	/**
471 	 * Draw an arrow triangle.
472 	 * 
473 	 * @param gc
474 	 *            GC
475 	 * @param p
476 	 *            Point of the vertex
477 	 * @param leftToRight
478 	 *            <code>true</code> for left to right pointing
479 	 * @param color
480 	 *            color to use
481 	 */
482 	private void drawArrow(GC gc, Point p, boolean leftToRight, Color color) {
483 		Color bg = gc.getBackground();
484 		gc.setBackground(color);
485 		int off = scaleX(_arrowSize);
486 		int[] points;
487 		if (leftToRight) {
488 			int[] pts = { p.x, p.y, p.x - off, p.y - off, p.x - off, p.y + off };
489 			points = pts;
490 		} else {
491 			int[] pts = { p.x, p.y, p.x + off, p.y - off, p.x + off, p.y + off };
492 			points = pts;
493 		}
494 		limitCoord(points);
495 		gc.fillPolygon(points);
496 		gc.setBackground(bg);
497 	}
498 
499 	/**
500 	 * {@inheritDoc}
501 	 */
502 	public List<IIntervalRelation> getRelationsForCoord(int x, int y) {
503 		List<IIntervalRelation> result = new ArrayList<IIntervalRelation>(2);
504 		if (_cache != null) {
505 			for (Line line : _cache) {
506 				if (line.hit(x, y, 2)) {
507 					result.add(line.relation);
508 				}
509 			}
510 		}
511 
512 		return result;
513 	}
514 
515 	/**
516 	 * {@inheritDoc}
517 	 */
518 	public String getTooltip(int x, int y) {
519 		List<IIntervalRelation> result = getRelationsForCoord(x, y);
520 		if (result.size() == 0) {
521 			return null;
522 		} else {
523 			return result.get(0).toString();
524 		}
525 	}
526 
527 	/**
528 	 * {@inheritDoc}
529 	 */
530 	public void dispose() {
531 		_cache = null;
532 		if (_lineColor != null) {
533 			_lineColor.dispose();
534 			_selectedColor.dispose();
535 		}
536 	}
537 
538 	/**
539 	 * {@inheritDoc}
540 	 */
541 	public IRelationRenderer createPrintRenderer(Printer printer) {
542 		return new RelationRenderer(printer);
543 	}
544 
545 	/**
546 	 * Internal line represenation for caching.
547 	 * 
548 	 * @author kliem
549 	 * @version $Id: RelationRenderer.java 1073 2010-11-22 21:25:33Z kliem $
550 	 */
551 	public class Line {
552 		/** relation the line belongs to. */
553 		public IIntervalRelation relation;
554 		/** start x coordinate. */
555 		public int x1;
556 		/** start y coordinate. */
557 		public int y1;
558 		/** end x coordinate. */
559 		public int x2;
560 		/** end y coordinate. */
561 		public int y2;
562 
563 		/**
564 		 * Construct a line.
565 		 * 
566 		 * @param relation
567 		 *            relation the line belongs to
568 		 * @param x1
569 		 *            start x
570 		 * @param y1
571 		 *            start y
572 		 * @param x2
573 		 *            end x
574 		 * @param y2
575 		 *            end y
576 		 */
577 		public Line(IIntervalRelation relation, int x1, int y1, int x2, int y2) {
578 			this.relation = relation;
579 			this.x1 = x1;
580 			this.y1 = y1;
581 			this.x2 = x2;
582 			this.y2 = y2;
583 		}
584 
585 		/**
586 		 * Check whether the line is hit by a certain coordinate.
587 		 * 
588 		 * @param x
589 		 *            x coord
590 		 * @param y
591 		 *            y coord
592 		 * @param tolerance
593 		 *            the max delta
594 		 * @return true if hit
595 		 */
596 		public boolean hit(int x, int y, int tolerance) {
597 			if (x1 == x2) {
598 				if (x1 - tolerance <= x && x <= x1 + tolerance) {
599 					if ((y1 < y2 && y1 - tolerance <= y && y <= y2 + tolerance)
600 							|| (y2 < y1 && y2 - tolerance <= y && y <= y1
601 									+ tolerance)) {
602 						return true;
603 					}
604 				}
605 			} else {
606 				if (y1 == y2) {
607 					if (y1 - tolerance <= y && y <= y1 + tolerance) {
608 						if ((x1 < x2 && x1 - tolerance <= x && x <= x2
609 								+ tolerance)
610 								|| (x2 < x1 && x2 - tolerance <= x && x <= x1
611 										+ tolerance)) {
612 							return true;
613 						}
614 					}
615 				}
616 			}
617 			return false;
618 		}
619 	}
620 
621 	/**
622 	 * Retrieve the current line width.
623 	 * 
624 	 * @return the current line width
625 	 */
626 	public int getLineWidth() {
627 		return _lineWidth;
628 	}
629 
630 	/**
631 	 * Set the line width for the connection lines.
632 	 * 
633 	 * @param lineWidth
634 	 *            line width to set
635 	 */
636 	public void setLineWidth(int lineWidth) {
637 		_lineWidth = lineWidth;
638 	}
639 
640 	/**
641 	 * Retrieve the current size set for the arrows.
642 	 * 
643 	 * @return the arrow size
644 	 */
645 	public int getArrowSize() {
646 		return _arrowSize;
647 	}
648 
649 	/**
650 	 * Set the arrow size.
651 	 * 
652 	 * @param arrowSize
653 	 *            arrow size
654 	 */
655 	public void setArrowSize(int arrowSize) {
656 		_arrowSize = arrowSize;
657 	}
658 
659 }