com.deepsky.findUsages.workarounds.UsageView2Impl.java Source code

Java tutorial

Introduction

Here is the source code for com.deepsky.findUsages.workarounds.UsageView2Impl.java

Source

/*
 * Copyright 2000-2009 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.deepsky.findUsages.workarounds;

import com.deepsky.findUsages.SqlUsageGroupingRuleProviderImpl;
import com.deepsky.findUsages.providers.UsageFilteringRuleProvider2Impl;
import com.intellij.ide.*;
import com.intellij.ide.ExporterToTextFile;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.SimpleToolWindowPanel;
import com.intellij.openapi.ui.Splitter;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Factory;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.vfs.ReadonlyStatusHandler;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.Navigatable;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.ui.PopupHandler;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.ui.SmartExpander;
import com.intellij.ui.content.Content;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewBundle;
import com.intellij.usages.*;
import com.intellij.usages.impl.*;
import com.intellij.usages.impl.UsageViewManagerImpl;
import com.intellij.usages.rules.*;
import com.intellij.util.Alarm;
import com.intellij.util.EditSourceOnDoubleClickHandler;
import com.intellij.util.Processor;
import com.intellij.util.containers.ConcurrentHashSet;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.ui.DialogUtil;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.tree.TreeUtil;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.plaf.ScrollPaneUI;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * @author max
 */
public class UsageView2Impl implements UsageView, UsageModelTracker.UsageModelTrackerListener {
    @NonNls
    public static final String SHOW_RECENT_FIND_USAGES_ACTION_ID = "UsageView.ShowRecentFindUsages";

    private final UsageNodeTreeBuilder myBuilder;
    private final MyPanel myRootPanel;
    private final JTree myTree;
    private Content myContent;

    private final UsageViewPresentation myPresentation;
    private final UsageTarget[] myTargets;
    private final Factory<UsageSearcher> myUsageSearcherFactory;
    private final Project myProject;

