diff options
Diffstat (limited to 'org.eclipse.egit.ui/src/org/eclipse/egit/ui')
10 files changed, 1152 insertions, 38 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 e10cebd56f..575d8fcb9e 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 @@ -1962,6 +1962,78 @@ public class UIText extends NLS { /** */ public static String Update_update; + /** */ + public static String TagAction_cannotCheckout; + + /** */ + public static String TagAction_cannotGetBranchName; + + /** */ + public static String TagAction_repositoryState; + + /** */ + public static String TagAction_errorCreatingTag; + + /** */ + public static String TagAction_unableToCreateTag; + + /** */ + public static String TagAction_errorDuringTagging; + + /** */ + public static String TagAction_errorWhileGettingRevCommits; + + /** */ + public static String TagAction_unableToResolveHeadObjectId; + + /** */ + public static String TagAction_errorWhileMappingRevTag; + + /** */ + public static String TagAction_creating; + + /** */ + public static String TagAction_taggingFailed; + + /** */ + public static String CreateTagDialog_tagName; + + /** */ + public static String CreateTagDialog_tagMessage; + + /** */ + public static String CreateTagDialog_questionNewTagTitle; + + /** */ + public static String CreateTagDialog_overwriteTag; + + /** */ + public static String CreateTagDialog_overwriteTagToolTip; + + /** */ + public static String CreateTagDialog_existingTags; + + /** */ + public static String CreateTagDialog_advanced; + + /** */ + public static String CreateTagDialog_advancedToolTip; + + /** */ + public static String CreateTagDialog_advancedMessage; + + /** */ + public static String CreateTagDialog_tagNameToolTip; + + /** */ + public static String CreateTagDialog_clearButton; + + /** */ + public static String CreateTagDialog_clearButtonTooltip; + + /** */ + public static String CommitCombo_showSuggestedCommits; + static { initializeMessages("org.eclipse.egit.ui.uitext", UIText.class); //$NON-NLS-1$ } diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/UIUtils.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/UIUtils.java index 58e3672a25..f811992493 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/UIUtils.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/UIUtils.java @@ -10,8 +10,12 @@ *******************************************************************************/ package org.eclipse.egit.ui; +import org.eclipse.jface.fieldassist.ControlDecoration; +import org.eclipse.jface.fieldassist.FieldDecorationRegistry; import org.eclipse.jface.resource.FontRegistry; +import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.widgets.Control; import org.eclipse.ui.PlatformUI; /** @@ -37,4 +41,25 @@ public class UIUtils { .getFontRegistry().getBold(id); } + /** + * Adds little bulb decoration to given control. Bulb will appear in top left + * corner of control after giving focus for this control. + * + * After clicking on bulb image text from <code>tooltip</code> will appear. + * + * @param control instance of {@link Control} object with should be decorated + * @param tooltip text value which should appear after clicking on bulb image. + */ + public static void addBulbDecorator(final Control control, final String tooltip) { + ControlDecoration dec = new ControlDecoration(control, SWT.TOP | SWT.LEFT); + + dec.setImage(FieldDecorationRegistry.getDefault().getFieldDecoration( + FieldDecorationRegistry.DEC_CONTENT_PROPOSAL).getImage()); + + dec.setShowOnlyOnFocus(true); + dec.setShowHover(true); + + dec.setDescriptionText(tooltip); + } + } diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/SWTUtils.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/SWTUtils.java index 976bc06879..7b2912017a 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/SWTUtils.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/SWTUtils.java @@ -19,7 +19,12 @@ import org.eclipse.swt.graphics.FontMetrics; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.*; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; import org.eclipse.ui.dialogs.PreferenceLinkArea; import org.eclipse.ui.preferences.IWorkbenchPreferenceContainer; diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/ValidationUtils.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/ValidationUtils.java new file mode 100644 index 0000000000..5f17754fef --- /dev/null +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/ValidationUtils.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (C) 2010, Dariusz Luksza <dariusz@luksza.org> + * + * 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 + *******************************************************************************/ + +package org.eclipse.egit.ui.internal; + +import java.io.IOException; + +import org.eclipse.egit.ui.Activator; +import org.eclipse.egit.ui.UIText; +import org.eclipse.jface.dialogs.IInputValidator; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.osgi.util.NLS; + +/** + * A collection of validators + */ +public class ValidationUtils { + + /** + * Creates and returns input validator for refNames + * + * @param repo + * @param refPrefix + * @return input validator for refNames + */ + public static IInputValidator getRefNameInputValidator(final Repository repo, final String refPrefix) { + return new IInputValidator() { + public String isValid(String newText) { + if (newText.length() == 0) { + // nothing entered, just don't let the user proceed, + // no need to prompt them with an error message + return ""; //$NON-NLS-1$ + } + + String testFor = refPrefix + newText; + try { + if (repo.resolve(testFor) != null) + return UIText.BranchSelectionDialog_ErrorAlreadyExists; + } catch (IOException e1) { + Activator.logError(NLS.bind( + UIText.BranchSelectionDialog_ErrorCouldNotResolve, testFor), e1); + return e1.getMessage(); + } + if (!Repository.isValidRefName(testFor)) + return UIText.BranchSelectionDialog_ErrorInvalidRefName; + return null; + } + }; + } + +} diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/actions/TagAction.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/actions/TagAction.java new file mode 100644 index 0000000000..ef6d198315 --- /dev/null +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/actions/TagAction.java @@ -0,0 +1,183 @@ +/******************************************************************************* + * Copyright (C) 2010, Dariusz Luksza <dariusz@luksza.org> + * + * 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 + *******************************************************************************/ +package org.eclipse.egit.ui.internal.actions; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.egit.core.op.TagOperation; +import org.eclipse.egit.ui.Activator; +import org.eclipse.egit.ui.UIText; +import org.eclipse.egit.ui.internal.ValidationUtils; +import org.eclipse.egit.ui.internal.decorators.GitLightweightDecorator; +import org.eclipse.egit.ui.internal.dialogs.CreateTagDialog; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.Tag; +import org.eclipse.jgit.revwalk.RevSort; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.osgi.util.NLS; + +/** + * An action for creating tag. + * + * @see TagOperation + */ +public class TagAction extends RepositoryAction { + + private Repository repo; + + @Override + public boolean isEnabled() { + return getRepository(false) != null; + } + + @Override + protected void execute(IAction action) throws InvocationTargetException, + InterruptedException { + repo = getRepository(true); + if (repo == null) + return; + + if (!repo.getRepositoryState().canCheckout()) { + MessageDialog.openError(getShell(), + UIText.TagAction_cannotCheckout, NLS.bind( + UIText.TagAction_repositoryState, repo + .getRepositoryState().getDescription())); + return; + } + + String currentBranchName; + try { + currentBranchName = repo.getBranch(); + } catch (IOException e) { + throw new InvocationTargetException(e, + UIText.TagAction_cannotGetBranchName); + } + + CreateTagDialog dialog = new CreateTagDialog(getShell(), + ValidationUtils + .getRefNameInputValidator(repo, Constants.R_TAGS), + currentBranchName); + + // get and set commits + RevWalk revCommits = getRevCommits(); + dialog.setRevCommitList(revCommits); + + // get and set existing tags + List<Tag> tags = getRevTags(); + dialog.setExistingTags(tags); + + if (dialog.open() != IDialogConstants.OK_ID) + return; + + final Tag tag = new Tag(repo); + PersonIdent personIdent = new PersonIdent(repo); + String tagName = dialog.getTagName(); + + tag.setTag(tagName); + tag.setTagger(personIdent); + tag.setMessage(dialog.getTagMessage()); + + ObjectId tagCommit = getTagCommit(dialog.getTagCommit()); + tag.setObjId(tagCommit); + + String tagJobName = NLS.bind(UIText.TagAction_creating, tagName); + final boolean shouldMoveTag = dialog.shouldOverWriteTag(); + + Job tagJob = new Job(tagJobName) { + protected IStatus run(IProgressMonitor monitor) { + try { + new TagOperation(repo, tag, shouldMoveTag).execute(monitor); + } catch (CoreException e) { + return Activator.createErrorStatus( + UIText.TagAction_taggingFailed, e); + } finally { + GitLightweightDecorator.refresh(); + } + + return Status.OK_STATUS; + } + + }; + + tagJob.setUser(true); + tagJob.schedule(); + } + + private List<Tag> getRevTags() { + Collection<Ref> revTags = repo.getTags().values(); + List<Tag> tags = new ArrayList<Tag>(); + for (Ref ref : revTags) { + try { + Tag tag = repo.mapTag(ref.getName()); + tags.add(tag); + } catch (IOException e) { + ErrorDialog.openError(getShell(), + UIText.TagAction_errorDuringTagging, NLS.bind( + UIText.TagAction_errorWhileMappingRevTag, ref + .getName()), new Status(IStatus.ERROR, + Activator.getPluginId(), e.getMessage(), e)); + } + } + return tags; + } + + private RevWalk getRevCommits() { + RevWalk revWalk = new RevWalk(repo); + revWalk.sort(RevSort.COMMIT_TIME_DESC, true); + revWalk.sort(RevSort.BOUNDARY, true); + + try { + AnyObjectId headId = repo.resolve(Constants.HEAD); + if (headId != null) + revWalk.markStart(revWalk.parseCommit(headId)); + } catch (IOException e) { + ErrorDialog.openError(getShell(), + UIText.TagAction_errorDuringTagging, + UIText.TagAction_errorWhileGettingRevCommits, new Status( + IStatus.ERROR, Activator.getPluginId(), e + .getMessage(), e)); + } + + return revWalk; + } + + private ObjectId getTagCommit(ObjectId objectId) + throws InvocationTargetException { + ObjectId result = null; + if (objectId == null) { + try { + result = repo.resolve(Constants.HEAD); + } catch (IOException e) { + throw new InvocationTargetException(e, + UIText.TagAction_unableToResolveHeadObjectId); + } + } else { + result = objectId; + } + return result; + } +} diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/components/RepositorySelectionPage.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/components/RepositorySelectionPage.java index d45e0fc040..694e55e85b 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/components/RepositorySelectionPage.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/components/RepositorySelectionPage.java @@ -24,9 +24,8 @@ import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.egit.ui.Activator; import org.eclipse.egit.ui.UIText; +import org.eclipse.egit.ui.UIUtils; import org.eclipse.jface.fieldassist.ContentProposalAdapter; -import org.eclipse.jface.fieldassist.ControlDecoration; -import org.eclipse.jface.fieldassist.FieldDecorationRegistry; import org.eclipse.jface.fieldassist.IContentProposal; import org.eclipse.jface.fieldassist.IContentProposalProvider; import org.eclipse.jface.fieldassist.TextContentAdapter; @@ -847,17 +846,7 @@ public class RepositorySelectionPage extends BaseWizardPage { private void addContentProposalToUriText(Text uriTextField) { - ControlDecoration dec = new ControlDecoration(uriTextField, SWT.TOP - | SWT.LEFT); - - dec.setImage(FieldDecorationRegistry.getDefault().getFieldDecoration( - FieldDecorationRegistry.DEC_CONTENT_PROPOSAL).getImage()); - - dec.setShowOnlyOnFocus(true); - dec.setShowHover(true); - - dec - .setDescriptionText(UIText.RepositorySelectionPage_ShowPreviousURIs_HoverText); + UIUtils.addBulbDecorator(uriTextField, UIText.RepositorySelectionPage_ShowPreviousURIs_HoverText); IContentProposalProvider cp = new IContentProposalProvider() { diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/BranchSelectionDialog.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/BranchSelectionDialog.java index 07723587d8..171d2c6c4d 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/BranchSelectionDialog.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/BranchSelectionDialog.java @@ -1,4 +1,4 @@ -/******************************************************************************* +/******************************************************************************* * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com> * Copyright (C) 2007, Robin Rosenberg <me@lathund.dewire.com.dewire.com> * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com> @@ -22,9 +22,9 @@ import org.eclipse.egit.ui.internal.repository.RepositoriesViewContentProvider; import org.eclipse.egit.ui.internal.repository.RepositoriesViewLabelProvider; import org.eclipse.egit.ui.internal.repository.RepositoryTreeNode; import org.eclipse.egit.ui.internal.repository.RepositoryTreeNode.RepositoryTreeNodeType; +import org.eclipse.egit.ui.internal.ValidationUtils; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogConstants; -import org.eclipse.jface.dialogs.IInputValidator; import org.eclipse.jface.dialogs.InputDialog; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.layout.GridDataFactory; @@ -308,28 +308,7 @@ public class BranchSelectionDialog extends Dialog { getShell(), UIText.BranchSelectionDialog_QuestionNewBranchTitle, prompt, - null, new IInputValidator() { - public String isValid(String newText) { - if (newText.length() == 0) { - // nothing entered, just don't let the user proceed, - // no need to prompt them with an error message - return ""; //$NON-NLS-1$ - } - - String testFor = refPrefix + newText; - try { - if (repo.resolve(testFor) != null) - return UIText.BranchSelectionDialog_ErrorAlreadyExists; - } catch (IOException e1) { - Activator.logError(NLS.bind( - UIText.BranchSelectionDialog_ErrorCouldNotResolve, testFor), e1); - return e1.getMessage(); - } - if (!Repository.isValidRefName(testFor)) - return UIText.BranchSelectionDialog_ErrorInvalidRefName; - return null; - } - }); + null, ValidationUtils.getRefNameInputValidator(repo, refPrefix)); labelDialog.setBlockOnOpen(true); return labelDialog; } diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitCombo.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitCombo.java new file mode 100644 index 0000000000..bfd7a74e96 --- /dev/null +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CommitCombo.java @@ -0,0 +1,223 @@ +/******************************************************************************* + * Copyright (C) 2010, Dariusz Luksza <dariusz@luksza.org> + * + * 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 + *******************************************************************************/ +package org.eclipse.egit.ui.internal.dialogs; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.egit.ui.UIText; +import org.eclipse.egit.ui.UIUtils; +import org.eclipse.jface.fieldassist.ComboContentAdapter; +import org.eclipse.jface.fieldassist.ContentProposalAdapter; +import org.eclipse.jface.fieldassist.IContentProposal; +import org.eclipse.jface.fieldassist.IContentProposalProvider; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; + +/** + * This is an extended version of {@link Combo} widget with is specialized in + * displaying commits and simplifying selection process. + * + * It is integrated with {@link ContentProposalAdapter} that helps select + * preferred tag by user. To get activate proposal provider simply just start + * writing commit SHA-1 or part of commit's message first line + */ +public class CommitCombo extends Composite { + + private final List<ComboCommitEnt> commits; + + private final Combo combo; + + private class ComboCommitEnt { + + private final String message; + + private final ObjectId objectId; + + public ComboCommitEnt(ObjectId objecId, String message) { + this.objectId = objecId; + this.message = message; + } + + } + + private class CommitContentProposalProvider implements + IContentProposalProvider { + + public IContentProposal[] getProposals(String contents, int position) { + List<IContentProposal> list = new ArrayList<IContentProposal>(); + Pattern pattern = Pattern.compile(contents, + Pattern.CASE_INSENSITIVE); + for (int i = 0; i < commits.size(); i++) { + String message = commits.get(i).message; + if (message.length() >= contents.length() + && pattern.matcher(message).find()) { + list.add(makeContentProposal(message)); + } + } + return list.toArray(new IContentProposal[] {}); + } + + /* + * Make an IContentProposal for showing the specified String. + */ + private IContentProposal makeContentProposal(final String proposal) { + return new IContentProposal() { + public String getContent() { + return proposal; + } + + public String getDescription() { + return null; + } + + public String getLabel() { + return null; + } + + public int getCursorPosition() { + return proposal.length(); + } + }; + } + } + + /** + * Constructs a new instance of this class given its parent and a style + * value describing its behavior and appearance. + * + * @param parent + * a widget which will be the parent of the new instance (cannot + * be null) + * @param style + * the SWT style bits + */ + public CommitCombo(Composite parent, int style) { + super(parent, style); + + combo = new Combo(this, SWT.DROP_DOWN); + commits = new ArrayList<ComboCommitEnt>(); + + setLayout(GridLayoutFactory.swtDefaults().create()); + setLayoutData(GridDataFactory.fillDefaults().create()); + + GridData totalLabelData = new GridData(); + totalLabelData.horizontalAlignment = SWT.FILL; + totalLabelData.grabExcessHorizontalSpace = true; + combo.setLayoutData(totalLabelData); + combo.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + if (null == getValue()) + combo.setText(""); //$NON-NLS-1$ + } + }); + + UIUtils + .addBulbDecorator(combo, + UIText.CommitCombo_showSuggestedCommits); + + ContentProposalAdapter adapter = new ContentProposalAdapter(combo, + new ComboContentAdapter(), new CommitContentProposalProvider(), + null, null); + adapter.setPropagateKeys(true); + adapter + .setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE); + } + + /** + * Add a {@link RevCommit} to widget. + * + * @param revCommit + */ + public void add(RevCommit revCommit) { + Assert.isNotNull(revCommit); + checkWidget(); + + String shortSha1 = revCommit.getName().substring(0, 8); + String message = shortSha1 + ": " + revCommit.getShortMessage(); //$NON-NLS-1$ + combo.add(message); + commits.add(new ComboCommitEnt(revCommit.getId(), message)); + } + + /** + * Returns value of SHA-1 for selected commit. + * + * @param index + * index of item in check box + * @return SHA-1 of selected commit + */ + public ObjectId getItem(int index) { + checkWidget(); + + if (!(0 <= index && index < commits.size())) { + SWT.error(SWT.ERROR_INVALID_RANGE); + } + return commits.get(index).objectId; + } + + /** + * @return index of selected element + */ + public int getSelectedIndex() { + return combo.getSelectionIndex(); + } + + /** + * @return SHA-1 of selected commit + */ + public ObjectId getValue() { + int selectionIndex = combo.getSelectionIndex(); + return -1 != selectionIndex ? getItem(selectionIndex) : null; + } + + /** + * Selects the item with is associated with given <code>objectId</code> + * + * @param objectId + */ + public void setSelectedElement(ObjectId objectId) { + if (objectId == null) { + return; + } + + for (int i = 0; i < commits.size(); i++) + if (objectId.equals(commits.get(i).objectId)) { + combo.select(i); + break; + } + } + + @Override + public void setEnabled(boolean enabled) { + combo.setEnabled(enabled); + super.setEnabled(enabled); + } + + /** + * Sets the selection in the receiver's text field to an empty selection + * starting just before the first character. If the text field is editable, + * this has the effect of placing the i-beam at the start of the text. + */ + public void clearSelection() { + combo.clearSelection(); + combo.setText(""); //$NON-NLS-1$ + } + +} diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CreateTagDialog.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CreateTagDialog.java new file mode 100644 index 0000000000..6bc60318c2 --- /dev/null +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/dialogs/CreateTagDialog.java @@ -0,0 +1,553 @@ +/******************************************************************************* + * Copyright (C) 2010, Dariusz Luksza <dariusz@luksza.org> + * + * 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 + *******************************************************************************/ +package org.eclipse.egit.ui.internal.dialogs; + +import java.util.List; +import java.util.regex.Pattern; + +import org.eclipse.egit.ui.UIText; +import org.eclipse.egit.ui.UIUtils; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.IInputValidator; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.StringConverter; +import org.eclipse.jface.viewers.ColumnWeightData; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.ITableLabelProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.TableLayout; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.Tag; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +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.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.forms.events.ExpansionAdapter; +import org.eclipse.ui.forms.events.ExpansionEvent; +import org.eclipse.ui.forms.widgets.ExpandableComposite; +import org.eclipse.ui.model.IWorkbenchAdapter; +import org.eclipse.ui.model.WorkbenchContentProvider; +import org.eclipse.ui.model.WorkbenchLabelProvider; + +/** + * Dialog for creating and editing tags. + * + */ +public class CreateTagDialog extends Dialog { + + /** + * Button id for a "Clear" button (value 22). + */ + public static final int CLEAR_ID = 22; + + private String tagName; + + private String tagMessage; + + private ObjectId tagCommit; + + private boolean overwriteTag; + + private RevWalk revCommits; + + private List<Tag> existingTags; + + private Tag tag; + + private Text tagNameText; + + private Text tagMessageText; + + private Text tagNameErrorText; + + private Button overwriteButton; + + private TableViewer tagViewer; + + private CommitCombo commitCombo; + + private Pattern tagNamePattern; + + private final String branchName; + + private final IInputValidator tagNameValidator; + + class TagInputList extends LabelProvider implements IWorkbenchAdapter { + + private final List<Tag> tagList; + + public TagInputList(List<Tag> tagList) { + this.tagList = tagList; + } + + public Object[] getChildren(Object o) { + return tagList.toArray(new Object[] {}); + } + + public ImageDescriptor getImageDescriptor(Object object) { + return null; + } + + public String getLabel(Object o) { + if (o instanceof Tag) + return ((Tag) o).getTag(); + + return null; + } + + public Object getParent(Object o) { + return null; + } + + public Object getAdapter(Class adapter) { + if (adapter == IWorkbenchAdapter.class) + return this; + + return null; + } + } + + class TagLabelProvider extends WorkbenchLabelProvider implements + ITableLabelProvider { + + public Image getColumnImage(Object element, int columnIndex) { + return null; + } + + public String getColumnText(Object element, int columnIndex) { + return ((Tag) element).getTag(); + } + + } + + /** + * Construct dialog to creating or editing tag. + * + * @param parent + * @param tagNameValidator + * @param branchName + */ + public CreateTagDialog(Shell parent, IInputValidator tagNameValidator, + String branchName) { + super(parent); + this.tagNameValidator = tagNameValidator; + this.branchName = branchName; + } + + /** + * @return {@link ObjectId} of commit with new or edited tag should be + * associated with + */ + public ObjectId getTagCommit() { + return tagCommit; + } + + /** + * @return message for created or edited tag. + */ + public String getTagMessage() { + return tagMessage; + } + + /** + * @return name of new tag + */ + public String getTagName() { + return tagName; + } + + /** + * Indicates does tag should be forced to update (overwritten) or created. + * + * @return <code>true</code> if tag should be forced to update, + * <code>false</code> if tag should be created + */ + public boolean shouldOverWriteTag() { + return overwriteTag; + } + + /** + * Sets list of already existing tags. This list will be loaded in + * <code>Details</code> section of this dialog. + * + * @param existingTags + */ + public void setExistingTags(List<Tag> existingTags) { + this.existingTags = existingTags; + } + + /** + * Sets list of existing commits. This list will be loaded in + * {@link CommitCombo} widget in <code>Advanced</code> section of this + * dialog. + * + * @param revCommits + */ + public void setRevCommitList(RevWalk revCommits) { + this.revCommits = revCommits; + } + + /** + * Data from <code>tag</code> argument will be set in this dialog box. + * + * @param tag + */ + public void setTag(Tag tag) { + this.tag = tag; + } + + @Override + protected void configureShell(Shell newShell) { + super.configureShell(newShell); + + if (branchName != null) { + newShell.setText(NLS.bind( + UIText.CreateTagDialog_questionNewTagTitle, branchName)); + } + + newShell.setMinimumSize(703, 345); + } + + @Override + protected void createButtonsForButtonBar(Composite parent) { + parent.setLayout(GridLayoutFactory.swtDefaults().create()); + parent.setLayoutData(GridDataFactory.fillDefaults().grab(true, false) + .create()); + + Button clearButton = createButton(parent, CLEAR_ID, + UIText.CreateTagDialog_clearButton, false); + clearButton.setToolTipText(UIText.CreateTagDialog_clearButtonTooltip); + setButtonLayoutData(clearButton); + + Composite margin = new Composite(parent, SWT.NONE); + margin.setLayoutData(GridDataFactory.fillDefaults().grab(true, false) + .create()); + + super.createButtonsForButtonBar(parent); + + validateInput(); + } + + @Override + protected Control createDialogArea(final Composite parent) { + initializeDialogUnits(parent); + + Composite composite = (Composite) super.createDialogArea(parent); + + final SashForm mainForm = new SashForm(composite, SWT.HORIZONTAL | SWT.FILL); + mainForm.setLayoutData(GridDataFactory.fillDefaults().grab(true, true) + .create()); + + createLeftSection(mainForm); + createExistingTagsSection(mainForm); + + mainForm.setWeights(new int[] { 70, 30 }); + if (tag != null) { + setTagImpl(); + } + + applyDialogFont(parent); + return composite; + } + + @Override + protected void buttonPressed(int buttonId) { + switch (buttonId) { + case CLEAR_ID: + tagNameText.setText(""); //$NON-NLS-1$ + tagMessageText.setText(""); //$NON-NLS-1$ + commitCombo.clearSelection(); + + commitCombo.setEnabled(true); + tagNameText.setEnabled(true); + tagMessageText.setEnabled(true); + overwriteButton.setEnabled(false); + overwriteButton.setSelection(false); + break; + case IDialogConstants.OK_ID: + // read and store data from widgets + tagName = tagNameText.getText(); + tagCommit = commitCombo.getValue(); + tagMessage = tagMessageText.getText(); + overwriteTag = overwriteButton.getSelection(); + //$FALL-THROUGH$ continue propagating OK button action + default: + super.buttonPressed(buttonId); + } + } + + @Override + protected boolean isResizable() { + return true; + } + + private void createLeftSection(SashForm mainForm) { + Composite left = new Composite(mainForm, SWT.RESIZE); + left.setLayout(GridLayoutFactory.swtDefaults().margins(10, 5).create()); + left.setLayoutData(GridDataFactory.fillDefaults().grab(true, true) + .create()); + + Label label = new Label(left, SWT.WRAP); + label.setText(UIText.CreateTagDialog_tagName); + GridData data = new GridData(GridData.GRAB_HORIZONTAL + | GridData.HORIZONTAL_ALIGN_FILL + | GridData.VERTICAL_ALIGN_CENTER); + data.widthHint = convertHorizontalDLUsToPixels(IDialogConstants.MINIMUM_MESSAGE_AREA_WIDTH); + label.setLayoutData(data); + label.setFont(left.getFont()); + + tagNameText = new Text(left, SWT.SINGLE | SWT.BORDER); + tagNameText.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL + | GridData.HORIZONTAL_ALIGN_FILL)); + tagNameText.addModifyListener(new ModifyListener() { + + public void modifyText(ModifyEvent e) { + String textValue = Pattern.quote(tagNameText.getText()); + tagNamePattern = Pattern.compile(textValue, + Pattern.CASE_INSENSITIVE); + tagViewer.refresh(); + validateInput(); + } + }); + + UIUtils.addBulbDecorator(tagNameText, + UIText.CreateTagDialog_tagNameToolTip); + + tagNameErrorText = new Text(left, SWT.READ_ONLY | SWT.WRAP); + tagNameErrorText.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL + | GridData.HORIZONTAL_ALIGN_FILL)); + tagNameErrorText.setBackground(tagNameErrorText.getDisplay() + .getSystemColor(SWT.COLOR_WIDGET_BACKGROUND)); + + new Label(left, SWT.WRAP).setText(UIText.CreateTagDialog_tagMessage); + + tagMessageText = new Text(left, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL); + tagMessageText.setLayoutData(GridDataFactory.fillDefaults().minSize(50, + 50).grab(true, true).create()); + + // key listener taken from CommitDialog.createDialogArea() allow to + // commit with ctrl-enter + tagMessageText.addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent arg0) { + if (arg0.keyCode == SWT.CR + && (arg0.stateMask & SWT.CONTROL) > 0) { + Control button = getButton(IDialogConstants.OK_ID); + // fire OK action only when button is enabled + if (button != null && button.isEnabled()) + buttonPressed(IDialogConstants.OK_ID); + } else if (arg0.keyCode == SWT.TAB + && (arg0.stateMask & SWT.SHIFT) == 0) { + arg0.doit = false; + tagMessageText.traverse(SWT.TRAVERSE_TAB_NEXT); + } + validateInput(); + } + }); + + overwriteButton = new Button(left, SWT.CHECK); + overwriteButton.setEnabled(false); + overwriteButton.setText(UIText.CreateTagDialog_overwriteTag); + overwriteButton + .setToolTipText(UIText.CreateTagDialog_overwriteTagToolTip); + overwriteButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + boolean state = overwriteButton.getSelection(); + tagNameText.setEnabled(state); + commitCombo.setEnabled(state); + tagMessageText.setEnabled(state); + validateInput(); + } + }); + + createAdvancedSection(left); + } + + private void createAdvancedSection(final Composite composite) { + ExpandableComposite advanced = new ExpandableComposite(composite, + ExpandableComposite.TREE_NODE + | ExpandableComposite.CLIENT_INDENT); + + advanced.setText(UIText.CreateTagDialog_advanced); + advanced.setToolTipText(UIText.CreateTagDialog_advancedToolTip); + advanced.setLayoutData(GridDataFactory.fillDefaults().grab(true, false) + .create()); + + Composite advancedComposite = new Composite(advanced, SWT.WRAP); + advancedComposite.setLayout(GridLayoutFactory.swtDefaults().create()); + advancedComposite.setLayoutData(GridDataFactory.fillDefaults().grab( + true, true).create()); + + Label advancedLabel = new Label(advancedComposite, SWT.WRAP); + advancedLabel.setText(UIText.CreateTagDialog_advancedMessage); + advancedLabel.setLayoutData(GridDataFactory.fillDefaults().grab(true, + false).create()); + + commitCombo = new CommitCombo(advancedComposite, SWT.NORMAL); + commitCombo.setLayoutData(GridDataFactory.fillDefaults().grab(true, + false).create()); + + for (RevCommit revCommit : revCommits) + commitCombo.add(revCommit); + + advanced.setClient(advancedComposite); + advanced.addExpansionListener(new ExpansionAdapter() { + public void expansionStateChanged(ExpansionEvent e) { + composite.layout(); + } + }); + } + + private void createExistingTagsSection(Composite parent) { + Composite right = new Composite(parent, SWT.NORMAL); + right.setLayout(GridLayoutFactory.swtDefaults().create()); + right.setLayoutData(GridLayoutFactory.fillDefaults().create()); + + new Label(right, SWT.WRAP).setText(UIText.CreateTagDialog_existingTags); + + Table table = new Table(right, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER + | SWT.SINGLE); + table.setLayoutData(GridDataFactory.fillDefaults().grab(true, true) + .hint(80, 100).create()); + + TableLayout layout = new TableLayout(); + layout.addColumnData(new ColumnWeightData(100, 20)); + table.setLayout(layout); + + tagViewer = new TableViewer(table); + tagViewer.setLabelProvider(new TagLabelProvider()); + tagViewer.setContentProvider(new WorkbenchContentProvider()); + tagViewer.addSelectionChangedListener(new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + fillTagDialog(); + } + }); + + tagViewer.setInput(new TagInputList(existingTags)); + tagViewer.addFilter(new ViewerFilter() { + + @Override + public boolean select(Viewer viewer, Object parentElement, + Object element) { + Tag tag = (Tag) element; + + if (tagNamePattern != null) + return tagNamePattern.matcher(tag.getTag()).find(); + else + return true; + } + }); + + applyDialogFont(parent); + } + + private void validateInput() { + // don't validate if dialog is disposed + if (getShell() == null) { + return; + } + + // validate tag name + String tagNameMessage = tagNameValidator.isValid(tagNameText.getText()); + setTagNameError(tagNameMessage); + + String tagMessageVal = tagMessageText.getText().trim(); + + Control button = getButton(IDialogConstants.OK_ID); + if (button != null) { + boolean containsTagNameAndMessage = (tagNameMessage == null || tagMessageVal + .length() == 0) + && tagMessageVal.length() != 0; + boolean shouldOverwriteTag = (overwriteButton.getSelection() && Repository + .isValidRefName(Constants.R_TAGS + tagNameText.getText())); + + button.setEnabled(containsTagNameAndMessage || shouldOverwriteTag); + } + } + + private void fillTagDialog() { + IStructuredSelection selection = (IStructuredSelection) tagViewer + .getSelection(); + Object firstSelected = selection.getFirstElement(); + + if (firstSelected instanceof Tag) { + tag = (Tag) firstSelected; + + if (!overwriteButton.isEnabled()) { + String tagMessageValue = tag.getMessage(); + // don't enable OK button if we are dealing with un-annotated + // tag because JGit doesn't support them + if (tagMessageValue != null + && tagMessageValue.trim().length() != 0) + overwriteButton.setEnabled(true); + + tagNameText.setEnabled(false); + commitCombo.setEnabled(false); + tagMessageText.setEnabled(false); + } + + setTagImpl(); + } + } + + private void setTagImpl() { + tagNameText.setText(tag.getTag()); + commitCombo.setSelectedElement(tag.getObjId()); + + // handle un-annotated tags + String message = tag.getMessage(); + tagMessageText.setText(null != message ? message : ""); //$NON-NLS-1$ + } + + private void setTagNameError(String tagNameMessage) { + // copied form + // org.eclipse.jface.dialogs.InputDialog.setErrorMessage(String) + if (tagNameErrorText != null && !tagNameErrorText.isDisposed()) { + tagNameErrorText + .setText(tagNameMessage == null ? " \n " : tagNameMessage); //$NON-NLS-1$ + // Disable the error message text control if there is no error, or + // no error text (empty or whitespace only). Hide it also to avoid + // color change. + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=130281 + boolean hasError = tagNameMessage != null + && (StringConverter.removeWhiteSpaces(tagNameMessage)) + .length() > 0; + tagNameErrorText.setEnabled(hasError); + tagNameErrorText.setVisible(hasError); + tagNameErrorText.getParent().update(); + } + } + +} 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 3d26a4c08f..232a28330a 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 @@ -514,6 +514,7 @@ BranchSelectionDialog_NewBranch=&New branch BranchSelectionDialog_OkCheckout=&Checkout BranchSelectionDialog_OkReset=&Reset BranchSelectionDialog_QuestionNewBranchMessage=Enter name of the new branch. It will branch from the selected branch {0}. {1} will be prepended to the name you type +BranchSelectionDialog_QuestionNewBranchNameMessage=Enter new name of the {0} branch. refs/heads/ will be prepended to the name you type BranchSelectionDialog_QuestionNewBranchNameMessage=Enter new name of the {0} branch. {1} will be prepended to the name you type BranchSelectionDialog_QuestionNewBranchTitle=New branch BranchSelectionDialog_QuestionNewBranchNameTitle=Rename branch @@ -699,3 +700,30 @@ UIIcons_errorDeterminingIconBase=Can't determine icon base. UIIcons_errorLoadingPluginImage=Can't load plugin image. Untrack_untrack=Untrack Update_update=Update + +TagAction_cannotCheckout=Cannot checkout now +TagAction_cannotGetBranchName=Cannot get actual branch name +TagAction_repositoryState=Cannot checkout repository because it is in state: {0} +TagAction_errorCreatingTag=Error while creating tag {0} +TagAction_unableToCreateTag=Unable to create tag {0} +TagAction_errorDuringTagging=Error during tagging +TagAction_errorWhileMappingRevTag=Unable to get information about {0} tag. +TagAction_errorWhileGettingRevCommits=An error occurred while getting list of commits. +TagAction_unableToResolveHeadObjectId=Unable to resolve object id associated with current HEAD. +TagAction_creating=Creating {0} tag. +TagAction_taggingFailed=Tagging failed + +CreateTagDialog_tagName=Tag &name*: +CreateTagDialog_tagMessage=Tag &message*: +CreateTagDialog_questionNewTagTitle=Create new tag on branch "{0}" +CreateTagDialog_overwriteTag=Force &replace existing tag +CreateTagDialog_overwriteTagToolTip=Select this option if you want to change message or commit associated with already existing tag. +CreateTagDialog_existingTags=Existing tags: +CreateTagDialog_advanced=&Advanced +CreateTagDialog_advancedToolTip=In the advanced section you may choose the commit to be tagged. +CreateTagDialog_advancedMessage=Choose commit that should be associated with this tag. +CreateTagDialog_tagNameToolTip=Start typing tag name to filter list of existing tags. +CreateTagDialog_clearButton=C&lear +CreateTagDialog_clearButtonTooltip=Clear all dialog fields. + +CommitCombo_showSuggestedCommits=Start typing SHA-1 of existing commit or part of first line in commit message to see suggested commits. |