com.aptana.snippets.ui.views.SnippetsView.java Source code

Java tutorial

Introduction

Here is the source code for com.aptana.snippets.ui.views.SnippetsView.java

Source

/**
 * Aptana Studio
 * Copyright (c) 2012 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions).
 * Please see the license.html included with this distribution for details.
 * Any modifications to this file must keep this entire header intact.
 */
package com.aptana.snippets.ui.views;

import java.io.File;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.search.internal.core.text.PatternConstructor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.ExpandEvent;
import org.eclipse.swt.events.ExpandListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.ExpandBar;
import org.eclipse.swt.widgets.ExpandItem;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.IPartService;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.MultiPageEditorPart;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.texteditor.ITextEditor;

import com.aptana.core.util.CollectionsUtil;
import com.aptana.core.util.EclipseUtil;
import com.aptana.core.util.StringUtil;
import com.aptana.editor.common.AbstractThemeableEditor;
import com.aptana.editor.common.CommonEditorPlugin;
import com.aptana.editor.common.dnd.SnippetTransfer;
import com.aptana.editor.common.scripting.commands.CommandExecutionUtils;
import com.aptana.editor.common.scripting.commands.TextEditorUtils;
import com.aptana.scripting.model.BundleManager;
import com.aptana.scripting.model.CommandResult;
import com.aptana.scripting.model.InvocationType;
import com.aptana.scripting.model.LoadCycleListener;
import com.aptana.scripting.model.SnippetCategoryElement;
import com.aptana.scripting.model.SnippetElement;
import com.aptana.scripting.ui.ScriptingUIPlugin;
import com.aptana.theme.ColorManager;
import com.aptana.theme.IControlThemerFactory;
import com.aptana.theme.IThemeManager;
import com.aptana.theme.Theme;
import com.aptana.theme.ThemePlugin;
import com.aptana.theme.preferences.IPreferenceConstants;
import com.aptana.ui.util.UIUtils;
import com.aptana.ui.widgets.SearchComposite;

/**
 * View displays snippets that match the current active editor's context
 * 
 * @author nle
 */
public class SnippetsView extends ViewPart {

    private static final String ICONS_FULL_ELCL16_EXPANDALL_GIF = "icons/full/elcl16/expandall.gif"; //$NON-NLS-1$
    public static final String ID = "com.aptana.snippets.ui.views.SnippetsView"; //$NON-NLS-1$
    private Image informationImage = ScriptingUIPlugin.getImage("icons/full/obj16/information.png"); //$NON-NLS-1$$
    private Image genericSnippetImage = ScriptingUIPlugin.getImage("icons/snippet.png"); //$NON-NLS-1$
    private Image genericDisabledSnippetImage = new Image(genericSnippetImage.getDevice(), genericSnippetImage,
            SWT.IMAGE_DISABLE);
    private Image insertSnippetImage = ScriptingUIPlugin.getImage("icons/full/elcl16/insert_snippet_tsk.png"); //$NON-NLS-1$

    private Map<String, ExpandItem> expandItems = new HashMap<String, ExpandItem>();
    private ScrolledComposite scrolledComposite;
    private ExpandBar expandBar;
    private SearchComposite search;
    private SnippetsFilter filter;

    private Map<String, SnippetCategoryElement> snippetCategories = new LinkedHashMap<String, SnippetCategoryElement>();
    private HashMap<String, List<SnippetData>> sortedSnippets = new LinkedHashMap<String, List<SnippetData>>();
    private Map<SnippetData, SnippetItem> snippetItems = new HashMap<SnippetData, SnippetItem>();
    private Map<String, List<ToolItem>> toolItemMap = new HashMap<String, List<ToolItem>>();

    private Font defaultFont, themeFont;
    private ImageRegistry imageRegistry, disabledImageRegistry;
    private ImageRegistry hotTagImageRegistry;
    private ColorManager colorManager;
    private Color themeBgColor, themeFgColor, themeSelectionColor;
    private IControlThemerFactory themeFactory;
    private IPreferenceChangeListener themeListener;
    private RGB defaultTagHotFg = new RGB(135, 160, 208);
    private RGB defaultTagHotBg = new RGB(192, 201, 219);
    private RGB defaultTagBg = new RGB(222, 231, 249);
    private RGB defaultTagFg = new RGB(165, 190, 238);
    private static final int FG_ALPHA = 128;

    private List<Control> themedControls = new ArrayList<Control>();
    private List<String> collapsedCategories = new ArrayList<String>();

    private String currentScope;
    private SnippetPopupDialog snippetDialog;
    private SnippetBundleListener snippetBundleListener;
    private int tagWidth = -1;
    private static Pattern descriptionReplacePattern = Pattern.compile("[\r\n\t]+"); //$NON-NLS-1$ 

    class SnippetBundleListener implements LoadCycleListener {
        private final Job job = new Job("Force reconcile on bundle change") //$NON-NLS-1$
        {
            @Override
            protected IStatus run(IProgressMonitor monitor) {
                Display.getDefault().asyncExec(new Runnable() {

                    public void run() {
                        if (scrolledComposite != null && !scrolledComposite.isDisposed()) {
                            createSnippetDrawers();
                            updateBasedOnPart(getSite().getPage().getActiveEditor());
                        }
                    }
                });
                return Status.OK_STATUS;
            }
        };

        /**
         *
         */
        public SnippetBundleListener() {
            BundleManager.getInstance().addLoadCycleListener(this);
        }

        public void dispose() {
            BundleManager.getInstance().removeLoadCycleListener(this);
        }

        /*
         * (non-Javadoc)
         * @see com.aptana.scripting.model.LoadCycleListener#scriptLoaded(java.io.File)
         */
        public void scriptLoaded(File script) {
            bundleFileChanged(script);
        }

