com.intellij.ui.content.impl.ContentManagerImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.ui.content.impl.ContentManagerImpl.java

Source

/*
 * Copyright 2000-2014 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.intellij.ui.content.impl;

import com.intellij.ide.DataManager;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.DataProvider;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.*;
import com.intellij.openapi.wm.FocusCommand;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy;
import com.intellij.ui.components.panels.NonOpaquePanel;
import com.intellij.ui.components.panels.Wrapper;
import com.intellij.ui.content.*;
import com.intellij.ui.switcher.SwitchProvider;
import com.intellij.ui.switcher.SwitchTarget;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author Anton Katilin
 * @author Vladimir Kondratyev
 */
public class ContentManagerImpl implements ContentManager, PropertyChangeListener, Disposable.Parent {
    private static final Logger LOG = Logger.getInstance("#com.intellij.ui.content.impl.ContentManagerImpl");

    private ContentUI myUI;
    private final List<Content> myContents = new ArrayList<Content>();
    private final List<ContentManagerListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
    private final List<Content> mySelection = new ArrayList<Content>();
    private final boolean myCanCloseContents;

    private Wrapper.FocusHolder myFocusProxy;
    private MyNonOpaquePanel myComponent;

    private final Set<Content> myContentWithChangedComponent = new HashSet<Content>();

    private boolean myDisposed;
    private final Project myProject;

    private final List<DataProvider> dataProviders = new SmartList<DataProvider>();

    /**
     * WARNING: as this class adds listener to the ProjectManager which is removed on projectClosed event, all instances of this class
     * must be created on already OPENED projects, otherwise there will be memory leak!
     */
    public ContentManagerImpl(@NotNull ContentUI contentUI, boolean canCloseContents, @NotNull Project project) {
        myProject = project;
        myCanCloseContents = canCloseContents;
        myUI = contentUI;
        myUI.setManager(this);

        Disposer.register(project, this);
        Disposer.register(this, contentUI);
    }

    @Override
    public boolean canCloseContents() {
        return myCanCloseContents;
    }

    @NotNull
    @Override
    public JComponent getComponent() {
        if (myComponent == null) {
            myComponent = new MyNonOpaquePanel();

            myFocusProxy = new Wrapper.FocusHolder();
            myFocusProxy.setOpaque(false);
            myFocusProxy.setPreferredSize(new Dimension(0, 0));

            MyContentComponent contentComponent = new MyContentComponent();
            contentComponent.setContent(myUI.getComponent());
            contentComponent.setFocusCycleRoot(true);

            myComponent.add(myFocusProxy, BorderLayout.NORTH);
            myComponent.add(contentComponent, BorderLayout.CENTER);
        }
        return myComponent;
    }

    @NotNull
    @Override
    public ActionCallback getReady(@NotNull Object requestor) {
        Content selected = getSelectedContent();
        if (selected == null)
            return new ActionCallback.Done();
        BusyObject busyObject = selected.getBusyObject();
        return busyObject != null ? busyObject.getReady(requestor) : new ActionCallback.Done();
    }

    private class MyNonOpaquePanel extends NonOpaquePanel implements DataProvider {
        public MyNonOpaquePanel() {
            super(new BorderLayout());
        }

        @Override
        @Nullable
        public Object getData(@NonNls String dataId) {
            if (PlatformDataKeys.CONTENT_MANAGER.is(dataId)
                    || PlatformDataKeys.NONEMPTY_CONTENT_MANAGER.is(dataId) && getContentCount() > 1) {
                return ContentManagerImpl.this;
            }

            for (DataProvider dataProvider : dataProviders) {
                Object data = dataProvider.getData(dataId);
                if (data != null) {
                    return data;
                }
            }

            if (myUI instanceof DataProvider) {
                return ((DataProvider) myUI).getData(dataId);
            }

            DataProvider provider = DataManager.getDataProvider(this);
            return provider == null ? null : provider.getData(dataId);
        }
    }

