Better align repository hyperlink in commit viewer
[egit/eclipse.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / UIUtils.java
blobe38f1a15430b5d8394d95877bbb8a335f6deba34
1 /*******************************************************************************
2 * Copyright (c) 2010, 2015 SAP AG and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
8 * Contributors:
9 * Mathias Kinzler (SAP AG) - initial implementation
10 *******************************************************************************/
11 package org.eclipse.egit.ui;
13 import java.lang.ref.SoftReference;
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.LinkedHashSet;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.regex.Pattern;
22 import java.util.regex.PatternSyntaxException;
24 import org.eclipse.core.commands.ExecutionException;
25 import org.eclipse.core.commands.NotEnabledException;
26 import org.eclipse.core.commands.NotHandledException;
27 import org.eclipse.core.commands.common.NotDefinedException;
28 import org.eclipse.core.expressions.IEvaluationContext;
29 import org.eclipse.core.runtime.Path;
30 import org.eclipse.egit.core.AdapterUtils;
31 import org.eclipse.egit.ui.internal.CommonUtils;
32 import org.eclipse.egit.ui.internal.RepositorySaveableFilter;
33 import org.eclipse.egit.ui.internal.UIIcons;
34 import org.eclipse.egit.ui.internal.UIText;
35 import org.eclipse.egit.ui.internal.components.RefContentProposal;
36 import org.eclipse.jgit.annotations.Nullable;
37 import org.eclipse.jface.action.MenuManager;
38 import org.eclipse.jface.bindings.Trigger;
39 import org.eclipse.jface.bindings.TriggerSequence;
40 import org.eclipse.jface.bindings.keys.KeyStroke;
41 import org.eclipse.jface.dialogs.Dialog;
42 import org.eclipse.jface.dialogs.IDialogConstants;
43 import org.eclipse.jface.dialogs.IDialogSettings;
44 import org.eclipse.jface.dialogs.MessageDialog;
45 import org.eclipse.jface.fieldassist.ContentProposalAdapter;
46 import org.eclipse.jface.fieldassist.ControlDecoration;
47 import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
48 import org.eclipse.jface.fieldassist.IContentProposal;
49 import org.eclipse.jface.fieldassist.IContentProposalProvider;
50 import org.eclipse.jface.fieldassist.TextContentAdapter;
51 import org.eclipse.jface.resource.FontRegistry;
52 import org.eclipse.jface.resource.ImageDescriptor;
53 import org.eclipse.jface.resource.JFaceResources;
54 import org.eclipse.jface.resource.ResourceManager;
55 import org.eclipse.jface.text.BadLocationException;
56 import org.eclipse.jface.text.IDocument;
57 import org.eclipse.jface.text.IRegion;
58 import org.eclipse.jface.text.ITextViewer;
59 import org.eclipse.jface.text.hyperlink.IHyperlink;
60 import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
61 import org.eclipse.jface.viewers.AbstractTreeViewer;
62 import org.eclipse.jface.viewers.ISelection;
63 import org.eclipse.jface.viewers.Viewer;
64 import org.eclipse.jgit.lib.Ref;
65 import org.eclipse.jgit.lib.Repository;
66 import org.eclipse.osgi.util.NLS;
67 import org.eclipse.swt.SWT;
68 import org.eclipse.swt.custom.StyleRange;
69 import org.eclipse.swt.custom.StyledText;
70 import org.eclipse.swt.events.DisposeEvent;
71 import org.eclipse.swt.events.DisposeListener;
72 import org.eclipse.swt.events.KeyEvent;
73 import org.eclipse.swt.events.SelectionAdapter;
74 import org.eclipse.swt.events.SelectionEvent;
75 import org.eclipse.swt.graphics.Font;
76 import org.eclipse.swt.graphics.FontMetrics;
77 import org.eclipse.swt.graphics.GC;
78 import org.eclipse.swt.graphics.Image;
79 import org.eclipse.swt.graphics.ImageData;
80 import org.eclipse.swt.graphics.Point;
81 import org.eclipse.swt.graphics.Resource;
82 import org.eclipse.swt.layout.GridData;
83 import org.eclipse.swt.widgets.Button;
84 import org.eclipse.swt.widgets.Composite;
85 import org.eclipse.swt.widgets.Control;
86 import org.eclipse.swt.widgets.Display;
87 import org.eclipse.swt.widgets.Event;
88 import org.eclipse.swt.widgets.Text;
89 import org.eclipse.swt.widgets.ToolBar;
90 import org.eclipse.swt.widgets.ToolItem;
91 import org.eclipse.swt.widgets.Widget;
92 import org.eclipse.ui.IEditorDescriptor;
93 import org.eclipse.ui.IEditorRegistry;
94 import org.eclipse.ui.ISelectionListener;
95 import org.eclipse.ui.ISharedImages;
96 import org.eclipse.ui.ISources;
97 import org.eclipse.ui.IWorkbench;
98 import org.eclipse.ui.IWorkbenchCommandConstants;
99 import org.eclipse.ui.IWorkbenchPart;
100 import org.eclipse.ui.IWorkbenchWindow;
101 import org.eclipse.ui.PlatformUI;
102 import org.eclipse.ui.actions.ContributionItemFactory;
103 import org.eclipse.ui.handlers.IHandlerService;
104 import org.eclipse.ui.keys.IBindingService;
105 import org.eclipse.ui.services.IServiceLocator;
108 * Some utilities for UI code
110 public class UIUtils {
112 /** Default image descriptor for files */
113 public static final ImageDescriptor DEFAULT_FILE_IMG = PlatformUI
114 .getWorkbench().getSharedImages()
115 .getImageDescriptor(ISharedImages.IMG_OBJ_FILE);
118 * these activate the content assist; alphanumeric, space plus some expected
119 * special chars
121 private static final char[] VALUE_HELP_ACTIVATIONCHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123457890*@ <>".toCharArray(); //$NON-NLS-1$
124 * A keystroke for a "submit" action, see {@link #isSubmitKeyEvent(KeyEvent)}
126 public static final KeyStroke SUBMIT_KEY_STROKE = KeyStroke.getInstance(SWT.MOD1, SWT.CR);
129 * Handles a "previously used values" content assist.
130 * <p>
131 * Adding this to a text field will enable "content assist" by keeping track
132 * of the previously used valued for this field. The previously used values
133 * will be shown in the order they were last used (most recently used ones
134 * coming first in the list) and the number of entries is limited.
135 * <p>
136 * A "bulb" decorator will indicate that content assist is available for the
137 * field, and a tool tip is provided giving more information.
138 * <p>
139 * Content assist is activated by either typing in the field or by using a
140 * dedicated key stroke which is indicated in the tool tip. The list will be
141 * filtered with the content already in the text field with '*' being usable
142 * as wild card.
143 * <p>
144 * Note that the application must issue a call to {@link #updateProposals()}
145 * in order to add a new value to the "previously used values" list.
146 * <p>
147 * The list will be persisted in the plug-in dialog settings.
149 * @noextend not to be extended by clients
150 * @noimplement not to be implemented by clients, use
151 * {@link UIUtils#addPreviousValuesContentProposalToText(Text, String)}
152 * to create instances of this
154 public interface IPreviousValueProposalHandler {
156 * Updates the proposal list from the value in the text field.
157 * <p>
158 * The value will be truncated to the first 2000 characters in order to
159 * limit data size.
160 * <p>
161 * Note that this must be called in the UI thread, since it accesses the
162 * text field.
163 * <p>
164 * If the value is already in the list, it will become the first entry,
165 * otherwise it will be added at the beginning. Note that empty Strings
166 * will not be stored. The length of the list is limited, and the
167 * "oldest" entries will be removed once the limit is exceeded.
168 * <p>
169 * This call should only be issued if the value in the text field is
170 * "valid" in terms of the application.
172 public void updateProposals();
176 * A provider of candidate elements for which content proposals may be
177 * generated.
179 * @param <T>
180 * type of the candidate elements
182 public interface IContentProposalCandidateProvider<T> {
185 * Retrieves the collection of candidates eligible for content proposal
186 * generation.
188 * @return collection of candidates
190 public Collection<? extends T> getCandidates();
194 * A factory for creating {@link IContentProposal}s for {@link Ref}s.
196 * @param <T>
197 * type of elements to create proposals for
199 public interface IContentProposalFactory<T> {
202 * Gets a new {@link IContentProposal} for the given element. May or may
203 * not consider the {@link Pattern} and creates a proposal only if it
204 * matches the element with implementation-defined semantics.
206 * @param pattern
207 * constructed from current input to aid in selecting
208 * meaningful proposals; may be {@code null}
209 * @param element
210 * to consider creating a proposal for
211 * @return a new {@link IContentProposal}, or {@code null} if none
213 public IContentProposal getProposal(Pattern pattern, T element);
217 * @param id
218 * see {@link FontRegistry#get(String)}
219 * @return the font
221 public static Font getFont(final String id) {
222 return PlatformUI.getWorkbench().getThemeManager().getCurrentTheme()
223 .getFontRegistry().get(id);
227 * @param id
228 * see {@link FontRegistry#getBold(String)}
229 * @return the font
231 public static Font getBoldFont(final String id) {
232 return PlatformUI.getWorkbench().getThemeManager().getCurrentTheme()
233 .getFontRegistry().getBold(id);
237 * @param id
238 * see {@link FontRegistry#getItalic(String)}
239 * @return the font
241 public static Font getItalicFont(final String id) {
242 return PlatformUI.getWorkbench().getThemeManager().getCurrentTheme()
243 .getFontRegistry().getItalic(id);
247 * @return the indent of controls that depend on the previous control (e.g.
248 * a checkbox that is only enabled when the checkbox above it is
249 * checked)
251 public static int getControlIndent() {
252 // Eclipse 4.3: Use LayoutConstants.getIndent once we depend on 4.3
253 return 20;
257 * @param parent
258 * @param style
259 * @return a text field which is read-only but can be selected
261 public static Text createSelectableLabel(Composite parent, int style) {
262 // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=71765
263 Text text = new Text(parent, style | SWT.READ_ONLY);
264 text.setBackground(text.getDisplay().getSystemColor(
265 SWT.COLOR_WIDGET_BACKGROUND));
266 return text;
270 * Adds little bulb decoration to given control. Bulb will appear in top
271 * left corner of control after giving focus for this control.
273 * After clicking on bulb image text from <code>tooltip</code> will appear.
275 * @param control
276 * instance of {@link Control} object with should be decorated
277 * @param tooltip
278 * text value which should appear after clicking on bulb image.
279 * @return the {@link ControlDecoration} created
281 public static ControlDecoration addBulbDecorator(final Control control,
282 final String tooltip) {
283 ControlDecoration dec = new ControlDecoration(control, SWT.TOP
284 | SWT.LEFT);
286 dec.setImage(FieldDecorationRegistry.getDefault().getFieldDecoration(
287 FieldDecorationRegistry.DEC_CONTENT_PROPOSAL).getImage());
289 dec.setShowOnlyOnFocus(true);
290 dec.setShowHover(true);
292 dec.setDescriptionText(tooltip);
293 return dec;
297 * Creates a simple {@link Pattern} that can be used for matching content
298 * assist proposals. The pattern ignores leading blanks and allows '*' as a
299 * wildcard matching multiple arbitrary characters.
301 * @param content
302 * to create the pattern from
303 * @return the pattern, or {@code null} if none could be created
305 public static Pattern createProposalPattern(String content) {
306 // Make the simplest possible pattern check: allow "*"
307 // for multiple characters.
308 String patternString = content;
309 // Ignore spaces in the beginning.
310 while (patternString.length() > 0 && patternString.charAt(0) == ' ') {
311 patternString = patternString.substring(1);
314 // We quote the string as it may contain spaces
315 // and other stuff colliding with the pattern.
316 patternString = Pattern.quote(patternString);
318 patternString = patternString.replaceAll("\\x2A", ".*"); //$NON-NLS-1$ //$NON-NLS-2$
320 // Make sure we add a (logical) * at the end.
321 if (!patternString.endsWith(".*")) { //$NON-NLS-1$
322 patternString = patternString + ".*"; //$NON-NLS-1$
325 // Compile a case-insensitive pattern (assumes ASCII only).
326 Pattern pattern;
327 try {
328 pattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE);
329 } catch (PatternSyntaxException e) {
330 pattern = null;
332 return pattern;
336 * Adds a "previously used values" content proposal handler to a text field.
337 * <p>
338 * The list will be limited to 10 values.
340 * @param textField
341 * the text field
342 * @param preferenceKey
343 * the key under which to store the "previously used values" in
344 * the dialog settings
345 * @return the handler the proposal handler
347 public static IPreviousValueProposalHandler addPreviousValuesContentProposalToText(
348 final Text textField, final String preferenceKey) {
349 KeyStroke stroke = UIUtils
350 .getKeystrokeOfBestActiveBindingFor(IWorkbenchCommandConstants.EDIT_CONTENT_ASSIST);
351 if (stroke == null)
352 addBulbDecorator(textField,
353 UIText.UIUtils_StartTypingForPreviousValuesMessage);
354 else
355 addBulbDecorator(
356 textField,
357 NLS.bind(UIText.UIUtils_PressShortcutMessage,
358 stroke.format()));
360 IContentProposalProvider cp = new IContentProposalProvider() {
362 @Override
363 public IContentProposal[] getProposals(String contents, int position) {
364 List<IContentProposal> resultList = new ArrayList<>();
366 Pattern pattern = createProposalPattern(contents);
367 String[] proposals = org.eclipse.egit.ui.Activator.getDefault()
368 .getDialogSettings().getArray(preferenceKey);
369 if (proposals != null) {
370 for (final String uriString : proposals) {
372 if (pattern != null
373 && !pattern.matcher(uriString).matches()) {
374 continue;
376 IContentProposal propsal = new IContentProposal() {
378 @Override
379 public String getLabel() {
380 return null;
383 @Override
384 public String getDescription() {
385 return null;
388 @Override
389 public int getCursorPosition() {
390 return 0;
393 @Override
394 public String getContent() {
395 return uriString;
398 resultList.add(propsal);
401 return resultList.toArray(new IContentProposal[resultList
402 .size()]);
406 ContentProposalAdapter adapter = new ContentProposalAdapter(textField,
407 new TextContentAdapter(), cp, stroke,
408 VALUE_HELP_ACTIVATIONCHARS);
409 // set the acceptance style to always replace the complete content
410 adapter.setProposalAcceptanceStyle(
411 ContentProposalAdapter.PROPOSAL_REPLACE);
413 return new IPreviousValueProposalHandler() {
414 @Override
415 public void updateProposals() {
416 String value = textField.getText();
417 // don't store empty values
418 if (value.length() > 0) {
419 // we don't want to save too much in the preferences
420 if (value.length() > 2000) {
421 value = value.substring(0, 1999);
423 // now we need to mix the value into the list
424 IDialogSettings settings = org.eclipse.egit.ui.Activator
425 .getDefault().getDialogSettings();
426 String[] existingValues = settings.getArray(preferenceKey);
427 if (existingValues == null) {
428 existingValues = new String[] { value };
429 settings.put(preferenceKey, existingValues);
430 } else {
432 List<String> values = new ArrayList<>(
433 existingValues.length + 1);
435 for (String existingValue : existingValues)
436 values.add(existingValue);
437 // if it is already the first value, we don't need to do
438 // anything
439 if (values.indexOf(value) == 0)
440 return;
442 values.remove(value);
443 // we insert at the top
444 values.add(0, value);
445 // make sure to not store more than the maximum number
446 // of values
447 while (values.size() > 10)
448 values.remove(values.size() - 1);
450 settings.put(preferenceKey, values
451 .toArray(new String[values.size()]));
459 * Adds a content proposal for {@link Ref}s (branches, tags...) to a text
460 * field
462 * @param textField
463 * the text field
464 * @param repository
465 * the repository
466 * @param refListProvider
467 * provides the {@link Ref}s to show in the proposal
469 public static final void addRefContentProposalToText(Text textField,
470 Repository repository,
471 IContentProposalCandidateProvider<Ref> refListProvider) {
472 UIUtils.<Ref> addContentProposalToText(textField,
473 refListProvider, (pattern, ref) -> {
474 String shortenedName = Repository
475 .shortenRefName(ref.getName());
476 if (pattern != null
477 && !pattern.matcher(ref.getName()).matches()
478 && !pattern.matcher(shortenedName).matches()) {
479 return null;
481 return new RefContentProposal(repository, ref);
482 }, UIText.UIUtils_StartTypingForRemoteRefMessage,
483 UIText.UIUtils_PressShortcutForRemoteRefMessage);
487 * Adds a content proposal for arbitrary elements to a text field.
489 * @param <T>
490 * type of the proposal candidate objects
492 * @param textField
493 * the text field
494 * @param candidateProvider
495 * {@link IContentProposalCandidateProvider} providing the
496 * candidates eligible for creating {@link IContentProposal}s
497 * @param factory
498 * {@link IContentProposalFactory} to use to create proposals
499 * from candidates
500 * @param startTypingMessage
501 * hover message if no content assist key binding is active
502 * @param shortcutMessage
503 * hover message if a content assist key binding is active,
504 * should have a "{0}" placeholder that will be filled by the
505 * appropriate keystroke
507 public static final <T> void addContentProposalToText(Text textField,
508 IContentProposalCandidateProvider<T> candidateProvider,
509 IContentProposalFactory<T> factory, String startTypingMessage,
510 String shortcutMessage) {
511 KeyStroke stroke = UIUtils
512 .getKeystrokeOfBestActiveBindingFor(IWorkbenchCommandConstants.EDIT_CONTENT_ASSIST);
513 if (stroke == null) {
514 addBulbDecorator(textField, startTypingMessage);
515 } else {
516 addBulbDecorator(textField,
517 NLS.bind(shortcutMessage, stroke.format()));
519 IContentProposalProvider cp = new IContentProposalProvider() {
520 @Override
521 public IContentProposal[] getProposals(String contents, int position) {
522 List<IContentProposal> resultList = new ArrayList<>();
524 Pattern pattern = createProposalPattern(contents);
525 Collection<? extends T> candidates = candidateProvider
526 .getCandidates();
527 if (candidates != null) {
528 for (final T candidate : candidates) {
529 IContentProposal proposal = factory.getProposal(pattern,
530 candidate);
531 if (proposal != null) {
532 resultList.add(proposal);
536 return resultList.toArray(new IContentProposal[resultList
537 .size()]);
541 ContentProposalAdapter adapter = new ContentProposalAdapter(textField,
542 new TextContentAdapter(), cp, stroke,
543 UIUtils.VALUE_HELP_ACTIVATIONCHARS);
544 // set the acceptance style to always replace the complete content
545 adapter.setProposalAcceptanceStyle(
546 ContentProposalAdapter.PROPOSAL_REPLACE);
550 * Set enabled state of the control and all its children
551 * @param control
552 * @param enable
554 public static void setEnabledRecursively(final Control control,
555 final boolean enable) {
556 control.setEnabled(enable);
557 if (control instanceof Composite)
558 for (final Control child : ((Composite) control).getChildren())
559 setEnabledRecursively(child, enable);
563 * Dispose of the resource when the widget is disposed
565 * @param widget
566 * @param resource
568 public static void hookDisposal(Widget widget, final Resource resource) {
569 if (widget == null || resource == null)
570 return;
572 widget.addDisposeListener(new DisposeListener() {
574 @Override
575 public void widgetDisposed(DisposeEvent e) {
576 resource.dispose();
582 * Dispose of the resource manager when the widget is disposed
584 * @param widget
585 * @param resources
587 public static void hookDisposal(Widget widget,
588 final ResourceManager resources) {
589 if (widget == null || resources == null)
590 return;
592 widget.addDisposeListener(new DisposeListener() {
594 @Override
595 public void widgetDisposed(DisposeEvent e) {
596 resources.dispose();
601 /** Key is file extension, value is the reference to the image descriptor */
602 private static Map<String, SoftReference<ImageDescriptor>> extensionToDescriptor = new HashMap<>();
605 * Get editor image for path
607 * @param path
608 * @return image descriptor
610 public static ImageDescriptor getEditorImage(final String path) {
611 if (path == null || path.length() <= 0) {
612 return DEFAULT_FILE_IMG;
614 final String fileName = new Path(path).lastSegment();
615 if (fileName == null) {
616 return DEFAULT_FILE_IMG;
618 IEditorRegistry registry = PlatformUI.getWorkbench()
619 .getEditorRegistry();
620 IEditorDescriptor defaultEditor = registry.getDefaultEditor(fileName);
621 if (defaultEditor != null) {
622 return defaultEditor.getImageDescriptor();
624 // now we know there is no Eclipse editor for the file, and Eclipse will
625 // check Program.findProgram() and this will be slow, see bug 464891
626 int extensionIndex = fileName.lastIndexOf('.');
627 if (extensionIndex < 0) {
628 // Program.findProgram() uses extensions only
629 return DEFAULT_FILE_IMG;
631 String key = fileName.substring(extensionIndex);
632 SoftReference<ImageDescriptor> cached = extensionToDescriptor.get(key);
633 if (cached != null) {
634 ImageDescriptor descriptor = cached.get();
635 if (descriptor != null) {
636 return descriptor;
639 // In worst case this calls Program.findProgram() and blocks UI
640 ImageDescriptor descriptor = registry.getImageDescriptor(fileName);
641 extensionToDescriptor.put(key, new SoftReference<>(descriptor));
642 return descriptor;
646 * Get size of image descriptor as point.
648 * @param descriptor
649 * @return size
651 public static Point getSize(ImageDescriptor descriptor) {
652 ImageData data = descriptor.getImageData();
653 if (data == null)
654 return new Point(0, 0);
655 return new Point(data.width, data.height);
659 * Add expand all and collapse all toolbar items to the given toolbar bound
660 * to the given tree viewer
662 * @param toolbar
663 * @param viewer
664 * @return given toolbar
666 public static ToolBar addExpansionItems(final ToolBar toolbar,
667 final AbstractTreeViewer viewer) {
668 ToolItem collapseItem = new ToolItem(toolbar, SWT.PUSH);
669 Image collapseImage = UIIcons.COLLAPSEALL.createImage();
670 UIUtils.hookDisposal(collapseItem, collapseImage);
671 collapseItem.setImage(collapseImage);
672 collapseItem.setToolTipText(UIText.UIUtils_CollapseAll);
673 collapseItem.addSelectionListener(new SelectionAdapter() {
675 @Override
676 public void widgetSelected(SelectionEvent e) {
677 viewer.collapseAll();
682 ToolItem expandItem = new ToolItem(toolbar, SWT.PUSH);
683 Image expandImage = UIIcons.EXPAND_ALL.createImage();
684 UIUtils.hookDisposal(expandItem, expandImage);
685 expandItem.setImage(expandImage);
686 expandItem.setToolTipText(UIText.UIUtils_ExpandAll);
687 expandItem.addSelectionListener(new SelectionAdapter() {
689 @Override
690 public void widgetSelected(SelectionEvent e) {
691 viewer.expandAll();
695 return toolbar;
699 * Get dialog bound settings for given class using standard section name
701 * @param clazz
702 * @return dialog setting
704 public static IDialogSettings getDialogBoundSettings(final Class<?> clazz) {
705 return getDialogSettings(clazz.getName() + ".dialogBounds"); //$NON-NLS-1$
709 * Get dialog settings for given section name
711 * @param sectionName
712 * @return dialog settings
714 public static IDialogSettings getDialogSettings(final String sectionName) {
715 IDialogSettings settings = Activator.getDefault().getDialogSettings();
716 IDialogSettings section = settings.getSection(sectionName);
717 if (section == null)
718 section = settings.addNewSection(sectionName);
719 return section;
723 * Is viewer in a usable state?
725 * @param viewer
726 * @return true if usable, false if null or underlying control is null or
727 * disposed
729 public static boolean isUsable(final Viewer viewer) {
730 return viewer != null && isUsable(viewer.getControl());
734 * Is control usable?
736 * @param control
737 * @return true if usable, false if null or disposed
739 public static boolean isUsable(final Control control) {
740 return control != null && !control.isDisposed();
744 * Run command with specified id
746 * @param service
747 * @param id
749 public static void executeCommand(IHandlerService service, String id) {
750 executeCommand(service, id, null);
754 * Run command with specified id
756 * @param service
757 * @param id
758 * @param event
760 public static void executeCommand(IHandlerService service, String id,
761 Event event) {
762 try {
763 service.executeCommand(id, event);
764 } catch (ExecutionException e) {
765 Activator.handleError(e.getMessage(), e, false);
766 } catch (NotDefinedException e) {
767 Activator.handleError(e.getMessage(), e, false);
768 } catch (NotEnabledException e) {
769 Activator.handleError(e.getMessage(), e, false);
770 } catch (NotHandledException e) {
771 Activator.handleError(e.getMessage(), e, false);
776 * Determine if the key event represents a "submit" action
777 * (&lt;modifier&gt;+Enter).
779 * @param event
780 * @return true, if it means submit, false otherwise
782 public static boolean isSubmitKeyEvent(KeyEvent event) {
783 return (event.stateMask & SWT.MODIFIER_MASK) != 0
784 && event.keyCode == SUBMIT_KEY_STROKE.getNaturalKey();
788 * Prompt for saving all dirty editors for resources in the working
789 * directory of the specified repository.
791 * @param repository
792 * @return true, if the user opted to continue, false otherwise
793 * @see IWorkbench#saveAllEditors(boolean)
795 public static boolean saveAllEditors(Repository repository) {
796 return saveAllEditors(repository, null);
800 * Prompt for saving all dirty editors for resources in the working
801 * directory of the specified repository.
803 * If at least one file was saved, a dialog is displayed, asking the user if
804 * she wants to cancel the operation. Cancelling allows the user to do
805 * something with the newly saved files, before possibly restarting the
806 * operation.
808 * @param repository
809 * @param cancelConfirmationQuestion
810 * A string asking the user if she wants to cancel the operation.
811 * May be null to not open a dialog, but rather always continue.
812 * @return true, if the user opted to continue, false otherwise
813 * @see IWorkbench#saveAllEditors(boolean)
815 public static boolean saveAllEditors(Repository repository,
816 String cancelConfirmationQuestion) {
817 IWorkbench workbench = PlatformUI.getWorkbench();
818 IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
819 RepositorySaveableFilter filter = new RepositorySaveableFilter(
820 repository);
821 boolean success = workbench.saveAll(window, window, filter, true);
822 if (success && cancelConfirmationQuestion != null && filter.isAnythingSaved()){
823 // allow the user to cancel the operation to first do something with
824 // the newly saved files
825 String[] buttons = new String[] { IDialogConstants.YES_LABEL,
826 IDialogConstants.NO_LABEL };
827 MessageDialog dialog = new MessageDialog(window.getShell(),
828 UIText.CancelAfterSaveDialog_Title, null,
829 cancelConfirmationQuestion,
830 MessageDialog.QUESTION, buttons, 0) {
831 @Override
832 protected int getShellStyle() {
833 return (SWT.TITLE | SWT.BORDER | SWT.APPLICATION_MODAL
834 | SWT.SHEET | getDefaultOrientation());
837 int choice = dialog.open();
838 if (choice != 1) // user clicked "yes" or closed dialog -> cancel
839 return false;
841 return success;
845 * @param workbenchWindow the workbench window to use for creating the show in menu.
846 * @return the show in menu
848 public static MenuManager createShowInMenu(IWorkbenchWindow workbenchWindow) {
849 MenuManager showInSubMenu = new MenuManager(getShowInMenuLabel());
850 showInSubMenu.add(ContributionItemFactory.VIEWS_SHOW_IN.create(workbenchWindow));
851 return showInSubMenu;
855 * Use hyperlink detectors to find a text viewer's hyperlinks and apply them
856 * to the text widget. Existing overlapping styles are overwritten by new
857 * styles from this.
859 * @param textViewer
860 * @param hyperlinkDetectors
861 * @deprecated Instead of applying SWT styling directly use JFace
862 * infrastructure (
863 * {@link org.eclipse.jface.text.rules.DefaultDamagerRepairer
864 * DefaultDamagerRepairer},
865 * {@link org.eclipse.jface.text.rules.ITokenScanner
866 * ITokenScanner}) to do syntax coloring. See also
867 * {@link org.eclipse.egit.ui.internal.dialogs.HyperlinkTokenScanner}
870 @Deprecated
871 public static void applyHyperlinkDetectorStyleRanges(
872 ITextViewer textViewer, IHyperlinkDetector[] hyperlinkDetectors) {
873 StyleRange[] styleRanges = getHyperlinkDetectorStyleRanges(textViewer,
874 hyperlinkDetectors);
875 StyledText styledText = textViewer.getTextWidget();
876 // Apply hyperlink style ranges one by one. setStyleRange takes care to
877 // do the right thing in case they overlap with an existing style range.
878 for (StyleRange styleRange : styleRanges)
879 styledText.setStyleRange(styleRange);
883 * Use hyperlink detectors to find a text viewer's hyperlinks and return the
884 * style ranges to render them.
886 * @param textViewer
887 * @param hyperlinkDetectors
888 * @return the style ranges to render the detected hyperlinks
889 * @deprecated Instead of applying SWT styling directly use JFace
890 * infrastructure (
891 * {@link org.eclipse.jface.text.rules.DefaultDamagerRepairer
892 * DefaultDamagerRepairer},
893 * {@link org.eclipse.jface.text.rules.ITokenScanner
894 * ITokenScanner}) to do syntax coloring. See also
895 * {@link org.eclipse.egit.ui.internal.dialogs.HyperlinkTokenScanner}
898 @Deprecated
899 public static StyleRange[] getHyperlinkDetectorStyleRanges(
900 ITextViewer textViewer, IHyperlinkDetector[] hyperlinkDetectors) {
901 HashSet<StyleRange> styleRangeList = new LinkedHashSet<>();
902 if (hyperlinkDetectors != null && hyperlinkDetectors.length > 0) {
903 IDocument doc = textViewer.getDocument();
904 for (int line = 0; line < doc.getNumberOfLines(); line++) {
905 try {
906 IRegion region = doc.getLineInformation(line);
907 for (IHyperlinkDetector hyperLinkDetector : hyperlinkDetectors) {
908 IHyperlink[] hyperlinks = hyperLinkDetector
909 .detectHyperlinks(textViewer, region, true);
910 if (hyperlinks != null) {
911 for (IHyperlink hyperlink : hyperlinks) {
912 StyleRange hyperlinkStyleRange = new StyleRange(
913 hyperlink.getHyperlinkRegion()
914 .getOffset(), hyperlink
915 .getHyperlinkRegion()
916 .getLength(), Display
917 .getDefault().getSystemColor(
918 SWT.COLOR_BLUE),
919 Display.getDefault().getSystemColor(
920 SWT.COLOR_WHITE));
921 hyperlinkStyleRange.underline = true;
922 styleRangeList.add(hyperlinkStyleRange);
926 } catch (BadLocationException e) {
927 Activator.logError(e.getMessage(), e);
928 break;
932 StyleRange[] styleRangeArray = new StyleRange[styleRangeList.size()];
933 styleRangeList.toArray(styleRangeArray);
934 return styleRangeArray;
937 private static String getShowInMenuLabel() {
938 IBindingService bindingService = AdapterUtils.adapt(PlatformUI
939 .getWorkbench(), IBindingService.class);
940 if (bindingService != null) {
941 String keyBinding = bindingService
942 .getBestActiveBindingFormattedFor(IWorkbenchCommandConstants.NAVIGATE_SHOW_IN_QUICK_MENU);
943 if (keyBinding != null)
944 return UIText.UIUtils_ShowInMenuLabel + '\t' + keyBinding;
947 return UIText.UIUtils_ShowInMenuLabel;
951 * Look up best active binding's keystroke for the given command
953 * @param commandId
954 * The identifier of the command for which the best active
955 * binding's keystroke should be retrieved; must not be null.
956 * @return {@code KeyStroke} for the best active binding for the specified
957 * commandId or {@code null} if no binding is defined or if the
958 * binding service returns a {@code TriggerSequence} containing more
959 * than one {@code Trigger}.
961 @Nullable
962 public static KeyStroke getKeystrokeOfBestActiveBindingFor(String commandId) {
963 IBindingService bindingService = AdapterUtils
964 .adapt(PlatformUI.getWorkbench(), IBindingService.class);
965 if (bindingService == null) {
966 return null;
968 TriggerSequence ts = bindingService.getBestActiveBindingFor(commandId);
969 if (ts == null)
970 return null;
972 Trigger[] triggers = ts.getTriggers();
973 if (triggers.length == 1 && triggers[0] instanceof KeyStroke)
974 return (KeyStroke) triggers[0];
975 else
976 return null;
980 * Copy from {@link org.eclipse.jface.dialogs.DialogPage} with changes to
981 * accommodate the lack of a Dialog context.
983 * @param button
984 * the button to set the <code>GridData</code>
986 public static void setButtonLayoutData(Button button) {
987 GC gc = new GC(button);
988 gc.setFont(JFaceResources.getDialogFont());
989 FontMetrics fontMetrics = gc.getFontMetrics();
990 gc.dispose();
992 GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
993 int widthHint = Dialog.convertHorizontalDLUsToPixels(fontMetrics,
994 IDialogConstants.BUTTON_WIDTH);
995 Point minSize = button.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
996 data.widthHint = Math.max(widthHint, minSize.x);
997 button.setLayoutData(data);
1001 * Locates the current part and selection and fires
1002 * {@link ISelectionListener#selectionChanged(IWorkbenchPart, ISelection)}
1003 * on the passed listener.
1005 * @param serviceLocator
1006 * @param selectionListener
1008 public static void notifySelectionChangedWithCurrentSelection(
1009 ISelectionListener selectionListener, IServiceLocator serviceLocator) {
1010 IHandlerService handlerService = CommonUtils.getService(serviceLocator, IHandlerService.class);
1011 IEvaluationContext state = handlerService.getCurrentState();
1012 // This seems to be the most reliable way to get the active part, it
1013 // also returns a part when it is called while creating a view that is
1014 // being shown.Getting the active part through the active workbench
1015 // window returned null in that case.
1016 Object partObject = state.getVariable(ISources.ACTIVE_PART_NAME);
1017 Object selectionObject = state
1018 .getVariable(ISources.ACTIVE_CURRENT_SELECTION_NAME);
1019 if (partObject instanceof IWorkbenchPart
1020 && selectionObject instanceof ISelection) {
1021 IWorkbenchPart part = (IWorkbenchPart) partObject;
1022 ISelection selection = (ISelection) selectionObject;
1023 if (!selection.isEmpty())
1024 selectionListener.selectionChanged(part, selection);