blob: 11b8a76a24c2d941f1834868078aea055756d08b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2011 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
* Sebastian Davids - bug 57208
* Maik Schreiber - bug 102461
* Eugene Kuleshov (eu@md.pp.ru) - Bug 112742 [Wizards] Add spell check to commit dialog
* Brock Janiczak <brockj@tpg.com.au> - Bug 179183 Use spelling support from JFace in CVS commit dialog
* Brock Janiczak <brockj@tpg.com.au> - Bug 77944 [Change Sets] Comment dialog: Use comment as title
* Brock Janiczak <brockj@tpg.com.au> - Bug 194992 [Wizards] Display quick assists on context menu of commit dialog
*******************************************************************************/
package org.eclipse.team.internal.ccvs.ui;
import java.util.*;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.action.*;
import org.eclipse.jface.commands.ActionHandler;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.*;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.source.*;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.*;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.*;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.internal.ccvs.core.*;
import org.eclipse.team.internal.ccvs.core.util.Util;
import org.eclipse.team.internal.ui.SWTUtils;
import org.eclipse.team.internal.ui.dialogs.DialogArea;
import org.eclipse.ui.*;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.ui.editors.text.TextSourceViewerConfiguration;
import org.eclipse.ui.handlers.IHandlerActivation;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.texteditor.*;
/**
* This area provides the widgets for providing the CVS commit comment
*/
public class CommitCommentArea extends DialogArea {
private class TextBox implements ModifyListener, TraverseListener, FocusListener, Observer, IDocumentListener {
private final StyledText fTextField; // updated only by modify events
private final String fMessage;
private String fText;
private IDocument fDocument;
public TextBox(Composite composite, String message, String initialText) {
fMessage= message;
fText= initialText;
AnnotationModel annotationModel = new AnnotationModel();
IAnnotationAccess annotationAccess = new DefaultMarkerAnnotationAccess();
Composite cc = new Composite(composite, SWT.BORDER);
cc.setLayout(new FillLayout());
cc.setLayoutData(new GridData(GridData.FILL_BOTH));
final SourceViewer sourceViewer = new SourceViewer(cc, null, null, true, SWT.MULTI | SWT.V_SCROLL | SWT.WRAP);
fTextField = sourceViewer.getTextWidget();
fTextField.setIndent(2);
final SourceViewerDecorationSupport support = new SourceViewerDecorationSupport(sourceViewer, null, annotationAccess, EditorsUI.getSharedTextColors());
Iterator e= new MarkerAnnotationPreferences().getAnnotationPreferences().iterator();
while (e.hasNext())
support.setAnnotationPreference((AnnotationPreference) e.next());
support.install(EditorsUI.getPreferenceStore());
final IHandlerService handlerService = PlatformUI.getWorkbench().getService(IHandlerService.class);
final IHandlerActivation handlerActivation = installQuickFixActionHandler(handlerService, sourceViewer);
final TextViewerAction cutAction = new TextViewerAction(sourceViewer, ITextOperationTarget.CUT);
cutAction.setText(CVSUIMessages.CommitCommentArea_7);
cutAction.setActionDefinitionId(IWorkbenchCommandConstants.EDIT_CUT);
final TextViewerAction copyAction = new TextViewerAction(sourceViewer, ITextOperationTarget.COPY);
copyAction.setText(CVSUIMessages.CommitCommentArea_8);
copyAction.setActionDefinitionId(IWorkbenchCommandConstants.EDIT_COPY);
final TextViewerAction pasteAction = new TextViewerAction(sourceViewer, ITextOperationTarget.PASTE);
pasteAction.setText(CVSUIMessages.CommitCommentArea_9);
pasteAction.setActionDefinitionId(IWorkbenchCommandConstants.EDIT_PASTE);
final TextViewerAction selectAllAction = new TextViewerAction(sourceViewer, ITextOperationTarget.SELECT_ALL);
selectAllAction.setText(CVSUIMessages.CommitCommentArea_10);
selectAllAction.setActionDefinitionId(IWorkbenchCommandConstants.EDIT_SELECT_ALL);
MenuManager contextMenu = new MenuManager();
contextMenu.add(cutAction);
contextMenu.add(copyAction);
contextMenu.add(pasteAction);
contextMenu.add(selectAllAction);
contextMenu.add(new Separator());
final SubMenuManager quickFixMenu = new SubMenuManager(contextMenu);
quickFixMenu.setVisible(true);
quickFixMenu.addMenuListener(manager -> {
quickFixMenu.removeAll();
IAnnotationModel annotationModel1 = sourceViewer.getAnnotationModel();
Iterator annotationIterator = annotationModel1.getAnnotationIterator();
while (annotationIterator.hasNext()) {
Annotation annotation = (Annotation) annotationIterator.next();
if (!annotation.isMarkedDeleted()
&& includes(annotationModel1.getPosition(annotation),
sourceViewer.getTextWidget().getCaretOffset())
&& sourceViewer.getQuickAssistAssistant().canFix(annotation)) {
ICompletionProposal[] computeQuickAssistProposals = sourceViewer.getQuickAssistAssistant()
.getQuickAssistProcessor()
.computeQuickAssistProposals(sourceViewer.getQuickAssistInvocationContext());
for (int i = 0; i < computeQuickAssistProposals.length; i++) {
final ICompletionProposal proposal = computeQuickAssistProposals[i];
quickFixMenu.add(new Action(proposal.getDisplayString()) {
@Override
public void run() {
proposal.apply(sourceViewer.getDocument());
}
@Override
public ImageDescriptor getImageDescriptor() {
if (proposal.getImage() != null) {
return ImageDescriptor.createFromImage(proposal.getImage());
}
return null;
}
});
}
}
}
});
fTextField.addFocusListener(new FocusListener() {
private IHandlerActivation cutHandlerActivation;
private IHandlerActivation copyHandlerActivation;
private IHandlerActivation pasteHandlerActivation;
private IHandlerActivation selectAllHandlerActivation;
@Override
public void focusGained(FocusEvent e) {
cutAction.update();
copyAction.update();
IHandlerService service = PlatformUI.getWorkbench().getService(IHandlerService.class);
this.cutHandlerActivation = service.activateHandler(IWorkbenchCommandConstants.EDIT_CUT, new ActionHandler(cutAction), new ActiveShellExpression(getComposite().getShell()));
this.copyHandlerActivation = service.activateHandler(IWorkbenchCommandConstants.EDIT_COPY, new ActionHandler(copyAction), new ActiveShellExpression(getComposite().getShell()));
this.pasteHandlerActivation = service.activateHandler(IWorkbenchCommandConstants.EDIT_PASTE, new ActionHandler(pasteAction), new ActiveShellExpression(getComposite().getShell()));
this.selectAllHandlerActivation = service.activateHandler(IWorkbenchCommandConstants.EDIT_SELECT_ALL, new ActionHandler(selectAllAction), new ActiveShellExpression(getComposite().getShell()));
}
@Override
public void focusLost(FocusEvent e) {
IHandlerService service = PlatformUI.getWorkbench().getService(IHandlerService.class);
if (cutHandlerActivation != null) {
service.deactivateHandler(cutHandlerActivation);
}
if (copyHandlerActivation != null) {
service.deactivateHandler(copyHandlerActivation);
}
if (pasteHandlerActivation != null) {
service.deactivateHandler(pasteHandlerActivation);
}
if (selectAllHandlerActivation != null) {
service.deactivateHandler(selectAllHandlerActivation);
}
}
});
sourceViewer.addSelectionChangedListener(event -> {
cutAction.update();
copyAction.update();
});
sourceViewer.getTextWidget().addDisposeListener(e1 -> {
support.uninstall();
handlerService.deactivateHandler(handlerActivation);
});
fDocument = new Document(initialText);
// NOTE: Configuration must be applied before the document is set in order for
// Hyperlink coloring to work. (Presenter needs document object up front)
sourceViewer.configure(new TextSourceViewerConfiguration(EditorsUI.getPreferenceStore()));
sourceViewer.setDocument(fDocument, annotationModel);
fDocument.addDocumentListener(this);
fTextField.addTraverseListener(this);
fTextField.addModifyListener(this);
fTextField.addFocusListener(this);
fTextField.setMenu(contextMenu.createContextMenu(fTextField));
fTextField.selectAll();
}
protected boolean includes(Position position, int caretOffset) {
return position.includes(caretOffset) || (position.offset + position.length) == caretOffset;
}
/**
* Installs the quick fix action handler
* and returns the handler activation.
*
* @param handlerService the handler service
* @param sourceViewer the source viewer
* @return the handler activation
* @since 3.4
*/
private IHandlerActivation installQuickFixActionHandler(IHandlerService handlerService, SourceViewer sourceViewer) {
return handlerService.activateHandler(
ITextEditorActionDefinitionIds.QUICK_ASSIST,
createQuickFixActionHandler(sourceViewer),
new ActiveShellExpression(sourceViewer.getTextWidget().getShell()));
}
/**
* Creates and returns a quick fix action handler.
*
* @param textOperationTarget the target for text operations
* @since 3.4
*/
private ActionHandler createQuickFixActionHandler(final ITextOperationTarget textOperationTarget) {
Action quickFixAction= new Action() {
@Override
public void run() {
textOperationTarget.doOperation(ISourceViewer.QUICK_ASSIST);
}
};
quickFixAction.setActionDefinitionId(ITextEditorActionDefinitionIds.QUICK_ASSIST);
return new ActionHandler(quickFixAction);
}
@Override
public void modifyText(ModifyEvent e) {
final String old = fText;
fText = fTextField.getText();
if (!fText.equals(old))
firePropertyChangeChange(COMMENT_MODIFIED, old, fText);
}
@Override
public void keyTraversed(TraverseEvent e) {
if (e.detail == SWT.TRAVERSE_RETURN && (e.stateMask & SWT.CTRL) != 0) {
e.doit = false;
firePropertyChangeChange(OK_REQUESTED, null, null);
}
}
@Override
public void focusGained(FocusEvent e) {
if (fText.length() > 0)
return;
fTextField.removeModifyListener(this);
fDocument.removeDocumentListener(this);
try {
fTextField.setText(fText);
} finally {
fTextField.addModifyListener(this);
fDocument.addDocumentListener(this);
}
}
@Override
public void focusLost(FocusEvent e) {
if (fText.length() > 0)
return;
fTextField.removeModifyListener(this);
fDocument.removeDocumentListener(this);
try {
fTextField.setText(fMessage);
fTextField.selectAll();
} finally {
fTextField.addModifyListener(this);
fDocument.addDocumentListener(this);
}
}
public void setEnabled(boolean enabled) {
fTextField.setEnabled(enabled);
}
@Override
public void update(Observable o, Object arg) {
if (arg instanceof String) {
setText((String)arg); // triggers a modify event
}
}
public String getText() {
return fText;
}
private void setText(String text) {
if (text.length() == 0) {
fTextField.setText(fMessage);
fTextField.selectAll();
} else
fTextField.setText(text);
}
public void setFocus() {
fTextField.setFocus();
}
@Override
public void documentAboutToBeChanged(DocumentEvent event) {
}
@Override
public void documentChanged(DocumentEvent event) {
modifyText(null);
}
}
private static class ComboBox extends Observable implements SelectionListener, FocusListener {
private final String fMessage;
private final String [] fComments;
private String[] fCommentTemplates;
private final Combo fCombo;
public ComboBox(Composite composite, String message, String [] options,
String[] commentTemplates) {
fMessage= message;
fComments= options;
fCommentTemplates = commentTemplates;
fCombo = new Combo(composite, SWT.READ_ONLY);
fCombo.setLayoutData(SWTUtils.createHFillGridData());
fCombo.setVisibleItemCount(20);
// populate the previous comment list
populateList();
// We don't want to have an initial selection
// (see bug 32078: http://bugs.eclipse.org/bugs/show_bug.cgi?id=32078)
fCombo.addFocusListener(this);
fCombo.addSelectionListener(this);
}
private void populateList() {
fCombo.removeAll();
fCombo.add(fMessage);
for (int i = 0; i < fCommentTemplates.length; i++) {
fCombo.add(CVSUIMessages.CommitCommentArea_6 + ": " + //$NON-NLS-1$
Util.flattenText(fCommentTemplates[i]));
}
for (int i = 0; i < fComments.length; i++) {
fCombo.add(Util.flattenText(fComments[i]));
}
fCombo.setText(fMessage);
}
@Override
public void widgetSelected(SelectionEvent e) {
int index = fCombo.getSelectionIndex();
if (index > 0) {
index--;
setChanged();
// map from combo box index to array index
String message;
if (index < fCommentTemplates.length) {
message = fCommentTemplates[index];
} else {
message = fComments[index - fCommentTemplates.length];
}
notifyObservers(message);
}
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
@Override
public void focusGained(FocusEvent e) {
}
@Override
public void focusLost(FocusEvent e) {
fCombo.removeSelectionListener(this);
try {
fCombo.setText(fMessage);
} finally {
fCombo.addSelectionListener(this);
}
}
public void setEnabled(boolean enabled) {
fCombo.setEnabled(enabled);
}
void setCommentTemplates(String[] templates) {
fCommentTemplates = templates;
populateList();
}
}
private static final String EMPTY_MESSAGE= CVSUIMessages.CommitCommentArea_0;
private static final String COMBO_MESSAGE= CVSUIMessages.CommitCommentArea_1;
private static final String CONFIGURE_TEMPLATES_MESSAGE= CVSUIMessages.CommitCommentArea_5;
public static final String OK_REQUESTED = "OkRequested";//$NON-NLS-1$
public static final String COMMENT_MODIFIED = "CommentModified";//$NON-NLS-1$
private TextBox fTextBox;
private ComboBox fComboBox;
private IProject fMainProject;
private String fProposedComment;
private Composite fComposite;
@Override
public void createArea(Composite parent) {
Dialog.applyDialogFont(parent);
initializeDialogUnits(parent);
fComposite = createGrabbingComposite(parent, 1);
initializeDialogUnits(fComposite);
fTextBox= new TextBox(fComposite, EMPTY_MESSAGE, getInitialComment());
final String [] comments = CVSUIPlugin.getPlugin().getRepositoryManager().getPreviousComments();
final String[] commentTemplates = CVSUIPlugin.getPlugin().getRepositoryManager().getCommentTemplates();
fComboBox= new ComboBox(fComposite, COMBO_MESSAGE, comments, commentTemplates);
Link templatesPrefsLink = new Link(fComposite, 0);
templatesPrefsLink.setText("<a href=\"configureTemplates\">" + //$NON-NLS-1$
CONFIGURE_TEMPLATES_MESSAGE + "</a>"); //$NON-NLS-1$
templatesPrefsLink.addSelectionListener(new SelectionListener() {
@Override
public void widgetDefaultSelected(SelectionEvent e) {
openCommentTemplatesPreferencePage();
}
@Override
public void widgetSelected(SelectionEvent e) {
openCommentTemplatesPreferencePage();
}
});
fComboBox.addObserver(fTextBox);
}
void openCommentTemplatesPreferencePage() {
PreferencesUtil.createPreferenceDialogOn(
null,
"org.eclipse.team.cvs.ui.CommentTemplatesPreferences", //$NON-NLS-1$
new String[] { "org.eclipse.team.cvs.ui.CommentTemplatesPreferences" }, //$NON-NLS-1$
null).open();
fComboBox.setCommentTemplates(
CVSUIPlugin.getPlugin().getRepositoryManager().getCommentTemplates());
}
public String getComment(boolean save) {
final String comment= fTextBox.getText();
if (comment == null)
return ""; //$NON-NLS-1$
final String stripped= strip(comment);
if (save && comment.length() > 0)
CVSUIPlugin.getPlugin().getRepositoryManager().addComment(comment);
return stripped;
}
/**
* Calculates a shortened form of the commit message for use as a commit set
* title
* @return The first line or sentence of the commit message. The commit template
* text will be removed, as will leading and trailing whitespace.
*/
public String getFirstLineOfComment() {
String comment= fTextBox.getText();
if (comment == null) {
comment= ""; //$NON-NLS-1$
}
comment= strip(comment);
int cr= comment.indexOf('\r');
if (cr != -1) {
comment= comment.substring(0, cr);
}
int lf= comment.indexOf('\n');
if (lf != -1) {
comment= comment.substring(0, lf);
}
int dot= comment.indexOf(". "); //$NON-NLS-1$
if (dot != -1) {
comment= comment.substring(0, dot);
}
comment= comment.trim();
return comment;
}
public String getCommentWithPrompt(Shell shell) {
final String comment= getComment(false);
if (comment.length() == 0) {
final IPreferenceStore store= CVSUIPlugin.getPlugin().getPreferenceStore();
final String value= store.getString(ICVSUIConstants.PREF_ALLOW_EMPTY_COMMIT_COMMENTS);
if (MessageDialogWithToggle.NEVER.equals(value))
return null;
if (MessageDialogWithToggle.PROMPT.equals(value)) {
final String title= CVSUIMessages.CommitCommentArea_2;
final String message= CVSUIMessages.CommitCommentArea_3;
final String toggleMessage= CVSUIMessages.CommitCommentArea_4;
final MessageDialogWithToggle dialog= MessageDialogWithToggle.openYesNoQuestion(shell, title, message, toggleMessage, false, store, ICVSUIConstants.PREF_ALLOW_EMPTY_COMMIT_COMMENTS);
if (dialog.getReturnCode() != IDialogConstants.YES_ID) {
fTextBox.setFocus();
return null;
}
}
}
return getComment(true);
}
public void setProject(IProject iProject) {
this.fMainProject = iProject;
}
public void setFocus() {
if (fTextBox != null) {
fTextBox.setFocus();
}
}
public void setProposedComment(String proposedComment) {
if (proposedComment == null || proposedComment.length() == 0) {
this.fProposedComment = null;
} else {
this.fProposedComment = proposedComment;
}
}
public boolean hasCommitTemplate() {
try {
String commitTemplate = getCommitTemplate();
return commitTemplate != null && commitTemplate.length() > 0;
} catch (CVSException e) {
CVSUIPlugin.log(e);
return false;
}
}
public void setEnabled(boolean enabled) {
fTextBox.setEnabled(enabled);
fComboBox.setEnabled(enabled);
}
public Composite getComposite() {
return fComposite;
}
@Override
protected void firePropertyChangeChange(String property, Object oldValue, Object newValue) {
super.firePropertyChangeChange(property, oldValue, newValue);
}
private String getInitialComment() {
if (fProposedComment != null)
return fProposedComment;
try {
return getCommitTemplate();
} catch (CVSException e) {
CVSUIPlugin.log(e);
return ""; //$NON-NLS-1$
}
}
private String strip(String comment) {
// strip template from the comment entered
try {
final String commitTemplate = getCommitTemplate();
if (comment.startsWith(commitTemplate)) {
return comment.substring(commitTemplate.length());
} else if (comment.endsWith(commitTemplate)) {
return comment.substring(0, comment.length() - commitTemplate.length());
}
} catch (CVSException e) {
// we couldn't get the commit template. Log the error and continue
CVSUIPlugin.log(e);
}
return comment;
}
private CVSTeamProvider getProvider() {
if (fMainProject == null) return null;
return (CVSTeamProvider) RepositoryProvider.getProvider(fMainProject, CVSProviderPlugin.getTypeId());
}
private String getCommitTemplate() throws CVSException {
CVSTeamProvider provider = getProvider();
if (provider == null)
return ""; //$NON-NLS-1$
final String template = provider.getCommitTemplate();
return template != null ? template : ""; //$NON-NLS-1$
}
}