Generalize UIUtils.addContentProposalToText a bit more
[egit/eclipse.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / UIUtils.java
blob75b42c262b057536d90445bcfff76b1f00723c11
1 /*******************************************************************************
2 * Copyright (c) 2010, 2018 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.function.Function;
22 import java.util.regex.Pattern;
23 import java.util.regex.PatternSyntaxException;
25 import org.eclipse.core.commands.ExecutionException;
26 import org.eclipse.core.commands.NotEnabledException;
27 import org.eclipse.core.commands.NotHandledException;
28 import org.eclipse.core.commands.common.NotDefinedException;
29 import org.eclipse.core.expressions.IEvaluationContext;
30 import org.eclipse.core.runtime.Path;
31 import org.eclipse.egit.core.AdapterUtils;
32 import org.eclipse.egit.ui.internal.CommonUtils;
33 import org.eclipse.egit.ui.internal.RepositorySaveableFilter;
34 import org.eclipse.egit.ui.internal.UIIcons;
35 import org.eclipse.egit.ui.internal.UIText;
36 import org.eclipse.egit.ui.internal.components.RefContentProposal;
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.IControlContentAdapter;
51 import org.eclipse.jface.fieldassist.TextContentAdapter;
52 import org.eclipse.jface.resource.FontRegistry;
53 import org.eclipse.jface.resource.ImageDescriptor;
54 import org.eclipse.jface.resource.JFaceResources;
55 import org.eclipse.jface.resource.ResourceManager;
56 import org.eclipse.jface.text.BadLocationException;
57 import org.eclipse.jface.text.IDocument;
58 import org.eclipse.jface.text.IRegion;
59 import org.eclipse.jface.text.ITextViewer;
60 import org.eclipse.jface.text.hyperlink.IHyperlink;
61 import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
62 import org.eclipse.jface.viewers.AbstractTreeViewer;
63 import org.eclipse.jface.viewers.ISelection;
64 import org.eclipse.jface.viewers.Viewer;
65 import org.eclipse.jgit.annotations.Nullable;
66 import org.eclipse.jgit.lib.Ref;
67 import org.eclipse.jgit.lib.Repository;
68 import org.eclipse.osgi.util.NLS;
69 import org.eclipse.swt.SWT;
70 import org.eclipse.swt.custom.StyleRange;
71 import org.eclipse.swt.custom.StyledText;
72 import org.eclipse.swt.events.DisposeEvent;
73 import org.eclipse.swt.events.DisposeListener;
74 import org.eclipse.swt.events.KeyEvent;
75 import org.eclipse.swt.events.SelectionAdapter;
76 import org.eclipse.swt.events.SelectionEvent;
77 import org.eclipse.swt.graphics.Font;
78 import org.eclipse.swt.graphics.FontMetrics;
79 import org.eclipse.swt.graphics.GC;
80 import org.eclipse.swt.graphics.Image;
81 import org.eclipse.swt.graphics.ImageData;
82 import org.eclipse.swt.graphics.Point;
83 import org.eclipse.swt.graphics.Resource;
84 import org.eclipse.swt.layout.GridData;
85 import org.eclipse.swt.widgets.Button;
86 import org.eclipse.swt.widgets.Composite;
87 import org.eclipse.swt.widgets.Control;
88 import org.eclipse.swt.widgets.Display;
89 import org.eclipse.swt.widgets.Event;
90 import org.eclipse.swt.widgets.Text;
91 import org.eclipse.swt.widgets.ToolBar;
92 import org.eclipse.swt.widgets.ToolItem;
93 import org.eclipse.swt.widgets.Widget;
94 import org.eclipse.ui.IEditorDescriptor;
95 import org.eclipse.ui.IEditorRegistry;
96 import org.eclipse.ui.ISelectionListener;
97 import org.eclipse.ui.ISharedImages;
98 import org.eclipse.ui.ISources;
99 import org.eclipse.ui.IWorkbench;
100 import org.eclipse.ui.IWorkbenchCommandConstants;
101 import org.eclipse.ui.IWorkbenchPart;
102 import org.eclipse.ui.IWorkbenchWindow;
103 import org.eclipse.ui.PlatformUI;
104 import org.eclipse.ui.actions.ContributionItemFactory;
105 import org.eclipse.ui.handlers.IHandlerService;
106 import org.eclipse.ui.keys.IBindingService;
107 import org.eclipse.ui.services.IServiceLocator;
110 * Some utilities for UI code
112 public class UIUtils {
114 /** Default image descriptor for files */
115 public static final ImageDescriptor DEFAULT_FILE_IMG = PlatformUI
116 .getWorkbench().getSharedImages()
117 .getImageDescriptor(ISharedImages.IMG_OBJ_FILE);
120 * these activate the content assist; alphanumeric, space plus some expected
121 * special chars
123 private static final char[] VALUE_HELP_ACTIVATIONCHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123457890*@ <>".toCharArray(); //$NON-NLS-1$
126 * A keystroke for a "submit" action, see {@link #isSubmitKeyEvent(KeyEvent)}
128 public static final KeyStroke SUBMIT_KEY_STROKE = KeyStroke.getInstance(SWT.MOD1, SWT.CR);
131 * Handles a "previously used values" content assist.
132 * <p>
133 * Adding this to a text field will enable "content assist" by keeping track
134 * of the previously used valued for this field. The previously used values
135 * will be shown in the order they were last used (most recently used ones
136 * coming first in the list) and the number of entries is limited.
137 * <p>
138 * A "bulb" decorator will indicate that content assist is available for the
139 * field, and a tool tip is provided giving more information.
140 * <p>
141 * Content assist is activated by either typing in the field or by using a
142 * dedicated key stroke which is indicated in the tool tip. The list will be
143 * filtered with the content already in the text field with '*' being usable
144 * as wild card.
145 * <p>
146 * Note that the application must issue a call to {@link #updateProposals()}
147 * in order to add a new value to the "previously used values" list.
148 * <p>
149 * The list will be persisted in the plug-in dialog settings.
151 * @noextend not to be extended by clients
152 * @noimplement not to be implemented by clients, use
153 * {@link UIUtils#addPreviousValuesContentProposalToText(Text, String)}
154 * to create instances of this
156 public interface IPreviousValueProposalHandler {
158 * Updates the proposal list from the value in the text field.
159 * <p>
160 * The value will be truncated to the first 2000 characters in order to
161 * limit data size.
162 * <p>
163 * Note that this must be called in the UI thread, since it accesses the
164 * text field.
165 * <p>
166 * If the value is already in the list, it will become the first entry,
167 * otherwise it will be added at the beginning. Note that empty Strings
168 * will not be stored. The length of the list is limited, and the
169 * "oldest" entries will be removed once the limit is exceeded.
170 * <p>
171 * This call should only be issued if the value in the text field is
172 * "valid" in terms of the application.
174 public void updateProposals();
178 * A provider of candidate elements for which content proposals may be
179 * generated.
181 * @param <T>
182 * type of the candidate elements
184 public interface IContentProposalCandidateProvider<T> {
187 * Retrieves the collection of candidates eligible for content proposal
188 * generation.
190 * @return collection of candidates
192 public Collection<? extends T> getCandidates();
196 * A factory for creating {@link IContentProposal}s for {@link Ref}s.
198 * @param <T>
199 * type of elements to create proposals for
201 public interface IContentProposalFactory<T> {
204 * Gets a new {@link IContentProposal} for the given element. May or may
205 * not consider the {@link Pattern} and creates a proposal only if it
206 * matches the element with implementation-defined semantics.
208 * @param pattern
209 * constructed from current input to aid in selecting
210 * meaningful proposals; may be {@code null}
211 * @param element
212 * to consider creating a proposal for
213 * @return a new {@link IContentProposal}, or {@code null} if none
215 public IContentProposal getProposal(Pattern pattern, T element);
219 * A {@link ContentProposalAdapter} with a <em>public</em>
220 * {@link #openProposalPopup()} method.
222 public static class ExplicitContentProposalAdapter
223 extends ContentProposalAdapter {
226 * Construct a content proposal adapter that can assist the user with
227 * choosing content for the field.
229 * @param control
230 * the control for which the adapter is providing content
231 * assist. May not be {@code null}.
232 * @param controlContentAdapter
233 * the {@link IControlContentAdapter} used to obtain and
234 * update the control's contents as proposals are accepted.
235 * May not be {@code null}.
236 * @param proposalProvider
237 * the {@link IContentProposalProvider}> used to obtain
238 * content proposals for this control.
239 * @param keyStroke
240 * the keystroke that will invoke the content proposal popup.
241 * If this value is {@code null}, then proposals will be
242 * activated automatically when any of the auto activation
243 * characters are typed.
244 * @param autoActivationCharacters
245 * characters that trigger auto-activation of content
246 * proposal. If specified, these characters will trigger
247 * auto-activation of the proposal popup, regardless of
248 * whether an explicit invocation keyStroke was specified. If
249 * this parameter is {@code null}, then only a specified
250 * keyStroke will invoke content proposal. If this parameter
251 * is {@code null} and the keyStroke parameter is
252 * {@code null}, then all alphanumeric characters will
253 * auto-activate content proposal.
255 public ExplicitContentProposalAdapter(Control control,
256 IControlContentAdapter controlContentAdapter,
257 IContentProposalProvider proposalProvider,
258 KeyStroke keyStroke, char[] autoActivationCharacters) {
259 super(control, controlContentAdapter, proposalProvider, keyStroke,
260 autoActivationCharacters);
263 @Override
264 public void openProposalPopup() {
265 // Make this method accessible
266 super.openProposalPopup();
271 * @param id
272 * see {@link FontRegistry#get(String)}
273 * @return the font
275 public static Font getFont(final String id) {
276 return PlatformUI.getWorkbench().getThemeManager().getCurrentTheme()
277 .getFontRegistry().get(id);
281 * @param id
282 * see {@link FontRegistry#getBold(String)}
283 * @return the font
285 public static Font getBoldFont(final String id) {
286 return PlatformUI.getWorkbench().getThemeManager().getCurrentTheme()
287 .getFontRegistry().getBold(id);
291 * @param id
292 * see {@link FontRegistry#getItalic(String)}
293 * @return the font
295 public static Font getItalicFont(final String id) {
296 return PlatformUI.getWorkbench().getThemeManager().getCurrentTheme()
297 .getFontRegistry().getItalic(id);
301 * @return the indent of controls that depend on the previous control (e.g.
302 * a checkbox that is only enabled when the checkbox above it is
303 * checked)
305 public static int getControlIndent() {
306 // Eclipse 4.3: Use LayoutConstants.getIndent once we depend on 4.3
307 return 20;
311 * @param parent
312 * @param style
313 * @return a text field which is read-only but can be selected
315 public static Text createSelectableLabel(Composite parent, int style) {
316 // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=71765
317 Text text = new Text(parent, style | SWT.READ_ONLY);
318 text.setBackground(text.getDisplay().getSystemColor(
319 SWT.COLOR_WIDGET_BACKGROUND));
320 return text;
324 * Adds little bulb decoration to given control. Bulb will appear in top
325 * left corner of control after giving focus for this control.
327 * After clicking on bulb image text from <code>tooltip</code> will appear.
329 * @param control
330 * instance of {@link Control} object with should be decorated
331 * @param tooltip
332 * text value which should appear after clicking on bulb image.
333 * @return the {@link ControlDecoration} created
335 public static ControlDecoration addBulbDecorator(final Control control,
336 final String tooltip) {
337 ControlDecoration dec = new ControlDecoration(control, SWT.TOP
338 | SWT.LEFT);
340 dec.setImage(FieldDecorationRegistry.getDefault().getFieldDecoration(
341 FieldDecorationRegistry.DEC_CONTENT_PROPOSAL).getImage());
343 dec.setShowOnlyOnFocus(true);
344 dec.setShowHover(true);
346 dec.setDescriptionText(tooltip);
347 return dec;
351 * Creates a simple {@link Pattern} that can be used for matching content
352 * assist proposals. The pattern ignores leading blanks and allows '*' as a
353 * wildcard matching multiple arbitrary characters.
355 * @param content
356 * to create the pattern from
357 * @return the pattern, or {@code null} if none could be created
359 public static Pattern createProposalPattern(String content) {
360 // Make the simplest possible pattern check: allow "*"
361 // for multiple characters.
362 String patternString = content;
363 // Ignore spaces in the beginning.
364 while (patternString.length() > 0 && patternString.charAt(0) == ' ') {
365 patternString = patternString.substring(1);
368 // We quote the string as it may contain spaces
369 // and other stuff colliding with the pattern.
370 patternString = Pattern.quote(patternString);
372 patternString = patternString.replaceAll("\\x2A", ".*"); //$NON-NLS-1$ //$NON-NLS-2$
374 // Make sure we add a (logical) * at the end.
375 if (!patternString.endsWith(".*")) { //$NON-NLS-1$
376 patternString = patternString + ".*"; //$NON-NLS-1$
379 // Compile a case-insensitive pattern (assumes ASCII only).
380 Pattern pattern;
381 try {
382 pattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE);
383 } catch (PatternSyntaxException e) {
384 pattern = null;
386 return pattern;
390 * Adds a "previously used values" content proposal handler to a text field.
391 * <p>
392 * The list will be limited to 10 values.
394 * @param textField
395 * the text field
396 * @param preferenceKey
397 * the key under which to store the "previously used values" in
398 * the dialog settings
399 * @return the handler the proposal handler
401 public static IPreviousValueProposalHandler addPreviousValuesContentProposalToText(
402 final Text textField, final String preferenceKey) {
403 KeyStroke stroke = UIUtils
404 .getKeystrokeOfBestActiveBindingFor(IWorkbenchCommandConstants.EDIT_CONTENT_ASSIST);
405 if (stroke == null)
406 addBulbDecorator(textField,
407 UIText.UIUtils_StartTypingForPreviousValuesMessage);
408 else
409 addBulbDecorator(
410 textField,
411 NLS.bind(UIText.UIUtils_PressShortcutMessage,
412 stroke.format()));
414 IContentProposalProvider cp = new IContentProposalProvider() {
416 @Override
417 public IContentProposal[] getProposals(String contents, int position) {
418 List<IContentProposal> resultList = new ArrayList<>();
420 Pattern pattern = createProposalPattern(contents);
421 String[] proposals = org.eclipse.egit.ui.Activator.getDefault()
422 .getDialogSettings().getArray(preferenceKey);
423 if (proposals != null) {
424 for (final String uriString : proposals) {
426 if (pattern != null
427 && !pattern.matcher(uriString).matches()) {
428 continue;
430 IContentProposal propsal = new IContentProposal() {
432 @Override
433 public String getLabel() {
434 return null;
437 @Override
438 public String getDescription() {
439 return null;
442 @Override
443 public int getCursorPosition() {
444 return 0;
447 @Override
448 public String getContent() {
449 return uriString;
452 resultList.add(propsal);
455 return resultList.toArray(new IContentProposal[resultList
456 .size()]);
460 ContentProposalAdapter adapter = new ContentProposalAdapter(textField,
461 new TextContentAdapter(), cp, stroke,
462 VALUE_HELP_ACTIVATIONCHARS);
463 // set the acceptance style to always replace the complete content
464 adapter.setProposalAcceptanceStyle(
465 ContentProposalAdapter.PROPOSAL_REPLACE);
467 return new IPreviousValueProposalHandler() {
468 @Override
469 public void updateProposals() {
470 String value = textField.getText();
471 // don't store empty values
472 if (value.length() > 0) {
473 // we don't want to save too much in the preferences
474 if (value.length() > 2000) {
475 value = value.substring(0, 1999);
477 // now we need to mix the value into the list
478 IDialogSettings settings = org.eclipse.egit.ui.Activator
479 .getDefault().getDialogSettings();
480 String[] existingValues = settings.getArray(preferenceKey);
481 if (existingValues == null) {
482 existingValues = new String[] { value };
483 settings.put(preferenceKey, existingValues);
484 } else {
486 List<String> values = new ArrayList<>(
487 existingValues.length + 1);
489 for (String existingValue : existingValues)
490 values.add(existingValue);
491 // if it is already the first value, we don't need to do
492 // anything
493 if (values.indexOf(value) == 0)
494 return;
496 values.remove(value);
497 // we insert at the top
498 values.add(0, value);
499 // make sure to not store more than the maximum number
500 // of values
501 while (values.size() > 10)
502 values.remove(values.size() - 1);
504 settings.put(preferenceKey, values
505 .toArray(new String[values.size()]));
513 * Adds a content proposal for {@link Ref}s (branches, tags...) to a text
514 * field
516 * @param textField
517 * the text field
518 * @param repository
519 * the repository
520 * @param refListProvider
521 * provides the {@link Ref}s to show in the proposal
522 * @return the content proposal adapter set on the {@code textField}
524 public static final ExplicitContentProposalAdapter addRefContentProposalToText(
525 Text textField,
526 Repository repository,
527 IContentProposalCandidateProvider<Ref> refListProvider) {
528 return UIUtils.<Ref> addContentProposalToText(textField,
529 refListProvider, (pattern, ref) -> {
530 String shortenedName = Repository
531 .shortenRefName(ref.getName());
532 if (pattern != null
533 && !pattern.matcher(ref.getName()).matches()
534 && !pattern.matcher(shortenedName).matches()) {
535 return null;
537 return new RefContentProposal(repository, ref);
538 }, null,
539 UIText.UIUtils_StartTypingForRemoteRefMessage,
540 UIText.UIUtils_PressShortcutForRemoteRefMessage);
544 * Adds a content proposal for arbitrary elements to a text field.
546 * @param <T>
547 * type of the proposal candidate objects
549 * @param textField
550 * the text field
551 * @param candidateProvider
552 * {@link IContentProposalCandidateProvider} providing the
553 * candidates eligible for creating {@link IContentProposal}s
554 * @param factory
555 * {@link IContentProposalFactory} to use to create proposals
556 * from candidates
557 * @param patternProvider
558 * to convert the current text of the field into a pattern
559 * suitable for filtering the candidates. If {@code null}, a
560 * default pattern is constructed using
561 * {@link #createProposalPattern(String)}.
562 * @param startTypingMessage
563 * hover message if no content assist key binding is active
564 * @param shortcutMessage
565 * hover message if a content assist key binding is active,
566 * should have a "{0}" placeholder that will be filled by the
567 * appropriate keystroke
568 * @return the content proposal adapter set on the {@code textField}
570 public static final <T> ExplicitContentProposalAdapter addContentProposalToText(
571 Text textField,
572 IContentProposalCandidateProvider<T> candidateProvider,
573 IContentProposalFactory<T> factory,
574 Function<String, Pattern> patternProvider,
575 String startTypingMessage,
576 String shortcutMessage) {
577 KeyStroke stroke = UIUtils
578 .getKeystrokeOfBestActiveBindingFor(IWorkbenchCommandConstants.EDIT_CONTENT_ASSIST);
579 if (stroke == null) {
580 if (startTypingMessage == null) {
581 return null;
583 addBulbDecorator(textField, startTypingMessage);
584 } else {
585 addBulbDecorator(textField,
586 NLS.bind(shortcutMessage, stroke.format()));
588 IContentProposalProvider cp = new IContentProposalProvider() {
589 @Override
590 public IContentProposal[] getProposals(String contents, int position) {
591 List<IContentProposal> resultList = new ArrayList<>();
593 Collection<? extends T> candidates = candidateProvider
594 .getCandidates();
595 if (candidates == null) {
596 return null;
598 Pattern pattern = patternProvider != null
599 ? patternProvider.apply(contents)
600 : createProposalPattern(contents);
601 for (final T candidate : candidates) {
602 IContentProposal proposal = factory.getProposal(pattern,
603 candidate);
604 if (proposal != null) {
605 resultList.add(proposal);
608 return resultList.toArray(new IContentProposal[resultList
609 .size()]);
613 ExplicitContentProposalAdapter adapter = new ExplicitContentProposalAdapter(
614 textField, new TextContentAdapter(), cp, stroke,
615 UIUtils.VALUE_HELP_ACTIVATIONCHARS);
616 // set the acceptance style to always replace the complete content
617 adapter.setProposalAcceptanceStyle(
618 ContentProposalAdapter.PROPOSAL_REPLACE);
619 return adapter;
623 * Set enabled state of the control and all its children
624 * @param control
625 * @param enable
627 public static void setEnabledRecursively(final Control control,
628 final boolean enable) {
629 control.setEnabled(enable);
630 if (control instanceof Composite)
631 for (final Control child : ((Composite) control).getChildren())
632 setEnabledRecursively(child, enable);
636 * Dispose of the resource when the widget is disposed
638 * @param widget
639 * @param resource
641 public static void hookDisposal(Widget widget, final Resource resource) {
642 if (widget == null || resource == null)
643 return;
645 widget.addDisposeListener(new DisposeListener() {
647 @Override
648 public void widgetDisposed(DisposeEvent e) {
649 resource.dispose();
655 * Dispose of the resource manager when the widget is disposed
657 * @param widget
658 * @param resources
660 public static void hookDisposal(Widget widget,
661 final ResourceManager resources) {
662 if (widget == null || resources == null)
663 return;
665 widget.addDisposeListener(new DisposeListener() {
667 @Override
668 public void widgetDisposed(DisposeEvent e) {
669 resources.dispose();
674 /** Key is file extension, value is the reference to the image descriptor */
675 private static Map<String, SoftReference<ImageDescriptor>> extensionToDescriptor = new HashMap<>();
678 * Get editor image for path
680 * @param path
681 * @return image descriptor
683 public static ImageDescriptor getEditorImage(final String path) {
684 if (path == null || path.length() <= 0) {
685 return DEFAULT_FILE_IMG;
687 final String fileName = new Path(path).lastSegment();
688 if (fileName == null) {
689 return DEFAULT_FILE_IMG;
691 IEditorRegistry registry = PlatformUI.getWorkbench()
692 .getEditorRegistry();
693 IEditorDescriptor defaultEditor = registry.getDefaultEditor(fileName);
694 if (defaultEditor != null) {
695 return defaultEditor.getImageDescriptor();
697 // now we know there is no Eclipse editor for the file, and Eclipse will
698 // check Program.findProgram() and this will be slow, see bug 464891
699 int extensionIndex = fileName.lastIndexOf('.');
700 if (extensionIndex < 0) {
701 // Program.findProgram() uses extensions only
702 return DEFAULT_FILE_IMG;
704 String key = fileName.substring(extensionIndex);
705 SoftReference<ImageDescriptor> cached = extensionToDescriptor.get(key);
706 if (cached != null) {
707 ImageDescriptor descriptor = cached.get();
708 if (descriptor != null) {
709 return descriptor;
712 // In worst case this calls Program.findProgram() and blocks UI
713 ImageDescriptor descriptor = registry.getImageDescriptor(fileName);
714 extensionToDescriptor.put(key, new SoftReference<>(descriptor));
715 return descriptor;
719 * Get size of image descriptor as point.
721 * @param descriptor
722 * @return size
724 public static Point getSize(ImageDescriptor descriptor) {
725 ImageData data = descriptor.getImageData();
726 if (data == null)
727 return new Point(0, 0);
728 return new Point(data.width, data.height);
732 * Add expand all and collapse all toolbar items to the given toolbar bound
733 * to the given tree viewer
735 * @param toolbar
736 * @param viewer
737 * @return given toolbar
739 public static ToolBar addExpansionItems(final ToolBar toolbar,
740 final AbstractTreeViewer viewer) {
741 ToolItem collapseItem = new ToolItem(toolbar, SWT.PUSH);
742 Image collapseImage = UIIcons.COLLAPSEALL.createImage();
743 UIUtils.hookDisposal(collapseItem, collapseImage);
744 collapseItem.setImage(collapseImage);
745 collapseItem.setToolTipText(UIText.UIUtils_CollapseAll);
746 collapseItem.addSelectionListener(new SelectionAdapter() {
748 @Override
749 public void widgetSelected(SelectionEvent e) {
750 viewer.collapseAll();
755 ToolItem expandItem = new ToolItem(toolbar, SWT.PUSH);
756 Image expandImage = UIIcons.EXPAND_ALL.createImage();
757 UIUtils.hookDisposal(expandItem, expandImage);
758 expandItem.setImage(expandImage);
759 expandItem.setToolTipText(UIText.UIUtils_ExpandAll);
760 expandItem.addSelectionListener(new SelectionAdapter() {
762 @Override
763 public void widgetSelected(SelectionEvent e) {
764 viewer.expandAll();
768 return toolbar;
772 * Get dialog bound settings for given class using standard section name
774 * @param clazz
775 * @return dialog setting
777 public static IDialogSettings getDialogBoundSettings(final Class<?> clazz) {
778 return getDialogSettings(clazz.getName() + ".dialogBounds"); //$NON-NLS-1$
782 * Get dialog settings for given section name
784 * @param sectionName
785 * @return dialog settings
787 public static IDialogSettings getDialogSettings(final String sectionName) {
788 IDialogSettings settings = Activator.getDefault().getDialogSettings();
789 IDialogSettings section = settings.getSection(sectionName);
790 if (section == null)
791 section = settings.addNewSection(sectionName);
792 return section;
796 * Is viewer in a usable state?
798 * @param viewer
799 * @return true if usable, false if null or underlying control is null or
800 * disposed
802 public static boolean isUsable(final Viewer viewer) {
803 return viewer != null && isUsable(viewer.getControl());
807 * Is control usable?
809 * @param control
810 * @return true if usable, false if null or disposed
812 public static boolean isUsable(final Control control) {
813 return control != null && !control.isDisposed();
817 * Run command with specified id
819 * @param service
820 * @param id
822 public static void executeCommand(IHandlerService service, String id) {
823 executeCommand(service, id, null);
827 * Run command with specified id
829 * @param service
830 * @param id
831 * @param event
833 public static void executeCommand(IHandlerService service, String id,
834 Event event) {
835 try {
836 service.executeCommand(id, event);
837 } catch (ExecutionException e) {
838 Activator.handleError(e.getMessage(), e, false);
839 } catch (NotDefinedException e) {
840 Activator.handleError(e.getMessage(), e, false);
841 } catch (NotEnabledException e) {
842 Activator.handleError(e.getMessage(), e, false);
843 } catch (NotHandledException e) {
844 Activator.handleError(e.getMessage(), e, false);
849 * Determine if the key event represents a "submit" action
850 * (&lt;modifier&gt;+Enter).
852 * @param event
853 * @return true, if it means submit, false otherwise
855 public static boolean isSubmitKeyEvent(KeyEvent event) {
856 return (event.stateMask & SWT.MODIFIER_MASK) != 0
857 && event.keyCode == SUBMIT_KEY_STROKE.getNaturalKey();
861 * Prompt for saving all dirty editors for resources in the working
862 * directory of the specified repository.
864 * @param repository
865 * @return true, if the user opted to continue, false otherwise
866 * @see IWorkbench#saveAllEditors(boolean)
868 public static boolean saveAllEditors(Repository repository) {
869 return saveAllEditors(repository, null);
873 * Prompt for saving all dirty editors for resources in the working
874 * directory of the specified repository.
876 * If at least one file was saved, a dialog is displayed, asking the user if
877 * she wants to cancel the operation. Cancelling allows the user to do
878 * something with the newly saved files, before possibly restarting the
879 * operation.
881 * @param repository
882 * @param cancelConfirmationQuestion
883 * A string asking the user if she wants to cancel the operation.
884 * May be null to not open a dialog, but rather always continue.
885 * @return true, if the user opted to continue, false otherwise
886 * @see IWorkbench#saveAllEditors(boolean)
888 public static boolean saveAllEditors(Repository repository,
889 String cancelConfirmationQuestion) {
890 IWorkbench workbench = PlatformUI.getWorkbench();
891 IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
892 RepositorySaveableFilter filter = new RepositorySaveableFilter(
893 repository);
894 boolean success = workbench.saveAll(window, window, filter, true);
895 if (success && cancelConfirmationQuestion != null && filter.isAnythingSaved()){
896 // allow the user to cancel the operation to first do something with
897 // the newly saved files
898 String[] buttons = new String[] { IDialogConstants.YES_LABEL,
899 IDialogConstants.NO_LABEL };
900 MessageDialog dialog = new MessageDialog(window.getShell(),
901 UIText.CancelAfterSaveDialog_Title, null,
902 cancelConfirmationQuestion,
903 MessageDialog.QUESTION, buttons, 0) {
904 @Override
905 protected int getShellStyle() {
906 return (SWT.TITLE | SWT.BORDER | SWT.APPLICATION_MODAL
907 | SWT.SHEET | getDefaultOrientation());
910 int choice = dialog.open();
911 if (choice != 1) // user clicked "yes" or closed dialog -> cancel
912 return false;
914 return success;
918 * @param workbenchWindow the workbench window to use for creating the show in menu.
919 * @return the show in menu
921 public static MenuManager createShowInMenu(IWorkbenchWindow workbenchWindow) {
922 MenuManager showInSubMenu = new MenuManager(getShowInMenuLabel());
923 showInSubMenu.add(ContributionItemFactory.VIEWS_SHOW_IN.create(workbenchWindow));
924 return showInSubMenu;
928 * Use hyperlink detectors to find a text viewer's hyperlinks and apply them
929 * to the text widget. Existing overlapping styles are overwritten by new
930 * styles from this.
932 * @param textViewer
933 * @param hyperlinkDetectors
934 * @deprecated Instead of applying SWT styling directly use JFace
935 * infrastructure (
936 * {@link org.eclipse.jface.text.rules.DefaultDamagerRepairer
937 * DefaultDamagerRepairer},
938 * {@link org.eclipse.jface.text.rules.ITokenScanner
939 * ITokenScanner}) to do syntax coloring. See also
940 * {@link org.eclipse.egit.ui.internal.dialogs.HyperlinkTokenScanner}
943 @Deprecated
944 public static void applyHyperlinkDetectorStyleRanges(
945 ITextViewer textViewer, IHyperlinkDetector[] hyperlinkDetectors) {
946 StyleRange[] styleRanges = getHyperlinkDetectorStyleRanges(textViewer,
947 hyperlinkDetectors);
948 StyledText styledText = textViewer.getTextWidget();
949 // Apply hyperlink style ranges one by one. setStyleRange takes care to
950 // do the right thing in case they overlap with an existing style range.
951 for (StyleRange styleRange : styleRanges)
952 styledText.setStyleRange(styleRange);
956 * Use hyperlink detectors to find a text viewer's hyperlinks and return the
957 * style ranges to render them.
959 * @param textViewer
960 * @param hyperlinkDetectors
961 * @return the style ranges to render the detected hyperlinks
962 * @deprecated Instead of applying SWT styling directly use JFace
963 * infrastructure (
964 * {@link org.eclipse.jface.text.rules.DefaultDamagerRepairer
965 * DefaultDamagerRepairer},
966 * {@link org.eclipse.jface.text.rules.ITokenScanner
967 * ITokenScanner}) to do syntax coloring. See also
968 * {@link org.eclipse.egit.ui.internal.dialogs.HyperlinkTokenScanner}
971 @Deprecated
972 public static StyleRange[] getHyperlinkDetectorStyleRanges(
973 ITextViewer textViewer, IHyperlinkDetector[] hyperlinkDetectors) {
974 HashSet<StyleRange> styleRangeList = new LinkedHashSet<>();
975 if (hyperlinkDetectors != null && hyperlinkDetectors.length > 0) {
976 IDocument doc = textViewer.getDocument();
977 for (int line = 0; line < doc.getNumberOfLines(); line++) {
978 try {
979 IRegion region = doc.getLineInformation(line);
980 for (IHyperlinkDetector hyperLinkDetector : hyperlinkDetectors) {
981 IHyperlink[] hyperlinks = hyperLinkDetector
982 .detectHyperlinks(textViewer, region, true);
983 if (hyperlinks != null) {
984 for (IHyperlink hyperlink : hyperlinks) {
985 StyleRange hyperlinkStyleRange = new StyleRange(
986 hyperlink.getHyperlinkRegion()
987 .getOffset(), hyperlink
988 .getHyperlinkRegion()
989 .getLength(), Display
990 .getDefault().getSystemColor(
991 SWT.COLOR_BLUE),
992 Display.getDefault().getSystemColor(
993 SWT.COLOR_WHITE));
994 hyperlinkStyleRange.underline = true;
995 styleRangeList.add(hyperlinkStyleRange);
999 } catch (BadLocationException e) {
1000 Activator.logError(e.getMessage(), e);
1001 break;
1005 StyleRange[] styleRangeArray = new StyleRange[styleRangeList.size()];
1006 styleRangeList.toArray(styleRangeArray);
1007 return styleRangeArray;
1010 private static String getShowInMenuLabel() {
1011 IBindingService bindingService = AdapterUtils.adapt(PlatformUI
1012 .getWorkbench(), IBindingService.class);
1013 if (bindingService != null) {
1014 String keyBinding = bindingService
1015 .getBestActiveBindingFormattedFor(IWorkbenchCommandConstants.NAVIGATE_SHOW_IN_QUICK_MENU);
1016 if (keyBinding != null)
1017 return UIText.UIUtils_ShowInMenuLabel + '\t' + keyBinding;
1020 return UIText.UIUtils_ShowInMenuLabel;
1024 * Look up best active binding's keystroke for the given command
1026 * @param commandId
1027 * The identifier of the command for which the best active
1028 * binding's keystroke should be retrieved; must not be null.
1029 * @return {@code KeyStroke} for the best active binding for the specified
1030 * commandId or {@code null} if no binding is defined or if the
1031 * binding service returns a {@code TriggerSequence} containing more
1032 * than one {@code Trigger}.
1034 @Nullable
1035 public static KeyStroke getKeystrokeOfBestActiveBindingFor(String commandId) {
1036 IBindingService bindingService = AdapterUtils
1037 .adapt(PlatformUI.getWorkbench(), IBindingService.class);
1038 if (bindingService == null) {
1039 return null;
1041 TriggerSequence ts = bindingService.getBestActiveBindingFor(commandId);
1042 if (ts == null)
1043 return null;
1045 Trigger[] triggers = ts.getTriggers();
1046 if (triggers.length == 1 && triggers[0] instanceof KeyStroke)
1047 return (KeyStroke) triggers[0];
1048 else
1049 return null;
1053 * Copy from {@link org.eclipse.jface.dialogs.DialogPage} with changes to
1054 * accommodate the lack of a Dialog context.
1056 * @param button
1057 * the button to set the <code>GridData</code>
1059 public static void setButtonLayoutData(Button button) {
1060 GC gc = new GC(button);
1061 gc.setFont(JFaceResources.getDialogFont());
1062 FontMetrics fontMetrics = gc.getFontMetrics();
1063 gc.dispose();
1065 GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
1066 int widthHint = Dialog.convertHorizontalDLUsToPixels(fontMetrics,
1067 IDialogConstants.BUTTON_WIDTH);
1068 Point minSize = button.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
1069 data.widthHint = Math.max(widthHint, minSize.x);
1070 button.setLayoutData(data);
1074 * Locates the current part and selection and fires
1075 * {@link ISelectionListener#selectionChanged(IWorkbenchPart, ISelection)}
1076 * on the passed listener.
1078 * @param serviceLocator
1079 * @param selectionListener
1081 public static void notifySelectionChangedWithCurrentSelection(
1082 ISelectionListener selectionListener, IServiceLocator serviceLocator) {
1083 IHandlerService handlerService = CommonUtils.getService(serviceLocator, IHandlerService.class);
1084 IEvaluationContext state = handlerService.getCurrentState();
1085 // This seems to be the most reliable way to get the active part, it
1086 // also returns a part when it is called while creating a view that is
1087 // being shown.Getting the active part through the active workbench
1088 // window returned null in that case.
1089 Object partObject = state.getVariable(ISources.ACTIVE_PART_NAME);
1090 Object selectionObject = state
1091 .getVariable(ISources.ACTIVE_CURRENT_SELECTION_NAME);
1092 if (partObject instanceof IWorkbenchPart
1093 && selectionObject instanceof ISelection) {
1094 IWorkbenchPart part = (IWorkbenchPart) partObject;
1095 ISelection selection = (ISelection) selectionObject;
1096 if (!selection.isEmpty())
1097 selectionListener.selectionChanged(part, selection);