        /*
         * (non-Javadoc)
         * @see com.aptana.scripting.model.LoadCycleListener#scriptReloaded(java.io.File)
         */
        public void scriptReloaded(File script) {
            bundleFileChanged(script);
        }

        /*
         * (non-Javadoc)
         * @see com.aptana.scripting.model.LoadCycleListener#scriptUnloaded(java.io.File)
         */
        public void scriptUnloaded(File script) {
            bundleFileChanged(script);
        }

        private void bundleFileChanged(File script) {
            if (script == null
                    || (!script.getName().equals(BundleManager.BUNDLE_FILE) && !(script.getParentFile() != null
                            && BundleManager.SNIPPETS_DIRECTORY_NAME.equals(script.getParentFile().getName())))) {
                return;
            }
            // Run in a job on a delay and cancel/reschedule if it already exists and is scheduled... This should
            // basically only make us run once if we get hit multiple times in a row. We'll still probably run a few
            // times, but this should cut it down a lot.
            job.cancel();
            EclipseUtil.setSystemForJob(job);
            job.schedule(750);
        }
    }

    private class InsertSnippetListener extends MouseAdapter {
        private SnippetElement snippet;

        InsertSnippetListener(SnippetElement snippet) {
            this.snippet = snippet;
        }

        public void mouseDoubleClick(MouseEvent e) {
            insertSnippet(snippet);
        }
    }

    private ISelectionListener selectionListener = new ISelectionListener() {

        public void selectionChanged(IWorkbenchPart part, ISelection selection) {
            if (selection instanceof ITextSelection) {
                updateBasedOnPart(part);
            }
        }
    };

    Comparator<SnippetCategoryElement> snippetCategoryElementComparator = new Comparator<SnippetCategoryElement>() {
        public int compare(SnippetCategoryElement arg0, SnippetCategoryElement arg1) {
            if ((arg0 == null && arg1 == null) || (arg0 != null && arg0.getDisplayName() == null && arg1 != null
                    && arg1.getDisplayName() == null)) {
                return 0;
            } else if (arg0 == null || arg0.getDisplayName() == null) {
                return 1;
            } else if (arg1 == null || arg1.getDisplayName() == null) {
                return -1;
            }

            return arg0.getDisplayName().compareTo(arg1.getDisplayName());
        }
    };

    /**
     * UI model for a snippet element. Maintains UI state for a SnippetItem
     * 
     * @author nle
     */
    private class SnippetData {
        private SnippetElement snippet;
        private boolean visuallyEnabled = true;
        private boolean filtered = false;

        SnippetData(SnippetElement snippet) {
            this.snippet = snippet;
        }
    }

    /**
     * UI representation of a snippet in the Snippets view
     * 
     * @author nle
     */
    private class SnippetItem extends Composite {
        private SnippetData snippetData;
        private MouseListener mouseListener;
        private Label imageLabel;
        private Composite textComposite;
        private CLabel titleLabel;
        private CLabel descLabel;
        private ToolBar toolbar;
        private ToolBar tagToolBar;
        private String categoryName;

        // Default margin for the snippet item
        final int SNIPPET_MARGIN = 5;

        SnippetItem(Composite parent, SnippetData snippetData) {
            super(parent, SWT.NONE);
            this.snippetData = snippetData;
            mouseListener = new InsertSnippetListener(snippetData.snippet);
            categoryName = getSnippetCategoryName(snippetData.snippet);

            setLayout(GridLayoutFactory.fillDefaults()
                    .extendedMargins(SNIPPET_MARGIN, SNIPPET_MARGIN, 1, SNIPPET_MARGIN).spacing(2, 0).numColumns(3)
                    .create());
            GridData gridData = GridDataFactory.fillDefaults().grab(true, false).create();
            gridData.exclude = snippetData.filtered;
            setLayoutData(gridData);

            addPaintListener(new PaintListener() {
                public void paintControl(PaintEvent e) {
                    Color foreground = e.gc.getForeground();
                    e.gc.setForeground(colorManager
                            .getColor(Theme.alphaBlend(getBackground().getRGB(), getForeground().getRGB(), 100)));
                    Rectangle clientArea = getClientArea();
                    e.gc.drawLine(clientArea.x - SNIPPET_MARGIN, clientArea.y + clientArea.height - 2,
                            clientArea.x + clientArea.width + SNIPPET_MARGIN, clientArea.y + clientArea.height - 2);
                    e.gc.drawLine(clientArea.x, clientArea.y, clientArea.x, clientArea.y + clientArea.height - 1);
                    e.gc.drawLine(clientArea.x + clientArea.width - 1, clientArea.y,
                            clientArea.x + clientArea.width - 1, clientArea.y + clientArea.height - 1);
                    Point newPoint = UIUtils.getDisplay().map(SnippetItem.this, getParent(),
                            new Point(clientArea.x, clientArea.y));
                    if (newPoint.y == 0) {
                        e.gc.drawLine(clientArea.x - SNIPPET_MARGIN, clientArea.y,
                                clientArea.x + clientArea.width + SNIPPET_MARGIN, clientArea.y);
                    }
                    e.gc.setForeground(foreground);
                }
            });

            addMouseListener(mouseListener);
            addThemedControl(this, SWT.NONE, -1);

            addTitleAndDescriptionForSnippet(snippetData.snippet, this, mouseListener);
            addActionToobarForSnippet(snippetData.snippet, this);
            addTagToolbarForSnippet(snippetData.snippet, this);

            applyVisualEnablement();

            setBackground(getBackgroundColor());
        }

        protected void setFiltered(boolean filtered) {
            snippetData.filtered = filtered;
            applyFiltered();
        }

        protected void applyFiltered() {
            setVisible(!snippetData.filtered);
            ((GridData) getLayoutData()).exclude = snippetData.filtered;
        }

