/* * Copyright (c) 2012, 2016 Eike Stepper (Berlin, Germany) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Eike Stepper - initial API and implementation */ package org.eclipse.emf.cdo.internal.ui.history; import org.eclipse.emf.cdo.CDOObject; import org.eclipse.emf.cdo.common.branch.CDOBranchPoint; import org.eclipse.emf.cdo.common.commit.CDOCommitHistory.TriggerLoadElement; import org.eclipse.emf.cdo.common.commit.CDOCommitInfo; import org.eclipse.emf.cdo.common.id.CDOID; import org.eclipse.emf.cdo.internal.ui.bundle.OM; import org.eclipse.emf.cdo.session.CDOSession; import org.eclipse.emf.cdo.transaction.CDOTransactionCommentator; import org.eclipse.emf.cdo.ui.widgets.CommitHistoryComposite.Input; import org.eclipse.emf.cdo.ui.widgets.CommitHistoryComposite.LabelProvider; import org.eclipse.jface.resource.ResourceManager; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTException; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableItem; /** * @author Eike Stepper */ public final class NetRenderer implements Listener { private static final int ROUND_EDGE = 3; private static final int ARC_SIZE = 8; private static final int TRACK_OFFSET = 4; private static final int TRACK_WIDTH = 14; private static final int LINE_WIDTH = 2; private static final int UNKNOWN = -1; private final Color colorDotFill; private final Color colorDotOutline; private final Color colorBadgeFill; private final Color colorBadgeOutline; private Net net; private GC gc; private boolean antiAliasing = true; private int dotSize = UNKNOWN; private int dotSizeHalf; private int textHeight = UNKNOWN; private int cellHeight = UNKNOWN; private int cellHeightHalf; private int cellX; private int cellY; private Color cellForeground; private Color cellBackground; private LabelProvider labelProvider; public NetRenderer(TableViewer tableViewer) { labelProvider = (LabelProvider)tableViewer.getLabelProvider(); ResourceManager resourceManager = labelProvider.getResourceManager(); colorDotFill = resourceManager.createColor(new RGB(220, 220, 220)); colorDotOutline = resourceManager.createColor(new RGB(110, 110, 110)); colorBadgeFill = resourceManager.createColor(new RGB(188, 220, 188)); colorBadgeOutline = resourceManager.createColor(new RGB(0, 128, 0)); Table table = tableViewer.getTable(); table.addListener(SWT.MeasureItem, this); table.addListener(SWT.PaintItem, this); table.addListener(SWT.EraseItem, this); } public Net getNet() { return net; } public void addCommit(CDOCommitInfo commitInfo) { if (net != null && !(commitInfo instanceof TriggerLoadElement)) { net.addCommit(commitInfo); } } public void setInput(Input input) { if (input != null) { CDOSession session = input.getSession(); CDOObject object = input.getObject(); CDOID objectID = object == null ? null : object.cdoID(); ResourceManager resourceManager = labelProvider.getResourceManager(); net = new Net(session, objectID, resourceManager); } else { net = null; } } public void handleEvent(Event event) { switch (event.type) { case SWT.MeasureItem: event.width = handlePaintEvent(event, true) + TRACK_OFFSET; break; case SWT.PaintItem: handlePaintEvent(event, false); break; case SWT.EraseItem: event.detail &= ~SWT.FOREGROUND; break; } } public int handlePaintEvent(Event event, boolean justMeasureWidth) { try { gc = event.gc; if (antiAliasing) { try { gc.setAntialias(SWT.ON); } catch (SWTException ex) { antiAliasing = false; } } if (textHeight == UNKNOWN) { textHeight = gc.stringExtent("/").y; //$NON-NLS-1$ cellHeight = event.height; cellHeightHalf = cellHeight / 2; dotSize = (int)(Math.min(cellHeight, TRACK_WIDTH) * 0.5f); dotSize += dotSize & 1; dotSizeHalf = dotSize / 2; } cellX = event.x; cellY = event.y; cellForeground = gc.getForeground(); cellBackground = gc.getBackground(); CDOCommitInfo commitInfo = (CDOCommitInfo)((TableItem)event.item).getData(); return drawCell(commitInfo, event.index, justMeasureWidth); } catch (Throwable ex) { if (!net.isHideExceptions()) { OM.LOG.error(ex); net.hideExceptions(); } return 0; } } private int drawCell(CDOCommitInfo commitInfo, int columnIndex, boolean justMeasureWidth) { int x = TRACK_OFFSET; if (columnIndex == 1) { if (!(commitInfo instanceof TriggerLoadElement)) { x += drawCommit(commitInfo, justMeasureWidth); } } else { Image image = labelProvider.getColumnImage(commitInfo, columnIndex); if (image != null) { Rectangle bounds = image.getBounds(); x += 2; if (!justMeasureWidth) { gc.drawImage(image, bounds.x, bounds.y, bounds.width, bounds.height, // cellX + x - TRACK_OFFSET, cellY + 1, bounds.width, bounds.height); } x += bounds.width; } } String text = labelProvider.getColumnText(commitInfo, columnIndex); if (columnIndex == 1) { CDOBranchPoint mergeSource = commitInfo.getMergeSource(); if (mergeSource != null && !text.startsWith(CDOTransactionCommentator.MERGE_PREFIX)) { StringBuilder builder = new StringBuilder(); CDOTransactionCommentator.appendMerge(builder, mergeSource); if (text.length() != 0) { builder.append(", "); builder.append(text); } text = builder.toString(); } } int width = drawText(text, x, cellHeightHalf, false, justMeasureWidth); x += width; if (commitInfo instanceof TriggerLoadElement) { if (width != 0) { width += 4 * TRACK_OFFSET; } if (!justMeasureWidth) { int y = cellHeightHalf + 1; int x2 = gc.getClipping().width; drawLine(width, y, x2, y, colorDotOutline); } } return x; } private int drawCommit(CDOCommitInfo commitInfo, boolean justMeasureWidth) { Commit commit = net.getOrAddCommit(commitInfo); Branch commitBranch = commit.getBranch(); Segment[] segments = commit.getRowSegments(); Segment commitSegment = commit.getSegment(); long commitTime = commit.getTime(); boolean commitLastInBranch = commitTime == commitBranch.getLastCommitTime(); if (!justMeasureWidth) { Track commitTrack = commitSegment.getTrack(); Color commitColor = commitBranch.getColor(); int commitTrackPosition = commitTrack.getPosition(); int commitTrackCenter = getTrackCenter(commitTrackPosition); for (int i = 0; i < segments.length; i++) { Segment segment = segments[i]; if (segment != null && segment != commitSegment) { Branch branch = segment.getBranch(); boolean merge = segment.isMerge(); Color color = branch.getColor(); int lineStyle = merge ? SWT.LINE_DOT : SWT.LINE_SOLID; int trackCenter = getTrackCenter(i); int positionDelta = Math.abs(i - commitTrackPosition); int horizontal = (positionDelta - 1) * TRACK_WIDTH + 6 + ROUND_EDGE; if (i < commitTrackPosition) { horizontal = -horizontal; } if (merge ? commitTime == segment.getLastCommitTime() || commitTime == segment.getFirstCommitTime() : commitTime == segment.getFirstVisualTime() && segment.isComplete()) { // Plot horizontal line to left or right and a round edge up or down. boolean down = merge && commitTime == segment.getLastCommitTime(); drawHorizontalLineWithRoundEdge(color, lineStyle, commitTrackCenter, trackCenter, horizontal, down); } else { Commit mergeSource = commit.getMergeSource(); if (mergeSource != null && mergeSource == segment.getMergeSource()) { drawHorizontalLineWithRoundEdge(color, lineStyle, commitTrackCenter, trackCenter, horizontal, true); } // Draw full vertical line. drawLine(trackCenter, 0, trackCenter, cellHeight, color, lineStyle); } } } if (!commitLastInBranch) { // Draw half vertical line up (solid). drawLine(commitTrackCenter, 0, commitTrackCenter, cellHeightHalf, commitColor); } else if (commit.getMergeTargets() != null) { Segment mergeSegment = commit.getMergeSegment(); if (mergeSegment != null && mergeSegment.getTrack() == commitTrack) { // Draw half vertical line up (dotted). drawLine(commitTrackCenter, 0, commitTrackCenter, cellHeightHalf, commitColor, SWT.LINE_DOT); } } if (commitTime > commitSegment.getFirstVisualTime() || !commitSegment.isComplete()) { if (!commitInfo.isInitialCommit()) { // Draw half vertical line down (solid). drawLine(commitTrackCenter, cellHeightHalf, commitTrackCenter, cellHeight, commitColor); } } // Draw center dot. int dotX = commitTrackCenter - dotSizeHalf - 1; int dotY = cellHeightHalf - dotSizeHalf; drawDot(dotX, dotY, dotSize, dotSize); } int x = getTrackX(segments.length); if (commitLastInBranch) { String branchLabel = labelProvider.getBranchString(commit.getBranch().getCDOBranch()); x += TRACK_OFFSET; x += drawText(branchLabel, x + TRACK_OFFSET, cellHeightHalf, true, justMeasureWidth); } return x; } private void drawHorizontalLineWithRoundEdge(Color color, int lineStyle, int commitTrackCenter, int trackCenter, int horizontal, boolean down) { LinePlotter plotter = new LinePlotter(color, lineStyle, commitTrackCenter, cellHeightHalf); plotter.relative(horizontal, 0); plotter.absolute(trackCenter, down ? cellHeight - ROUND_EDGE : ROUND_EDGE); plotter.relative(0, down ? ROUND_EDGE : -ROUND_EDGE); } private void drawLine(int x1, int y1, int x2, int y2, Color color) { drawLine(x1, y1, x2, y2, color, SWT.LINE_SOLID); } private void drawLine(int x1, int y1, int x2, int y2, Color color, int lineStyle) { gc.setForeground(color); gc.setLineWidth(LINE_WIDTH); gc.setLineStyle(lineStyle); gc.drawLine(cellX + x1, cellY + y1, cellX + x2, cellY + y2); } private void drawDot(final int x, final int y, final int w, final int h) { int dotX = cellX + x + 2; int dotY = cellY + y + 1; int dotW = w - 2; int dotH = h - 2; gc.setBackground(colorDotFill); gc.fillOval(dotX, dotY, dotW, dotH); gc.setForeground(colorDotOutline); gc.setLineWidth(2); gc.setLineStyle(SWT.LINE_SOLID); gc.drawOval(dotX, dotY, dotW, dotH); } private int drawText(String msg, int x, int y, boolean badge, boolean justMeasureWidth) { Point extent = gc.textExtent(msg); int width = extent.x; if (badge) { width += 2 * TRACK_OFFSET; } if (!justMeasureWidth) { x += cellX; y = (y * 2 - extent.y) / 2 + cellY - 1; int height = extent.y + 1; if (badge) { gc.setBackground(colorBadgeFill); gc.fillRoundRectangle(x + 2, y + 2, width - 3, height - 3, ARC_SIZE, ARC_SIZE); gc.setLineWidth(1); gc.setLineStyle(SWT.LINE_SOLID); gc.setForeground(colorBadgeOutline); gc.drawRoundRectangle(x, y, width, height, ARC_SIZE, ARC_SIZE); } else { gc.setBackground(cellBackground); } gc.setForeground(cellForeground); gc.drawString(msg, x + TRACK_OFFSET + 1, y + 1, true); } return width; } private int getTrackX(int position) { return TRACK_OFFSET + TRACK_WIDTH * position; } private int getTrackCenter(int position) { return getTrackX(position) + TRACK_WIDTH / 2; } /** * @author Eike Stepper */ private final class LinePlotter { private final Color color; private final int lineStyle; private int x; private int y; public LinePlotter(Color color, int lineStyle, int x, int y) { this.color = color; this.lineStyle = lineStyle; this.x = x; this.y = y; } public void relative(int width, int height) { int fromX = x; int fromY = y; x += width; y += height; drawLine(fromX, fromY, x, y, color, lineStyle); } public void absolute(int x, int y) { drawLine(this.x, this.y, x, y, color, lineStyle); this.x = x; this.y = y; } } }