blob: c06143fb8ccb252acf351ad7066dbf613f3f6a81 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2018 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Brock Janiczak <brockj@tpg.com.au> - Bug 182267 "Add Date..." button shouldn't be visible in merge wizard
*******************************************************************************/
package org.eclipse.team.internal.ccvs.ui.tags;
import java.util.*;
import java.util.List;
import org.eclipse.jface.action.*;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.viewers.*;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.*;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.*;
import org.eclipse.team.internal.ccvs.core.CVSTag;
import org.eclipse.team.internal.ccvs.core.ICVSRepositoryLocation;
import org.eclipse.team.internal.ccvs.ui.CVSUIMessages;
import org.eclipse.team.internal.ccvs.ui.CVSUIPlugin;
import org.eclipse.team.internal.ccvs.ui.repo.NewDateTagAction;
import org.eclipse.team.internal.ccvs.ui.repo.RepositoryManager;
import org.eclipse.team.internal.ccvs.ui.tags.TagSourceWorkbenchAdapter.ProjectElementComparator;
import org.eclipse.team.internal.ui.PixelConverter;
import org.eclipse.team.internal.ui.SWTUtils;
import org.eclipse.team.internal.ui.actions.TeamAction;
import org.eclipse.team.internal.ui.dialogs.DialogArea;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.model.WorkbenchContentProvider;
import org.eclipse.ui.model.WorkbenchLabelProvider;
import org.eclipse.ui.part.PageBook;
/**
* A dialog area that displays a list of tags for selection and supports
* filtering of the displayed tags.
*/
public class TagSelectionArea extends DialogArea {
private static int COLUMN_TRIM = "carbon".equals(SWT.getPlatform()) ? 24 : 3; //$NON-NLS-1$
private static int ICON_WIDTH = 40;
/*
* Please see bug 184660
*/
private static final int SAFETY_MARGIN = 50;
/*
* Property constant which identifies the selected tag or
* null if no tag is selected
*/
public static final String SELECTED_TAG = "selectedTag"; //$NON-NLS-1$
/*
* Property constant which indicates that a tag has been selected in such
* a way as to indicate that this is the desired tag (e.g double-click)
*/
public static final String OPEN_SELECTED_TAG = "openSelectedTag"; //$NON-NLS-1$
/*
* Constants used to configure which tags are shown
*/
public static final int INCLUDE_HEAD_TAG = TagSourceWorkbenchAdapter.INCLUDE_HEAD_TAG;
public static final int INCLUDE_BASE_TAG = TagSourceWorkbenchAdapter.INCLUDE_BASE_TAG;
public static final int INCLUDE_BRANCHES = TagSourceWorkbenchAdapter.INCLUDE_BRANCHES;
public static final int INCLUDE_VERSIONS = TagSourceWorkbenchAdapter.INCLUDE_VERSIONS;
public static final int INCLUDE_DATES = TagSourceWorkbenchAdapter.INCLUDE_DATES;
public static final int INCLUDE_ALL_TAGS = TagSourceWorkbenchAdapter.INCLUDE_ALL_TAGS;
private String tagAreaLabel;
private final int includeFlags;
private CVSTag selection;
private String helpContext;
private Text filterText;
private TagSource tagSource;
private final Shell shell;
private TagRefreshButtonArea tagRefreshArea;
private final TagSource.ITagSourceChangeListener listener = source -> {
Shell shell = getShell();
if (!shell.isDisposed()) {
shell.getDisplay().syncExec(() -> refresh());
}
};
private final DisposeListener disposeListener = e -> {
if (tagSource != null)
tagSource.removeListener(listener);
};
private PageBook switcher;
private TreeViewer tagTree;
private TableViewer tagTable;
private boolean treeVisible = true;
private boolean includeFilterInputArea = true;
private String filterPattern = ""; //$NON-NLS-1$
private IRunnableContext context;
public TagSelectionArea(Shell shell, TagSource tagSource, int includeFlags, String helpContext) {
this.shell = shell;
this.includeFlags = includeFlags;
this.helpContext = helpContext;
this.tagSource = tagSource;
setSelection(null);
}
@Override
public void createArea(Composite parent) {
initializeDialogUnits(parent);
Dialog.applyDialogFont(parent);
final PixelConverter converter= new PixelConverter(parent);
// Create a composite for the entire area
Composite composite= new Composite(parent, SWT.NONE);
composite.setLayoutData(SWTUtils.createHVFillGridData());
composite.setLayout(SWTUtils.createGridLayout(1, converter, SWTUtils.MARGINS_NONE));
// Add F1 help
if (helpContext != null) {
PlatformUI.getWorkbench().getHelpSystem().setHelp(composite, helpContext);
}
// Create the tree area and refresh buttons with the possibility to add stuff in between
createTagDisplayArea(composite);
createCustomArea(composite);
createRefreshButtons(composite);
Dialog.applyDialogFont(parent);
updateTagDisplay(true);
}
private void createTagDisplayArea(Composite parent) {
Composite inner = createGrabbingComposite(parent, 1);
if (isIncludeFilterInputArea()) {
createFilterInput(inner);
createWrappingLabel(inner, CVSUIMessages.TagSelectionArea_0, 1);
} else {
createWrappingLabel(inner, NLS.bind(CVSUIMessages.TagSelectionArea_1, new String[] { getTagAreaLabel() }), 1);
}
switcher = new PageBook(inner, SWT.NONE);
GridData gridData = new GridData(GridData.FILL_BOTH);
gridData.heightHint = 0;
gridData.widthHint = 0;
switcher.setLayoutData(gridData);
tagTree = createTree(switcher);
tagTable = createTable(switcher);
}
private void createFilterInput(Composite inner) {
createWrappingLabel(inner, NLS.bind(CVSUIMessages.TagSelectionArea_2, new String[] { getTagAreaLabel() }), 1);
filterText = createText(inner, 1);
filterText.addModifyListener(e -> setFilter(filterText.getText()));
filterText.addKeyListener(new KeyListener() {
@Override
public void keyPressed(KeyEvent e) {
if (e.keyCode == SWT.ARROW_DOWN && e.stateMask == 0) {
tagTable.getControl().setFocus();
}
}
@Override
public void keyReleased(KeyEvent e) {
// Ignore
}
});
}
/**
* Return the label that should be used for the tag area.
* It should not have any trailing punctuations as the tag area
* may position it differently depending on whether the filter
* text input is included in the area.
* @return the tag area label
*/
public String getTagAreaLabel() {
if (tagAreaLabel == null)
tagAreaLabel = CVSUIMessages.TagSelectionArea_3;
return tagAreaLabel;
}
/**
* Set the label that should be used for the tag area.
* It should not have any trailing punctuations as the tag area
* may position it differently depending on whether the filter
* text input is included in the area.
* @param tagAreaLabel the tag area label
*/
public void setTagAreaLabel(String tagAreaLabel) {
this.tagAreaLabel = tagAreaLabel;
}
/**
* Update the tag display to show the tags that match the
* include flags and the filter entered by the user.
*/
protected void updateTagDisplay(boolean firstTime) {
String filter = getFilterString();
if ((filter != null && filter.length() > 0) || isTableOnly()) {
// Show the table and filter it accordingly
try {
switcher.setRedraw(false);
treeVisible = false;
switcher.showPage(tagTable.getControl());
FilteredTagList list = (FilteredTagList)tagTable.getInput();
list.setPattern(filter);
tagTable.refresh();
int maxWidth = getMaxWidth(list.getChildren(null));
if (maxWidth > 0) {
maxWidth = maxWidth + ICON_WIDTH + COLUMN_TRIM + SAFETY_MARGIN; /* space for the tag icon */
tagTable.getTable().getColumn(0).setWidth(maxWidth);
}
if (filterText == null || filter == null || filter.length() == 0) {
setSelection(selection);
} else {
// Only set the top selection if there is a filter from the filter text
// of this area. This is done to avoid selection loops
selectTopElement();
}
} finally {
switcher.setRedraw(true);
}
} else {
// Show the tree
if (!isTreeVisible() || firstTime) {
try {
switcher.setRedraw(false);
treeVisible = true;
switcher.showPage(tagTree.getControl());
tagTree.refresh();
setSelection(selection);
} finally {
switcher.setRedraw(true);
}
}
}
}
private int getMaxWidth(Object[] children) {
PixelConverter converter = new PixelConverter(tagTable.getTable());
int maxWidth = 0;
for (int i = 0; i < children.length; i++) {
Object object = children[i];
if (object instanceof TagElement) {
TagElement tag = (TagElement) object;
int width = tag.getTag().getName().length();
if (width > maxWidth) {
maxWidth = width;
}
}
}
return converter.convertWidthInCharsToPixels(maxWidth);
}
/**
* Return whether only a table should be used
* @return whether only a table should be used
*/
protected boolean isTableOnly() {
return (includeFlags == INCLUDE_VERSIONS) || (includeFlags == INCLUDE_BRANCHES);
}
private String getFilterString() {
return filterPattern;
}
/*
* Select the top element in the tag table
*/
private void selectTopElement() {
if (tagTable.getTable().getItemCount() > 0) {
TableItem item = tagTable.getTable().getItem(0);
tagTable.getTable().setSelection(new TableItem[] { item });
tagTable.setSelection(tagTable.getSelection());
}
}
private FilteredTagList createFilteredInput() {
return new FilteredTagList(tagSource, TagSource.convertIncludeFlaqsToTagTypes(includeFlags));
}
private Text createText(Composite parent, int horizontalSpan) {
Text text = new Text(parent, SWT.SEARCH);
GridData data = new GridData();
data.horizontalSpan = horizontalSpan;
data.horizontalAlignment = GridData.FILL;
data.grabExcessHorizontalSpace = true;
data.widthHint= 0;
text.setLayoutData(data);
return text;
}
protected void createRefreshButtons(Composite parent) {
tagSource.addListener(listener);
parent.addDisposeListener(disposeListener);
Listener listener = null;
if ((includeFlags & TagSourceWorkbenchAdapter.INCLUDE_DATES) != 0) {
listener = event -> {
CVSTag dateTag = NewDateTagAction.getDateTag(getShell(), getLocation());
addDateTag(dateTag);
};
}
tagRefreshArea = new TagRefreshButtonArea(shell, tagSource, listener);
if (context != null)
tagRefreshArea.setRunnableContext(context);
tagRefreshArea.createArea(parent);
}
protected void createTreeMenu(TreeViewer tagTree) {
if ((includeFlags & TagSourceWorkbenchAdapter.INCLUDE_DATES) != 0) {
// Create the popup menu
MenuManager menuMgr = new MenuManager();
Tree tree = tagTree.getTree();
Menu menu = menuMgr.createContextMenu(tree);
menuMgr.addMenuListener(manager -> addMenuItemActions(manager));
menuMgr.setRemoveAllWhenShown(true);
tree.setMenu(menu);
}
}
/**
* Create aq custom area that is below the tag selection area but above the refresh busson group
* @param parent
*/
protected void createCustomArea(Composite parent) {
// No default custom area
}
protected TreeViewer createTree(Composite parent) {
Tree tree = new Tree(parent, SWT.SINGLE | SWT.BORDER);
GridData data = new GridData(GridData.FILL_BOTH);
tree.setLayoutData(data);
TreeViewer result = new TreeViewer(tree);
initialize(result);
result.getControl().addKeyListener(new KeyListener() {
@Override
public void keyPressed(KeyEvent event) {
handleKeyPressed(event);
}
@Override
public void keyReleased(KeyEvent event) {
handleKeyReleased(event);
}
});
result.setInput(createUnfilteredInput());
createTreeMenu(result);
return result;
}
protected TableViewer createTable(Composite parent) {
Table table = new Table(parent, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.SINGLE | SWT.FULL_SELECTION);
GridData data = new GridData(GridData.FILL_BOTH);
table.setLayoutData(data);
TableLayout layout = new TableLayout();
layout.addColumnData(new ColumnWeightData(100));
table.setLayout(layout);
new TableColumn(table, SWT.NONE);
TableViewer viewer = new TableViewer(table);
initialize(viewer);
viewer.setInput(createFilteredInput());
return viewer;
}
private void initialize(StructuredViewer viewer) {
viewer.setContentProvider(new WorkbenchContentProvider());
viewer.setLabelProvider(new WorkbenchLabelProvider());
viewer.setComparator(new ProjectElementComparator());
viewer.addSelectionChangedListener(event -> handleSelectionChange());
// select and close on double click
// To do: use defaultselection instead of double click
viewer.getControl().addMouseListener(new MouseAdapter() {
@Override
public void mouseDoubleClick(MouseEvent e) {
CVSTag tag = internalGetSelectedTag();
if (tag != null) {
firePropertyChangeChange(OPEN_SELECTED_TAG, null, tag);
}
}
});
}
private Object createUnfilteredInput() {
return TagSourceWorkbenchAdapter.createInput(tagSource, includeFlags);
}
public void handleKeyPressed(KeyEvent event) {
if (event.character == SWT.DEL && event.stateMask == 0) {
deleteDateTag();
}
}
private void deleteDateTag() {
TagElement[] selectedDateTagElements = getSelectedDateTagElement();
if (selectedDateTagElements.length == 0) return;
for(int i = 0; i < selectedDateTagElements.length; i++){
RepositoryManager mgr = CVSUIPlugin.getPlugin().getRepositoryManager();
CVSTag tag = selectedDateTagElements[i].getTag();
if(tag.getType() == CVSTag.DATE){
mgr.removeDateTag(getLocation(),tag);
}
}
tagTree.refresh();
handleSelectionChange();
}
/**
* Returns the selected date tag elements
*/
private TagElement[] getSelectedDateTagElement() {
ArrayList<Object> dateTagElements = null;
IStructuredSelection selection = tagTree.getStructuredSelection();
if (selection!=null && !selection.isEmpty()) {
dateTagElements = new ArrayList<>();
Iterator elements = selection.iterator();
while (elements.hasNext()) {
Object next = TeamAction.getAdapter(elements.next(), TagElement.class);
if (next instanceof TagElement) {
if(((TagElement)next).getTag().getType() == CVSTag.DATE){
dateTagElements.add(next);
}
}
}
}
if (dateTagElements != null && !dateTagElements.isEmpty()) {
TagElement[] result = new TagElement[dateTagElements.size()];
dateTagElements.toArray(result);
return result;
}
return new TagElement[0];
}
private void addDateTag(CVSTag tag){
if(tag == null) return;
List<CVSTag> dateTags = new ArrayList<>();
ICVSRepositoryLocation location = getLocation();
dateTags.addAll(Arrays.asList(CVSUIPlugin.getPlugin().getRepositoryManager().getDateTags(location)));
if(!dateTags.contains( tag)){
CVSUIPlugin.getPlugin().getRepositoryManager().addDateTag(location, tag);
}
try {
tagTree.getControl().setRedraw(false);
tagTree.refresh();
setSelection(tag);
} finally {
tagTree.getControl().setRedraw(true);
}
handleSelectionChange();
}
private void addMenuItemActions(IMenuManager manager) {
manager.add(new Action(CVSUIMessages.TagSelectionDialog_0) {
@Override
public void run() {
CVSTag dateTag = NewDateTagAction.getDateTag(getShell(), getLocation());
addDateTag(dateTag);
}
});
if(getSelectedDateTagElement().length > 0){
manager.add(new Action(CVSUIMessages.TagSelectionDialog_1) {
@Override
public void run() {
deleteDateTag();
}
});
}
}
protected void handleKeyReleased(KeyEvent event) {
}
/**
* handle a selection change event from the visible tag display
* (which could be either the table or the tree).
*/
protected void handleSelectionChange() {
CVSTag newSelection = internalGetSelectedTag();
if (selection != null && newSelection != null && selection.equals(newSelection)) {
// the selection hasn't change so return
return;
}
CVSTag oldSelection = selection;
selection = newSelection;
firePropertyChangeChange(SELECTED_TAG, oldSelection, selection);
}
private CVSTag internalGetSelectedTag() {
IStructuredSelection selection;
if (isTreeVisible()) {
selection = tagTree.getStructuredSelection();
} else {
selection = tagTable.getStructuredSelection();
}
Object o = selection.getFirstElement();
if (o instanceof TagElement)
return ((TagElement)o).getTag();
return null;
}
private boolean isTreeVisible() {
return treeVisible;
}
private ICVSRepositoryLocation getLocation(){
return tagSource.getLocation();
}
public CVSTag getSelection() {
return selection;
}
public Shell getShell() {
return shell;
}
/**
* Set the focus to the filter text widget
*/
public void setFocus() {
if (filterText != null)
filterText.setFocus();
else if (switcher != null)
switcher.setFocus();
// Refresh in case tags were added since the last time the area had focus
refresh();
}
/**
* Select the given tag
* @param selectedTag the tag to be selected
*/
public void setSelection(CVSTag selectedTag) {
if (isTreeVisible())
if (tagTree != null && !tagTree.getControl().isDisposed()) {
// TODO: Hack to instantiate the model before revealing the selection
tagTree.expandToLevel(2);
tagTree.collapseAll();
// Reveal the selection
tagTree.reveal(new TagElement(selectedTag));
tagTree.setSelection(new StructuredSelection(new TagElement(selectedTag)));
}
else
if (tagTable != null && !tagTable.getControl().isDisposed()) {
tagTable.setSelection(new StructuredSelection(new TagElement(selectedTag)));
}
}
/**
* Refresh the state of the tag selection area
*/
public void refresh() {
if (isTreeVisible()) {
if (tagTree != null && !tagTree.getControl().isDisposed()) {
tagTree.refresh();
}
} else {
if (tagTable != null && !tagTable.getControl().isDisposed()) {
tagTable.refresh();
}
}
}
public void refreshTagList() {
tagRefreshArea.refresh(true);
}
/**
* Set the enablement state of the area
* @param enabled the enablement state
*/
public void setEnabled(boolean enabled) {
if (filterText != null)
filterText.setEnabled(enabled);
tagTree.getControl().setEnabled(enabled);
tagTable.getControl().setEnabled(enabled);
}
/**
* Set the tag source from which the displayed tags are determined
* @param tagSource the source of the tags being displayed
*/
public void setTagSource(TagSource tagSource) {
if (this.tagSource != null) {
this.tagSource.removeListener(listener);
}
this.tagSource = tagSource;
this.tagSource.addListener(listener);
tagRefreshArea.setTagSource(this.tagSource);
setTreeAndTableInput();
}
private void setTreeAndTableInput() {
if (tagTree != null) {
tagTree.setInput(createUnfilteredInput());
}
if (tagTable != null) {
tagTable.setInput(createFilteredInput());
}
}
/**
* Set whether the input filter text is to be included in the tag selection area.
* If excluded, clientscan still set the filter text directly using
* <code>setFilter</code>.
* @param include whether filter text input should be included
*/
public void setIncludeFilterInputArea(boolean include) {
includeFilterInputArea = include;
}
/**
* Return whether the input filter text is to be included in the tag selection area.
* If excluded, clientscan still set the filter text directly using
* <code>setFilter</code>.
* @return whether filter text input should be included
*/
public boolean isIncludeFilterInputArea() {
return includeFilterInputArea;
}
/**
* Set the text used to filter the tag list.
* @param filter the filter pattern
*/
public void setFilter(String filter) {
this.filterPattern = filter;
updateTagDisplay(false);
}
public void setRunnableContext(IRunnableContext context) {
this.context = context;
if (tagRefreshArea != null)
tagRefreshArea.setRunnableContext(context);
}
}