blob: fbf4108508b268824130a06447be2f7461d4d197 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 Ericsson AB.
* 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:
* Ericsson - initial API and implementation
*******************************************************************************/
package org.eclipse.egerrit.internal.ui.compare;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.CompareEditorInput;
import org.eclipse.compare.CompareViewerSwitchingPane;
import org.eclipse.compare.structuremergeviewer.DiffNode;
import org.eclipse.compare.structuremergeviewer.DiffTreeViewer;
import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.core.databinding.beans.IBeanValueProperty;
import org.eclipse.core.databinding.beans.PojoProperties;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.map.IObservableMap;
import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
import org.eclipse.core.databinding.property.Properties;
import org.eclipse.core.databinding.property.value.IValueProperty;
import org.eclipse.egerrit.internal.model.FileInfo;
import org.eclipse.egerrit.internal.model.ModelPackage;
import org.eclipse.egerrit.internal.model.RevisionInfo;
import org.eclipse.egerrit.internal.ui.EGerritImages;
import org.eclipse.egerrit.internal.ui.editors.QueryHelpers;
import org.eclipse.egerrit.internal.ui.table.model.ITableModel;
import org.eclipse.egerrit.internal.ui.table.model.ReviewTableSorter;
import org.eclipse.egerrit.internal.ui.table.provider.DynamicMenuBuilder;
import org.eclipse.egerrit.internal.ui.table.provider.FileInfoCompareCellLabelProvider;
import org.eclipse.egerrit.internal.ui.utils.Messages;
import org.eclipse.egerrit.internal.ui.utils.PersistentStorage;
import org.eclipse.emf.databinding.EMFProperties;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.databinding.viewers.ObservableListTreeContentProvider;
import org.eclipse.jface.databinding.viewers.TreeStructureAdvisor;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.menus.CommandContributionItem;
import org.eclipse.ui.menus.CommandContributionItemParameter;
import org.eclipse.ui.services.IServiceLocator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class represents the upper section of the compare editor. This is where we are showing the list of files that
* are part of the patchset.
*/
public class CompareUpperSection extends CompareViewerSwitchingPane {
private static Logger logger = LoggerFactory.getLogger(CompareUpperSection.class);
private static final String COMPARE_FILES = "egerrit.CompareFiles"; //$NON-NLS-1$
private PersistentStorage persistStorage;
private GerritMultipleInput compareInput;
private DiffTreeViewer viewer;
private Label leftPatch;
private Label rightPatch;
private DynamicMenuBuilder dynamicMenu = new DynamicMenuBuilder();
private Composite headerComposite;
CompareUpperSection(Composite parent, int style, boolean visibility, GerritMultipleInput cei) {
super(parent, style, visibility);
compareInput = cei;
createViewer(this);
}
private CompareConfiguration getCompareConfiguration() {
return compareInput.getCompareConfiguration();
}
@Override
protected Viewer getViewer(Viewer oldViewer, Object input) {
viewer.setInput(input);
revealFile();
return viewer;
}
@Override
public void setInput(Object input) {
super.setInput(input);
viewer.setInput(input);
revealFile();
updateLabels();
}
private void updateLabels() {
leftPatch.setText(
GerritCompareHelper.resolveShortName(compareInput.getChangeInfo(), compareInput.getLeftSide()));
rightPatch.setText(
GerritCompareHelper.resolveShortName(compareInput.getChangeInfo(), compareInput.getRightSide()));
headerComposite.layout();
compareInput.setTitle(compareInput.getTitle());
}
private void revealFile() {
if (compareInput.nodeToReveal != null) {
StructuredSelection selection = new StructuredSelection(compareInput.nodeToReveal);
viewer.setSelection(selection, true);
callFeed1(selection);
} else {
//When there is no file to reveal, we force the input to null to hide what was there
callFeed1(null);
}
}
private void callFeed1(StructuredSelection nodeToReveal) {
Class<CompareEditorInput> clazz = CompareEditorInput.class;
Method feed1Method;
try {
feed1Method = clazz.getDeclaredMethod("feed1", ISelection.class); //$NON-NLS-1$
feed1Method.setAccessible(true);
feed1Method.invoke(compareInput, nodeToReveal);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException
| SecurityException e) {
logger.debug("Exception revealing the element selected by the user", nodeToReveal); //$NON-NLS-1$
}
}
private void createViewer(Composite parent) {
viewer = new DiffTreeViewer(new Tree(parent, SWT.FULL_SELECTION), getCompareConfiguration()) {
@Override
protected void createToolItems(ToolBarManager toolbarManager) {
IServiceLocator serviceLocator = PlatformUI.getWorkbench();
CommandContributionItemParameter showReviewEditorContributionParameter = new CommandContributionItemParameter(
serviceLocator, null, "org.eclipse.egerrit.internal.ui.compare.showReviewEditor", //$NON-NLS-1$
CommandContributionItem.STYLE_PUSH);
showReviewEditorContributionParameter.icon = EGerritImages
.getDescriptor(EGerritImages.SHOW_REVIEW_EDITOR_IMAGE);
toolbarManager.appendToGroup("merge", //$NON-NLS-1$
new CommandContributionItem(showReviewEditorContributionParameter));
CommandContributionItemParameter replyContributionParameter = new CommandContributionItemParameter(
serviceLocator, null, "org.eclipse.egerrit.internal.ui.compare.reply", //$NON-NLS-1$
CommandContributionItem.STYLE_PUSH);
replyContributionParameter.icon = EGerritImages.getDescriptor(EGerritImages.REPLY);
toolbarManager.appendToGroup("merge", new CommandContributionItem(replyContributionParameter)); //$NON-NLS-1$
toolbarManager.appendToGroup("modes", new ShowCommentedFileAction(() -> viewer)); //$NON-NLS-1$
CommandContributionItemParameter showFileContributionParameter = new CommandContributionItemParameter(
serviceLocator, null, "org.eclipse.egerrit.internal.ui.compare.showFilePath", //$NON-NLS-1$
CommandContributionItem.STYLE_CHECK);
showFileContributionParameter.icon = EGerritImages.getDescriptor(EGerritImages.TOGGLE_FILEPATH);
toolbarManager.appendToGroup("modes", new CommandContributionItem(showFileContributionParameter)); //$NON-NLS-1$
CommandContributionItemParameter nextContributionParameter = new CommandContributionItemParameter(
serviceLocator, null, "org.eclipse.egerrit.internal.ui.compare.selectNextFile", //$NON-NLS-1$
CommandContributionItem.STYLE_PUSH);
nextContributionParameter.icon = EGerritImages.getDescriptor(EGerritImages.DOWN_ARROW);
toolbarManager.appendToGroup("navigation", new CommandContributionItem(nextContributionParameter)); //$NON-NLS-1$
CommandContributionItemParameter previousContributionParameter = new CommandContributionItemParameter(
serviceLocator, null, "org.eclipse.egerrit.internal.ui.compare.selectPreviousFile", //$NON-NLS-1$
CommandContributionItem.STYLE_PUSH);
previousContributionParameter.icon = EGerritImages.getDescriptor(EGerritImages.UP_ARROW);
toolbarManager.appendToGroup("navigation", new CommandContributionItem(previousContributionParameter)); //$NON-NLS-1$
}
};
//Set a content provider
ObservableListTreeContentProvider cp = new ObservableListTreeContentProvider(new TreeFactoryImpl(),
new TreeStructureAdvisorImpl());
viewer.setContentProvider(cp);
//Create the tree columns with the Cell provider
final ITableModel[] tableInfo = CompareUpperSectionColumn.values();
int size = tableInfo.length;
for (int index = 0; index < size; index++) {
TreeViewerColumn val = createTreeViewerColumn(tableInfo[index]);
dynamicMenu.getTreeViewerColumn().put(tableInfo[index], val);
}
viewer.getTree().setHeaderVisible(true);
viewer.getTree().setLinesVisible(true);
//Add a mouse listener to toggle the review state
if (!compareInput.gerritClient.getRepository().getServerInfo().isAnonymous()) {
viewer.getTree().addMouseListener(toggleReviewedStateListener());
}
// Set the content sorter
ReviewTableSorter.bind(viewer);
viewer.setComparator(new ReviewTableSorter(2)); // sort by File Path, descending. This way we are sorted like in the files tab
//The reviewed flag is always watched from the gerritDiffNode#fileInfo object.
IValueProperty leftReviewedFlag = PojoProperties.value("fileInfo") //$NON-NLS-1$
.value(EMFProperties.value(ModelPackage.Literals.FILE_INFO__REVIEWED));
IBeanValueProperty leftFileInfo = PojoProperties.value("left").value("fileInfo"); //$NON-NLS-1$ //$NON-NLS-2$
IValueProperty leftComments = leftFileInfo
.value(EMFProperties.value(ModelPackage.Literals.FILE_INFO__COMMENTS_COUNT));
IValueProperty leftDraftComments = leftFileInfo
.value(EMFProperties.value(ModelPackage.Literals.FILE_INFO__DRAFTS_COUNT));
IBeanValueProperty rightFileInfo = PojoProperties.value("right").value("fileInfo"); //$NON-NLS-1$ //$NON-NLS-2$
IValueProperty rightComments = rightFileInfo
.value(EMFProperties.value(ModelPackage.Literals.FILE_INFO__COMMENTS_COUNT));
IValueProperty rightDraftComments = rightFileInfo
.value(EMFProperties.value(ModelPackage.Literals.FILE_INFO__DRAFTS_COUNT));
final IObservableMap[] watchedProperties = Properties.observeEach(cp.getKnownElements(), new IValueProperty[] {
leftReviewedFlag, leftComments, leftDraftComments, rightComments, rightDraftComments });
FileInfoCompareCellLabelProvider labelProvider = new FileInfoCompareCellLabelProvider(watchedProperties);
viewer.setLabelProvider(labelProvider);
dynamicMenu.addPulldownMenu(viewer, compareInput.gerritClient, true);
persistStorage = new PersistentStorage(viewer, COMPARE_FILES);
persistStorage.restoreDialogSettings();
}
private void fillMenuItemForChangeInfo(MenuManager menu, boolean side) {
ArrayList<RevisionInfo> revisions = new ArrayList<>(compareInput.getChangeInfo().getRevisions().values());
revisions.sort((o1, o2) -> o2.get_number() - o1.get_number());
revisions.stream().forEach(rev -> menu.add(new SwitchPatchAction(compareInput, rev, side)));
}
@Override
//This method is responsible for creating the drop downs for where the user can select the patchset
protected Control createTopLeft(Composite p) {
headerComposite = new Composite(p, SWT.NONE) {
@Override
//Limit the height of the toolbar
public Point computeSize(int wHint, int hHint, boolean changed) {
return super.computeSize(wHint, Math.max(24, hHint), changed);
}
};
headerComposite.setLayout(new GridLayout(6, false));
Label compareWord = new Label(headerComposite, SWT.LEFT);
compareWord.setText(Messages.CompareUpperSection_0);
createLeftDropDown(headerComposite);
Label separator = new Label(headerComposite, SWT.LEFT);
separator.setText(Messages.CompareUpperSection_1);
createRightDropDown(headerComposite);
return headerComposite;
}
//Setup the text and button to select the right revision
private void createRightDropDown(final Composite composite) {
rightPatch = new Label(composite, SWT.LEFT);
GridData rightPatchData = new GridData(SWT.LEFT, SWT.CENTER, false, false);
rightPatch.setLayoutData(rightPatchData);
Button rightPatchSelector = new Button(composite, SWT.DROP_DOWN | SWT.ARROW | SWT.DOWN);
GridData gridDataRightPatchSelector = new GridData(SWT.LEFT, SWT.CENTER, false, false);
gridDataRightPatchSelector.heightHint = 20;
rightPatchSelector.setLayoutData(gridDataRightPatchSelector);
rightPatchSelector.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
MenuManager mgr = new MenuManager();
fillMenuItemForChangeInfo(mgr, false);
mgr.add(new SwitchPatchAction(compareInput, GerritMultipleInput.WORKSPACE, false));
mgr.add(new SwitchPatchAction(compareInput, GerritMultipleInput.BASE, false));
mgr.createContextMenu(composite).setVisible(true);
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
//Nothing to do
}
});
}
//Setup the text and button to select the left revision
private void createLeftDropDown(final Composite composite) {
leftPatch = new Label(composite, SWT.LEFT);
GridData gridData = new GridData(SWT.LEFT, SWT.CENTER, false, false);
leftPatch.setLayoutData(gridData);
Button leftPatchSelector = new Button(composite, SWT.ARROW | SWT.DOWN);
GridData gridDataLeftPathSelector = new GridData(SWT.LEFT, SWT.CENTER, false, false);
gridDataLeftPathSelector.heightHint = 20;
leftPatchSelector.setLayoutData(gridDataLeftPathSelector);
leftPatchSelector.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
MenuManager mgr = new MenuManager();
fillMenuItemForChangeInfo(mgr, true);
mgr.add(new SwitchPatchAction(compareInput, GerritMultipleInput.WORKSPACE, true));
mgr.add(new SwitchPatchAction(compareInput, GerritMultipleInput.BASE, true));
mgr.createContextMenu(composite).setVisible(true);
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
//Nothing to do
}
});
}
@Override
protected boolean inputChanged(Object input) {
return getInput() != input;
}
@Override
public void setText(String label) {
//Do nothing
}
@Override
public void setImage(Image image) {
Composite c = (Composite) getTopLeft();
Control[] children = c.getChildren();
for (Control element : children) {
if (element instanceof CLabel) {
CLabel cl = (CLabel) element;
if (!cl.isDisposed()) {
cl.setImage(image);
}
return;
}
}
}
@Override
public void addMouseListener(MouseListener listener) {
Composite c = (Composite) getTopLeft();
Control[] children = c.getChildren();
for (Control element : children) {
if (element instanceof CLabel) {
CLabel cl = (CLabel) element;
cl.addMouseListener(listener);
}
}
}
/**
* This method is the listener to toggle a file's reviewed state
*
* @return MouseAdapter
*/
private MouseAdapter toggleReviewedStateListener() {
return new MouseAdapter() {
@Override
public void mouseDown(MouseEvent e) {
ViewerCell viewerCell = viewer.getCell(new Point(e.x, e.y));
if (viewerCell != null && viewerCell.getColumnIndex() == 0) {
//Selected the first column, so we can send the delete option
//Otherwise, do not delete
ISelection selection = viewer.getSelection();
if (selection instanceof IStructuredSelection) {
IStructuredSelection structuredSelection = (IStructuredSelection) selection;
Object element = structuredSelection.getFirstElement();
FileInfo fileInfo = ((GerritDiffNode) element).getFileInfo();
toggleReviewed(fileInfo);
}
}
}
};
}
private void toggleReviewed(FileInfo fileInfo) {
CompletableFuture.runAsync(() -> {
if (fileInfo.isReviewed()) {
QueryHelpers.markAsNotReviewed(compareInput.gerritClient, fileInfo);
} else {
QueryHelpers.markAsReviewed(compareInput.gerritClient, fileInfo);
}
});
}
private static class TreeFactoryImpl implements IObservableFactory {
public IObservableList<?> createObservable(final Object target) {
return BeanProperties.list("children").observe(target); //$NON-NLS-1$
}
}
private static class TreeStructureAdvisorImpl extends TreeStructureAdvisor {
@Override
public Object getParent(Object element) {
if (element instanceof DiffNode) {
return ((DiffNode) element).getParent();
}
return null;
}
@Override
public Boolean hasChildren(Object element) {
if (element instanceof DiffNode && (((DiffNode) element).getChildren().length > 0)) {
return Boolean.TRUE;
}
return super.hasChildren(element);
}
}
/**
* Create each column in the review tree list
*
* @param CompareUpperSectionColumn
* @return TreeViewerColumn
*/
private TreeViewerColumn createTreeViewerColumn(ITableModel tableInfo) {
TreeViewerColumn treeColumViewer = new TreeViewerColumn(viewer, SWT.NONE);
final TreeColumn column = treeColumViewer.getColumn();
column.setText(tableInfo.getName());
column.setWidth(tableInfo.getWidth());
column.setAlignment(tableInfo.getAlignment());
column.setResizable(tableInfo.getResize());
column.setMoveable(tableInfo.getMoveable());
column.addControlListener(new ControlListener() {
@Override
public void controlResized(ControlEvent e) {
persistStorage.storeDialogSettings();
}
@Override
public void controlMoved(ControlEvent e) {
}
});
return treeColumViewer;
}
public DiffTreeViewer getDiffTreeViewer() {
return viewer;
}
}