    private class MyContentComponent extends NonOpaquePanel implements SwitchProvider {
        @Override
        public List<SwitchTarget> getTargets(boolean onlyVisible, boolean originalProvider) {
            if (myUI instanceof SwitchProvider) {
                return ((SwitchProvider) myUI).getTargets(onlyVisible, false);
            }
            return new SmartList<SwitchTarget>();
        }

        @Override
        public SwitchTarget getCurrentTarget() {
            return myUI instanceof SwitchProvider ? ((SwitchProvider) myUI).getCurrentTarget() : null;
        }

        @Override
        public JComponent getComponent() {
            return myUI instanceof SwitchProvider ? myUI.getComponent() : this;
        }

        @Override
        public boolean isCycleRoot() {
            return myUI instanceof SwitchProvider && ((SwitchProvider) myUI).isCycleRoot();
        }
    }

    @Override
    public void addContent(@NotNull Content content, final int order) {
        doAddContent(content, order);
    }

    @Override
    public void addContent(@NotNull Content content) {
        doAddContent(content, -1);
    }

    @Override
    public void addContent(@NotNull final Content content, final Object constraints) {
        doAddContent(content, -1);
    }

    private void doAddContent(@NotNull final Content content, final int index) {
        ApplicationManager.getApplication().assertIsDispatchThread();
        if (myContents.contains(content))
            return;

        ((ContentImpl) content).setManager(this);
        final int insertIndex = index == -1 ? myContents.size() : index;
        myContents.add(insertIndex, content);
        content.addPropertyChangeListener(this);
        fireContentAdded(content, insertIndex, ContentManagerEvent.ContentOperation.add);
        if (myUI.isToSelectAddedContent() || mySelection.isEmpty() && !myUI.canBeEmptySelection()) {
            if (myUI.isSingleSelection()) {
                setSelectedContent(content);
            } else {
                addSelectedContent(content);
            }
        }

        Disposer.register(this, content);
    }

    @Override
    public boolean removeContent(@NotNull Content content, final boolean dispose) {
        return removeContent(content, true, dispose).isDone();
    }

    @NotNull
    @Override
    public ActionCallback removeContent(@NotNull Content content, boolean dispose, final boolean trackFocus,
            final boolean forcedFocus) {
        final ActionCallback result = new ActionCallback();
        removeContent(content, true, dispose).doWhenDone(new Runnable() {
            @Override
            public void run() {
                if (trackFocus) {
                    Content current = getSelectedContent();
                    if (current != null) {
                        setSelectedContent(current, true, true, !forcedFocus);
                    } else {
                        result.setDone();
                    }
                } else {
                    result.setDone();
                }
            }
        });

        return result;
    }

    @NotNull
    private ActionCallback removeContent(@NotNull Content content, boolean trackSelection, boolean dispose) {
        ApplicationManager.getApplication().assertIsDispatchThread();
        int indexToBeRemoved = getIndexOfContent(content);
        if (indexToBeRemoved == -1)
            return new ActionCallback.Rejected();

        try {
            Content selection = mySelection.isEmpty() ? null : mySelection.get(mySelection.size() - 1);
            int selectedIndex = selection != null ? myContents.indexOf(selection) : -1;

            if (!fireContentRemoveQuery(content, indexToBeRemoved,
                    ContentManagerEvent.ContentOperation.undefined)) {
                return new ActionCallback.Rejected();
            }
            if (!content.isValid()) {
                return new ActionCallback.Rejected();
            }

            boolean wasSelected = isSelected(content);
            if (wasSelected) {
                removeFromSelection(content);
            }

            int indexToSelect = -1;
            if (wasSelected) {
                int i = indexToBeRemoved - 1;
                if (i >= 0) {
                    indexToSelect = i;
                } else if (getContentCount() > 1) {
                    indexToSelect = 0;
                }
            } else if (selectedIndex > indexToBeRemoved) {
                indexToSelect = selectedIndex - 1;
            }

            myContents.remove(content);
            content.removePropertyChangeListener(this);

            fireContentRemoved(content, indexToBeRemoved, ContentManagerEvent.ContentOperation.remove);
            ((ContentImpl) content).setManager(null);

            if (dispose) {
                Disposer.dispose(content);
            }

            int newSize = myContents.size();

            ActionCallback result = new ActionCallback();

            if (newSize > 0 && trackSelection) {
                if (indexToSelect > -1) {
                    final Content toSelect = myContents.get(indexToSelect);
                    if (!isSelected(toSelect)) {
                        if (myUI.isSingleSelection()) {
                            setSelectedContentCB(toSelect).notify(result);
                        } else {
                            addSelectedContent(toSelect);
                            result.setDone();
                        }
                    }
                }
            } else {
                mySelection.clear();
            }

            return result;
        } finally {
            if (ApplicationManager.getApplication().isDispatchThread()) {
                myUI.getComponent().updateUI(); //cleanup visibleComponent from Alloy...TabbedPaneUI
            }
        }
    }

