Java tutorial
/******************************************************************************* * Copyright (c) 2010, 2012 SAP AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Mathias Kinzler (SAP AG) - initial implementation *******************************************************************************/ package org.eclipse.egit.ui; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.commands.NotEnabledException; import org.eclipse.core.commands.NotHandledException; import org.eclipse.core.commands.common.NotDefinedException; import org.eclipse.core.runtime.Path; import org.eclipse.egit.ui.internal.RepositorySaveableFilter; import org.eclipse.egit.ui.internal.UIIcons; import org.eclipse.egit.ui.internal.UIText; import org.eclipse.egit.ui.internal.components.RefContentProposal; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.bindings.keys.KeyStroke; import org.eclipse.jface.bindings.keys.ParseException; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.fieldassist.ContentProposalAdapter; import org.eclipse.jface.fieldassist.ControlDecoration; import org.eclipse.jface.fieldassist.FieldDecorationRegistry; import org.eclipse.jface.fieldassist.IContentProposal; import org.eclipse.jface.fieldassist.IContentProposalProvider; import org.eclipse.jface.fieldassist.TextContentAdapter; import org.eclipse.jface.resource.FontRegistry; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.ResourceManager; import org.eclipse.jface.viewers.AbstractTreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Resource; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; import org.eclipse.swt.widgets.Widget; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchCommandConstants; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.actions.ContributionItemFactory; import org.eclipse.ui.handlers.IHandlerService; import org.eclipse.ui.keys.IBindingService; /** * Some utilities for UI code */ public class UIUtils { /** * these activate the content assist; alphanumeric, space plus some expected * special chars */ private static final char[] VALUE_HELP_ACTIVATIONCHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123457890*@ <>" //$NON-NLS-1$ .toCharArray(); /** * A keystroke for a "submit" action, see {@link #isSubmitKeyEvent(KeyEvent)} */ public static final KeyStroke SUBMIT_KEY_STROKE = KeyStroke.getInstance(SWT.MOD1, SWT.CR); /** * Handles a "previously used values" content assist. * <p> * Adding this to a text field will enable "content assist" by keeping track * of the previously used valued for this field. The previously used values * will be shown in the order they were last used (most recently used ones * coming first in the list) and the number of entries is limited. * <p> * A "bulb" decorator will indicate that content assist is available for the * field, and a tool tip is provided giving more information. * <p> * Content assist is activated by either typing in the field or by using a * dedicated key stroke which is indicated in the tool tip. The list will be * filtered with the content already in the text field with '*' being usable * as wild card. * <p> * Note that the application must issue a call to {@link #updateProposals()} * in order to add a new value to the "previously used values" list. * <p> * The list will be persisted in the plug-in dialog settings. * * @noextend not to be extended by clients * @noimplement not to be implemented by clients, use * {@link UIUtils#addPreviousValuesContentProposalToText(Text, String)} * to create instances of this */ public interface IPreviousValueProposalHandler { /** * Updates the proposal list from the value in the text field. * <p> * The value will be truncated to the first 2000 characters in order to * limit data size. * <p> * Note that this must be called in the UI thread, since it accesses the * text field. * <p> * If the value is already in the list, it will become the first entry, * otherwise it will be added at the beginning. Note that empty Strings * will not be stored. The length of the list is limited, and the * "oldest" entries will be removed once the limit is exceeded. * <p> * This call should only be issued if the value in the text field is * "valid" in terms of the application. */ public void updateProposals(); } /** * Used for * {@link UIUtils#addRefContentProposalToText(Text, Repository, IRefListProvider)} */ public interface IRefListProvider { /** * @return the List of {@link Ref}s to propose */ public List<Ref> getRefList(); } /** * @param id * see {@link FontRegistry#get(String)} * @return the font */ public static Font getFont(final String id) { return PlatformUI.getWorkbench().getThemeManager().getCurrentTheme().getFontRegistry().get(id); } /** * @param id * see {@link FontRegistry#getBold(String)} * @return the font */ public static Font getBoldFont(final String id) { return PlatformUI.getWorkbench().getThemeManager().getCurrentTheme().getFontRegistry().getBold(id); } /** * @param id * see {@link FontRegistry#getItalic(String)} * @return the font */ public static Font getItalicFont(final String id) { return PlatformUI.getWorkbench().getThemeManager().getCurrentTheme().getFontRegistry().getItalic(id); } /** * Adds little bulb decoration to given control. Bulb will appear in top * left corner of control after giving focus for this control. * * After clicking on bulb image text from <code>tooltip</code> will appear. * * @param control * instance of {@link Control} object with should be decorated * @param tooltip * text value which should appear after clicking on bulb image. */ public static void addBulbDecorator(final Control control, final String tooltip) { ControlDecoration dec = new ControlDecoration(control, SWT.TOP | SWT.LEFT); dec.setImage(FieldDecorationRegistry.getDefault() .getFieldDecoration(FieldDecorationRegistry.DEC_CONTENT_PROPOSAL).getImage()); dec.setShowOnlyOnFocus(true); dec.setShowHover(true); dec.setDescriptionText(tooltip); } /** * Adds a "previously used values" content proposal handler to a text field. * <p> * The keyboard shortcut will be "M1+SPACE" and the list will be limited to * 10 values. * * @param textField * the text field * @param preferenceKey * the key under which to store the "previously used values" in * the dialog settings * @return the handler the proposal handler */ public static IPreviousValueProposalHandler addPreviousValuesContentProposalToText(final Text textField, final String preferenceKey) { KeyStroke stroke; try { stroke = KeyStroke.getInstance("M1+SPACE"); //$NON-NLS-1$ addBulbDecorator(textField, NLS.bind(UIText.UIUtils_PressShortcutMessage, stroke.format())); } catch (ParseException e1) { Activator.handleError(e1.getMessage(), e1, false); stroke = null; addBulbDecorator(textField, UIText.UIUtils_StartTypingForPreviousValuesMessage); } IContentProposalProvider cp = new IContentProposalProvider() { public IContentProposal[] getProposals(String contents, int position) { List<IContentProposal> resultList = new ArrayList<IContentProposal>(); // make the simplest possible pattern check: allow "*" // for multiple characters String patternString = contents; // ignore spaces in the beginning while (patternString.length() > 0 && patternString.charAt(0) == ' ') { patternString = patternString.substring(1); } // we quote the string as it may contain spaces // and other stuff colliding with the Pattern patternString = Pattern.quote(patternString); patternString = patternString.replaceAll("\\x2A", ".*"); //$NON-NLS-1$ //$NON-NLS-2$ // make sure we add a (logical) * at the end if (!patternString.endsWith(".*")) { //$NON-NLS-1$ patternString = patternString + ".*"; //$NON-NLS-1$ } // let's compile a case-insensitive pattern (assumes ASCII only) Pattern pattern; try { pattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE); } catch (PatternSyntaxException e) { pattern = null; } String[] proposals = org.eclipse.egit.ui.Activator.getDefault().getDialogSettings() .getArray(preferenceKey); if (proposals != null) for (final String uriString : proposals) { if (pattern != null && !pattern.matcher(uriString).matches()) continue; IContentProposal propsal = new IContentProposal() { public String getLabel() { return null; } public String getDescription() { return null; } public int getCursorPosition() { return 0; } public String getContent() { return uriString; } }; resultList.add(propsal); } return resultList.toArray(new IContentProposal[resultList.size()]); } }; ContentProposalAdapter adapter = new ContentProposalAdapter(textField, new TextContentAdapter(), cp, stroke, VALUE_HELP_ACTIVATIONCHARS); // set the acceptance style to always replace the complete content adapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE); return new IPreviousValueProposalHandler() { public void updateProposals() { String value = textField.getText(); // don't store empty values if (value.length() > 0) { // we don't want to save too much in the preferences if (value.length() > 2000) { value = value.substring(0, 1999); } // now we need to mix the value into the list IDialogSettings settings = org.eclipse.egit.ui.Activator.getDefault().getDialogSettings(); String[] existingValues = settings.getArray(preferenceKey); if (existingValues == null) { existingValues = new String[] { value }; settings.put(preferenceKey, existingValues); } else { List<String> values = new ArrayList<String>(existingValues.length + 1); for (String existingValue : existingValues) values.add(existingValue); // if it is already the first value, we don't need to do // anything if (values.indexOf(value) == 0) return; values.remove(value); // we insert at the top values.add(0, value); // make sure to not store more than the maximum number // of values while (values.size() > 10) values.remove(values.size() - 1); settings.put(preferenceKey, values.toArray(new String[values.size()])); } } } }; } /** * Adds a content proposal for {@link Ref}s (branches, tags...) to a text * field * * @param textField * the text field * @param repository * the repository * @param refListProvider * provides the {@link Ref}s to show in the proposal */ public static final void addRefContentProposalToText(final Text textField, final Repository repository, final IRefListProvider refListProvider) { KeyStroke stroke; try { stroke = KeyStroke.getInstance("M1+SPACE"); //$NON-NLS-1$ UIUtils.addBulbDecorator(textField, NLS.bind(UIText.UIUtils_PressShortcutMessage, stroke.format())); } catch (ParseException e1) { Activator.handleError(e1.getMessage(), e1, false); stroke = null; UIUtils.addBulbDecorator(textField, UIText.UIUtils_StartTypingForPreviousValuesMessage); } IContentProposalProvider cp = new IContentProposalProvider() { public IContentProposal[] getProposals(String contents, int position) { List<IContentProposal> resultList = new ArrayList<IContentProposal>(); // make the simplest possible pattern check: allow "*" // for multiple characters String patternString = contents; // ignore spaces in the beginning while (patternString.length() > 0 && patternString.charAt(0) == ' ') { patternString = patternString.substring(1); } // we quote the string as it may contain spaces // and other stuff colliding with the Pattern patternString = Pattern.quote(patternString); patternString = patternString.replaceAll("\\x2A", ".*"); //$NON-NLS-1$ //$NON-NLS-2$ // make sure we add a (logical) * at the end if (!patternString.endsWith(".*")) { //$NON-NLS-1$ patternString = patternString + ".*"; //$NON-NLS-1$ } // let's compile a case-insensitive pattern (assumes ASCII only) Pattern pattern; try { pattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE); } catch (PatternSyntaxException e) { pattern = null; } List<Ref> proposals = refListProvider.getRefList(); if (proposals != null) for (final Ref ref : proposals) { final String shortenedName = Repository.shortenRefName(ref.getName()); if (pattern != null && !pattern.matcher(ref.getName()).matches() && !pattern.matcher(shortenedName).matches()) continue; IContentProposal propsal = new RefContentProposal(repository, ref); resultList.add(propsal); } return resultList.toArray(new IContentProposal[resultList.size()]); } }; ContentProposalAdapter adapter = new ContentProposalAdapter(textField, new TextContentAdapter(), cp, stroke, UIUtils.VALUE_HELP_ACTIVATIONCHARS); // set the acceptance style to always replace the complete content adapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE); } /** * Set enabled state of the control and all its children * @param control * @param enable */ public static void setEnabledRecursively(final Control control, final boolean enable) { control.setEnabled(enable); if (control instanceof Composite) for (final Control child : ((Composite) control).getChildren()) setEnabledRecursively(child, enable); } /** * Dispose of the resource when the widget is disposed * * @param widget * @param resource */ public static void hookDisposal(Widget widget, final Resource resource) { if (widget == null || resource == null) return; widget.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { resource.dispose(); } }); } /** * Dispose of the resource manager when the widget is disposed * * @param widget * @param resources */ public static void hookDisposal(Widget widget, final ResourceManager resources) { if (widget == null || resources == null) return; widget.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { resources.dispose(); } }); } /** * Get editor image for path * * @param path * @return image descriptor */ public static ImageDescriptor getEditorImage(final String path) { if (path != null && path.length() > 0) { final String name = new Path(path).lastSegment(); if (name != null) return PlatformUI.getWorkbench().getEditorRegistry().getImageDescriptor(name); } return PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_OBJ_FILE); } /** * Get size of image descriptor as point. * * @param descriptor * @return size */ public static Point getSize(ImageDescriptor descriptor) { ImageData data = descriptor.getImageData(); if (data == null) return new Point(0, 0); return new Point(data.width, data.height); } /** * Add expand all and collapse all toolbar items to the given toolbar bound * to the given tree viewer * * @param toolbar * @param viewer * @return given toolbar */ public static ToolBar addExpansionItems(final ToolBar toolbar, final AbstractTreeViewer viewer) { ToolItem collapseItem = new ToolItem(toolbar, SWT.PUSH); Image collapseImage = UIIcons.COLLAPSEALL.createImage(); UIUtils.hookDisposal(collapseItem, collapseImage); collapseItem.setImage(collapseImage); collapseItem.setToolTipText(UIText.UIUtils_CollapseAll); collapseItem.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { viewer.collapseAll(); } }); ToolItem expandItem = new ToolItem(toolbar, SWT.PUSH); Image expandImage = UIIcons.EXPAND_ALL.createImage(); UIUtils.hookDisposal(expandItem, expandImage); expandItem.setImage(expandImage); expandItem.setToolTipText(UIText.UIUtils_ExpandAll); expandItem.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { viewer.expandAll(); } }); return toolbar; } /** * Get dialog bound settings for given class using standard section name * * @param clazz * @return dialog setting */ public static IDialogSettings getDialogBoundSettings(final Class<?> clazz) { return getDialogSettings(clazz.getName() + ".dialogBounds"); //$NON-NLS-1$ } /** * Get dialog settings for given section name * * @param sectionName * @return dialog settings */ public static IDialogSettings getDialogSettings(final String sectionName) { IDialogSettings settings = Activator.getDefault().getDialogSettings(); IDialogSettings section = settings.getSection(sectionName); if (section == null) section = settings.addNewSection(sectionName); return section; } /** * Is viewer in a usable state? * * @param viewer * @return true if usable, false if null or underlying control is null or * disposed */ public static boolean isUsable(final Viewer viewer) { return viewer != null && isUsable(viewer.getControl()); } /** * Is control usable? * * @param control * @return true if usable, false if null or disposed */ public static boolean isUsable(final Control control) { return control != null && !control.isDisposed(); } /** * Run command with specified id * * @param service * @param id */ public static void executeCommand(IHandlerService service, String id) { executeCommand(service, id, null); } /** * Run command with specified id * * @param service * @param id * @param event */ public static void executeCommand(IHandlerService service, String id, Event event) { try { service.executeCommand(id, event); } catch (ExecutionException e) { Activator.handleError(e.getMessage(), e, false); } catch (NotDefinedException e) { Activator.handleError(e.getMessage(), e, false); } catch (NotEnabledException e) { Activator.handleError(e.getMessage(), e, false); } catch (NotHandledException e) { Activator.handleError(e.getMessage(), e, false); } } /** * Determine if the key event represents a "submit" action * (<modifier>+Enter). * * @param event * @return true, if it means submit, false otherwise */ public static boolean isSubmitKeyEvent(KeyEvent event) { return (event.stateMask & SWT.MODIFIER_MASK) != 0 && event.keyCode == SUBMIT_KEY_STROKE.getNaturalKey(); } /** * Prompt for saving all dirty editors for resources in the working * directory of the specified repository. * * @param repository * @return true, if the user opted to continue, false otherwise * @see IWorkbench#saveAllEditors(boolean) */ public static boolean saveAllEditors(Repository repository) { IWorkbench workbench = PlatformUI.getWorkbench(); IWorkbenchWindow window = workbench.getActiveWorkbenchWindow(); return workbench.saveAll(window, window, new RepositorySaveableFilter(repository), true); } /** * @param workbenchWindow the workbench window to use for creating the show in menu. * @return the show in menu */ public static MenuManager createShowInMenu(IWorkbenchWindow workbenchWindow) { MenuManager showInSubMenu = new MenuManager(getShowInMenuLabel()); showInSubMenu.add(ContributionItemFactory.VIEWS_SHOW_IN.create(workbenchWindow)); return showInSubMenu; } private static String getShowInMenuLabel() { IBindingService bindingService = (IBindingService) PlatformUI.getWorkbench() .getAdapter(IBindingService.class); if (bindingService != null) { String keyBinding = bindingService .getBestActiveBindingFormattedFor(IWorkbenchCommandConstants.NAVIGATE_SHOW_IN_QUICK_MENU); if (keyBinding != null) return UIText.UIUtils_ShowInMenuLabel + '\t' + keyBinding; } return UIText.UIUtils_ShowInMenuLabel; } }