        /**
         * Enablement for the snippets is mostly visual. We still want to allow users to operate on snippets even though
         * they my not be compatible with the current scope
         * 
         * @param enabled
         */
        protected void setVisualEnabled(boolean enabled) {
            snippetData.visuallyEnabled = enabled;
            applyVisualEnablement();
        }

        protected void applyVisualEnablement() {
            if (snippetData.visuallyEnabled) {
                titleLabel.setForeground(themeFgColor);
            } else {
                titleLabel.setForeground(descLabel.getForeground());
            }

            if (tagToolBar != null && tagToolBar.isDisposed()) {
                tagToolBar.setEnabled(snippetData.visuallyEnabled);
            }

            imageLabel.setImage(getSnippetImage(snippetData.visuallyEnabled));
        }

        private void addTitleAndDescriptionForSnippet(SnippetElement snippet, Composite itemComposite,
                MouseListener mouseListener) {
            imageLabel = new Label(itemComposite, SWT.NONE);
            imageLabel.setLayoutData(GridDataFactory.fillDefaults().align(SWT.CENTER, SWT.CENTER).create());
            imageLabel.setImage(getSnippetImage(true));
            imageLabel.addMouseListener(mouseListener);
            imageLabel.setBackground(getBackgroundColor());
            addThemedControl(imageLabel, SWT.NONE, -1);

            textComposite = new Composite(itemComposite, SWT.NONE);
            textComposite.setLayout(GridLayoutFactory.fillDefaults().spacing(3, 0).numColumns(2).create());
            textComposite.setLayoutData(
                    GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.CENTER).grab(true, false).create());
            textComposite.addMouseListener(mouseListener);
            textComposite.setBackground(getBackgroundColor());
            addThemedControl(textComposite, SWT.NONE, -1);

            titleLabel = new CLabel(textComposite, SWT.NONE);
            titleLabel.setText(snippet.getDisplayName());
            titleLabel.setLayoutData(
                    GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.CENTER).grab(false, false).create());
            titleLabel.addMouseListener(mouseListener);
            titleLabel.setBackground(getBackgroundColor());
            addThemedControl(titleLabel, SWT.BOLD, -1);

            descLabel = new CLabel(textComposite, SWT.NONE);
            String descText = snippet.getDescription();
            if (descText == null || StringUtil.isEmpty(descText)) {
                descText = descriptionReplacePattern.matcher(snippet.getExpansion()).replaceAll(" "); //$NON-NLS-1$
            }