    @Override
    public void removeAllContents(final boolean dispose) {
        Content[] contents = getContents();
        for (Content content : contents) {
            removeContent(content, dispose);
        }
    }

    @Override
    public int getContentCount() {
        return myContents.size();
    }

    @Override
    @NotNull
    public Content[] getContents() {
        return myContents.toArray(new Content[myContents.size()]);
    }

    //TODO[anton,vova] is this method needed?
    @Override
    public Content findContent(String displayName) {
        for (Content content : myContents) {
            if (content.getDisplayName().equals(displayName)) {
                return content;
            }
        }
        return null;
    }

    @Override
    public Content getContent(int index) {
        return index >= 0 && index < myContents.size() ? myContents.get(index) : null;
    }

    @Override
    public Content getContent(JComponent component) {
        Content[] contents = getContents();
        for (Content content : contents) {
            if (Comparing.equal(component, content.getComponent())) {
                return content;
            }
        }
        return null;
    }

    @Override
    public int getIndexOfContent(Content content) {
        return myContents.indexOf(content);
    }

    @NotNull
    @Override
    public String getCloseActionName() {
        return myUI.getCloseActionName();
    }

    @NotNull
    @Override
    public String getCloseAllButThisActionName() {
        return myUI.getCloseAllButThisActionName();
    }

    @NotNull
    @Override
    public String getPreviousContentActionName() {
        return myUI.getPreviousContentActionName();
    }

    @NotNull
    @Override
    public String getNextContentActionName() {
        return myUI.getNextContentActionName();
    }

    @Override
    public List<AnAction> getAdditionalPopupActions(@NotNull final Content content) {
        return null;
    }

