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 }