1 /*
2 * Copyright (C) 2008, 2014 Shawn O. Pearce <spearce@spearce.org>
3 * and other copyright owners as documented in the project's IP log.
4 *
5 * This program and the accompanying materials are made available
6 * under the terms of the Eclipse Distribution License v1.0 which
7 * accompanies this distribution, is reproduced below, and is
8 * available at http://www.eclipse.org/org/documents/edl-v10.php
9 *
10 * All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or
13 * without modification, are permitted provided that the following
14 * conditions are met:
15 *
16 * - Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 *
19 * - Redistributions in binary form must reproduce the above
20 * copyright notice, this list of conditions and the following
21 * disclaimer in the documentation and/or other materials provided
22 * with the distribution.
23 *
24 * - Neither the name of the Eclipse Foundation, Inc. nor the
25 * names of its contributors may be used to endorse or promote
26 * products derived from this software without specific prior
27 * written permission.
28 *
29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42 */
43
44 package org.eclipse.jgit.revplot;
45
46 import org.eclipse.jgit.lib.Ref;
47 import org.eclipse.jgit.revwalk.RevFlag;
48
49 /**
50 * Basic commit graph renderer for graphical user interfaces.
51 * <p>
52 * Lanes are drawn as columns left-to-right in the graph, and the commit short
53 * message is drawn to the right of the lane lines for this cell. It is assumed
54 * that the commits are being drawn as rows of some sort of table.
55 * <p>
56 * Client applications can subclass this implementation to provide the necessary
57 * drawing primitives required to display a commit graph. Most of the graph
58 * layout is handled by this class, allowing applications to implement only a
59 * handful of primitive stubs.
60 * <p>
61 * This class is suitable for us within an AWT TableCellRenderer or within a SWT
62 * PaintListener registered on a Table instance. It is meant to rubber stamp the
63 * graphics necessary for one row of a plotted commit list.
64 * <p>
65 * Subclasses should call {@link #paintCommit(PlotCommit, int)} after they have
66 * otherwise configured their instance to draw one commit into the current
67 * location.
68 * <p>
69 * All drawing methods assume the coordinate space for the current commit's cell
70 * starts at (upper left corner is) 0,0. If this is not true (like say in SWT)
71 * the implementation must perform the cell offset computations within the
72 * various draw methods.
73 *
74 * @param <TLane>
75 * type of lane being used by the application.
76 * @param <TColor>
77 * type of color object used by the graphics library.
78 */
79 public abstract class AbstractPlotRenderer<TLane extends PlotLane, TColor> {
80 private static final int LANE_WIDTH = 14;
81
82 private static final int LINE_WIDTH = 2;
83
84 private static final int LEFT_PAD = 2;
85
86 /**
87 * Paint one commit using the underlying graphics library.
88 *
89 * @param commit
90 * the commit to render in this cell. Must not be null.
91 * @param h
92 * total height (in pixels) of this cell.
93 */
94 @SuppressWarnings("unchecked")
95 protected void paintCommit(PlotCommit<TLane> commit, int h) {
96 final int dotSize = computeDotSize(h);
97 final TLane myLane = commit.getLane();
98 final int myLaneX = laneC(myLane);
99 final TColor myColor = laneColor(myLane);
100
101 int maxCenter = myLaneX;
102 for (TLane passingLane : (TLane[]) commit.passingLanes) {
103 final int cx = laneC(passingLane);
104 final TColor c = laneColor(passingLane);
105 drawLine(c, cx, 0, cx, h, LINE_WIDTH);
106 maxCenter = Math.max(maxCenter, cx);
107 }
108
109 final int dotX = myLaneX - dotSize / 2 - 1;
110 final int dotY = (h - dotSize) / 2;
111
112 final int nParent = commit.getParentCount();
113 if (nParent > 0) {
114 drawLine(myColor, myLaneX, h, myLaneX, (h + dotSize) / 2,
115 LINE_WIDTH);
116
117 for (PlotLane mergingLane : commit.mergingLanes) {
118 final TLane pLane = (TLane) mergingLane;
119 final TColor pColor = laneColor(pLane);
120 final int cx = laneC(pLane);
121 if (Math.abs(myLaneX - cx) > LANE_WIDTH) {
122 final int ix;
123 if (myLaneX < cx) {
124 ix = cx - LANE_WIDTH / 2;
125 } else {
126 ix = cx + LANE_WIDTH / 2;
127 }
128
129 drawLine(pColor, myLaneX, h / 2, ix, h / 2, LINE_WIDTH);
130 drawLine(pColor, ix, h / 2, cx, h, LINE_WIDTH);
131 } else
132 drawLine(pColor, myLaneX, h / 2, cx, h, LINE_WIDTH);
133 maxCenter = Math.max(maxCenter, cx);
134 }
135 }
136
137
138 if (commit.getChildCount() > 0) {
139 for (PlotLane forkingOffLane : commit.forkingOffLanes) {
140 final TLane childLane = (TLane) forkingOffLane;
141 final TColor cColor = laneColor(childLane);
142 final int cx = laneC(childLane);
143 if (Math.abs(myLaneX - cx) > LANE_WIDTH) {
144 final int ix;
145 if (myLaneX < cx) {
146 ix = cx - LANE_WIDTH / 2;
147 } else {
148 ix = cx + LANE_WIDTH / 2;
149 }
150
151 drawLine(cColor, myLaneX, h / 2, ix, h / 2, LINE_WIDTH);
152 drawLine(cColor, ix, h / 2, cx, 0, LINE_WIDTH);
153 } else {
154 drawLine(cColor, myLaneX, h / 2, cx, 0, LINE_WIDTH);
155 }
156 maxCenter = Math.max(maxCenter, cx);
157 }
158
159 int nonForkingChildren = commit.getChildCount()
160 - commit.forkingOffLanes.length;
161 if (nonForkingChildren > 0)
162 drawLine(myColor, myLaneX, 0, myLaneX, dotY, LINE_WIDTH);
163 }
164
165 if (commit.has(RevFlag.UNINTERESTING))
166 drawBoundaryDot(dotX, dotY, dotSize, dotSize);
167 else
168 drawCommitDot(dotX, dotY, dotSize, dotSize);
169
170 int textx = Math.max(maxCenter + LANE_WIDTH / 2, dotX + dotSize) + 8;
171 int n = commit.refs.length;
172 for (int i = 0; i < n; ++i) {
173 textx += drawLabel(textx + dotSize, h/2, commit.refs[i]);
174 }
175
176 final String msg = commit.getShortMessage();
177 drawText(msg, textx + dotSize, h);
178 }
179
180 /**
181 * Draw a decoration for the Ref ref at x,y
182 *
183 * @param x
184 * left
185 * @param y
186 * top
187 * @param ref
188 * A peeled ref
189 * @return width of label in pixels
190 */
191 protected abstract int drawLabel(int x, int y, Ref ref);
192
193 private static int computeDotSize(int h) {
194 int d = (int) (Math.min(h, LANE_WIDTH) * 0.50f);
195 d += (d & 1);
196 return d;
197 }
198
199 /**
200 * Obtain the color reference used to paint this lane.
201 * <p>
202 * Colors returned by this method will be passed to the other drawing
203 * primitives, so the color returned should be application specific.
204 * <p>
205 * If a null lane is supplied the return value must still be acceptable to a
206 * drawing method. Usually this means the implementation should return a
207 * default color.
208 *
209 * @param myLane
210 * the current lane. May be null.
211 * @return graphics specific color reference. Must be a valid color.
212 */
213 protected abstract TColor laneColor(TLane myLane);
214
215 /**
216 * Draw a single line within this cell.
217 *
218 * @param color
219 * the color to use while drawing the line.
220 * @param x1
221 * starting X coordinate, 0 based.
222 * @param y1
223 * starting Y coordinate, 0 based.
224 * @param x2
225 * ending X coordinate, 0 based.
226 * @param y2
227 * ending Y coordinate, 0 based.
228 * @param width
229 * number of pixels wide for the line. Always at least 1.
230 */
231 protected abstract void drawLine(TColor color, int x1, int y1, int x2,
232 int y2, int width);
233
234 /**
235 * Draw a single commit dot.
236 * <p>
237 * Usually the commit dot is a filled oval in blue, then a drawn oval in
238 * black, using the same coordinates for both operations.
239 *
240 * @param x
241 * upper left of the oval's bounding box.
242 * @param y
243 * upper left of the oval's bounding box.
244 * @param w
245 * width of the oval's bounding box.
246 * @param h
247 * height of the oval's bounding box.
248 */
249 protected abstract void drawCommitDot(int x, int y, int w, int h);
250
251 /**
252 * Draw a single boundary commit (aka uninteresting commit) dot.
253 * <p>
254 * Usually a boundary commit dot is a light gray oval with a white center.
255 *
256 * @param x
257 * upper left of the oval's bounding box.
258 * @param y
259 * upper left of the oval's bounding box.
260 * @param w
261 * width of the oval's bounding box.
262 * @param h
263 * height of the oval's bounding box.
264 */
265 protected abstract void drawBoundaryDot(int x, int y, int w, int h);
266
267 /**
268 * Draw a single line of text.
269 * <p>
270 * The font and colors used to render the text are left up to the
271 * implementation.
272 *
273 * @param msg
274 * the text to draw. Does not contain LFs.
275 * @param x
276 * first pixel from the left that the text can be drawn at.
277 * Character data must not appear before this position.
278 * @param y
279 * pixel coordinate of the baseline of the text. Implementations
280 * must adjust this coordinate to account for the way their
281 * implementation handles font rendering.
282 */
283 protected abstract void drawText(String msg, int x, int y);
284
285 private static int laneX(PlotLane myLane) {
286 final int p = myLane != null ? myLane.getPosition() : 0;
287 return LEFT_PAD + LANE_WIDTH * p;
288 }
289
290 private static int laneC(PlotLane myLane) {
291 return laneX(myLane) + LANE_WIDTH / 2;
292 }
293 }