blob: c94da2e1f4ff5dd02781a89661b0ef92befc4d4a [file] [log] [blame]
/*
* 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.intellij.ide.impl;
import com.intellij.ide.DataManager;
import com.intellij.ide.IdeEventQueue;
import com.intellij.ide.impl.dataRules.*;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.components.ApplicationComponent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.keymap.impl.IdeKeyEventDispatcher;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.AsyncResult;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.UserDataHolder;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.openapi.wm.ex.WindowManagerEx;
import com.intellij.util.KeyedLazyInstanceEP;
import com.intellij.util.containers.WeakValueHashMap;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class DataManagerImpl extends DataManager implements ApplicationComponent {
private static final Logger LOG = Logger.getInstance("#com.intellij.ide.impl.DataManagerImpl");
private final Map<String, GetDataRule> myDataConstantToRuleMap = new THashMap<String, GetDataRule>();
private WindowManagerEx myWindowManager;
public DataManagerImpl() {
registerRules();
}
@Override
public void initComponent() {
}
@Override
public void disposeComponent() {
}
@Nullable
private Object getData(@NotNull String dataId, final Component focusedComponent) {
for (Component c = focusedComponent; c != null; c = c.getParent()) {
final DataProvider dataProvider = getDataProvider(c);
if (dataProvider == null) continue;
Object data = getDataFromProvider(dataProvider, dataId, null);
if (data != null) return data;
}
return null;
}
@Nullable
private Object getDataFromProvider(@NotNull final DataProvider provider, @NotNull String dataId, @Nullable Set<String> alreadyComputedIds) {
if (alreadyComputedIds != null && alreadyComputedIds.contains(dataId)) {
return null;
}
try {
Object data = provider.getData(dataId);
if (data != null) return validated(data, dataId, provider);
GetDataRule dataRule = getDataRule(dataId);
if (dataRule != null) {
final Set<String> ids = alreadyComputedIds == null ? new THashSet<String>() : alreadyComputedIds;
ids.add(dataId);
data = dataRule.getData(new DataProvider() {
@Override
public Object getData(String dataId) {
return getDataFromProvider(provider, dataId, ids);
}
});
if (data != null) return validated(data, dataId, provider);
}
return null;
}
finally {
if (alreadyComputedIds != null) alreadyComputedIds.remove(dataId);
}
}
@Nullable
private static DataProvider getDataProvider(Object component) {
DataProvider dataProvider = null;
if (component instanceof DataProvider) {
dataProvider = (DataProvider)component;
}
else if (component instanceof TypeSafeDataProvider) {
dataProvider = new TypeSafeDataProviderAdapter((TypeSafeDataProvider) component);
}
else if (component instanceof JComponent) {
dataProvider = getDataProvider((JComponent)component);
}
return dataProvider;
}
@Nullable
public GetDataRule getDataRule(@NotNull String dataId) {
GetDataRule rule = getRuleFromMap(dataId);
if (rule != null) {
return rule;
}
final GetDataRule plainRule = getRuleFromMap(AnActionEvent.uninjectedId(dataId));
if (plainRule != null) {
return new GetDataRule() {
@Override
public Object getData(final DataProvider dataProvider) {
return plainRule.getData(new DataProvider() {
@Override
@Nullable
public Object getData(@NonNls String dataId) {
return dataProvider.getData(AnActionEvent.injectedId(dataId));
}
});
}
};
}
return null;
}
@Nullable
private GetDataRule getRuleFromMap(@NotNull String dataId) {
GetDataRule rule = myDataConstantToRuleMap.get(dataId);
if (rule == null && !myDataConstantToRuleMap.containsKey(dataId)) {
final KeyedLazyInstanceEP<GetDataRule>[] eps = Extensions.getExtensions(GetDataRule.EP_NAME);
for(KeyedLazyInstanceEP<GetDataRule> ruleEP: eps) {
if (ruleEP.key.equals(dataId)) {
rule = ruleEP.getInstance();
}
}
myDataConstantToRuleMap.put(dataId, rule);
}
return rule;
}
@Nullable
private static Object validated(@NotNull Object data, @NotNull String dataId, @NotNull Object dataSource) {
Object invalidData = DataValidator.findInvalidData(dataId, data, dataSource);
if (invalidData != null) {
return null;
/*
LOG.assertTrue(false, "Data isn't valid. " + dataId + "=" + invalidData + " Provided by: " + dataSource.getClass().getName() + " (" +
dataSource.toString() + ")");
*/
}
return data;
}
@Override
public DataContext getDataContext(Component component) {
return new MyDataContext(component);
}
@Override
public DataContext getDataContext(@NotNull Component component, int x, int y) {
if (x < 0 || x >= component.getWidth() || y < 0 || y >= component.getHeight()) {
throw new IllegalArgumentException("wrong point: x=" + x + "; y=" + y);
}
// Point inside JTabbedPane has special meaning. If point is inside tab bounds then
// we construct DataContext by the component which corresponds to the (x, y) tab.
if (component instanceof JTabbedPane) {
JTabbedPane tabbedPane = (JTabbedPane)component;
int index = tabbedPane.getUI().tabForCoordinate(tabbedPane, x, y);
return getDataContext(index != -1 ? tabbedPane.getComponentAt(index) : tabbedPane);
}
else {
return getDataContext(component);
}
}
public void setWindowManager(WindowManagerEx windowManager) {
myWindowManager = windowManager;
}
@Override
@NotNull
public DataContext getDataContext() {
return getDataContext(getFocusedComponent());
}
@Override
public AsyncResult<DataContext> getDataContextFromFocus() {
final AsyncResult<DataContext> context = new AsyncResult<DataContext>();
IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(new Runnable() {
@Override
public void run() {
context.setDone(getDataContext());
}
});
return context;
}
public DataContext getDataContextTest(Component component) {
DataContext dataContext = getDataContext(component);
if (myWindowManager == null) {
return dataContext;
}
Project project = CommonDataKeys.PROJECT.getData(dataContext);
Component focusedComponent = myWindowManager.getFocusedComponent(project);
if (focusedComponent != null) {
dataContext = getDataContext(focusedComponent);
}
return dataContext;
}
@Nullable
private Component getFocusedComponent() {
if (myWindowManager == null) {
return null;
}
Window activeWindow = myWindowManager.getMostRecentFocusedWindow();
if (activeWindow == null) {
activeWindow = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
if (activeWindow == null) {
activeWindow = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
if (activeWindow == null) return null;
}
}
if (Registry.is("actionSystem.noContextComponentWhileFocusTransfer")) {
IdeFocusManager fm = IdeFocusManager.findInstanceByComponent(activeWindow);
if (fm.isFocusBeingTransferred()) {
return null;
}
}
// try to find first parent window that has focus
Window window = activeWindow;
Component focusedComponent = null;
while (window != null) {
focusedComponent = myWindowManager.getFocusedComponent(window);
if (focusedComponent != null) {
break;
}
window = window.getOwner();
}
if (focusedComponent == null) {
focusedComponent = activeWindow;
}
return focusedComponent;
}
private void registerRules() {
myDataConstantToRuleMap.put(PlatformDataKeys.COPY_PROVIDER.getName(), new CopyProviderRule());
myDataConstantToRuleMap.put(PlatformDataKeys.CUT_PROVIDER.getName(), new CutProviderRule());
myDataConstantToRuleMap.put(PlatformDataKeys.PASTE_PROVIDER.getName(), new PasteProviderRule());
myDataConstantToRuleMap.put(PlatformDataKeys.FILE_TEXT.getName(), new FileTextRule());
myDataConstantToRuleMap.put(PlatformDataKeys.FILE_EDITOR.getName(), new FileEditorRule());
myDataConstantToRuleMap.put(CommonDataKeys.NAVIGATABLE_ARRAY.getName(), new NavigatableArrayRule());
myDataConstantToRuleMap.put(PlatformDataKeys.EDITOR_EVEN_IF_INACTIVE.getName(), new InactiveEditorRule());
}
@Override
@NotNull
public String getComponentName() {
return "DataManager";
}
@Override
public <T> void saveInDataContext(DataContext dataContext, @NotNull Key<T> dataKey, @Nullable T data) {
if (dataContext instanceof UserDataHolder) {
((UserDataHolder)dataContext).putUserData(dataKey, data);
}
}
@Override
@Nullable
public <T> T loadFromDataContext(@NotNull DataContext dataContext, @NotNull Key<T> dataKey) {
return dataContext instanceof UserDataHolder ? ((UserDataHolder)dataContext).getUserData(dataKey) : null;
}
private static class NullResult {
public static final NullResult INSTANCE = new NullResult();
}
private static final Set<String> ourSafeKeys = new HashSet<String>(Arrays.asList(
CommonDataKeys.PROJECT.getName(),
CommonDataKeys.EDITOR.getName(),
PlatformDataKeys.IS_MODAL_CONTEXT.getName(),
PlatformDataKeys.CONTEXT_COMPONENT.getName(),
PlatformDataKeys.MODALITY_STATE.getName()
));
public static class MyDataContext implements DataContext, UserDataHolder {
private int myEventCount;
// To prevent memory leak we have to wrap passed component into
// the weak reference. For example, Swing often remembers menu items
// that have DataContext as a field.
private final WeakReference<Component> myRef;
private WeakValueHashMap<Key, Object> myUserData;
private final WeakValueHashMap<String, Object> myCachedData = new WeakValueHashMap<String, Object>();
public MyDataContext(final Component component) {
myEventCount = -1;
myRef = new WeakReference<Component>(component);
}
public void setEventCount(int eventCount, Object caller) {
assert caller instanceof IdeKeyEventDispatcher : "This method might be accessible from " + IdeKeyEventDispatcher.class.getName() + " only";
myCachedData.clear();
myEventCount = eventCount;
}
@Override
public Object getData(String dataId) {
if (dataId == null) return null;
int currentEventCount = IdeEventQueue.getInstance().getEventCount();
if (myEventCount != -1 && myEventCount != currentEventCount) {
LOG.error("cannot share data context between Swing events; initial event count = " + myEventCount + "; current event count = " +
currentEventCount);
return doGetData(dataId);
}
if (ourSafeKeys.contains(dataId)) {
Object answer = myCachedData.get(dataId);
if (answer == null) {
answer = doGetData(dataId);
myCachedData.put(dataId, answer == null ? NullResult.INSTANCE : answer);
}
return answer != NullResult.INSTANCE ? answer : null;
}
else {
return doGetData(dataId);
}
}
@Nullable
private Object doGetData(@NotNull String dataId) {
Component component = myRef.get();
if (PlatformDataKeys.IS_MODAL_CONTEXT.is(dataId)) {
if (component == null) {
return null;
}
return IdeKeyEventDispatcher.isModalContext(component) ? Boolean.TRUE : Boolean.FALSE;
}
if (PlatformDataKeys.CONTEXT_COMPONENT.is(dataId)) {
return component;
}
if (PlatformDataKeys.MODALITY_STATE.is(dataId)) {
return component != null ? ModalityState.stateForComponent(component) : ModalityState.NON_MODAL;
}
if (CommonDataKeys.EDITOR.is(dataId)) {
Editor editor = (Editor)((DataManagerImpl)DataManager.getInstance()).getData(dataId, component);
return validateEditor(editor);
}
return ((DataManagerImpl)DataManager.getInstance()).getData(dataId, component);
}
@Nullable
private static Editor validateEditor(Editor editor) {
Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
if (focusOwner instanceof JComponent) {
final JComponent jComponent = (JComponent)focusOwner;
if (jComponent.getClientProperty("AuxEditorComponent") != null) return null; // Hack for EditorSearchComponent
}
return editor;
}
@NonNls
public String toString() {
return "component=" + myRef.get();
}
@Override
public <T> T getUserData(@NotNull Key<T> key) {
//noinspection unchecked
return (T)getOrCreateMap().get(key);
}
@Override
public <T> void putUserData(@NotNull Key<T> key, @Nullable T value) {
getOrCreateMap().put(key, value);
}
private WeakValueHashMap<Key, Object> getOrCreateMap() {
WeakValueHashMap<Key, Object> userData = myUserData;
if (userData == null) {
myUserData = userData = new WeakValueHashMap<Key, Object>();
}
return userData;
}
}
}