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 2.0
5 * which accompanies this distribution, and is available at
6 * https://www.eclipse.org/legal/epl-2.0/
8 * SPDX-License-Identifier: EPL-2.0
11 * Mathias Kinzler (SAP AG) - initial implementation
12 *******************************************************************************/
13 package org
.eclipse
.egit
.ui
;
15 import java
.lang
.ref
.SoftReference
;
16 import java
.util
.ArrayList
;
17 import java
.util
.Arrays
;
18 import java
.util
.Collection
;
19 import java
.util
.HashMap
;
20 import java
.util
.List
;
22 import java
.util
.function
.Function
;
23 import java
.util
.regex
.Pattern
;
24 import java
.util
.regex
.PatternSyntaxException
;
26 import org
.eclipse
.core
.commands
.ExecutionException
;
27 import org
.eclipse
.core
.commands
.NotEnabledException
;
28 import org
.eclipse
.core
.commands
.NotHandledException
;
29 import org
.eclipse
.core
.commands
.common
.NotDefinedException
;
30 import org
.eclipse
.core
.expressions
.IEvaluationContext
;
31 import org
.eclipse
.core
.runtime
.Adapters
;
32 import org
.eclipse
.core
.runtime
.Path
;
33 import org
.eclipse
.egit
.core
.internal
.Utils
;
34 import org
.eclipse
.egit
.ui
.internal
.RepositorySaveableFilter
;
35 import org
.eclipse
.egit
.ui
.internal
.UIIcons
;
36 import org
.eclipse
.egit
.ui
.internal
.UIText
;
37 import org
.eclipse
.egit
.ui
.internal
.components
.RefContentProposal
;
38 import org
.eclipse
.jface
.action
.MenuManager
;
39 import org
.eclipse
.jface
.bindings
.Trigger
;
40 import org
.eclipse
.jface
.bindings
.TriggerSequence
;
41 import org
.eclipse
.jface
.bindings
.keys
.KeyStroke
;
42 import org
.eclipse
.jface
.dialogs
.Dialog
;
43 import org
.eclipse
.jface
.dialogs
.IDialogConstants
;
44 import org
.eclipse
.jface
.dialogs
.IDialogSettings
;
45 import org
.eclipse
.jface
.dialogs
.MessageDialog
;
46 import org
.eclipse
.jface
.fieldassist
.ContentProposalAdapter
;
47 import org
.eclipse
.jface
.fieldassist
.ControlDecoration
;
48 import org
.eclipse
.jface
.fieldassist
.FieldDecorationRegistry
;
49 import org
.eclipse
.jface
.fieldassist
.IContentProposal
;
50 import org
.eclipse
.jface
.fieldassist
.IContentProposalProvider
;
51 import org
.eclipse
.jface
.fieldassist
.IControlContentAdapter
;
52 import org
.eclipse
.jface
.fieldassist
.TextContentAdapter
;
53 import org
.eclipse
.jface
.resource
.FontRegistry
;
54 import org
.eclipse
.jface
.resource
.ImageDescriptor
;
55 import org
.eclipse
.jface
.resource
.JFaceResources
;
56 import org
.eclipse
.jface
.resource
.ResourceManager
;
57 import org
.eclipse
.jface
.viewers
.AbstractTreeViewer
;
58 import org
.eclipse
.jface
.viewers
.ISelection
;
59 import org
.eclipse
.jface
.viewers
.StructuredSelection
;
60 import org
.eclipse
.jface
.viewers
.Viewer
;
61 import org
.eclipse
.jgit
.annotations
.NonNull
;
62 import org
.eclipse
.jgit
.annotations
.Nullable
;
63 import org
.eclipse
.jgit
.lib
.Ref
;
64 import org
.eclipse
.jgit
.lib
.Repository
;
65 import org
.eclipse
.osgi
.util
.NLS
;
66 import org
.eclipse
.swt
.SWT
;
67 import org
.eclipse
.swt
.accessibility
.AccessibleAdapter
;
68 import org
.eclipse
.swt
.accessibility
.AccessibleEvent
;
69 import org
.eclipse
.swt
.events
.DisposeEvent
;
70 import org
.eclipse
.swt
.events
.DisposeListener
;
71 import org
.eclipse
.swt
.events
.KeyEvent
;
72 import org
.eclipse
.swt
.events
.SelectionAdapter
;
73 import org
.eclipse
.swt
.events
.SelectionEvent
;
74 import org
.eclipse
.swt
.graphics
.Font
;
75 import org
.eclipse
.swt
.graphics
.FontMetrics
;
76 import org
.eclipse
.swt
.graphics
.GC
;
77 import org
.eclipse
.swt
.graphics
.Image
;
78 import org
.eclipse
.swt
.graphics
.Point
;
79 import org
.eclipse
.swt
.graphics
.Resource
;
80 import org
.eclipse
.swt
.layout
.GridData
;
81 import org
.eclipse
.swt
.widgets
.Button
;
82 import org
.eclipse
.swt
.widgets
.Composite
;
83 import org
.eclipse
.swt
.widgets
.Control
;
84 import org
.eclipse
.swt
.widgets
.Event
;
85 import org
.eclipse
.swt
.widgets
.Label
;
86 import org
.eclipse
.swt
.widgets
.Text
;
87 import org
.eclipse
.swt
.widgets
.ToolBar
;
88 import org
.eclipse
.swt
.widgets
.ToolItem
;
89 import org
.eclipse
.swt
.widgets
.Widget
;
90 import org
.eclipse
.ui
.IEditorDescriptor
;
91 import org
.eclipse
.ui
.IEditorRegistry
;
92 import org
.eclipse
.ui
.ISelectionListener
;
93 import org
.eclipse
.ui
.ISharedImages
;
94 import org
.eclipse
.ui
.ISources
;
95 import org
.eclipse
.ui
.IWorkbench
;
96 import org
.eclipse
.ui
.IWorkbenchCommandConstants
;
97 import org
.eclipse
.ui
.IWorkbenchPart
;
98 import org
.eclipse
.ui
.IWorkbenchWindow
;
99 import org
.eclipse
.ui
.PlatformUI
;
100 import org
.eclipse
.ui
.actions
.ContributionItemFactory
;
101 import org
.eclipse
.ui
.handlers
.IHandlerService
;
102 import org
.eclipse
.ui
.keys
.IBindingService
;
103 import org
.eclipse
.ui
.services
.IServiceLocator
;
106 * Some utilities for UI code
108 public class UIUtils
{
110 private static final String CSS_CLASS_KEY
= "org.eclipse.e4.ui.css.CssClassName"; //$NON-NLS-1$
112 private static final String CSS_DISABLED_KEY
= "org.eclipse.e4.ui.css.disabled"; //$NON-NLS-1$
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
123 private static final char[] VALUE_HELP_ACTIVATIONCHARS
= "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123457890*@ <>".toCharArray(); //$NON-NLS-1$
125 /** Vertical or horizontal spaces. */
126 private static final Pattern SPACES
= Pattern
.compile("(?:\\v|\\h)+"); //$NON-NLS-1$
129 * A keystroke for a "submit" action, see {@link #isSubmitKeyEvent(KeyEvent)}
131 public static final KeyStroke SUBMIT_KEY_STROKE
= KeyStroke
.getInstance(SWT
.MOD1
, SWT
.CR
);
134 * Handles a "previously used values" content assist.
136 * Adding this to a text field will enable "content assist" by keeping track
137 * of the previously used valued for this field. The previously used values
138 * will be shown in the order they were last used (most recently used ones
139 * coming first in the list) and the number of entries is limited.
141 * A "bulb" decorator will indicate that content assist is available for the
142 * field, and a tool tip is provided giving more information.
144 * Content assist is activated by either typing in the field or by using a
145 * dedicated key stroke which is indicated in the tool tip. The list will be
146 * filtered with the content already in the text field with '*' being usable
149 * Note that the application must issue a call to {@link #updateProposals()}
150 * in order to add a new value to the "previously used values" list.
152 * The list will be persisted in the plug-in dialog settings.
154 * @noextend not to be extended by clients
155 * @noimplement not to be implemented by clients, use
156 * {@link UIUtils#addPreviousValuesContentProposalToText(Text, String)}
157 * to create instances of this
159 public interface IPreviousValueProposalHandler
{
161 * Updates the proposal list from the value in the text field.
163 * The value will be truncated to the first 2000 characters in order to
166 * Note that this must be called in the UI thread, since it accesses the
169 * If the value is already in the list, it will become the first entry,
170 * otherwise it will be added at the beginning. Note that empty Strings
171 * will not be stored. The length of the list is limited, and the
172 * "oldest" entries will be removed once the limit is exceeded.
174 * This call should only be issued if the value in the text field is
175 * "valid" in terms of the application.
177 public void updateProposals();
181 * A provider of candidate elements for which content proposals may be
185 * type of the candidate elements
187 public interface IContentProposalCandidateProvider
<T
> {
190 * Retrieves the collection of candidates eligible for content proposal
193 * @return collection of candidates
195 public Collection
<?
extends T
> getCandidates();
199 * A factory for creating {@link IContentProposal}s for {@link Ref}s.
202 * type of elements to create proposals for
204 public interface IContentProposalFactory
<T
> {
207 * Gets a new {@link IContentProposal} for the given element. May or may
208 * not consider the {@link Pattern} and creates a proposal only if it
209 * matches the element with implementation-defined semantics.
212 * constructed from current input to aid in selecting
213 * meaningful proposals; may be {@code null}
215 * to consider creating a proposal for
216 * @return a new {@link IContentProposal}, or {@code null} if none
218 public IContentProposal
getProposal(Pattern pattern
, T element
);
222 * A {@link ContentProposalAdapter} with a <em>public</em>
223 * {@link #openProposalPopup()} method.
225 public static class ExplicitContentProposalAdapter
226 extends ContentProposalAdapter
{
229 * Construct a content proposal adapter that can assist the user with
230 * choosing content for the field.
233 * the control for which the adapter is providing content
234 * assist. May not be {@code null}.
235 * @param controlContentAdapter
236 * the {@link IControlContentAdapter} used to obtain and
237 * update the control's contents as proposals are accepted.
238 * May not be {@code null}.
239 * @param proposalProvider
240 * the {@link IContentProposalProvider}> used to obtain
241 * content proposals for this control.
243 * the keystroke that will invoke the content proposal popup.
244 * If this value is {@code null}, then proposals will be
245 * activated automatically when any of the auto activation
246 * characters are typed.
247 * @param autoActivationCharacters
248 * characters that trigger auto-activation of content
249 * proposal. If specified, these characters will trigger
250 * auto-activation of the proposal popup, regardless of
251 * whether an explicit invocation keyStroke was specified. If
252 * this parameter is {@code null}, then only a specified
253 * keyStroke will invoke content proposal. If this parameter
254 * is {@code null} and the keyStroke parameter is
255 * {@code null}, then all alphanumeric characters will
256 * auto-activate content proposal.
258 public ExplicitContentProposalAdapter(Control control
,
259 IControlContentAdapter controlContentAdapter
,
260 IContentProposalProvider proposalProvider
,
261 KeyStroke keyStroke
, char[] autoActivationCharacters
) {
262 super(control
, controlContentAdapter
, proposalProvider
, keyStroke
,
263 autoActivationCharacters
);
267 public void openProposalPopup() {
268 // Make this method accessible
269 super.openProposalPopup();
275 * see {@link FontRegistry#get(String)}
278 public static Font
getFont(final String id
) {
279 return PlatformUI
.getWorkbench().getThemeManager().getCurrentTheme()
280 .getFontRegistry().get(id
);
285 * see {@link FontRegistry#getBold(String)}
288 public static Font
getBoldFont(final String id
) {
289 return PlatformUI
.getWorkbench().getThemeManager().getCurrentTheme()
290 .getFontRegistry().getBold(id
);
295 * see {@link FontRegistry#getItalic(String)}
298 public static Font
getItalicFont(final String id
) {
299 return PlatformUI
.getWorkbench().getThemeManager().getCurrentTheme()
300 .getFontRegistry().getItalic(id
);
304 * @return the indent of controls that depend on the previous control (e.g.
305 * a checkbox that is only enabled when the checkbox above it is
308 public static int getControlIndent() {
309 // Eclipse 4.3: Use LayoutConstants.getIndent once we depend on 4.3
316 * @return a text field which is read-only but can be selected
318 public static Text
createSelectableLabel(Composite parent
, int style
) {
319 // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=71765
320 Text text
= new Text(parent
, style
| SWT
.READ_ONLY
);
321 text
.setBackground(text
.getDisplay().getSystemColor(
322 SWT
.COLOR_WIDGET_BACKGROUND
));
327 * Adds little bulb decoration to given control. Bulb will appear in top
328 * left corner of control after giving focus for this control.
330 * After clicking on bulb image text from <code>tooltip</code> will appear.
333 * instance of {@link Control} object with should be decorated
335 * text value which should appear after clicking on bulb image.
336 * @return the {@link ControlDecoration} created
338 public static ControlDecoration
addBulbDecorator(final Control control
,
339 final String tooltip
) {
340 ControlDecoration dec
= new ControlDecoration(control
, SWT
.TOP
343 dec
.setImage(FieldDecorationRegistry
.getDefault().getFieldDecoration(
344 FieldDecorationRegistry
.DEC_CONTENT_PROPOSAL
).getImage());
346 dec
.setShowOnlyOnFocus(true);
347 dec
.setShowHover(true);
349 dec
.setDescriptionText(tooltip
);
354 * Creates a simple {@link Pattern} that can be used for matching content
355 * assist proposals. The pattern ignores leading blanks and allows '*' as a
356 * wildcard matching multiple arbitrary characters.
359 * to create the pattern from
360 * @return the pattern, or {@code null} if none could be created
362 public static Pattern
createProposalPattern(String content
) {
363 // Make the simplest possible pattern check: allow "*"
364 // for multiple characters.
365 String patternString
= content
;
366 // Ignore spaces in the beginning.
367 while (patternString
.length() > 0 && patternString
.charAt(0) == ' ') {
368 patternString
= patternString
.substring(1);
371 // We quote the string as it may contain spaces
372 // and other stuff colliding with the pattern.
373 patternString
= Pattern
.quote(patternString
);
375 patternString
= patternString
.replaceAll("\\x2A", ".*"); //$NON-NLS-1$ //$NON-NLS-2$
377 // Make sure we add a (logical) * at the end.
378 if (!patternString
.endsWith(".*")) { //$NON-NLS-1$
379 patternString
= patternString
+ ".*"; //$NON-NLS-1$
382 // Compile a case-insensitive pattern (assumes ASCII only).
385 pattern
= Pattern
.compile(patternString
, Pattern
.CASE_INSENSITIVE
);
386 } catch (PatternSyntaxException e
) {
393 * Adds a "previously used values" content proposal handler to a text field.
395 * The list will be limited to 10 values.
399 * @param preferenceKey
400 * the key under which to store the "previously used values" in
401 * the dialog settings
402 * @return the handler the proposal handler
404 public static IPreviousValueProposalHandler
addPreviousValuesContentProposalToText(
405 final Text textField
, final String preferenceKey
) {
406 KeyStroke stroke
= UIUtils
407 .getKeystrokeOfBestActiveBindingFor(IWorkbenchCommandConstants
.EDIT_CONTENT_ASSIST
);
409 addBulbDecorator(textField
,
410 UIText
.UIUtils_StartTypingForPreviousValuesMessage
);
414 NLS
.bind(UIText
.UIUtils_PressShortcutMessage
,
417 IContentProposalProvider cp
= new IContentProposalProvider() {
420 public IContentProposal
[] getProposals(String contents
, int position
) {
421 List
<IContentProposal
> resultList
= new ArrayList
<>();
423 Pattern pattern
= createProposalPattern(contents
);
424 String
[] proposals
= org
.eclipse
.egit
.ui
.Activator
.getDefault()
425 .getDialogSettings().getArray(preferenceKey
);
426 if (proposals
!= null) {
427 for (final String uriString
: proposals
) {
430 && !pattern
.matcher(uriString
).matches()) {
433 IContentProposal propsal
= new IContentProposal() {
436 public String
getLabel() {
441 public String
getDescription() {
446 public int getCursorPosition() {
451 public String
getContent() {
455 resultList
.add(propsal
);
458 return resultList
.toArray(new IContentProposal
[0]);
462 ContentProposalAdapter adapter
= new ContentProposalAdapter(textField
,
463 new TextContentAdapter(), cp
, stroke
,
464 VALUE_HELP_ACTIVATIONCHARS
);
465 // set the acceptance style to always replace the complete content
466 adapter
.setProposalAcceptanceStyle(
467 ContentProposalAdapter
.PROPOSAL_REPLACE
);
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
);
486 List
<String
> values
= new ArrayList
<>(
487 existingValues
.length
+ 1);
489 values
.addAll(Arrays
.asList(existingValues
));
490 // if it is already the first value, we don't need to do
492 if (values
.indexOf(value
) == 0)
495 values
.remove(value
);
496 // we insert at the top
497 values
.add(0, value
);
498 // make sure to not store more than the maximum number
500 while (values
.size() > 10)
501 values
.remove(values
.size() - 1);
503 settings
.put(preferenceKey
, values
.toArray(new String
[0]));
510 * Adds a content proposal for {@link Ref}s (branches, tags...) to a text
517 * @param refListProvider
518 * provides the {@link Ref}s to show in the proposal
520 * {@code true} if the candidates provided by the
521 * {@code refListProvider} are from an upstream repository
522 * @return the content proposal adapter set on the {@code textField}
524 public static final ExplicitContentProposalAdapter
addRefContentProposalToText(
526 Repository repository
,
527 IContentProposalCandidateProvider
<Ref
> refListProvider
,
529 return UIUtils
.<Ref
> addContentProposalToText(textField
,
530 refListProvider
, (pattern
, ref
) -> {
531 String shortenedName
= Repository
532 .shortenRefName(ref
.getName());
534 && !pattern
.matcher(ref
.getName()).matches()
535 && !pattern
.matcher(shortenedName
).matches()) {
538 return new RefContentProposal(repository
, ref
, upstream
);
540 UIText
.UIUtils_StartTypingForRemoteRefMessage
,
541 UIText
.UIUtils_PressShortcutForRemoteRefMessage
);
545 * Adds a content proposal for arbitrary elements to a text field.
548 * type of the proposal candidate objects
552 * @param candidateProvider
553 * {@link IContentProposalCandidateProvider} providing the
554 * candidates eligible for creating {@link IContentProposal}s
556 * {@link IContentProposalFactory} to use to create proposals
558 * @param patternProvider
559 * to convert the current text of the field into a pattern
560 * suitable for filtering the candidates. If {@code null}, a
561 * default pattern is constructed using
562 * {@link #createProposalPattern(String)}.
563 * @param startTypingMessage
564 * hover message if no content assist key binding is active
565 * @param shortcutMessage
566 * hover message if a content assist key binding is active,
567 * should have a "{0}" placeholder that will be filled by the
568 * appropriate keystroke
569 * @return the content proposal adapter set on the {@code textField}
572 public static final <T
> ExplicitContentProposalAdapter
addContentProposalToText(
574 IContentProposalCandidateProvider
<T
> candidateProvider
,
575 IContentProposalFactory
<T
> factory
,
576 Function
<String
, Pattern
> patternProvider
,
577 String startTypingMessage
,
578 String shortcutMessage
) {
579 KeyStroke stroke
= UIUtils
580 .getKeystrokeOfBestActiveBindingFor(IWorkbenchCommandConstants
.EDIT_CONTENT_ASSIST
);
581 if (stroke
== null) {
582 addBulbDecorator(textField
, startTypingMessage
);
584 addBulbDecorator(textField
,
585 NLS
.bind(shortcutMessage
, stroke
.format()));
587 IContentProposalProvider cp
= new IContentProposalProvider() {
589 public IContentProposal
[] getProposals(String contents
, int position
) {
590 List
<IContentProposal
> resultList
= new ArrayList
<>();
592 Collection
<?
extends T
> candidates
= candidateProvider
594 if (candidates
== null) {
597 Pattern pattern
= patternProvider
!= null
598 ? patternProvider
.apply(contents
)
599 : createProposalPattern(contents
);
600 for (final T candidate
: candidates
) {
601 IContentProposal proposal
= factory
.getProposal(pattern
,
603 if (proposal
!= null) {
604 resultList
.add(proposal
);
607 return resultList
.toArray(new IContentProposal
[0]);
611 ExplicitContentProposalAdapter adapter
= new ExplicitContentProposalAdapter(
612 textField
, new TextContentAdapter(), cp
, stroke
,
613 UIUtils
.VALUE_HELP_ACTIVATIONCHARS
);
614 // set the acceptance style to always replace the complete content
615 adapter
.setProposalAcceptanceStyle(
616 ContentProposalAdapter
.PROPOSAL_REPLACE
);
621 * Set enabled state of the control and all its children
625 public static void setEnabledRecursively(final Control control
,
626 final boolean enable
) {
627 control
.setEnabled(enable
);
628 if (control
instanceof Composite
)
629 for (final Control child
: ((Composite
) control
).getChildren())
630 setEnabledRecursively(child
, enable
);
634 * Dispose of the resource when the widget is disposed
639 public static void hookDisposal(Widget widget
, final Resource resource
) {
640 if (widget
== null || resource
== null)
643 widget
.addDisposeListener(new DisposeListener() {
646 public void widgetDisposed(DisposeEvent e
) {
653 * Dispose of the resource manager when the widget is disposed
658 public static void hookDisposal(Widget widget
,
659 final ResourceManager resources
) {
660 if (widget
== null || resources
== null)
663 widget
.addDisposeListener(new DisposeListener() {
666 public void widgetDisposed(DisposeEvent e
) {
672 /** Key is file extension, value is the reference to the image descriptor */
673 private static Map
<String
, SoftReference
<ImageDescriptor
>> extensionToDescriptor
= new HashMap
<>();
676 * Get editor image for path
679 * @return image descriptor
681 public static ImageDescriptor
getEditorImage(final String path
) {
682 if (path
== null || path
.length() <= 0) {
683 return DEFAULT_FILE_IMG
;
685 final String fileName
= new Path(path
).lastSegment();
686 if (fileName
== null) {
687 return DEFAULT_FILE_IMG
;
689 IEditorRegistry registry
= PlatformUI
.getWorkbench()
690 .getEditorRegistry();
691 IEditorDescriptor defaultEditor
= registry
.getDefaultEditor(fileName
);
692 if (defaultEditor
!= null) {
693 return defaultEditor
.getImageDescriptor();
695 // now we know there is no Eclipse editor for the file, and Eclipse will
696 // check Program.findProgram() and this will be slow, see bug 464891
697 int extensionIndex
= fileName
.lastIndexOf('.');
698 if (extensionIndex
< 0) {
699 // Program.findProgram() uses extensions only
700 return DEFAULT_FILE_IMG
;
702 String key
= fileName
.substring(extensionIndex
);
703 SoftReference
<ImageDescriptor
> cached
= extensionToDescriptor
.get(key
);
704 if (cached
!= null) {
705 ImageDescriptor descriptor
= cached
.get();
706 if (descriptor
!= null) {
710 // In worst case this calls Program.findProgram() and blocks UI
711 ImageDescriptor descriptor
= registry
.getImageDescriptor(fileName
);
712 extensionToDescriptor
.put(key
, new SoftReference
<>(descriptor
));
717 * Add expand all and collapse all toolbar items to the given toolbar bound
718 * to the given tree viewer
722 * @return given toolbar
724 public static ToolBar
addExpansionItems(final ToolBar toolbar
,
725 final AbstractTreeViewer viewer
) {
726 ToolItem collapseItem
= new ToolItem(toolbar
, SWT
.PUSH
);
727 Image collapseImage
= UIIcons
.COLLAPSEALL
.createImage();
728 UIUtils
.hookDisposal(collapseItem
, collapseImage
);
729 collapseItem
.setImage(collapseImage
);
730 collapseItem
.setToolTipText(UIText
.UIUtils_CollapseAll
);
731 collapseItem
.addSelectionListener(new SelectionAdapter() {
734 public void widgetSelected(SelectionEvent e
) {
735 UIUtils
.collapseAll(viewer
);
740 ToolItem expandItem
= new ToolItem(toolbar
, SWT
.PUSH
);
741 Image expandImage
= UIIcons
.EXPAND_ALL
.createImage();
742 UIUtils
.hookDisposal(expandItem
, expandImage
);
743 expandItem
.setImage(expandImage
);
744 expandItem
.setToolTipText(UIText
.UIUtils_ExpandAll
);
745 expandItem
.addSelectionListener(new SelectionAdapter() {
748 public void widgetSelected(SelectionEvent e
) {
749 UIUtils
.expandAll(viewer
);
757 * Get dialog bound settings for given class using standard section name
760 * @return dialog setting
762 public static IDialogSettings
getDialogBoundSettings(final Class
<?
> clazz
) {
763 return getDialogSettings(clazz
.getName() + ".dialogBounds"); //$NON-NLS-1$
767 * Get dialog settings for given section name
770 * @return dialog settings
772 public static IDialogSettings
getDialogSettings(final String sectionName
) {
773 IDialogSettings settings
= Activator
.getDefault().getDialogSettings();
774 IDialogSettings section
= settings
.getSection(sectionName
);
776 section
= settings
.addNewSection(sectionName
);
781 * Is viewer in a usable state?
784 * @return true if usable, false if null or underlying control is null or
787 public static boolean isUsable(final Viewer viewer
) {
788 return viewer
!= null && isUsable(viewer
.getControl());
795 * @return true if usable, false if null or disposed
797 public static boolean isUsable(final Control control
) {
798 return control
!= null && !control
.isDisposed();
802 * Associate a label with a control to make it known to screen readers and
803 * similar accessibility tools.
806 * to associate the label with
808 * to associate with the control
810 public static void associateLabel(Control control
, Label label
) {
811 control
.getAccessible().addAccessibleListener(new AccessibleAdapter() {
814 public void getName(AccessibleEvent e
) {
815 e
.result
= label
.getText();
821 * Run command with specified id
826 public static void executeCommand(IHandlerService service
, String id
) {
827 executeCommand(service
, id
, null);
831 * Run command with specified id
837 public static void executeCommand(IHandlerService service
, String id
,
840 service
.executeCommand(id
, event
);
841 } catch (ExecutionException
| NotDefinedException
| NotEnabledException
842 | NotHandledException e
) {
843 Activator
.handleError(e
.getMessage(), e
, false);
848 * Determine if the key event represents a "submit" action
849 * (<modifier>+Enter).
852 * @return true, if it means submit, false otherwise
854 public static boolean isSubmitKeyEvent(KeyEvent event
) {
855 return (event
.stateMask
& SWT
.MODIFIER_MASK
) != 0
856 && event
.keyCode
== SUBMIT_KEY_STROKE
.getNaturalKey();
860 * Prompt for saving all dirty editors for resources in the working
861 * directory of the specified repository.
864 * @return true, if the user opted to continue, false otherwise
865 * @see IWorkbench#saveAllEditors(boolean)
867 public static boolean saveAllEditors(Repository repository
) {
868 return saveAllEditors(repository
, null);
872 * Prompt for saving all dirty editors for resources in the working
873 * directory of the specified repository.
875 * If at least one file was saved, a dialog is displayed, asking the user if
876 * she wants to cancel the operation. Cancelling allows the user to do
877 * something with the newly saved files, before possibly restarting the
881 * @param cancelConfirmationQuestion
882 * A string asking the user if she wants to cancel the operation.
883 * May be null to not open a dialog, but rather always continue.
884 * @return true, if the user opted to continue, false otherwise
885 * @see IWorkbench#saveAllEditors(boolean)
887 public static boolean saveAllEditors(Repository repository
,
888 String cancelConfirmationQuestion
) {
889 IWorkbench workbench
= PlatformUI
.getWorkbench();
890 IWorkbenchWindow window
= workbench
.getActiveWorkbenchWindow();
891 RepositorySaveableFilter filter
= new RepositorySaveableFilter(
893 boolean success
= workbench
.saveAll(window
, window
, filter
, true);
894 if (success
&& cancelConfirmationQuestion
!= null && filter
.isAnythingSaved()){
895 // allow the user to cancel the operation to first do something with
896 // the newly saved files
897 String
[] buttons
= new String
[] { IDialogConstants
.YES_LABEL
,
898 IDialogConstants
.NO_LABEL
};
899 MessageDialog dialog
= new MessageDialog(window
.getShell(),
900 UIText
.CancelAfterSaveDialog_Title
, null,
901 cancelConfirmationQuestion
,
902 MessageDialog
.QUESTION
, buttons
, 0) {
904 protected int getShellStyle() {
905 return (SWT
.TITLE
| SWT
.BORDER
| SWT
.APPLICATION_MODAL
906 | SWT
.SHEET
| getDefaultOrientation());
909 int choice
= dialog
.open();
910 if (choice
!= 1) // user clicked "yes" or closed dialog -> cancel
917 * @param workbenchWindow the workbench window to use for creating the show in menu.
918 * @return the show in menu
920 public static MenuManager
createShowInMenu(IWorkbenchWindow workbenchWindow
) {
921 MenuManager showInSubMenu
= new MenuManager(getShowInMenuLabel());
922 showInSubMenu
.add(ContributionItemFactory
.VIEWS_SHOW_IN
.create(workbenchWindow
));
923 return showInSubMenu
;
926 private static String
getShowInMenuLabel() {
927 IBindingService bindingService
= Adapters
.adapt(PlatformUI
928 .getWorkbench(), IBindingService
.class);
929 if (bindingService
!= null) {
930 String keyBinding
= bindingService
931 .getBestActiveBindingFormattedFor(IWorkbenchCommandConstants
.NAVIGATE_SHOW_IN_QUICK_MENU
);
932 if (keyBinding
!= null)
933 return UIText
.UIUtils_ShowInMenuLabel
+ '\t' + keyBinding
;
936 return UIText
.UIUtils_ShowInMenuLabel
;
940 * Look up best active binding's keystroke for the given command
943 * The identifier of the command for which the best active
944 * binding's keystroke should be retrieved; must not be null.
945 * @return {@code KeyStroke} for the best active binding for the specified
946 * commandId or {@code null} if no binding is defined or if the
947 * binding service returns a {@code TriggerSequence} containing more
948 * than one {@code Trigger}.
951 public static KeyStroke
getKeystrokeOfBestActiveBindingFor(String commandId
) {
952 IBindingService bindingService
= Adapters
953 .adapt(PlatformUI
.getWorkbench(), IBindingService
.class);
954 if (bindingService
== null) {
957 TriggerSequence ts
= bindingService
.getBestActiveBindingFor(commandId
);
961 Trigger
[] triggers
= ts
.getTriggers();
962 if (triggers
.length
== 1 && triggers
[0] instanceof KeyStroke
)
963 return (KeyStroke
) triggers
[0];
969 * Copy from {@link org.eclipse.jface.dialogs.DialogPage} with changes to
970 * accommodate the lack of a Dialog context.
973 * the button to set the <code>GridData</code>
975 public static void setButtonLayoutData(Button button
) {
976 GC gc
= new GC(button
);
977 gc
.setFont(JFaceResources
.getDialogFont());
978 FontMetrics fontMetrics
= gc
.getFontMetrics();
981 GridData data
= new GridData(GridData
.HORIZONTAL_ALIGN_FILL
);
982 int widthHint
= Dialog
.convertHorizontalDLUsToPixels(fontMetrics
,
983 IDialogConstants
.BUTTON_WIDTH
);
984 Point minSize
= button
.computeSize(SWT
.DEFAULT
, SWT
.DEFAULT
, true);
985 data
.widthHint
= Math
.max(widthHint
, minSize
.x
);
986 button
.setLayoutData(data
);
990 * Locates the current part and selection and fires
991 * {@link ISelectionListener#selectionChanged(IWorkbenchPart, ISelection)}
992 * on the passed listener.
994 * @param serviceLocator
995 * @param selectionListener
997 public static void notifySelectionChangedWithCurrentSelection(
998 ISelectionListener selectionListener
, IServiceLocator serviceLocator
) {
999 IHandlerService handlerService
= serviceLocator
1000 .getService(IHandlerService
.class);
1001 IEvaluationContext state
= handlerService
.getCurrentState();
1002 // This seems to be the most reliable way to get the active part, it
1003 // also returns a part when it is called while creating a view that is
1004 // being shown.Getting the active part through the active workbench
1005 // window returned null in that case.
1006 Object partObject
= state
.getVariable(ISources
.ACTIVE_PART_NAME
);
1007 if (!(partObject
instanceof IWorkbenchPart
)) {
1008 partObject
= state
.getVariable(ISources
.ACTIVE_EDITOR_NAME
);
1010 Object selectionObject
= state
1011 .getVariable(ISources
.ACTIVE_CURRENT_SELECTION_NAME
);
1012 if (partObject
instanceof IWorkbenchPart
) {
1013 IWorkbenchPart part
= (IWorkbenchPart
) partObject
;
1014 ISelection selection
= selectionObject
instanceof ISelection
1015 ?
(ISelection
) selectionObject
1016 : StructuredSelection
.EMPTY
;
1017 selectionListener
.selectionChanged(part
, selection
);
1022 * Expand all tree nodes while disabling redraw.
1027 public static void expandAll(AbstractTreeViewer viewer
) {
1028 // TODO: When 4.8 becomes the minimum target (including jface 3.14),
1029 // then this method can be replaced by
1030 // AbstractTreeViewer.expandAll(boolean)
1031 viewer
.getControl().setRedraw(false);
1035 viewer
.getControl().setRedraw(true);
1040 * Collapse all tree nodes while disabling redraw.
1045 public static void collapseAll(AbstractTreeViewer viewer
) {
1046 viewer
.getControl().setRedraw(false);
1048 viewer
.collapseAll();
1050 viewer
.getControl().setRedraw(true);
1055 * Truncates a text to at most {@code maxLength} characters and replaces any
1056 * white space by single blanks.
1062 * @return the resulting shortened string, may be shorter than
1063 * {@code maxLength} characters
1065 public static String
menuText(String text
, int maxLength
) {
1066 String result
= Utils
.shortenText(text
, maxLength
);
1067 return SPACES
.matcher(result
).replaceAll(" "); //$NON-NLS-1$
1071 * Sets whether CSS styling of a widget is enabled (the default) or not.
1074 * to set the CSS styling mode on
1076 * {@code true} to enable CSS styling; {@code false} to disable
1079 public static void setCssStyling(Widget widget
, boolean enabled
) {
1080 widget
.setData(CSS_DISABLED_KEY
, Boolean
.valueOf(!enabled
));
1084 * Sets the CSS class name for a widget.
1087 * to set the CSS class name on
1089 * the CSS class value
1091 public static void setCssClass(Widget widget
, String cssClass
) {
1092 widget
.setData(CSS_CLASS_KEY
, cssClass
);