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
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
;
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
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.
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.
138 * A "bulb" decorator will indicate that content assist is available for the
139 * field, and a tool tip is provided giving more information.
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
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.
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.
160 * The value will be truncated to the first 2000 characters in order to
163 * Note that this must be called in the UI thread, since it accesses the
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.
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
182 * type of the candidate elements
184 public interface IContentProposalCandidateProvider
<T
> {
187 * Retrieves the collection of candidates eligible for content proposal
190 * @return collection of candidates
192 public Collection
<?
extends T
> getCandidates();
196 * A factory for creating {@link IContentProposal}s for {@link Ref}s.
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.
209 * constructed from current input to aid in selecting
210 * meaningful proposals; may be {@code null}
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.
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.
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
);
264 public void openProposalPopup() {
265 // Make this method accessible
266 super.openProposalPopup();
272 * see {@link FontRegistry#get(String)}
275 public static Font
getFont(final String id
) {
276 return PlatformUI
.getWorkbench().getThemeManager().getCurrentTheme()
277 .getFontRegistry().get(id
);
282 * see {@link FontRegistry#getBold(String)}
285 public static Font
getBoldFont(final String id
) {
286 return PlatformUI
.getWorkbench().getThemeManager().getCurrentTheme()
287 .getFontRegistry().getBold(id
);
292 * see {@link FontRegistry#getItalic(String)}
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
305 public static int getControlIndent() {
306 // Eclipse 4.3: Use LayoutConstants.getIndent once we depend on 4.3
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
));
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.
330 * instance of {@link Control} object with should be decorated
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
340 dec
.setImage(FieldDecorationRegistry
.getDefault().getFieldDecoration(
341 FieldDecorationRegistry
.DEC_CONTENT_PROPOSAL
).getImage());
343 dec
.setShowOnlyOnFocus(true);
344 dec
.setShowHover(true);
346 dec
.setDescriptionText(tooltip
);
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.
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).
382 pattern
= Pattern
.compile(patternString
, Pattern
.CASE_INSENSITIVE
);
383 } catch (PatternSyntaxException e
) {
390 * Adds a "previously used values" content proposal handler to a text field.
392 * The list will be limited to 10 values.
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
);
406 addBulbDecorator(textField
,
407 UIText
.UIUtils_StartTypingForPreviousValuesMessage
);
411 NLS
.bind(UIText
.UIUtils_PressShortcutMessage
,
414 IContentProposalProvider cp
= new IContentProposalProvider() {
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
) {
427 && !pattern
.matcher(uriString
).matches()) {
430 IContentProposal propsal
= new IContentProposal() {
433 public String
getLabel() {
438 public String
getDescription() {
443 public int getCursorPosition() {
448 public String
getContent() {
452 resultList
.add(propsal
);
455 return resultList
.toArray(new IContentProposal
[resultList
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() {
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
);
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
493 if (values
.indexOf(value
) == 0)
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
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
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(
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());
533 && !pattern
.matcher(ref
.getName()).matches()
534 && !pattern
.matcher(shortenedName
).matches()) {
537 return new RefContentProposal(repository
, ref
);
539 UIText
.UIUtils_StartTypingForRemoteRefMessage
,
540 UIText
.UIUtils_PressShortcutForRemoteRefMessage
);
544 * Adds a content proposal for arbitrary elements to a text field.
547 * type of the proposal candidate objects
551 * @param candidateProvider
552 * {@link IContentProposalCandidateProvider} providing the
553 * candidates eligible for creating {@link IContentProposal}s
555 * {@link IContentProposalFactory} to use to create proposals
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(
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) {
583 addBulbDecorator(textField
, startTypingMessage
);
585 addBulbDecorator(textField
,
586 NLS
.bind(shortcutMessage
, stroke
.format()));
588 IContentProposalProvider cp
= new IContentProposalProvider() {
590 public IContentProposal
[] getProposals(String contents
, int position
) {
591 List
<IContentProposal
> resultList
= new ArrayList
<>();
593 Collection
<?
extends T
> candidates
= candidateProvider
595 if (candidates
== 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
,
604 if (proposal
!= null) {
605 resultList
.add(proposal
);
608 return resultList
.toArray(new IContentProposal
[resultList
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
);
623 * Set enabled state of the control and all its children
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
641 public static void hookDisposal(Widget widget
, final Resource resource
) {
642 if (widget
== null || resource
== null)
645 widget
.addDisposeListener(new DisposeListener() {
648 public void widgetDisposed(DisposeEvent e
) {
655 * Dispose of the resource manager when the widget is disposed
660 public static void hookDisposal(Widget widget
,
661 final ResourceManager resources
) {
662 if (widget
== null || resources
== null)
665 widget
.addDisposeListener(new DisposeListener() {
668 public void widgetDisposed(DisposeEvent e
) {
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
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) {
712 // In worst case this calls Program.findProgram() and blocks UI
713 ImageDescriptor descriptor
= registry
.getImageDescriptor(fileName
);
714 extensionToDescriptor
.put(key
, new SoftReference
<>(descriptor
));
719 * Get size of image descriptor as point.
724 public static Point
getSize(ImageDescriptor descriptor
) {
725 ImageData data
= descriptor
.getImageData();
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
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() {
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() {
763 public void widgetSelected(SelectionEvent e
) {
772 * Get dialog bound settings for given class using standard section name
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
785 * @return dialog settings
787 public static IDialogSettings
getDialogSettings(final String sectionName
) {
788 IDialogSettings settings
= Activator
.getDefault().getDialogSettings();
789 IDialogSettings section
= settings
.getSection(sectionName
);
791 section
= settings
.addNewSection(sectionName
);
796 * Is viewer in a usable state?
799 * @return true if usable, false if null or underlying control is null or
802 public static boolean isUsable(final Viewer viewer
) {
803 return viewer
!= null && isUsable(viewer
.getControl());
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
822 public static void executeCommand(IHandlerService service
, String id
) {
823 executeCommand(service
, id
, null);
827 * Run command with specified id
833 public static void executeCommand(IHandlerService service
, String id
,
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 * (<modifier>+Enter).
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.
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
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(
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) {
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
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
933 * @param hyperlinkDetectors
934 * @deprecated Instead of applying SWT styling directly use JFace
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}
944 public static void applyHyperlinkDetectorStyleRanges(
945 ITextViewer textViewer
, IHyperlinkDetector
[] hyperlinkDetectors
) {
946 StyleRange
[] styleRanges
= getHyperlinkDetectorStyleRanges(textViewer
,
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.
960 * @param hyperlinkDetectors
961 * @return the style ranges to render the detected hyperlinks
962 * @deprecated Instead of applying SWT styling directly use JFace
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}
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
++) {
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(
992 Display
.getDefault().getSystemColor(
994 hyperlinkStyleRange
.underline
= true;
995 styleRangeList
.add(hyperlinkStyleRange
);
999 } catch (BadLocationException e
) {
1000 Activator
.logError(e
.getMessage(), e
);
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
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}.
1035 public static KeyStroke
getKeystrokeOfBestActiveBindingFor(String commandId
) {
1036 IBindingService bindingService
= AdapterUtils
1037 .adapt(PlatformUI
.getWorkbench(), IBindingService
.class);
1038 if (bindingService
== null) {
1041 TriggerSequence ts
= bindingService
.getBestActiveBindingFor(commandId
);
1045 Trigger
[] triggers
= ts
.getTriggers();
1046 if (triggers
.length
== 1 && triggers
[0] instanceof KeyStroke
)
1047 return (KeyStroke
) triggers
[0];
1053 * Copy from {@link org.eclipse.jface.dialogs.DialogPage} with changes to
1054 * accommodate the lack of a Dialog context.
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();
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
);