diff options
| author | Benjamin Muskalla | 2010-08-03 01:21:24 +0000 |
|---|---|---|
| committer | Chris Aniszczyk | 2010-08-03 16:49:34 +0000 |
| commit | 71a9fd0e76343f4e13fd987600b2d703f8f12607 (patch) | |
| tree | 039803cfa273b9b7a9f5d7117d8cb441998f47f7 | |
| parent | 0fd896dd5cc4fdab36aeb99fea0245607baa18c1 (diff) | |
| download | egit-71a9fd0e76343f4e13fd987600b2d703f8f12607.tar.gz egit-71a9fd0e76343f4e13fd987600b2d703f8f12607.tar.xz egit-71a9fd0e76343f4e13fd987600b2d703f8f12607.zip | |
Support spellchecking in commit dialog
In order to improve user expierence during commit, this introduces
spellchecking and quick fixes for the commit message. In addition,
a margin was added to help wrapping the commit messages. In the
future, it may be feasible to have preferences for the margin width
and visibility.
Bug: 318530
Change-Id: Ic88c1ae82318311bf74eea9b979895dc39711856
Signed-off-by: Benjamin Muskalla <bmuskalla@eclipsesource.com>
Signed-off-by: Chris Aniszczyk <caniszczyk@gmail.com>
4 files changed, 402 insertions, 14 deletions
diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/UIText.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/UIText.java index fb2ca3a12c..a127be61f9 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/UIText.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/UIText.java @@ -1201,6 +1201,18 @@ public class UIText extends NLS { public static String ConfigureUriPage_Remove_button; /** */ + public static String CommitMessageArea_copy; + + /** */ + public static String CommitMessageArea_cut; + + /** */ + public static String CommitMessageArea_paste; + + /** */ + public static String CommitMessageArea_selectAll; + + /** */ public static String CommitMessageViewer_author; /** */ diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitDialog.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitDialog.java index ddcdff1bf0..a7816d38a8 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitDialog.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitDialog.java @@ -162,7 +162,7 @@ public class CommitDialog extends Dialog { IDialogConstants.CANCEL_LABEL, false); } - Text commitText; + CommitMessageArea commitText; Text authorText; Text committerText; Button amendingButton; @@ -191,19 +191,19 @@ public class CommitDialog extends Dialog { label.setText(UIText.CommitDialog_CommitMessage); label.setLayoutData(GridDataFactory.fillDefaults().span(2, 1).grab(true, false).create()); - commitText = new Text(container, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL); + commitText = new CommitMessageArea(container, commitMessage); commitText.setLayoutData(GridDataFactory.fillDefaults().span(2, 1).grab(true, true) .hint(600, 200).create()); // allow to commit with ctrl-enter - commitText.addKeyListener(new KeyAdapter() { - public void keyPressed(KeyEvent arg0) { - if (arg0.keyCode == SWT.CR - && (arg0.stateMask & SWT.CONTROL) > 0) { + commitText.getTextWidget().addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent event) { + if (event.keyCode == SWT.CR + && (event.stateMask & SWT.CONTROL) > 0) { okPressed(); - } else if (arg0.keyCode == SWT.TAB - && (arg0.stateMask & SWT.SHIFT) == 0) { - arg0.doit = false; + } else if (event.keyCode == SWT.TAB + && (event.stateMask & SWT.SHIFT) == 0) { + event.doit = false; commitText.traverse(SWT.TRAVERSE_TAB_NEXT); } } @@ -346,19 +346,16 @@ public class CommitDialog extends Dialog { showUntrackedButton.setSelection(showUntracked); - showUntrackedButton.addSelectionListener(new SelectionListener() { + showUntrackedButton.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { showUntracked = showUntrackedButton.getSelection(); filesViewer.refresh(true); } - public void widgetDefaultSelected(SelectionEvent e) { - // Empty - } }); - commitText.addModifyListener(new ModifyListener() { + commitText.getTextWidget().addModifyListener(new ModifyListener() { public void modifyText(ModifyEvent e) { updateSignedOffButton(); updateChangeIdButton(); diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageArea.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageArea.java new file mode 100644 index 0000000000..aebcfcdd97 --- /dev/null +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitMessageArea.java @@ -0,0 +1,375 @@ +/******************************************************************************* + * Copyright (C) 2010, Benjamin Muskalla <bmuskalla@eclipsesource.com> + * + * 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: + * Benjamin Muskalla (EclipseSource) - initial implementation + *******************************************************************************/ +package org.eclipse.egit.ui.internal.dialogs; + +import java.util.Iterator; +import java.util.List; + +import org.eclipse.egit.ui.UIText; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.IMenuListener; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.action.SubMenuManager; +import org.eclipse.jface.commands.ActionHandler; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.text.Document; +import org.eclipse.jface.text.ITextOperationTarget; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.MarginPainter; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext; +import org.eclipse.jface.text.quickassist.IQuickAssistProcessor; +import org.eclipse.jface.text.source.Annotation; +import org.eclipse.jface.text.source.AnnotationModel; +import org.eclipse.jface.text.source.IAnnotationAccess; +import org.eclipse.jface.text.source.IAnnotationModel; +import org.eclipse.jface.text.source.ISharedTextColors; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.jface.text.source.SourceViewer; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.ActiveShellExpression; +import org.eclipse.ui.IWorkbenchCommandConstants; +import org.eclipse.ui.PlatformUI; +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.AnnotationPreference; +import org.eclipse.ui.texteditor.DefaultMarkerAnnotationAccess; +import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; +import org.eclipse.ui.texteditor.IUpdate; +import org.eclipse.ui.texteditor.MarkerAnnotationPreferences; +import org.eclipse.ui.texteditor.SourceViewerDecorationSupport; + +/** + * Text field with support for spellchecking. + */ +public class CommitMessageArea extends Composite { + + private static class TextViewerAction extends Action implements IUpdate { + + private int fOperationCode= -1; + private ITextOperationTarget fOperationTarget; + + /** + * Creates a new action. + * + * @param viewer the viewer + * @param operationCode the opcode + */ + public TextViewerAction(ITextViewer viewer, int operationCode) { + fOperationCode= operationCode; + fOperationTarget= viewer.getTextOperationTarget(); + update(); + } + + /** + * Updates the enabled state of the action. + * Fires a property change if the enabled state changes. + * + * @see Action#firePropertyChange(String, Object, Object) + */ + public void update() { + // XXX: workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=206111 + if (fOperationCode == ITextOperationTarget.REDO) + return; + + boolean wasEnabled= isEnabled(); + boolean isEnabled= (fOperationTarget != null && fOperationTarget.canDoOperation(fOperationCode)); + setEnabled(isEnabled); + + if (wasEnabled != isEnabled) { + firePropertyChange(ENABLED, wasEnabled ? Boolean.TRUE : Boolean.FALSE, isEnabled ? Boolean.TRUE : Boolean.FALSE); + } + } + + /** + * @see Action#run() + */ + public void run() { + if (fOperationCode != -1 && fOperationTarget != null) { + fOperationTarget.doOperation(fOperationCode); + } + } + } + + private final SourceViewer sourceViewer; + + /** + * @param parent + * @param initialText + */ + public CommitMessageArea(Composite parent, String initialText) { + super(parent, SWT.BORDER); + setLayout(new FillLayout()); + + AnnotationModel annotationModel = new AnnotationModel(); + sourceViewer = new SourceViewer(this, null, null, true, SWT.MULTI + | SWT.V_SCROLL | SWT.WRAP); + getTextWidget().setIndent(2); + + createMarginPainter(); + + final SourceViewerDecorationSupport support = configureAnnotationPreferences(); + final IHandlerActivation handlerActivation = installQuickFixActionHandler(); + + configureContextMenu(); + + Document document = new Document(initialText); + + sourceViewer.configure(new TextSourceViewerConfiguration(EditorsUI + .getPreferenceStore())); + sourceViewer.setDocument(document, annotationModel); + + getTextWidget().addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent disposeEvent) { + support.uninstall(); + getHandlerService().deactivateHandler(handlerActivation); + } + }); + } + + private void configureContextMenu() { + final TextViewerAction cutAction = new TextViewerAction(sourceViewer, ITextOperationTarget.CUT); + cutAction.setText(UIText.CommitMessageArea_cut); + cutAction.setActionDefinitionId(IWorkbenchCommandConstants.EDIT_CUT); + + final TextViewerAction copyAction = new TextViewerAction(sourceViewer, ITextOperationTarget.COPY); + copyAction.setText(UIText.CommitMessageArea_copy); + copyAction.setActionDefinitionId(IWorkbenchCommandConstants.EDIT_COPY); + + final TextViewerAction pasteAction = new TextViewerAction(sourceViewer, ITextOperationTarget.PASTE); + pasteAction.setText(UIText.CommitMessageArea_paste); + pasteAction.setActionDefinitionId(IWorkbenchCommandConstants.EDIT_PASTE); + + final TextViewerAction selectAllAction = new TextViewerAction(sourceViewer, ITextOperationTarget.SELECT_ALL); + selectAllAction.setText(UIText.CommitMessageArea_selectAll); + 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(new IMenuListener() { + public void menuAboutToShow(IMenuManager manager) { + quickFixMenu.removeAll(); + addProposals(quickFixMenu); + } + }); + StyledText textWidget = getTextWidget(); + getTextWidget().setMenu(contextMenu.createContextMenu(textWidget)); + + getTextWidget().addFocusListener(new FocusListener() { + + private IHandlerActivation cutHandlerActivation; + private IHandlerActivation copyHandlerActivation; + private IHandlerActivation pasteHandlerActivation; + private IHandlerActivation selectAllHandlerActivation; + + public void focusGained(FocusEvent e) { + cutAction.update(); + copyAction.update(); + IHandlerService service = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class); + this.cutHandlerActivation = service.activateHandler(IWorkbenchCommandConstants.EDIT_CUT, new ActionHandler(cutAction), new ActiveShellExpression(getParent().getShell())); + this.copyHandlerActivation = service.activateHandler(IWorkbenchCommandConstants.EDIT_COPY, new ActionHandler(copyAction), new ActiveShellExpression(getParent().getShell())); + this.pasteHandlerActivation = service.activateHandler(IWorkbenchCommandConstants.EDIT_PASTE, new ActionHandler(pasteAction), new ActiveShellExpression(getParent().getShell())); + this.selectAllHandlerActivation = service.activateHandler(IWorkbenchCommandConstants.EDIT_SELECT_ALL, new ActionHandler(selectAllAction), new ActiveShellExpression(getParent().getShell())); + } + + /* (non-Javadoc) + * @see org.eclipse.swt.events.FocusAdapter#focusLost(org.eclipse.swt.events.FocusEvent) + */ + public void focusLost(FocusEvent e) { + IHandlerService service = (IHandlerService) 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(new ISelectionChangedListener() { + + public void selectionChanged(SelectionChangedEvent event) { + cutAction.update(); + copyAction.update(); + } + + }); + } + + private void addProposals(final SubMenuManager quickFixMenu) { + IAnnotationModel sourceModel = sourceViewer.getAnnotationModel(); + Iterator annotationIterator = sourceModel.getAnnotationIterator(); + while (annotationIterator.hasNext()) { + Annotation annotation = (Annotation) annotationIterator.next(); + boolean isDeleted = annotation.isMarkedDeleted(); + boolean isIncluded = includes(sourceModel.getPosition(annotation), + getTextWidget().getCaretOffset()); + boolean isFixable = sourceViewer.getQuickAssistAssistant().canFix( + annotation); + if (!isDeleted && isIncluded && isFixable) { + IQuickAssistProcessor processor = sourceViewer + .getQuickAssistAssistant() + .getQuickAssistProcessor(); + IQuickAssistInvocationContext context = sourceViewer + .getQuickAssistInvocationContext(); + ICompletionProposal[] proposals = processor + .computeQuickAssistProposals(context); + + for (ICompletionProposal proposal : proposals) + quickFixMenu.add(createQuickFixAction(proposal)); + } + } + } + + private boolean includes(Position position, int caretOffset) { + return position.includes(caretOffset) + || (position.offset + position.length) == caretOffset; + } + + private IAction createQuickFixAction(final ICompletionProposal proposal) { + return new Action(proposal.getDisplayString()) { + + public void run() { + proposal.apply(sourceViewer.getDocument()); + } + + public ImageDescriptor getImageDescriptor() { + Image image = proposal.getImage(); + if (image != null) { + return ImageDescriptor.createFromImage(image); + } + return null; + } + }; + } + + private IHandlerService getHandlerService() { + final IHandlerService handlerService = (IHandlerService) PlatformUI + .getWorkbench().getService(IHandlerService.class); + return handlerService; + } + + private SourceViewerDecorationSupport configureAnnotationPreferences() { + ISharedTextColors textColors = EditorsUI.getSharedTextColors(); + IAnnotationAccess annotationAccess = new DefaultMarkerAnnotationAccess(); + final SourceViewerDecorationSupport support = new SourceViewerDecorationSupport( + sourceViewer, null, annotationAccess, textColors); + + List annotationPreferences = new MarkerAnnotationPreferences() + .getAnnotationPreferences(); + Iterator e = annotationPreferences.iterator(); + while (e.hasNext()) + support.setAnnotationPreference((AnnotationPreference) e.next()); + + support.install(EditorsUI.getPreferenceStore()); + return support; + } + + private void createMarginPainter() { + MarginPainter marginPainter = new MarginPainter(sourceViewer); + marginPainter.setMarginRulerColumn(65); + marginPainter.setMarginRulerColor(Display.getDefault().getSystemColor( + SWT.COLOR_GRAY)); + sourceViewer.addPainter(marginPainter); + } + + /** + * @return widget + */ + public StyledText getTextWidget() { + return sourceViewer.getTextWidget(); + } + + private IHandlerActivation installQuickFixActionHandler() { + IHandlerService handlerService = getHandlerService(); + ActionHandler handler = createQuickFixActionHandler(sourceViewer); + ActiveShellExpression expression = new ActiveShellExpression( + sourceViewer.getTextWidget().getShell()); + return handlerService.activateHandler( + ITextEditorActionDefinitionIds.QUICK_ASSIST, handler, + expression); + } + + private ActionHandler createQuickFixActionHandler( + final ITextOperationTarget textOperationTarget) { + Action quickFixAction = new Action() { + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.action.Action#run() + */ + public void run() { + textOperationTarget.doOperation(ISourceViewer.QUICK_ASSIST); + } + }; + quickFixAction + .setActionDefinitionId(ITextEditorActionDefinitionIds.QUICK_ASSIST); + return new ActionHandler(quickFixAction); + } + + /** + * @return text + */ + public String getText() { + return getTextWidget().getText(); + } + + /** + * @param text + */ + public void setText(String text) { + getTextWidget().setText(text); + } + + /** + * + */ + public boolean setFocus() { + return getTextWidget().setFocus(); + } + +} diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/uitext.properties b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/uitext.properties index ec1d13ec4c..7f0aabc50b 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/uitext.properties +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/uitext.properties @@ -427,6 +427,10 @@ ConfigureUriPage_MissingUris_message=No URIs specified ConfigureUriPage_ParsingProblem_message=Can not parse this ConfigureUriPage_Remove_button=Remove +CommitMessageArea_copy=&Copy +CommitMessageArea_cut=C&ut +CommitMessageArea_paste=&Paste +CommitMessageArea_selectAll=Select &All CommitMessageViewer_author=Author CommitMessageViewer_child=Child CommitMessageViewer_commit=commit |