    private boolean mySearchInProgress = true;
    private ExporterToTextFile myTextFileExporter;
    private final Alarm myUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);

    private final Alarm myFlushAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
    private final UsageModelTracker myModelTracker;
    private final Set<Usage> myUsages = new ConcurrentHashSet<Usage>();
    private final Map<Usage, UsageNode> myUsageNodes = new ConcurrentHashMap<Usage, UsageNode>();
    public static final UsageNode NULL_NODE = new UsageNode(NullUsage.INSTANCE,
            new UsageViewTreeModelBuilder(new UsageViewPresentation(), UsageTarget.EMPTY_ARRAY));
    private final ButtonPanel myButtonPanel = new ButtonPanel();
    private volatile boolean isDisposed;
    private volatile boolean myChangesDetected = false;
    private final Queue<Usage> myUsagesToFlush = new ConcurrentLinkedQueue<Usage>();
    static final Comparator<Usage> USAGE_COMPARATOR = new Comparator<Usage>() {
        public int compare(final Usage o1, final Usage o2) {
            if (o1 == NULL_NODE || o2 == NULL_NODE)
                return -1;
            if (o1 instanceof Comparable && o2 instanceof Comparable) {
                final int selfcompared = ((Comparable<Usage>) o1).compareTo(o2);
                if (selfcompared != 0)
                    return selfcompared;

                if (o1 instanceof UsageInFile && o2 instanceof UsageInFile) {
                    UsageInFile u1 = (UsageInFile) o1;
                    UsageInFile u2 = (UsageInFile) o2;

                    VirtualFile f1 = u1.getFile();
                    VirtualFile f2 = u2.getFile();

                    if (f1 != null && f1.isValid() && f2 != null && f2.isValid()) {
                        return f1.getPresentableUrl().compareTo(f2.getPresentableUrl());
                    }
                }

                return 0;
            }
            return -1;
        }
    };
    @NonNls
    private static final String HELP_ID = "ideaInterface.find";
    private UsagePreviewPanel myUsagePreviewPanel;
    private JPanel myCentralPanel;
    private final GroupNode myRoot;
    private final UsageViewTreeModelBuilder myModel;

    private static UsageFilteringRuleProvider filteringRuleProvider = new UsageFilteringRuleProvider2Impl();

    public UsageView2Impl(@NotNull Project project, @NotNull UsageViewPresentation presentation,
            @NotNull UsageTarget[] targets, Factory<UsageSearcher> usageSearcherFactory) {
        myPresentation = presentation;
        myTargets = targets;
        myUsageSearcherFactory = usageSearcherFactory;
        myProject = project;
        myTree = new Tree() {
            {
                ToolTipManager.sharedInstance().registerComponent(this);
            }

            public String getToolTipText(MouseEvent e) {
                TreePath path = getPathForLocation(e.getX(), e.getY());
                if (path != null) {
                    if (getCellRenderer() instanceof UsageViewTreeCellRenderer) {
                        return UsageViewTreeCellRenderer.getTooltipText(path.getLastPathComponent());
                    }
                }
                return null;
            }

            public boolean isPathEditable(final TreePath path) {
                return path.getLastPathComponent() instanceof UsageViewTreeModelBuilder.TargetsRootNode;
            }
        };
        myRootPanel = new MyPanel(myTree);
        myModelTracker = new UsageModelTracker(project);
        Disposer.register(this, myModelTracker);

        myModel = new UsageViewTreeModelBuilder(myPresentation, targets);
        myRoot = (GroupNode) myModel.getRoot();

        myBuilder = new UsageNodeTreeBuilder(getActiveGroupingRules(project), getActiveFilteringRules(project),
                myRoot);

        final MessageBusConnection messageBusConnection = myProject.getMessageBus().connect(this);

        messageBusConnection.subscribe(UsageFilteringRuleProvider.RULES_CHANGED, new Runnable() {
            public void run() {
                rulesChanged();
            }
        });

        if (!myPresentation.isDetachedMode()) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    if (isDisposed)
                        return;
                    myTree.setModel(myModel);

                    myRootPanel.setLayout(new BorderLayout());

                    final SimpleToolWindowPanel twPanel = new SimpleToolWindowPanel(false, true);
                    myRootPanel.add(twPanel, BorderLayout.CENTER);

                    JPanel toolbarPanel = new JPanel(new BorderLayout());
                    toolbarPanel.add(createActionsToolbar(), BorderLayout.WEST);
                    toolbarPanel.add(createFiltersToolbar(), BorderLayout.CENTER);
                    twPanel.setToolbar(toolbarPanel);

                    myCentralPanel = new JPanel();
                    myCentralPanel.setLayout(new BorderLayout());
                    setupCentralPanel();

                    initTree();
                    twPanel.setContent(myCentralPanel);

                    myTree.setCellRenderer(new UsageViewTreeCellRenderer(UsageView2Impl.this));
                    collapseAll();

                    myModelTracker.addListener(UsageView2Impl.this);

                    if (myPresentation.isShowCancelButton()) {
                        addButtonToLowerPane(new Runnable() {
                            public void run() {
                                close();
                            }
                        }, UsageViewBundle.message("usage.view.cancel.button"));
                    }

                    myTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
                        public void valueChanged(final TreeSelectionEvent e) {
                            SwingUtilities.invokeLater(new Runnable() {
                                public void run() {
                                    if (isDisposed)
                                        return;
                                    java.util.List<UsageInfo> infos = getSelectedUsageInfos();
                                    if (infos != null && myUsagePreviewPanel != null) {
                                        myUsagePreviewPanel.updateLayout(infos);
                                    }
                                }
                            });
                        }
                    });
                }
            });
        }
    }

    private void setupCentralPanel() {
        myCentralPanel.removeAll();
        if (myUsagePreviewPanel != null) {
            Disposer.dispose(myUsagePreviewPanel);
            myUsagePreviewPanel = null;
        }
        if (UsageViewSettings.getInstance().IS_PREVIEW_USAGES) {
            Splitter splitter = new Splitter(false,
                    UsageViewSettings.getInstance().PREVIEW_USAGES_SPLITTER_PROPORTIONS);
            // collisions with RubyMine           splitter.setFirstComponent(ScrollPaneFactory.createScrollPane(myTree));
            splitter.setFirstComponent(new JScrollPaneEe(myTree));
            myUsagePreviewPanel = new UsagePreviewPanel(myProject);
            Disposer.register(this, myUsagePreviewPanel);
            splitter.setSecondComponent(myUsagePreviewPanel);
            myCentralPanel.add(splitter, BorderLayout.CENTER);
        } else {
            // collisions with RubyMine            myCentralPanel.add(ScrollPaneFactory.createScrollPane(myTree), BorderLayout.CENTER);
            myCentralPanel.add(new JScrollPaneEe(myTree), BorderLayout.CENTER);

        }
        myCentralPanel.add(myButtonPanel, BorderLayout.SOUTH);

        myRootPanel.revalidate();
    }

    private static UsageFilteringRule[] getActiveFilteringRules(final Project project) {
        //    final UsageFilteringRuleProvider[] providers = Extensions.getExtensions(UsageFilteringRuleProvider.EP_NAME);
        java.util.List<UsageFilteringRule> list = new ArrayList<UsageFilteringRule>(); //(providers.length);
        //    for (UsageFilteringRuleProvider provider : providers) {
        //      list.addAll(Arrays.asList(provider.getActiveRules(project)));
        //    }
        UsageFilteringRuleProvider provider = filteringRuleProvider; //new UsageFilteringRuleProvider2Impl();
        list.addAll(Arrays.asList(provider.getActiveRules(project)));
        return list.toArray(new UsageFilteringRule[list.size()]);
    }

    private static UsageGroupingRule[] getActiveGroupingRules(final Project project) {
        //final UsageGroupingRuleProvider[] providers = Extensions.getExtensions(UsageGroupingRuleProvider.EP_NAME);
        java.util.List<UsageGroupingRule> list = new ArrayList<UsageGroupingRule>(); //providers.length);
        UsageGroupingRuleProvider provider = new SqlUsageGroupingRuleProviderImpl();
        list.addAll(Arrays.asList(provider.getActiveRules(project)));
        //      for (UsageGroupingRuleProvider provider : providers) {
        //      list.addAll(Arrays.asList(provider.getActiveRules(project)));
        //    }

        /*
                Collections.sort(list, new Comparator<UsageGroupingRule>() {
        public int compare(final UsageGroupingRule o1, final UsageGroupingRule o2) {
            return getRank(o1) - getRank(o2);
        }
            
        private int getRank(final UsageGroupingRule rule) {
            if (rule instanceof OrderableUsageGroupingRule) {
                return ((OrderableUsageGroupingRule) rule).getRank();
            }
            
            return Integer.MAX_VALUE;
        }
                });
        */

        return list.toArray(new UsageGroupingRule[list.size()]);
    }

    public void modelChanged(boolean isPropertyChange) {
        if (!isPropertyChange) {
            myChangesDetected = true;
        }
        updateLater();
    }

    private void initTree() {
        myTree.setRootVisible(false);
        myTree.setShowsRootHandles(true);
        SmartExpander.installOn(myTree);
        TreeUtil.installActions(myTree);
        EditSourceOnDoubleClickHandler.install(myTree);
        myTree.addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent e) {
                if (KeyEvent.VK_ENTER == e.getKeyCode()) {
                    TreePath leadSelectionPath = myTree.getLeadSelectionPath();
                    if (leadSelectionPath == null)
                        return;

                    DefaultMutableTreeNode node = (DefaultMutableTreeNode) leadSelectionPath.getLastPathComponent();
                    if (node instanceof UsageNode) {
                        final Usage usage = ((UsageNode) node).getUsage();
                        usage.navigate(false);
                        usage.highlightInEditor();
                    } else if (node.isLeaf()) {
                        Navigatable navigatable = getNavigatableForNode(node);
                        if (navigatable != null && navigatable.canNavigate()) {
                            navigatable.navigate(false);
                        }
                    }
                }
            }
        });

        TreeUtil.selectFirstNode(myTree);
        PopupHandler.installPopupHandler(myTree, IdeActions.GROUP_USAGE_VIEW_POPUP, ActionPlaces.USAGE_VIEW_POPUP);
        //TODO: install speed search. Not in openapi though. It makes sense to create a common TreeEnchancer service.
    }

    private JComponent createActionsToolbar() {
        DefaultActionGroup group = new DefaultActionGroup() {
            public void update(AnActionEvent e) {
                super.update(e);
                myButtonPanel.update();
            }
        };

        AnAction[] actions = createActions();
        for (final AnAction action : actions) {
            if (action != null) {
                group.add(action);
            }
        }
        return toUsageViewToolbar(group);
    }

    private JComponent toUsageViewToolbar(final DefaultActionGroup group) {
        ActionToolbar actionToolbar = ActionManager.getInstance()
                .createActionToolbar(ActionPlaces.USAGE_VIEW_TOOLBAR, group, false);
        actionToolbar.setTargetComponent(myRootPanel);
        return actionToolbar.getComponent();
    }

    private JComponent createFiltersToolbar() {
        final DefaultActionGroup group = createFilteringActionsGroup();
        return toUsageViewToolbar(group);
    }

    private DefaultActionGroup createFilteringActionsGroup() {
        final DefaultActionGroup group = new DefaultActionGroup();

        final AnAction[] groupingActions = createGroupingActions();
        for (AnAction groupingAction : groupingActions) {
            group.add(groupingAction);
        }

        addFilteringActions(group);

        group.add(new PreviewUsageAction(this));
        group.add(new SortMembersAlphabeticallyAction(this));
        return group;
    }

    public void addFilteringActions(DefaultActionGroup group) {
        final JComponent component = getComponent();
        final MergeDupLines mergeDupLines = new MergeDupLines();
        mergeDupLines.registerCustomShortcutSet(mergeDupLines.getShortcutSet(), component);
        scheduleDisposeOnClose(new Disposable() {
            public void dispose() {
                mergeDupLines.unregisterCustomShortcutSet(component);
            }
        });
        group.add(mergeDupLines);

        /*
        todo -- 
            final UsageFilteringRuleProvider[] providers = Extensions.getExtensions(UsageFilteringRuleProvider.EP_NAME);
            for (UsageFilteringRuleProvider provider : providers) {
              AnAction[] actions = provider.createFilteringActions(this);
              for (AnAction action : actions) {
                group.add(action);
              }
            }
        */
        UsageFilteringRuleProvider provider = filteringRuleProvider; //new UsageFilteringRuleProvider2Impl();
        AnAction[] actions = provider.createFilteringActions(this);
        for (AnAction action : actions) {
            group.add(action);
        }

    }

    public void scheduleDisposeOnClose(@NotNull Disposable disposable) {
        Disposer.register(this, disposable);
    }

    private AnAction[] createActions() {
        final TreeExpander treeExpander = new TreeExpander() {
            public void expandAll() {
                UsageView2Impl.this.expandAll();
            }

            public boolean canExpand() {
                return true;
            }

            public void collapseAll() {
                UsageView2Impl.this.collapseAll();
            }

            public boolean canCollapse() {
                return true;
            }
        };

        CommonActionsManager actionsManager = CommonActionsManager.getInstance();

        myTextFileExporter = null; // tood -- !!! new ExporterToTextFile(this);

        final JComponent component = getComponent();

        final AnAction expandAllAction = actionsManager.createExpandAllAction(treeExpander, component);
        final AnAction collapseAllAction = actionsManager.createCollapseAllAction(treeExpander, component);

        scheduleDisposeOnClose(new Disposable() {
            public void dispose() {
                collapseAllAction.unregisterCustomShortcutSet(component);
                expandAllAction.unregisterCustomShortcutSet(component);
            }
        });

        return new AnAction[] { canPerformReRun() ? new ReRunAction() : null, new CloseAction(),
                ActionManager.getInstance().getAction("PinToolwindowTab"), createRecentFindUsagesAction(),
                expandAllAction, collapseAllAction, actionsManager.createPrevOccurenceAction(myRootPanel),
                actionsManager.createNextOccurenceAction(myRootPanel),
                actionsManager.installAutoscrollToSourceHandler(myProject, myTree,
                        new MyAutoScrollToSourceOptionProvider()),
                actionsManager.createExportToTextFileAction(myTextFileExporter),
                actionsManager.createHelpAction(HELP_ID) };
    }

    private AnAction createRecentFindUsagesAction() {
        AnAction action = ActionManager.getInstance().getAction(SHOW_RECENT_FIND_USAGES_ACTION_ID);
        action.registerCustomShortcutSet(action.getShortcutSet(), getComponent());
        return action;
    }

    private AnAction[] createGroupingActions() {
        //    final UsageGroupingRuleProvider[] providers = Extensions.getExtensions(UsageGroupingRuleProvider.EP_NAME);
        java.util.List<AnAction> list = new ArrayList<AnAction>(); //providers.length);
        //    for (UsageGroupingRuleProvider provider : providers) {
        //        if(!(provider instanceof UsageGroupingRuleProviderImpl)){
        //            list.addAll(Arrays.asList(provider.createGroupingActions(this)));
        //        }
        //    }
        UsageGroupingRuleProvider provider = new SqlUsageGroupingRuleProviderImpl();
        list.addAll(Arrays.asList(provider.createGroupingActions(this)));

        return list.toArray(new AnAction[list.size()]);
    }

    private void rulesChanged() {
        final ArrayList<UsageState> states = new ArrayList<UsageState>();
        captureUsagesExpandState(new TreePath(myTree.getModel().getRoot()), states);
        final java.util.List<Usage> allUsages = new ArrayList<Usage>(myUsageNodes.keySet());
        Collections.sort(allUsages, USAGE_COMPARATOR);
        final Set<Usage> excludedUsages = getExcludedUsages();
        reset();
        myBuilder.setGroupingRules(getActiveGroupingRules(myProject));
        myBuilder.setFilteringRules(getActiveFilteringRules(myProject));
        ApplicationManager.getApplication().runReadAction(new Runnable() {
            public void run() {
                for (Usage usage : allUsages) {
                    if (!usage.isValid()) {
                        continue;
                    }
                    if (usage instanceof MergeableUsage) {
                        ((MergeableUsage) usage).reset();
                    }
                    appendUsage(usage);
                }
            }
        });
        excludeUsages(excludedUsages.toArray(new Usage[excludedUsages.size()]));
        if (myCentralPanel != null) {
            setupCentralPanel();
        }
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                if (isDisposed)
                    return;
                restoreUsageExpandState(states);
                updateImmediately();
            }
        });
    }

    private void captureUsagesExpandState(TreePath pathFrom, final Collection<UsageState> states) {
        if (!myTree.isExpanded(pathFrom)) {
            return;
        }
        final DefaultMutableTreeNode node = (DefaultMutableTreeNode) pathFrom.getLastPathComponent();
        final int childCount = node.getChildCount();
        for (int idx = 0; idx < childCount; idx++) {
            final TreeNode child = node.getChildAt(idx);
            if (child instanceof UsageNode) {
                final Usage usage = ((UsageNode) child).getUsage();
                states.add(new UsageState(usage,
                        myTree.getSelectionModel().isPathSelected(pathFrom.pathByAddingChild(child))));
            } else {
                captureUsagesExpandState(pathFrom.pathByAddingChild(child), states);
            }
        }
    }

    private void restoreUsageExpandState(final Collection<UsageState> states) {
        //always expand the last level group
        final DefaultMutableTreeNode root = (DefaultMutableTreeNode) myTree.getModel().getRoot();
        for (int i = root.getChildCount() - 1; i >= 0; i--) {
            final DefaultMutableTreeNode child = (DefaultMutableTreeNode) root.getChildAt(i);
            if (child instanceof GroupNode) {
                final TreePath treePath = new TreePath(child.getPath());
                myTree.expandPath(treePath);
            }
        }
        myTree.getSelectionModel().clearSelection();
        for (final UsageState usageState : states) {
            usageState.restore();
        }
    }

    private void expandAll() {
        TreeUtil.expandAll(myTree);
    }

    private void collapseAll() {
        TreeUtil.collapseAll(myTree, 3);
        TreeUtil.expand(myTree, 2);
    }

    public DefaultMutableTreeNode getModelRoot() {
        return (DefaultMutableTreeNode) myTree.getModel().getRoot();
    }

    public void select() {
        if (myTree != null) {
            myTree.requestFocusInWindow();
        }
    }

    public Project getProject() {
        return myProject;
    }

    private class CloseAction extends AnAction implements DumbAware {
        private CloseAction() {
            super(UsageViewBundle.message("action.close"), null, IconLoader.getIcon("/actions/cancel.png"));
        }

        public void update(AnActionEvent e) {
            super.update(e);
            e.getPresentation().setVisible(myContent != null);
        }

        public void actionPerformed(AnActionEvent e) {
            close();
        }
    }

    private class MergeDupLines extends RuleAction {
        private MergeDupLines() {
            super(UsageView2Impl.this, UsageViewBundle.message("action.merge.same.line"),
                    IconLoader.getIcon("/toolbar/filterdups.png"));
            setShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK)));
        }

        protected boolean getOptionValue() {
            return UsageViewSettings.getInstance().isFilterDuplicatedLine();
        }

        protected void setOptionValue(boolean value) {
            UsageViewSettings.getInstance().setFilterDuplicatedLine(value);
        }
    }

    private class ReRunAction extends AnAction implements DumbAware {
        private ReRunAction() {
            super(UsageViewBundle.message("action.rerun"), UsageViewBundle.message("action.description.rerun"),
                    IconLoader.getIcon("/actions/refreshUsages.png"));
            registerCustomShortcutSet(CommonShortcuts.getRerun(), myRootPanel);
        }

        public void actionPerformed(AnActionEvent e) {
            refreshUsages();
        }

        public void update(AnActionEvent e) {
            super.update(e);
            final Presentation presentation = e.getPresentation();
            presentation.setEnabled(allTargetsAreValid());
        }
    }

    private void refreshUsages() {
        reset();
        doReRun();
    }

    private void doReRun() {
        ProgressManager.getInstance()
                .run(new Task.Backgroundable(myProject, UsageViewManagerImpl.getProgressTitle(myPresentation)) {
                    public void run(@NotNull final ProgressIndicator indicator) {
                        setSearchInProgress(true);
                        final com.intellij.usages.UsageViewManager usageViewManager = com.intellij.usages.UsageViewManager
                                .getInstance(myProject);
                        usageViewManager.setCurrentSearchCancelled(false);

                        myChangesDetected = false;
                        UsageSearcher usageSearcher = myUsageSearcherFactory.create();
                        usageSearcher.generate(new Processor<Usage>() {
                            public boolean process(final Usage usage) {
                                if (usageViewManager.searchHasBeenCancelled())
                                    return false;
                                appendUsageLater(usage);
                                ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
                                return indicator == null || !indicator.isCanceled();
                            }
                        });

                        setSearchInProgress(false);
                    }
                });
    }

    public void reset() {
        myUsageNodes.clear();
        myIsFirstVisibleUsageFound = false;

        myModel.reset();
        if (!myPresentation.isDetachedMode()) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    if (isDisposed)
                        return;
                    TreeUtil.expand(myTree, 2);
                }
            });
        }

        myUsages.clear();
    }

    public void appendUsageLater(final Usage usage) {
        myUsagesToFlush.offer(usage);
        if (myUsagesToFlush.size() > 50) {
            flush();
        }
        myFlushAlarm.cancelAllRequests();
        myFlushAlarm.addRequest(new Runnable() {
            public void run() {
                flush();
            }
        }, 300);
    }

    private void flush() {
        ApplicationManager.getApplication().runReadAction(new Runnable() {
            public void run() {
                Usage usage;
                while ((usage = myUsagesToFlush.poll()) != null) {
                    appendUsage(usage);
                }
            }
        });
    }

    private volatile boolean myIsFirstVisibleUsageFound = false;

    public void appendUsage(@NotNull Usage usage) {
        doAppendUsage(usage);
    }

    public UsageNode doAppendUsage(Usage usage) {
        // invoke in ReadAction to be be sure that usages are not invalidated while the tree is being built
        ApplicationManager.getApplication().assertReadAccessAllowed();
        if (!usage.isValid()) {
            // because the view is built incrementally with Alarm, the usage may be already invalid, so need filter such cases
            return null;
        }
        myUsages.add(usage);
        UsageNode node = myBuilder.appendUsage(usage);
        myUsageNodes.put(usage, node == null ? NULL_NODE : node);
        if (!myIsFirstVisibleUsageFound && node != null) { //first visible usage found;
            myIsFirstVisibleUsageFound = true;
            showNode(node);
        }
        return node;
    }

    public void removeUsage(@NotNull Usage usage) {
        final UsageNode node = myUsageNodes.remove(usage);
        if (node != NULL_NODE && node != null && !myPresentation.isDetachedMode()) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    if (isDisposed)
                        return;
                    TreeModel treeModel = myTree.getModel();
                    ((DefaultTreeModel) treeModel).removeNodeFromParent(node);
                    ((GroupNode) myTree.getModel().getRoot()).removeUsage(node);
                }
            });
        }
    }

    public void includeUsages(@NotNull Usage[] usages) {
        for (Usage usage : usages) {
            final UsageNode node = myUsageNodes.get(usage);
            if (node != NULL_NODE && node != null) {
                node.setUsageExcluded(false);
            }
        }
        updateImmediately();
    }

    public void excludeUsages(@NotNull Usage[] usages) {
        for (Usage usage : usages) {
            final UsageNode node = myUsageNodes.get(usage);
            if (node != NULL_NODE && node != null) {
                node.setUsageExcluded(true);
            }
        }
        updateImmediately();
    }

    public void selectUsages(@NotNull Usage[] usages) {
        java.util.List<TreePath> paths = new LinkedList<TreePath>();

        for (Usage usage : usages) {
            final UsageNode node = myUsageNodes.get(usage);

            if (node != NULL_NODE && node != null) {
                paths.add(new TreePath(node.getPath()));
            }
        }

        myTree.setSelectionPaths(paths.toArray(new TreePath[paths.size()]));
        if (!paths.isEmpty())
            myTree.scrollPathToVisible(paths.get(0));
    }

    @NotNull
    public JComponent getComponent() {
        return myRootPanel;
    }

    public int getUsagesCount() {
        return myUsageNodes.size();
    }

    public void setContent(Content content) {
        myContent = content;
        content.setDisposer(this);
    }

    private void updateImmediately() {
        if (myProject.isDisposed())
            return;
        checkNodeValidity((DefaultMutableTreeNode) myTree.getModel().getRoot());
        if (myUsagePreviewPanel != null) {
            myUsagePreviewPanel.updateLayout(getSelectedUsageInfos());
        }
    }

    private void checkNodeValidity(DefaultMutableTreeNode node) {
        Enumeration enumeration = node.children();
        while (enumeration.hasMoreElements()) {
            checkNodeValidity((DefaultMutableTreeNode) enumeration.nextElement());
        }
        if (node instanceof Node && node != getModelRoot())
            ((Node) node).update(this);
    }

    private void updateLater() {
        myUpdateAlarm.cancelAllRequests();
        myUpdateAlarm.addRequest(new Runnable() {
            public void run() {
                if (myProject.isDisposed())
                    return;
                PsiDocumentManager.getInstance(myProject).commitAllDocuments();

                updateImmediately();
            }
        }, 300);
    }

    public void close() {
        // todo ? crazyness
        com.intellij.usages.UsageViewManager.getInstance(myProject).setCurrentSearchCancelled(true);
        com.intellij.usageView.UsageViewManager.getInstance(myProject).closeContent(myContent);
    }

    public void dispose() {
        isDisposed = true;
        ToolTipManager.sharedInstance().unregisterComponent(myTree);
        myModelTracker.removeListener(this);
        myUpdateAlarm.cancelAllRequests();
        myRootPanel.dispose();
        if (myUsagePreviewPanel != null) {
            UsageViewSettings.getInstance().PREVIEW_USAGES_SPLITTER_PROPORTIONS = ((Splitter) myUsagePreviewPanel
                    .getParent()).getProportion();
            myUsagePreviewPanel = null;
        }
    }

    public boolean isSearchInProgress() {
        return mySearchInProgress;
    }

    public void setSearchInProgress(boolean searchInProgress) {
        mySearchInProgress = searchInProgress;
        flush();
        if (!myPresentation.isDetachedMode()) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    if (isDisposed)
                        return;
                    final UsageNode firstUsageNode = myModel.getFirstUsageNode();
                    if (firstUsageNode != null) { //first usage;
                        showNode(firstUsageNode);
                    }
                }
            });
        }
    }

    private void showNode(final UsageNode node) {
        if (!myPresentation.isDetachedMode()) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    if (isDisposed)
                        return;
                    TreePath usagePath = new TreePath(node.getPath());
                    myTree.expandPath(usagePath.getParentPath());
                    myTree.setSelectionPath(usagePath);
                }
            });
        }
    }

    public void addButtonToLowerPane(@NotNull Runnable runnable, @NotNull String text) {
        int index = myButtonPanel.getComponentCount();

        if (index > 0 && myPresentation.isShowCancelButton())
            index--;

        myButtonPanel.add(index, runnable, text);
    }

    public void addButtonToLowerPane(@NotNull final Runnable runnable, @NotNull String text, char mnemonic) {
        // implemented method is deprecated, so, it just calls non-deprecated overloading one
        addButtonToLowerPane(runnable, text);
    }

    public void addPerformOperationAction(@NotNull final Runnable processRunnable, final String commandName,
            final String cannotMakeString, @NotNull String shortDescription) {

        addButtonToLowerPane(new MyPerformOperationRunnable(cannotMakeString, processRunnable, commandName),
                shortDescription);
    }

    private boolean allTargetsAreValid() {
        for (UsageTarget target : myTargets) {
            if (!target.isValid()) {
                return false;
            }
        }

        return true;
    }

    public UsageViewPresentation getPresentation() {
        return myPresentation;
    }

    private boolean canPerformReRun() {
        return myUsageSearcherFactory != null;
    }

    private boolean checkReadonlyUsages() {
        final Set<VirtualFile> readOnlyUsages = getReadOnlyUsagesFiles();

        return readOnlyUsages.isEmpty() || !ReadonlyStatusHandler.getInstance(myProject)
                .ensureFilesWritable(VfsUtil.toVirtualFileArray(readOnlyUsages)).hasReadonlyFiles();
    }

    private Set<Usage> getReadOnlyUsages() {
        final Set<Usage> result = new THashSet<Usage>();
        final Set<Map.Entry<Usage, UsageNode>> usages = myUsageNodes.entrySet();
        for (Map.Entry<Usage, UsageNode> entry : usages) {
            Usage usage = entry.getKey();
            UsageNode node = entry.getValue();
            if (node != null && node != NULL_NODE && !node.isExcluded() && usage.isReadOnly()) {
                result.add(usage);
            }
        }
        return result;
    }

    private Set<VirtualFile> getReadOnlyUsagesFiles() {
        Set<Usage> usages = getReadOnlyUsages();
        Set<VirtualFile> result = new THashSet<VirtualFile>();
        for (Usage usage : usages) {
            if (usage instanceof UsageInFile) {
                UsageInFile usageInFile = (UsageInFile) usage;
                result.add(usageInFile.getFile());
            }

            if (usage instanceof UsageInFiles) {
                UsageInFiles usageInFiles = (UsageInFiles) usage;
                result.addAll(Arrays.asList(usageInFiles.getFiles()));
            }
        }
        for (UsageTarget target : myTargets) {
            VirtualFile[] files = target.getFiles();
            if (files == null)
                continue;
            result.addAll(Arrays.asList(files));
        }
        return result;
    }

    @NotNull
    public Set<Usage> getExcludedUsages() {
        Set<Usage> result = new THashSet<Usage>();
        for (Map.Entry<Usage, UsageNode> entry : myUsageNodes.entrySet()) {
            UsageNode node = entry.getValue();
            Usage usage = entry.getKey();
            if (node == NULL_NODE || node == null) {
                continue;
            }
            if (node.isExcluded()) {
                result.add(usage);
            }
        }

        return result;
    }

    @Nullable
    private Node getSelectedNode() {
        TreePath leadSelectionPath = myTree.getLeadSelectionPath();
        if (leadSelectionPath == null)
            return null;

        DefaultMutableTreeNode node = (DefaultMutableTreeNode) leadSelectionPath.getLastPathComponent();
        return node instanceof Node ? (Node) node : null;
    }

    @Nullable
    private Node[] getSelectedNodes() {
        TreePath[] leadSelectionPath = myTree.getSelectionPaths();
        if (leadSelectionPath == null || leadSelectionPath.length == 0)
            return null;

        final java.util.List<Node> result = new ArrayList<Node>();
        for (TreePath comp : leadSelectionPath) {
            final Object lastPathComponent = comp.getLastPathComponent();
            if (lastPathComponent instanceof Node) {
                final Node node = (Node) lastPathComponent;
                result.add(node);
            }
        }
        return result.isEmpty() ? null : result.toArray(new Node[result.size()]);
    }

    @Nullable
    public Set<Usage> getSelectedUsages() {
        TreePath[] selectionPaths = myTree.getSelectionPaths();
        if (selectionPaths == null) {
            return null;
        }

        Set<Usage> usages = new THashSet<Usage>();
        for (TreePath selectionPath : selectionPaths) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) selectionPath.getLastPathComponent();
            collectUsages(node, usages);
        }

        return usages;
    }

    @NotNull
    public Set<Usage> getUsages() {
        return myUsages;
    }

    @NotNull
    public java.util.List<Usage> getSortedUsages() {
        java.util.List<Usage> usages = new ArrayList<Usage>(myUsages);
        Collections.sort(usages, USAGE_COMPARATOR);
        return usages;
    }

    private static void collectUsages(DefaultMutableTreeNode node, Set<Usage> usages) {
        if (node instanceof UsageNode) {
            UsageNode usageNode = (UsageNode) node;
            final Usage usage = usageNode.getUsage();
            if (usage.isValid()) {
                usages.add(usage);
            }
        }

        Enumeration enumeration = node.children();
        while (enumeration.hasMoreElements()) {
            DefaultMutableTreeNode child = (DefaultMutableTreeNode) enumeration.nextElement();
            collectUsages(child, usages);
        }
    }

    @Nullable
    private UsageTarget[] getSelectedUsageTargets() {
        TreePath[] selectionPaths = myTree.getSelectionPaths();
        if (selectionPaths == null)
            return null;

        Set<UsageTarget> targets = new THashSet<UsageTarget>();
        for (TreePath selectionPath : selectionPaths) {
            Object lastPathComponent = selectionPath.getLastPathComponent();
            if (lastPathComponent instanceof UsageTargetNode) {
                UsageTargetNode usageTargetNode = (UsageTargetNode) lastPathComponent;
                UsageTarget target = usageTargetNode.getTarget();
                if (target != null && target.isValid()) {
                    targets.add(target);
                }
            }
        }

        return targets.isEmpty() ? null : targets.toArray(new UsageTarget[targets.size()]);
    }

    @Nullable
    private static Navigatable getNavigatableForNode(DefaultMutableTreeNode node) {
        Object userObject = node.getUserObject();
        if (userObject instanceof Navigatable) {
            final Navigatable navigatable = (Navigatable) userObject;
            return navigatable.canNavigate() ? navigatable : null;
        }
        return null;
    }

    /* nodes with non-valid data are not included */

    private static Navigatable[] getNavigatablesForNodes(Node[] nodes) {
        if (nodes == null) {
            return null;
        }
        final ArrayList<Navigatable> result = new ArrayList<Navigatable>();
        for (final Node node : nodes) {
            /*
            if (!node.isDataValid()) {
              continue;
            }
            */
            Object userObject = node.getUserObject();
            if (userObject instanceof Navigatable) {
                result.add((Navigatable) userObject);
            }
        }
        return result.toArray(new Navigatable[result.size()]);
    }

    public boolean areTargetsValid() {
        return myModel.areTargetsValid();
    }

    private class MyPanel extends JPanel implements TypeSafeDataProvider, OccurenceNavigator {
        @Nullable
        private OccurenceNavigatorSupport mySupport;

        private MyPanel(JTree tree) {
            mySupport = new OccurenceNavigatorSupport(tree) {
                protected Navigatable createDescriptorForNode(DefaultMutableTreeNode node) {
                    if (node.getChildCount() > 0)
                        return null;
                    return getNavigatableForNode(node);
                }

                public String getNextOccurenceActionName() {
                    return UsageViewBundle.message("action.next.occurrence");
                }

                public String getPreviousOccurenceActionName() {
                    return UsageViewBundle.message("action.previous.occurrence");
                }
            };
        }

        private void dispose() {
            mySupport = null;
        }

        public boolean hasNextOccurence() {
            return mySupport != null && mySupport.hasNextOccurence();
        }

        public boolean hasPreviousOccurence() {
            return mySupport != null && mySupport.hasPreviousOccurence();
        }

        public OccurenceInfo goNextOccurence() {
            return mySupport != null ? mySupport.goNextOccurence() : null;
        }

        public OccurenceInfo goPreviousOccurence() {
            return mySupport != null ? mySupport.goPreviousOccurence() : null;
        }

        public String getNextOccurenceActionName() {
            return mySupport != null ? mySupport.getNextOccurenceActionName() : "";
        }

        public String getPreviousOccurenceActionName() {
            return mySupport != null ? mySupport.getPreviousOccurenceActionName() : "";
        }

        public void calcData(final DataKey key, final DataSink sink) {
            Node node = getSelectedNode();

            if (key == PlatformDataKeys.PROJECT) {
                sink.put(PlatformDataKeys.PROJECT, myProject);
            } else if (key == USAGE_VIEW_KEY) {
                sink.put(USAGE_VIEW_KEY, UsageView2Impl.this);
            } else if (key == PlatformDataKeys.NAVIGATABLE_ARRAY) {
                sink.put(PlatformDataKeys.NAVIGATABLE_ARRAY, getNavigatablesForNodes(getSelectedNodes()));
            } else if (key == PlatformDataKeys.EXPORTER_TO_TEXT_FILE) {
                sink.put(PlatformDataKeys.EXPORTER_TO_TEXT_FILE, myTextFileExporter);
            } else if (key == USAGES_KEY) {
                final Set<Usage> selectedUsages = getSelectedUsages();
                sink.put(USAGES_KEY,
                        selectedUsages != null ? selectedUsages.toArray(new Usage[selectedUsages.size()]) : null);
            } else if (key == USAGE_TARGETS_KEY) {
                sink.put(USAGE_TARGETS_KEY, getSelectedUsageTargets());
            } else if (key == PlatformDataKeys.VIRTUAL_FILE_ARRAY) {
                final Set<Usage> usages = getSelectedUsages();
                VirtualFile[] data = provideVirtualFileArray(
                        usages != null ? usages.toArray(new Usage[usages.size()]) : null,
                        getSelectedUsageTargets());
                sink.put(PlatformDataKeys.VIRTUAL_FILE_ARRAY, data);
            } else if (key == PlatformDataKeys.HELP_ID) {
                sink.put(PlatformDataKeys.HELP_ID, HELP_ID);
            } else if (node != null) {
                Object userObject = node.getUserObject();
                if (userObject instanceof TypeSafeDataProvider) {
                    ((TypeSafeDataProvider) userObject).calcData(key, sink);
                } else if (userObject instanceof DataProvider) {
                    DataProvider dataProvider = (DataProvider) userObject;
                    Object data = dataProvider.getData(key.getName());
                    if (data != null) {
                        sink.put(key, data);
                    }
                }
            }
        }

        @Nullable
        private VirtualFile[] provideVirtualFileArray(Usage[] usages, UsageTarget[] usageTargets) {
            if (usages == null && usageTargets == null) {
                return null;
            }

            final Set<VirtualFile> result = new THashSet<VirtualFile>();

            if (usages != null) {
                for (Usage usage : usages) {
                    if (usage.isValid()) {
                        if (usage instanceof UsageInFile) {
                            result.add(((UsageInFile) usage).getFile());
                        }

                        if (usage instanceof UsageInFiles) {
                            result.addAll(Arrays.asList(((UsageInFiles) usage).getFiles()));
                        }
                    }
                }
            }

            if (usageTargets != null) {
                for (UsageTarget usageTarget : usageTargets) {
                    if (usageTarget.isValid()) {
                        final VirtualFile[] files = usageTarget.getFiles();
                        if (files != null) {
                            result.addAll(Arrays.asList(files));
                        }
                    }
                }
            }

            return VfsUtil.toVirtualFileArray(result);
        }

    }

    private static class MyAutoScrollToSourceOptionProvider implements AutoScrollToSourceOptionProvider {
        public boolean isAutoScrollMode() {
            return UsageViewSettings.getInstance().IS_AUTOSCROLL_TO_SOURCE;
        }

        public void setAutoScrollMode(boolean state) {
            UsageViewSettings.getInstance().IS_AUTOSCROLL_TO_SOURCE = state;
        }
    }

    private final class ButtonPanel extends JPanel {
        private ButtonPanel() {
            setLayout(new FlowLayout(FlowLayout.LEFT, 8, 0));
        }

        public void add(int index, final Runnable runnable, String text) {
            final JButton button = new JButton(UIUtil.replaceMnemonicAmpersand(text));
            DialogUtil.registerMnemonic(button);

            button.setFocusable(false);
            button.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    runnable.run();
                }
            });

            add(button, index);

            invalidate();
            if (getParent() != null) {
                getParent().validate();
            }
        }

        void update() {
            for (int i = 0; i < getComponentCount(); ++i) {
                Component component = getComponent(i);
                if (component instanceof JButton) {
                    final JButton button = (JButton) component;
                    button.setEnabled(!isSearchInProgress());
                }
            }
        }
    }

    private class UsageState {
        private final Usage myUsage;
        private final boolean mySelected;

        private UsageState(final Usage usage, boolean isSelected) {
            myUsage = usage;
            mySelected = isSelected;
        }

        public void restore() {
            final UsageNode node = myUsageNodes.get(myUsage);
            if (node == NULL_NODE || node == null) {
                return;
            }
            final DefaultMutableTreeNode parentGroupingNode = (DefaultMutableTreeNode) node.getParent();
            if (parentGroupingNode != null) {
                final TreePath treePath = new TreePath(parentGroupingNode.getPath());
                myTree.expandPath(treePath);
                if (mySelected) {
                    myTree.addSelectionPath(treePath.pathByAddingChild(node));
                }
            }
        }
    }

    private class MyPerformOperationRunnable implements Runnable {
        private final String myCannotMakeString;
        private final Runnable myProcessRunnable;
        private final String myCommandName;

        private MyPerformOperationRunnable(final String cannotMakeString, final Runnable processRunnable,
                final String commandName) {
            myCannotMakeString = cannotMakeString;
            myProcessRunnable = processRunnable;
            myCommandName = commandName;
        }

        public void run() {
            if (!checkReadonlyUsages())
                return;
            PsiDocumentManager.getInstance(myProject).commitAllDocuments();
            if (myCannotMakeString != null && myChangesDetected) {
                if (canPerformReRun() && allTargetsAreValid()) {
                    int answer = Messages.showYesNoDialog(myProject,
                            myCannotMakeString + "\n" + UsageViewBundle.message("dialog.rerun.search"),
                            UsageViewBundle.message("error.common.title"), Messages.getErrorIcon());
                    if (answer == 0) {
                        refreshUsages();
                    }
                } else {
                    Messages.showMessageDialog(myProject, myCannotMakeString,
                            UsageViewBundle.message("error.common.title"), Messages.getErrorIcon());
                    //todo[myakovlev] request focus to tree
                    //myUsageView.getTree().requestFocus();
                }
                return;
            }

            close();

            CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
                public void run() {
                    myProcessRunnable.run();
                }
            }, myCommandName, null);

        }
    }

    private java.util.List<UsageInfo> getSelectedUsageInfos() {
        return USAGE_INFO_LIST_KEY.getData(DataManager.getInstance().getDataContext(myRootPanel));
    }

    public GroupNode getRoot() {
        return myRoot;
    }

    public boolean isVisible(Usage usage) {
        return myBuilder != null && myBuilder.isVisible(usage);
    }

    private class JScrollPaneEe extends JScrollPane {
        JScrollPaneEe(JComponent view) {
            super(view);
        }

        /**
         * Scrollpane's background should be always in sync with view's background
         */
        public void setUI(ScrollPaneUI ui) {
            super.setUI(ui);
            // We need to set color of viewport later because UIManager
            // updates UI of scroll pane and only after that updates UI
            // of its children. To be the last in this sequence we need
            // set background later.
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    Component component = getViewport().getView();
                    if (component != null) {
                        getViewport().setBackground(component.getBackground());
                    }
                }
            });
        }

    }

}