            descLabel.setText(descText);
            descLabel.setLayoutData(
                    GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.CENTER).grab(true, false).create());
            descLabel.addMouseListener(mouseListener);
            descLabel.setBackground(getBackgroundColor());
            addThemedControl(descLabel, SWT.NONE, 128);

            addDragDropForSnippet();
        }

        private void addActionToobarForSnippet(final SnippetElement snippet, final Composite itemComposite) {
            toolbar = new ToolBar(itemComposite, SWT.HORIZONTAL);
            toolbar.setLayoutData(GridDataFactory.swtDefaults().align(SWT.BEGINNING, SWT.CENTER).create());
            toolbar.setVisible(false);
            toolbar.setBackground(getBackgroundColor());
            addThemedControl(toolbar, SWT.NONE, -1);

            ToolItem insertItem = new ToolItem(toolbar, SWT.NONE);
            insertItem.setImage(insertSnippetImage);
            insertItem.setToolTipText(Messages.SnippetsView_Insert_Snippet_desc);
            insertItem.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    insertSnippet(snippet);
                }
            });

            ToolItem infoItem = new ToolItem(toolbar, SWT.NONE);
            infoItem.setImage(informationImage);
            infoItem.setToolTipText(Messages.SnippetsView_Show_Information_desc);
            infoItem.addSelectionListener(new SelectionAdapter() {

                @Override
                public void widgetSelected(SelectionEvent e) {
                    closeSnippetDialog();

                    snippetDialog = new SnippetPopupDialog(getShell(), snippet, toolbar);
                    snippetDialog.open();
                }

            });

            itemComposite.addMouseMoveListener(new MouseMoveListener() {

                public void mouseMove(MouseEvent e) {
                    boolean contains = itemComposite.getClientArea().contains(e.x, e.y);
                    if (contains) {
                        for (SnippetItem item : snippetItems.values()) {
                            ToolBar tempToolbar = item.toolbar;
                            if (tempToolbar != toolbar) {
                                tempToolbar.setVisible(false);
                            }
                        }
                    }

                    toolbar.setVisible(contains);
                }
            });
        }

        private void addTagToolbarForSnippet(final SnippetElement snippet, final Composite itemComposite) {
            List<String> tags = snippet.getTags();
            if (!CollectionsUtil.isEmpty(tags)) {
                new Label(itemComposite, SWT.NONE);

                tagToolBar = new ToolBar(itemComposite, SWT.HORIZONTAL | SWT.WRAP);
                tagToolBar.setLayoutData(GridDataFactory.fillDefaults().align(SWT.END, SWT.CENTER).grab(true, false)
                        .span(2, 1).create());
                tagToolBar.setBackground(getBackgroundColor());
                addThemedControl(tagToolBar, SWT.NONE, -1);

                int height = -1;

                for (String tag : tags) {
                    height = createTagToolItem(tagToolBar, height, tag);
                }
            }
        }

        private void addDragDropForSnippet() {
            // Add DnD
            int operations = DND.DROP_COPY;
            Transfer[] types = new Transfer[] { SnippetTransfer.getInstance() };

            DragSourceListener listener = new DragSourceListener() {

                public void dragStart(DragSourceEvent event) {
                    event.doit = true;
                    event.image = insertSnippetImage;
                }

                public void dragSetData(DragSourceEvent event) {
                    event.data = snippetData.snippet;
                }

                public void dragFinished(DragSourceEvent event) {
                }
            };

            Control[] controls = new Control[] { imageLabel, titleLabel, descLabel };
            for (Control control : controls) {
                DragSource titleSource = new DragSource(control, operations);
                titleSource.setTransfer(types);
                titleSource.addDragListener(listener);
            }
        }

        private Image getSnippetImage(boolean enabled) {
            URL iconURL = snippetData.snippet.getIconURL();
            Image image = null;
            if (enabled) {
                image = iconURL != null ? getImage(iconURL) : genericSnippetImage;
            } else {
                image = iconURL != null ? getDisabledImage(iconURL) : genericDisabledSnippetImage;
            }

            return image;
        }
    }

    /**
     * Job used to show/hide snippets
     * 
     * @author nle
     */
    private class FilterSnippetsJob extends Job {
        List<SnippetData> snippets = new ArrayList<SnippetData>();

        public FilterSnippetsJob(List<SnippetData> newSnippets) {
            super("Filter snippets"); //$NON-NLS-1$
            snippets.addAll(newSnippets);
        }

        @Override
        protected IStatus run(IProgressMonitor monitor) {
            Display.getDefault().asyncExec(new Runnable() {
                public void run() {
                    showSnippets(snippets);
                }
            });
            return Status.OK_STATUS;
        }
    }

    FilterSnippetsJob filterSnippetsJob = null;

    private IPartListener partListener = new IPartListener() {

        public void partOpened(IWorkbenchPart part) {
            // No-op
        }

        public void partDeactivated(IWorkbenchPart part) {
            // No-op
        }

        public void partClosed(IWorkbenchPart part) {
            // No-op
        }

        public void partBroughtToTop(IWorkbenchPart part) {
            // No-op
        }

        public void partActivated(IWorkbenchPart part) {
            updateBasedOnPart(part);
        }
    };

    public SnippetsView() {
        setPartName(Messages.SnippetsView_partName);
        imageRegistry = new ImageRegistry();
        disabledImageRegistry = new ImageRegistry();
        hotTagImageRegistry = new ImageRegistry();
        colorManager = new ColorManager();
        themeFactory = ThemePlugin.getDefault().getControlThemerFactory();

        snippetBundleListener = new SnippetBundleListener();
    }

    private void updateThemeFont() {
        boolean isInvasiveFont = Platform.getPreferencesService().getBoolean(ThemePlugin.PLUGIN_ID,
                IPreferenceConstants.INVASIVE_FONT, false, null);

        if (isInvasiveFont) {
            themeFont = getFont();
        } else {
            themeFont = defaultFont;
        }
    }

    private void updateThemeColors() {
        updateThemeFont();

        if (ThemePlugin.applyToViews()) {
            themeBgColor = ThemePlugin.getDefault().getColorManager()
                    .getColor(ThemePlugin.getDefault().getThemeManager().getCurrentTheme().getBackground());
            themeFgColor = ThemePlugin.getDefault().getColorManager()
                    .getColor(ThemePlugin.getDefault().getThemeManager().getCurrentTheme().getForeground());
            themeSelectionColor = ThemePlugin.getDefault().getColorManager()
                    .getColor(ThemePlugin.getDefault().getThemeManager().getCurrentTheme().getSelectionAgainstBG());
        } else {
            themeBgColor = null;
            themeFgColor = null;
            themeSelectionColor = null;
        }

        updateBgColor();

        // Update tag images
        int height = -1;

        for (String tagName : toolItemMap.keySet()) {
            List<ToolItem> toolItems = toolItemMap.get(tagName);

            for (ToolItem toolItem : toolItems) {
                toolItem.setImage(null);
                toolItem.setHotImage(null);
            }

            imageRegistry.remove(tagName);
            hotTagImageRegistry.remove(tagName);

            for (ToolItem toolItem : toolItems) {
                toolItem.getParent().setBackground(getBackgroundColor());
                height = createTagImagesForToolItem(toolItem.getParent(), height, tagName, toolItem);
            }
        }

        for (SnippetItem item : snippetItems.values()) {
            item.applyVisualEnablement();
        }

        updateSnippetDrawers();
    }

    protected Font getFont() {
        Font font = JFaceResources.getFont(IThemeManager.VIEW_FONT_NAME);
        if (font == null) {
            font = JFaceResources.getTextFont();
        }
        return font;
    }

    synchronized private void closeSnippetDialog() {
        if (snippetDialog != null) {
            snippetDialog.close();
            snippetDialog = null;
        }
    }

    /**
     * Create contents of the view part.
     * 
     * @param parent
     */
    @Override
    public void createPartControl(final Composite parent) {
        filter = new SnippetsFilter();
        defaultFont = parent.getFont();

        updateThemeColors();

        themeListener = new IPreferenceChangeListener() {

            public void preferenceChange(PreferenceChangeEvent event) {
                String key = event.getKey();
                if (IThemeManager.THEME_CHANGED.equals(key) || IPreferenceConstants.APPLY_TO_ALL_VIEWS.equals(key)
                        || IPreferenceConstants.INVASIVE_FONT.equals(key)) {
                    updateThemeColors();
                }
            }
        };

        parent.setLayout(GridLayoutFactory.fillDefaults().margins(0, 1).spacing(0, 0).create());

        search = new SearchComposite(parent, SWT.NONE, false, new SearchComposite.Client() {

            @SuppressWarnings("restriction")
            public void search(String text, boolean isCaseSensitive, boolean isRegularExpression) {
                Pattern pattern = PatternConstructor.createPattern(text, isCaseSensitive, isRegularExpression);
                filter.setPattern(pattern);

                List<SnippetElement> snippets = new ArrayList<SnippetElement>();
                Map<SnippetElement, SnippetData> mapping = new HashMap<SnippetElement, SnippetsView.SnippetData>();

                for (List<SnippetData> list : sortedSnippets.values()) {
                    for (SnippetData data : list) {
                        snippets.add(data.snippet);
                        mapping.put(data.snippet, data);
                    }
                }

                Object[] filtered = filter.filter(null, (Object) null,
                        snippets.toArray(new SnippetElement[snippets.size()]));

                List<SnippetData> snippetDatas = new ArrayList<SnippetsView.SnippetData>();

                for (Object object : filtered) {
                    snippetDatas.add(mapping.get(object));
                }

                // Updates the snippets on a delay
                if (filterSnippetsJob != null) {
                    filterSnippetsJob.cancel();
                }

                filterSnippetsJob = new FilterSnippetsJob(snippetDatas);
                filterSnippetsJob.schedule(750);
            }
        });

        search.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).create());
        search.setSearchOnEnter(false);
        search.setInitialText(Messages.SnippetsView_Initial_filter_text);

        scrolledComposite = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
        scrolledComposite.setLayoutData(GridDataFactory.fillDefaults().grab(true, true).create());
        scrolledComposite.setLayout(GridLayoutFactory.fillDefaults().spacing(0, 0).create());
        scrolledComposite.getVerticalBar().setIncrement(6);

        expandBar = new ExpandBar(scrolledComposite, SWT.NONE);
        expandBar.setLayoutData(GridDataFactory.fillDefaults().grab(true, true).create());

        scrolledComposite.setBackground(getBackgroundColor());
        scrolledComposite.setForeground(themeFgColor);

        scrolledComposite.setContent(expandBar);
        scrolledComposite.setExpandHorizontal(true);
        scrolledComposite.setExpandVertical(true);

        updateBgColor();

        createSnippetDrawers();

        expandBar.addExpandListener(new ExpandListener() {

            public void itemExpanded(ExpandEvent e) {
                expandDrawer((String) ((ExpandItem) e.item).getData(), false);
                updateScrollMinSize((ExpandItem) e.item, true);
                collapsedCategories.remove((String) e.item.getData());
            }

            public void itemCollapsed(ExpandEvent e) {
                updateScrollMinSize((ExpandItem) e.item, false);
                collapsedCategories.add((String) e.item.getData());
            }
        });

        initializeToolBar();

        IPartService service = (IPartService) getSite().getService(IPartService.class);
        if (service != null) {
            service.addPartListener(partListener);
        }

        IWorkbenchPage page = getSite().getPage();
        if (page != null) {
            updateBasedOnPart(page.getActiveEditor());
        }

        getSite().getWorkbenchWindow().getSelectionService().addPostSelectionListener(selectionListener);
    }

    private void updateBasedOnPart(IWorkbenchPart part) {
        if (part instanceof AbstractThemeableEditor) {
            AbstractThemeableEditor textEditor = (AbstractThemeableEditor) part;
            String scope = null;

            try {
                ISourceViewer viewer = TextEditorUtils.getSourceViewer(textEditor);
                int caretOffset = textEditor.getCaretOffset();
                if (viewer == null) {
                    IDocument document = textEditor.getDocumentProvider().getDocument(textEditor.getEditorInput());
                    scope = CommonEditorPlugin.getDefault().getDocumentScopeManager().getScopeAtOffset(document,
                            caretOffset);
                } else {
                    // Get the scope at caret offset
                    scope = CommonEditorPlugin.getDefault().getDocumentScopeManager().getScopeAtOffset(viewer,
                            caretOffset);
                }
            } catch (Exception e) {

            }

            if (currentScope == null || !currentScope.equals(scope)) {
                updateSnippetEnablement(scope);
            }
        } else if (part instanceof IEditorPart) {
            updateSnippetEnablement(null);
        }
    }

    /*
     * Updates the minimum size of the scrolled composite after a drawer is expanded/collapsed. This ensures the
     * scrolling works properly
     */
    private void updateScrollMinSize(ExpandItem item, boolean expanded) {
        if (item == null) {
            Point size = expandBar.computeSize(SWT.DEFAULT, SWT.DEFAULT);
            scrolledComposite.setMinSize(size.x, size.y);
        } else {
            int minHeight = scrolledComposite.getMinHeight();
            minHeight = expanded ? minHeight + item.getHeight() : minHeight - item.getHeight();
            scrolledComposite.setMinHeight(minHeight);
        }
    }

    protected void showSnippets(List<SnippetData> snippets) {
        Map<String, List<SnippetElement>> localSnippets = new HashMap<String, List<SnippetElement>>();

        for (SnippetData snippetData : snippets) {
            String category = getSnippetCategoryName(snippetData.snippet);
            List<SnippetElement> snippetList = localSnippets.get(category);
            if (snippetList == null) {
                snippetList = new ArrayList<SnippetElement>();
                localSnippets.put(category, snippetList);
            }

            snippetList.add(snippetData.snippet);
        }

        // Determine whether to show or hide the snippet
        Map<String, List<Control>> changedSnippets = new HashMap<String, List<Control>>();
        for (String category : sortedSnippets.keySet()) {
            List<SnippetData> datas = sortedSnippets.get(category);
            for (SnippetData data : datas) {
                SnippetItem item = snippetItems.get(data);
                boolean newExclude = !snippets.contains(data);
                boolean oldExclude = data.filtered;
                data.filtered = newExclude;
                boolean itemChanged = false;

                if (item != null) {
                    if (newExclude != oldExclude) {
                        itemChanged = true;
                    }

                    item.setFiltered(newExclude);
                } else if (!newExclude) {
                    ExpandItem expandItem = expandItems.get(category);
                    if (expandItem.getExpanded()) {
                        expandDrawer(category, false);
                        itemChanged = true;
                        item = snippetItems.get(data);
                        if (item != null) {
                            item.setFiltered(newExclude);
                        }
                    }
                }

                if (itemChanged) {
                    List<Control> list = changedSnippets.get(category);
                    if (list == null) {
                        list = new ArrayList<Control>();
                        changedSnippets.put(category, list);
                    }
                    list.add(item);
                }
            }
        }

        // Update category text and layout if necessary
        for (String category : expandItems.keySet()) {
            int size = 0;
            List<SnippetElement> sortedSnippets = localSnippets.get(category);
            if (sortedSnippets != null) {
                size = sortedSnippets.size();
            }

            ExpandItem expandItem = expandItems.get(category);
            expandItem.setText(MessageFormat.format(Messages.SnippetsView_Snippet_drawer_title,
                    category != null ? category : Messages.SnippetsView_Snippet_drawer_other,
                    String.valueOf(size)));

            List<Control> list = changedSnippets.get(category);
            if (!CollectionsUtil.isEmpty(list)) {
                ((Composite) expandItem.getControl()).layout(list.toArray(new Control[list.size()]));
                expandItem.setHeight(expandItem.getControl().computeSize(SWT.DEFAULT, SWT.DEFAULT).y);
            }
        }

        updateScrollMinSize(null, false);
    }

    protected String getSnippetCategoryName(SnippetElement snippet) {
        return snippet.getCategory() != null ? snippet.getCategory() : snippet.getOwningBundle().getDisplayName();
    }

    protected void createSnippetDrawers() {
        disposeThemers();
        themedControls.clear();
        snippetItems.clear();
        toolItemMap.clear();
        currentScope = null;

        EclipseUtil.instanceScope().getNode(ThemePlugin.PLUGIN_ID).removePreferenceChangeListener(themeListener);

        List<String> expandedDrawers = new ArrayList<String>();
        for (ExpandItem item : expandItems.values()) {
            if (item.isDisposed()) {
                continue;
            } else if (item.getExpanded()) {
                expandedDrawers.add((String) item.getData());
            }

            item.getControl().dispose();
            item.dispose();
        }

        expandItems.clear();

        updateCategoriesAndSnippets(expandedDrawers);

        updateScrollMinSize(null, false);
        scrolledComposite.layout(true, true);

        updateSnippetDrawers();

        EclipseUtil.instanceScope().getNode(ThemePlugin.PLUGIN_ID).addPreferenceChangeListener(themeListener);
    }

    /**
     * Lazy load the create of SnippetItems when the category is expanded
     * 
     * @param category
     * @param shouldExpand
     */
    private void expandDrawer(String category, boolean shouldExpand) {
        ExpandItem expandItem = expandItems.get(category);
        if (expandItem != null) {
            boolean adjustHeight = false;
            Composite composite = (Composite) expandItem.getControl();
            List<SnippetData> elements = sortedSnippets.get(category);
            if (!CollectionsUtil.isEmpty(elements)) {
                for (SnippetData element : elements) {
                    if (snippetItems.get(element) == null) {
                        snippetItems.put(element, new SnippetItem(composite, element));
                        adjustHeight = true;
                    }
                }
            }

            if (adjustHeight) {
                expandItem.setHeight(composite.computeSize(SWT.DEFAULT, SWT.DEFAULT).y);
            }

            if (shouldExpand) {
                expandItem.setExpanded(true);
            }
        }
    }

    private void updateSnippetEnablement(String scope) {
        currentScope = scope;
        for (List<SnippetData> datas : sortedSnippets.values()) {
            for (SnippetData data : datas) {
                boolean matches = data.snippet.getScopeSelector().matches(scope);
                SnippetItem item = snippetItems.get(data);
                if (item != null) {
                    item.setVisualEnabled(matches);
                } else {
                    data.visuallyEnabled = matches;
                }
            }
        }

        updateSnippetDrawers();
    }

    private void updateCategoriesAndSnippets(List<String> expandedDrawers) {
        snippetCategories.clear();

        for (SnippetCategoryElement category : BundleManager.getInstance().getSnippetCategories(null)) {
            snippetCategories.put(category.getDisplayName(), category);
        }

        List<SnippetElement> snippets = BundleManager.getInstance().getSnippets(null);

        if (CollectionsUtil.isEmpty(snippets)) {
            currentScope = null;
        }

        snippetItems.clear();
        sortedSnippets.clear();
        toolItemMap.clear();

        Set<String> tags = new HashSet<String>();

        for (SnippetElement snippet : snippets) {
            String category = getSnippetCategoryName(snippet);

            if (!snippetCategories.containsKey(category)) {
                SnippetCategoryElement snippetCategoryElement = new SnippetCategoryElement(null);
                snippetCategoryElement.setDisplayName(category);
                snippetCategories.put(category, snippetCategoryElement);
            }

            List<SnippetData> snippetList = sortedSnippets.get(category);
            if (snippetList == null) {
                snippetList = new ArrayList<SnippetData>();
                sortedSnippets.put(category, snippetList);
            }

            snippetList.add(new SnippetData(snippet));
            tags.addAll(snippet.getTags());
        }

        // Only on windows: Ensure the image widths are all the same for tags, due to the platform scaling all toolitems
        // to be the same width
        if (Platform.OS_WIN32.equals(Platform.getOS())) {
            tagWidth = -1;
            GC gc = new GC(UIUtils.getDisplay());
            if (!CollectionsUtil.isEmpty(tags)) {
                for (String tag : tags) {
                    if (!StringUtil.isEmpty(tag)) {
                        int extent = gc.textExtent(MessageFormat.format("__{0}__", tag)).x; //$NON-NLS-1$
                        if (extent > tagWidth) {
                            tagWidth = extent;
                        }
                    }
                }
            }

            gc.dispose();
        }

        List<SnippetCategoryElement> categories = new ArrayList<SnippetCategoryElement>();
        categories.addAll(snippetCategories.values());
        Collections.sort(categories, snippetCategoryElementComparator);

        for (SnippetCategoryElement category : categories) {
            List<SnippetData> snippetList = sortedSnippets.get(category.getDisplayName());
            ExpandItem expandItem = createSnippetDrawer(expandBar, category, snippetList);
            if (expandedDrawers.contains(expandItem.getData())) {
                expandDrawer((String) expandItem.getData(), true);
            }
        }
    }

    private void addThemedControl(Control control, int fontStyle, int alpha) {
        themedControls.add(control);
        Color defaultBg = UIUtils.getDisplay().getSystemColor(SWT.COLOR_WHITE);
        if (fontStyle != SWT.NONE) {
            themeFactory.applyWithFontStyle(control, fontStyle, defaultBg);
        } else if (alpha > -1) {
            themeFactory.applyWithAlpha(control, alpha, defaultBg);
        } else {
            themeFactory.apply(control, defaultBg);
        }
    }

    private void disposeThemers() {
        for (Control control : themedControls) {
            themeFactory.dispose(control);
        }
    }

    private void updateBgColor() {
        if (scrolledComposite != null && expandBar != null) {
            scrolledComposite.setBackground(getBackgroundColor());
            expandBar.setBackground(getBackgroundColor());
        }
    }

    private Color getBackgroundColor() {
        return themeBgColor != null ? themeBgColor : UIUtils.getDisplay().getSystemColor(SWT.COLOR_WHITE);
    }

    private void updateSnippetDrawers() {
        for (ExpandItem item : expandItems.values()) {
            Composite control = (Composite) item.getControl();
            control.layout(true, true);
            item.setHeight(control.computeSize(SWT.DEFAULT, SWT.DEFAULT).y);
        }
    }

    protected ExpandItem createSnippetDrawer(ExpandBar expandBar, SnippetCategoryElement category,
            List<SnippetData> snippets) {
        Composite composite = new Composite(expandBar, SWT.NONE);
        composite.setForeground(
                themeFgColor != null ? themeFgColor : UIUtils.getDisplay().getSystemColor(SWT.COLOR_WIDGET_BORDER));
        composite.setLayout(GridLayoutFactory.fillDefaults().spacing(0, 0).create());

        ExpandItem expandItem = new ExpandItem(expandBar, SWT.NONE);
        int size = 0;
        if (snippets != null) {
            size = snippets.size();
        }

        expandItem.setText(MessageFormat.format(Messages.SnippetsView_Snippet_drawer_title,
                category.getDisplayName(), String.valueOf(size)));
        expandItem.setControl(composite);
        expandItem.setImage(category != null && category.getIconURL() != null ? getImage(category.getIconURL())
                : genericSnippetImage);
        expandItem.setData(category != null ? category.getDisplayName() : null);
        expandItem.setHeight(composite.computeSize(SWT.DEFAULT, SWT.DEFAULT).y);

        expandItems.put(category != null ? category.getDisplayName() : null, expandItem);

        return expandItem;
    }

    private void insertSnippet(SnippetElement snippet) {
        IEditorPart activeEditor = UIUtils.getActiveEditor();
        ITextEditor textEditor = null;
        if (activeEditor instanceof MultiPageEditorPart) {
            Object selectedPage = ((MultiPageEditorPart) activeEditor).getSelectedPage();
            if (selectedPage instanceof ITextEditor) {
                textEditor = (ITextEditor) selectedPage;
            }
        } else if (activeEditor instanceof ITextEditor) {
            textEditor = (ITextEditor) activeEditor;
        }

        if (textEditor != null) {
            CommandResult commandResult = CommandExecutionUtils.executeCommand(snippet, InvocationType.MENU,
                    textEditor);
            if (commandResult != null) {
                CommandExecutionUtils.processCommandResult(snippet, commandResult, textEditor);
                activeEditor.setFocus();
            }
        }
    }

    private int createTagToolItem(ToolBar tagToolBar, int height, final String tag) {
        ToolItem item = new ToolItem(tagToolBar, SWT.PUSH);
        item.setData("tag", tag); //$NON-NLS-1$
        height = createTagImagesForToolItem(tagToolBar, height, tag, item);

        item.addSelectionListener(new SelectionAdapter() {
            /*
             * (non-Javadoc)
             * @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent)
             */
            @Override
            public void widgetSelected(SelectionEvent e) {
                search.getTextControl().setFocus();
                search.getTextControl().setText(tag);
            }
        });

        List<ToolItem> toolItems = toolItemMap.get(tag);
        if (toolItems == null) {
            toolItems = new ArrayList<ToolItem>();
            toolItemMap.put(tag, toolItems);
        }

        toolItems.add(item);

        return height;
    }

    private int createTagImagesForToolItem(ToolBar tagToolBar, int height, String tag, ToolItem item) {
        item.setHotImage(null);
        item.setImage(null);

        item.setText(MessageFormat.format("__{0}__", tag)); //$NON-NLS-1$
        Rectangle bounds = item.getBounds();
        int width = bounds.width;
        item.setText(StringUtil.EMPTY);

        if (height < 0) {
            GC gc = new GC(Display.getCurrent());
            gc.setFont(themeFont);
            Point textExtent = gc.textExtent("apt", SWT.DRAW_TRANSPARENT); //$NON-NLS-1$
            height = textExtent.y + (textExtent.y % 2 == 0 ? 4 : 5);
            gc.dispose();
        }

        Image image = imageRegistry.get(tag);

        if (image == null) {
            image = createTagImage(tagToolBar, height, width, tag, false);
        }
        item.setImage(image);

        Image hotImage = hotTagImageRegistry.get(tag);

        if (hotImage == null) {
            hotImage = createTagImage(tagToolBar, height, width, tag, true);
        }
        item.setHotImage(hotImage);

        return height;
    }

    private Image createTagImage(ToolBar tagToolBar, int height, int width, String tagName, boolean isHot) {
        if (tagWidth > 0) {
            width = tagWidth;
        }

        Image image = new Image(UIUtils.getDisplay(), width, height);
        GC gc = new GC(image);
        gc.setAntialias(SWT.ON);
        Color fg = gc.getForeground();
        Color bgColor = tagToolBar.getBackground();
        if (themeBgColor != null) {
            bgColor = themeBgColor;
        }

        gc.setFont(themeFont);
        Point textSize = gc.textExtent(tagName);

        gc.setBackground(bgColor);
        gc.fillRectangle(0, 0, width, height);

        Color tempFg = null;
        Color tempBg = null;

        if (isHot) {
            if (themeFgColor == null) {
                tempFg = colorManager.getColor(defaultTagHotFg);
                tempBg = colorManager.getColor(defaultTagHotBg);
            } else {
                tempFg = colorManager
                        .getColor(Theme.alphaBlend(themeBgColor.getRGB(), themeFgColor.getRGB(), FG_ALPHA));
                tempBg = themeSelectionColor;
            }
        } else {
            if (themeFgColor == null) {
                tempFg = colorManager.getColor(defaultTagFg);
                tempBg = colorManager.getColor(defaultTagBg);
            } else {
                tempFg = colorManager
                        .getColor(Theme.alphaBlend(themeBgColor.getRGB(), themeFgColor.getRGB(), FG_ALPHA));
                tempBg = colorManager
                        .getColor(Theme.alphaBlend(themeBgColor.getRGB(), themeSelectionColor.getRGB(), FG_ALPHA));
            }
        }

        gc.setForeground(tempFg);
        gc.setBackground(tempBg);

        gc.setAntialias(SWT.OFF);
        gc.fillRoundRectangle(1, 1, width - 2, height - 2, height - 2, height - 2);
        gc.setAntialias(SWT.ON);
        gc.drawRoundRectangle(0, 0, width - 1, height - 1, height - 1, height - 1);

        gc.setForeground(themeFgColor == null ? fg : themeFgColor);

        gc.drawText(tagName, (width - textSize.x) / 2, (height - textSize.y) / 2, true);

        ImageData imageData = image.getImageData();
        imageData.transparentPixel = imageData.palette.getPixel(bgColor.getRGB());
        image.dispose();
        gc.dispose();

        image = new Image(UIUtils.getDisplay(), imageData);

        ImageRegistry registry = isHot ? hotTagImageRegistry : imageRegistry;
        registry.put(tagName, image);

        return image;
    }

    private Image getImage(URL url) {
        String urlString = url.toString();
        Image image = imageRegistry.get(urlString);

        if (image == null) {
            imageRegistry.put(urlString, ImageDescriptor.createFromURL(url));
            image = imageRegistry.get(urlString);
        }

        return image;
    }

    private Image getDisabledImage(URL url) {
        String urlString = url.toString();
        Image image = disabledImageRegistry.get(urlString);

        if (image == null) {
            ImageDescriptor descriptor = imageRegistry.getDescriptor(urlString);
            disabledImageRegistry.put(urlString, ImageDescriptor.createWithFlags(descriptor, SWT.IMAGE_DISABLE));
            image = disabledImageRegistry.get(urlString);
        }

        return image;
    }

    /**
     * Initialize the toolbar.
     */
    private void initializeToolBar() {
        IToolBarManager toolbarManager = getViewSite().getActionBars().getToolBarManager();
        Action collapseAll = new Action() {

            /*
             * (non-Javadoc)
             * @see org.eclipse.jface.action.Action#run()
             */
            @Override
            public void run() {
                for (ExpandItem item : expandItems.values()) {
                    boolean expanded = item.getExpanded();
                    item.setExpanded(false);
                    if (expanded) {
                        updateScrollMinSize(item, false);
                    }
                }
            }
        };

        collapseAll.setText(Messages.SnippetsView_Collapse_All_Action);
        collapseAll.setImageDescriptor(
                PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_ELCL_COLLAPSEALL));
        toolbarManager.add(collapseAll);

        Action expandAll = new Action() {
            /*
             * (non-Javadoc)
             * @see org.eclipse.jface.action.Action#run()
             */
            @Override
            public void run() {
                for (ExpandItem item : expandItems.values()) {
                    boolean expanded = item.getExpanded();
                    expandDrawer((String) item.getData(), true);
                    if (!expanded) {
                        updateScrollMinSize(item, true);
                    }
                }
            }
        };

        expandAll.setText(Messages.SnippetsView_Expand_All_Action);
        expandAll.setImageDescriptor(ScriptingUIPlugin.getImageDescriptor(ICONS_FULL_ELCL16_EXPANDALL_GIF));
        toolbarManager.add(expandAll);
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.ui.part.WorkbenchPart#dispose()
     */
    @Override
    public void dispose() {
        imageRegistry.dispose();
        disabledImageRegistry.dispose();
        hotTagImageRegistry.dispose();

        EclipseUtil.instanceScope().getNode(ThemePlugin.PLUGIN_ID).removePreferenceChangeListener(themeListener);
        snippetBundleListener.dispose();

        super.dispose();
        colorManager.dispose();
        disposeThemers();

        getSite().getWorkbenchWindow().getSelectionService().removePostSelectionListener(selectionListener);

        IPartService service = (IPartService) getSite().getService(IPartService.class);
        if (service != null) {
            service.removePartListener(partListener);
        }
    }

    @Override
    public void setFocus() {
        expandBar.setFocus();
    }
}