    @Override
    public boolean canCloseAllContents() {
        if (!canCloseContents()) {
            return false;
        }
        for (Content content : myContents) {
            if (content.isCloseable()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void addSelectedContent(@NotNull final Content content) {
        if (!checkSelectionChangeShouldBeProcessed(content, false))
            return;

        if (getIndexOfContent(content) == -1) {
            throw new IllegalArgumentException("content not found: " + content);
        }
        if (!isSelected(content)) {
            mySelection.add(content);
            fireSelectionChanged(content, ContentManagerEvent.ContentOperation.add);
        }
    }

    private boolean checkSelectionChangeShouldBeProcessed(Content content, boolean implicit) {
        if (!myUI.canChangeSelectionTo(content, implicit)) {
            return false;
        }

        final boolean result = !isSelected(content) || myContentWithChangedComponent.contains(content);
        myContentWithChangedComponent.remove(content);

        return result;
    }

    @Override
    public void removeFromSelection(@NotNull Content content) {
        if (!isSelected(content))
            return;
        mySelection.remove(content);
        fireSelectionChanged(content, ContentManagerEvent.ContentOperation.remove);
    }

    @Override
    public boolean isSelected(@NotNull Content content) {
        return mySelection.contains(content);
    }

    @Override
    @NotNull
    public Content[] getSelectedContents() {
        return mySelection.toArray(new Content[mySelection.size()]);
    }

    @Override
    @Nullable
    public Content getSelectedContent() {
        return mySelection.isEmpty() ? null : mySelection.get(0);
    }

    @Override
    public void setSelectedContent(@NotNull Content content, boolean requestFocus) {
        setSelectedContentCB(content, requestFocus);
    }

    @NotNull
    @Override
    public ActionCallback setSelectedContentCB(@NotNull final Content content, final boolean requestFocus) {
        return setSelectedContentCB(content, requestFocus, true);
    }

    @Override
    public void setSelectedContent(@NotNull Content content, boolean requestFocus, boolean forcedFocus) {
        setSelectedContentCB(content, requestFocus, forcedFocus);
    }

    @NotNull
    @Override
    public ActionCallback setSelectedContentCB(@NotNull final Content content, final boolean requestFocus,
            final boolean forcedFocus) {
        return setSelectedContent(content, requestFocus, forcedFocus, false);
    }

    @NotNull
    @Override
    public ActionCallback setSelectedContent(@NotNull final Content content, final boolean requestFocus,
            final boolean forcedFocus, boolean implicit) {
        if (isSelected(content) && requestFocus) {
            return requestFocus(content, forcedFocus);
        }

        if (!checkSelectionChangeShouldBeProcessed(content, implicit)) {
            return new ActionCallback.Rejected();
        }
        if (!myContents.contains(content)) {
            throw new IllegalArgumentException("Cannot find content:" + content.getDisplayName());
        }

        final boolean focused = isSelectionHoldsFocus();

        final Content[] old = getSelectedContents();

        final ActiveRunnable selection = new ActiveRunnable() {
            @NotNull
            @Override
            public ActionCallback run() {
                if (myDisposed || getIndexOfContent(content) == -1)
                    return new ActionCallback.Rejected();

                for (Content each : old) {
                    removeFromSelection(each);
                }

                addSelectedContent(content);

                if (requestFocus) {
                    return requestFocus(content, forcedFocus);
                }
                return new ActionCallback.Done();
            }
        };

        final ActionCallback result = new ActionCallback();
        boolean enabledFocus = getFocusManager().isFocusTransferEnabled();
        if (focused || requestFocus) {
            if (enabledFocus) {
                return getFocusManager().requestFocus(myFocusProxy, true).doWhenProcessed(new Runnable() {
                    @Override
                    public void run() {
                        selection.run().notify(result);
                    }
                });
            }
            return selection.run().notify(result);
        } else {
            return selection.run().notify(result);
        }
    }

    private boolean isSelectionHoldsFocus() {
        boolean focused = false;
        final Content[] selection = getSelectedContents();
        for (Content each : selection) {
            if (UIUtil.isFocusAncestor(each.getComponent())) {
                focused = true;
                break;
            }
        }
        return focused;
    }

    @NotNull
    @Override
    public ActionCallback setSelectedContentCB(@NotNull Content content) {
        return setSelectedContentCB(content, false);
    }

    @Override
    public void setSelectedContent(@NotNull final Content content) {
        setSelectedContentCB(content);
    }

    @Override
    public ActionCallback selectPreviousContent() {
        int contentCount = getContentCount();
        LOG.assertTrue(contentCount > 1);
        Content selectedContent = getSelectedContent();
        int index = getIndexOfContent(selectedContent);
        index = (index - 1 + contentCount) % contentCount;
        final Content content = getContent(index);
        if (content == null) {
            return null;
        }
        return setSelectedContentCB(content, true);
    }

    @Override
    public ActionCallback selectNextContent() {
        int contentCount = getContentCount();
        LOG.assertTrue(contentCount > 1);
        Content selectedContent = getSelectedContent();
        int index = getIndexOfContent(selectedContent);
        index = (index + 1) % contentCount;
        final Content content = getContent(index);
        if (content == null) {
            return null;
        }
        return setSelectedContentCB(content, true);
    }

    @Override
    public void addContentManagerListener(@NotNull ContentManagerListener l) {
        myListeners.add(0, l);
    }

    @Override
    public void removeContentManagerListener(@NotNull ContentManagerListener l) {
        myListeners.remove(l);
    }

    private void fireContentAdded(Content content, int newIndex, ContentManagerEvent.ContentOperation operation) {
        ContentManagerEvent event = new ContentManagerEvent(this, content, newIndex, operation);
        for (ContentManagerListener listener : myListeners) {
            listener.contentAdded(event);
        }
    }

    private void fireContentRemoved(Content content, int oldIndex, ContentManagerEvent.ContentOperation operation) {
        ContentManagerEvent event = new ContentManagerEvent(this, content, oldIndex, operation);
        for (ContentManagerListener listener : myListeners) {
            listener.contentRemoved(event);
        }
    }

    private void fireSelectionChanged(Content content, ContentManagerEvent.ContentOperation operation) {
        ContentManagerEvent event = new ContentManagerEvent(this, content, myContents.indexOf(content), operation);
        for (ContentManagerListener listener : myListeners) {
            listener.selectionChanged(event);
        }
    }

    private boolean fireContentRemoveQuery(Content content, int oldIndex,
            ContentManagerEvent.ContentOperation operation) {
        ContentManagerEvent event = new ContentManagerEvent(this, content, oldIndex, operation);
        for (ContentManagerListener listener : myListeners) {
            listener.contentRemoveQuery(event);
            if (event.isConsumed()) {
                return false;
            }
        }
        return true;
    }

    @NotNull
    @Override
    public ActionCallback requestFocus(final Content content, final boolean forced) {
        final Content toSelect = content == null ? getSelectedContent() : content;
        if (toSelect == null)
            return new ActionCallback.Rejected();
        assert myContents.contains(toSelect);

        return getFocusManager().requestFocus(new FocusCommand(content, toSelect.getPreferredFocusableComponent()) {
            @NotNull
            @Override
            public ActionCallback run() {
                return doRequestFocus(toSelect);
            }
        }, forced);
    }

    private IdeFocusManager getFocusManager() {
        return IdeFocusManager.getInstance(myProject);
    }

    private static ActionCallback doRequestFocus(final Content toSelect) {
        JComponent toFocus = computeWillFocusComponent(toSelect);

        if (toFocus != null) {
            toFocus.requestFocus();
        }

        return new ActionCallback.Done();
    }

    private static JComponent computeWillFocusComponent(Content toSelect) {
        JComponent toFocus = toSelect.getPreferredFocusableComponent();
        if (toFocus != null) {
            toFocus = IdeFocusTraversalPolicy.getPreferredFocusedComponent(toFocus);
        }

        if (toFocus == null)
            toFocus = toSelect.getPreferredFocusableComponent();
        return toFocus;
    }

    @Override
    public void addDataProvider(@NotNull final DataProvider provider) {
        dataProviders.add(provider);
    }

    @Override
    public void propertyChange(@NotNull PropertyChangeEvent event) {
        if (Content.PROP_COMPONENT.equals(event.getPropertyName())) {
            myContentWithChangedComponent.add((Content) event.getSource());
        }
    }

    @Override
    @NotNull
    public ContentFactory getFactory() {
        return ServiceManager.getService(ContentFactory.class);
    }

    @Override
    public void beforeTreeDispose() {
        myUI.beforeDispose();
    }

    @Override
    public void dispose() {
        myDisposed = true;

        myContents.clear();
        mySelection.clear();
        myContentWithChangedComponent.clear();
        myUI = null;
        myListeners.clear();
        dataProviders.clear();
    }

    @Override
    public boolean isDisposed() {
        return myDisposed;
    }

    @Override
    public boolean isSingleSelection() {
        return myUI.isSingleSelection();
    }
}