View Javadoc
1   /*
2    * Copyright (C) 2008, 2014 Shawn O. Pearce <spearce@spearce.org> and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  
11  package org.eclipse.jgit.revplot;
12  
13  import org.eclipse.jgit.lib.Ref;
14  import org.eclipse.jgit.revwalk.RevFlag;
15  
16  /**
17   * Basic commit graph renderer for graphical user interfaces.
18   * <p>
19   * Lanes are drawn as columns left-to-right in the graph, and the commit short
20   * message is drawn to the right of the lane lines for this cell. It is assumed
21   * that the commits are being drawn as rows of some sort of table.
22   * <p>
23   * Client applications can subclass this implementation to provide the necessary
24   * drawing primitives required to display a commit graph. Most of the graph
25   * layout is handled by this class, allowing applications to implement only a
26   * handful of primitive stubs.
27   * <p>
28   * This class is suitable for us within an AWT TableCellRenderer or within a SWT
29   * PaintListener registered on a Table instance. It is meant to rubber stamp the
30   * graphics necessary for one row of a plotted commit list.
31   * <p>
32   * Subclasses should call {@link #paintCommit(PlotCommit, int)} after they have
33   * otherwise configured their instance to draw one commit into the current
34   * location.
35   * <p>
36   * All drawing methods assume the coordinate space for the current commit's cell
37   * starts at (upper left corner is) 0,0. If this is not true (like say in SWT)
38   * the implementation must perform the cell offset computations within the
39   * various draw methods.
40   *
41   * @param <TLane>
42   *            type of lane being used by the application.
43   * @param <TColor>
44   *            type of color object used by the graphics library.
45   */
46  public abstract class AbstractPlotRenderer<TLane extends PlotLane, TColor> {
47  	private static final int LANE_WIDTH = 14;
48  
49  	private static final int LINE_WIDTH = 2;
50  
51  	private static final int LEFT_PAD = 2;
52  
53  	/**
54  	 * Paint one commit using the underlying graphics library.
55  	 *
56  	 * @param commit
57  	 *            the commit to render in this cell. Must not be null.
58  	 * @param h
59  	 *            total height (in pixels) of this cell.
60  	 */
61  	@SuppressWarnings("unchecked")
62  	protected void paintCommit(PlotCommit<TLane> commit, int h) {
63  		final int dotSize = computeDotSize(h);
64  		final TLane myLane = commit.getLane();
65  		final int myLaneX = laneC(myLane);
66  		final TColor myColor = laneColor(myLane);
67  
68  		int maxCenter = myLaneX;
69  		for (TLane passingLane : (TLane[]) commit.passingLanes) {
70  			final int cx = laneC(passingLane);
71  			final TColor c = laneColor(passingLane);
72  			drawLine(c, cx, 0, cx, h, LINE_WIDTH);
73  			maxCenter = Math.max(maxCenter, cx);
74  		}
75  
76  		final int dotX = myLaneX - dotSize / 2 - 1;
77  		final int dotY = (h - dotSize) / 2;
78  
79  		final int nParent = commit.getParentCount();
80  		if (nParent > 0) {
81  			drawLine(myColor, myLaneX, h, myLaneX, (h + dotSize) / 2,
82  					LINE_WIDTH);
83  
84  			for (PlotLane mergingLane : commit.mergingLanes) {
85  				final TLane pLane = (TLane) mergingLane;
86  				final TColor pColor = laneColor(pLane);
87  				final int cx = laneC(pLane);
88  				if (Math.abs(myLaneX - cx) > LANE_WIDTH) {
89  					final int ix;
90  					if (myLaneX < cx) {
91  						ix = cx - LANE_WIDTH / 2;
92  					} else {
93  						ix = cx + LANE_WIDTH / 2;
94  					}
95  
96  					drawLine(pColor, myLaneX, h / 2, ix, h / 2, LINE_WIDTH);
97  					drawLine(pColor, ix, h / 2, cx, h, LINE_WIDTH);
98  				} else
99  					drawLine(pColor, myLaneX, h / 2, cx, h, LINE_WIDTH);
100 				maxCenter = Math.max(maxCenter, cx);
101 			}
102 		}
103 
104 
105 		if (commit.getChildCount() > 0) {
106 			for (PlotLane forkingOffLane : commit.forkingOffLanes) {
107 				final TLane childLane = (TLane) forkingOffLane;
108 				final TColor cColor = laneColor(childLane);
109 				final int cx = laneC(childLane);
110 				if (Math.abs(myLaneX - cx) > LANE_WIDTH) {
111 					final int ix;
112 					if (myLaneX < cx) {
113 						ix = cx - LANE_WIDTH / 2;
114 					} else {
115 						ix = cx + LANE_WIDTH / 2;
116 					}
117 
118 					drawLine(cColor, myLaneX, h / 2, ix, h / 2, LINE_WIDTH);
119 					drawLine(cColor, ix, h / 2, cx, 0, LINE_WIDTH);
120 				} else {
121 					drawLine(cColor, myLaneX, h / 2, cx, 0, LINE_WIDTH);
122 				}
123 				maxCenter = Math.max(maxCenter, cx);
124 			}
125 
126 			int nonForkingChildren = commit.getChildCount()
127 					- commit.forkingOffLanes.length;
128 			if (nonForkingChildren > 0)
129 						drawLine(myColor, myLaneX, 0, myLaneX, dotY, LINE_WIDTH);
130 		}
131 
132 		if (commit.has(RevFlag.UNINTERESTING))
133 			drawBoundaryDot(dotX, dotY, dotSize, dotSize);
134 		else
135 			drawCommitDot(dotX, dotY, dotSize, dotSize);
136 
137 		int textx = Math.max(maxCenter + LANE_WIDTH / 2, dotX + dotSize) + 8;
138 		int n = commit.refs.length;
139 		for (int i = 0; i < n; ++i) {
140 			textx += drawLabel(textx + dotSize, h/2, commit.refs[i]);
141 		}
142 
143 		final String msg = commit.getShortMessage();
144 		drawText(msg, textx + dotSize, h);
145 	}
146 
147 	/**
148 	 * Draw a decoration for the Ref ref at x,y
149 	 *
150 	 * @param x
151 	 *            left
152 	 * @param y
153 	 *            top
154 	 * @param ref
155 	 *            A peeled ref
156 	 * @return width of label in pixels
157 	 */
158 	protected abstract int drawLabel(int x, int y, Ref ref);
159 
160 	private static int computeDotSize(int h) {
161 		int d = (int) (Math.min(h, LANE_WIDTH) * 0.50f);
162 		d += (d & 1);
163 		return d;
164 	}
165 
166 	/**
167 	 * Obtain the color reference used to paint this lane.
168 	 * <p>
169 	 * Colors returned by this method will be passed to the other drawing
170 	 * primitives, so the color returned should be application specific.
171 	 * <p>
172 	 * If a null lane is supplied the return value must still be acceptable to a
173 	 * drawing method. Usually this means the implementation should return a
174 	 * default color.
175 	 *
176 	 * @param myLane
177 	 *            the current lane. May be null.
178 	 * @return graphics specific color reference. Must be a valid color.
179 	 */
180 	protected abstract TColor laneColor(TLane myLane);
181 
182 	/**
183 	 * Draw a single line within this cell.
184 	 *
185 	 * @param color
186 	 *            the color to use while drawing the line.
187 	 * @param x1
188 	 *            starting X coordinate, 0 based.
189 	 * @param y1
190 	 *            starting Y coordinate, 0 based.
191 	 * @param x2
192 	 *            ending X coordinate, 0 based.
193 	 * @param y2
194 	 *            ending Y coordinate, 0 based.
195 	 * @param width
196 	 *            number of pixels wide for the line. Always at least 1.
197 	 */
198 	protected abstract void drawLine(TColor color, int x1, int y1, int x2,
199 			int y2, int width);
200 
201 	/**
202 	 * Draw a single commit dot.
203 	 * <p>
204 	 * Usually the commit dot is a filled oval in blue, then a drawn oval in
205 	 * black, using the same coordinates for both operations.
206 	 *
207 	 * @param x
208 	 *            upper left of the oval's bounding box.
209 	 * @param y
210 	 *            upper left of the oval's bounding box.
211 	 * @param w
212 	 *            width of the oval's bounding box.
213 	 * @param h
214 	 *            height of the oval's bounding box.
215 	 */
216 	protected abstract void drawCommitDot(int x, int y, int w, int h);
217 
218 	/**
219 	 * Draw a single boundary commit (aka uninteresting commit) dot.
220 	 * <p>
221 	 * Usually a boundary commit dot is a light gray oval with a white center.
222 	 *
223 	 * @param x
224 	 *            upper left of the oval's bounding box.
225 	 * @param y
226 	 *            upper left of the oval's bounding box.
227 	 * @param w
228 	 *            width of the oval's bounding box.
229 	 * @param h
230 	 *            height of the oval's bounding box.
231 	 */
232 	protected abstract void drawBoundaryDot(int x, int y, int w, int h);
233 
234 	/**
235 	 * Draw a single line of text.
236 	 * <p>
237 	 * The font and colors used to render the text are left up to the
238 	 * implementation.
239 	 *
240 	 * @param msg
241 	 *            the text to draw. Does not contain LFs.
242 	 * @param x
243 	 *            first pixel from the left that the text can be drawn at.
244 	 *            Character data must not appear before this position.
245 	 * @param y
246 	 *            pixel coordinate of the baseline of the text. Implementations
247 	 *            must adjust this coordinate to account for the way their
248 	 *            implementation handles font rendering.
249 	 */
250 	protected abstract void drawText(String msg, int x, int y);
251 
252 	private static int laneX(PlotLane myLane) {
253 		final int p = myLane != null ? myLane.getPosition() : 0;
254 		return LEFT_PAD + LANE_WIDTH * p;
255 	}
256 
257 	private static int laneC(PlotLane myLane) {
258 		return laneX(myLane) + LANE_WIDTH / 2;
259 	}
260 }