/*
* Copyright 2000-2012 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.actions;
import com.intellij.ide.util.DefaultPsiElementCellRenderer;
import com.intellij.navigation.GotoRelatedItem;
import com.intellij.navigation.GotoRelatedProvider;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.PopupStep;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.ui.ColoredListCellRenderer;
import com.intellij.ui.JBColor;
import com.intellij.ui.SeparatorWithText;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.ui.popup.list.ListPopupImpl;
import com.intellij.ui.popup.list.PopupListElementRenderer;
import com.intellij.util.Processor;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.*;
import java.util.List;
/**
* @author Dmitry Avdeev
*/
public class GotoRelatedFileAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent e) {
DataContext context = e.getDataContext();
Editor editor = PlatformDataKeys.EDITOR.getData(context);
PsiFile psiFile = LangDataKeys.PSI_FILE.getData(context);
if (psiFile == null) return;
List<GotoRelatedItem> items = getItems(psiFile, editor, context);
if (items.isEmpty()) return;
if (items.size() == 1 && items.get(0).getElement() != null) {
items.get(0).navigate();
return;
}
if (ApplicationManager.getApplication().isUnitTestMode()) {
//noinspection UseOfSystemOutOrSystemErr
System.out.println(items);
}
createPopup(items, "Go to Related Files").showInBestPositionFor(context);
}
public static JBPopup createPopup(final List<? extends GotoRelatedItem> items, final String title) {
Object[] elements = new Object[items.size()];
//todo[nik] move presentation logic to GotoRelatedItem class
final Map<PsiElement, GotoRelatedItem> itemsMap = new HashMap<PsiElement, GotoRelatedItem>();
for (int i = 0; i < items.size(); i++) {
GotoRelatedItem item = items.get(i);
elements[i] = item.getElement() != null ? item.getElement() : item;
itemsMap.put(item.getElement(), item);
}
return getPsiElementPopup(elements, itemsMap, title, new Processor<Object>() {
@Override
public boolean process(Object element) {
if (element instanceof PsiElement) {
//noinspection SuspiciousMethodCalls
itemsMap.get(element).navigate();
}
else {
((GotoRelatedItem)element).navigate();
}
return true;
}
}
);
}
private static JBPopup getPsiElementPopup(final Object[] elements, final Map<PsiElement, GotoRelatedItem> itemsMap,
final String title, final Processor<Object> processor) {
final Ref<Boolean> hasMnemonic = Ref.create(false);
final Ref<ListCellRenderer> rendererRef = Ref.create(null);
final DefaultPsiElementCellRenderer renderer = new DefaultPsiElementCellRenderer() {
{
setFocusBorderEnabled(false);
}
@Override
public String getElementText(PsiElement element) {
String customName = itemsMap.get(element).getCustomName();
return (customName != null ? customName : super.getElementText(element));
}
@Override
protected Icon getIcon(PsiElement element) {
Icon customIcon = itemsMap.get(element).getCustomIcon();
return customIcon != null ? customIcon : super.getIcon(element);
}
@Override
public String getContainerText(PsiElement element, String name) {
String customContainerName = itemsMap.get(element).getCustomContainerName();
if (customContainerName != null) {
return customContainerName;
}
PsiFile file = element.getContainingFile();
return file != null && !getElementText(element).equals(file.getName())
? "(" + file.getName() + ")"
: null;
}
@Override
protected DefaultListCellRenderer getRightCellRenderer(Object value) {
return null;
}
@Override
protected boolean customizeNonPsiElementLeftRenderer(ColoredListCellRenderer renderer,
JList list,
Object value,
int index,
boolean selected,
boolean hasFocus) {
final GotoRelatedItem item = (GotoRelatedItem)value;
Color color = list.getForeground();
final SimpleTextAttributes nameAttributes = new SimpleTextAttributes(Font.PLAIN, color);
final String name = item.getCustomName();
if (name == null) return false;
renderer.append(name, nameAttributes);
renderer.setIcon(item.getCustomIcon());
return true;
}
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
final JPanel component = (JPanel)super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (!hasMnemonic.get()) return component;
final JPanel panelWithMnemonic = new JPanel(new BorderLayout());
final int mnemonic = getMnemonic(value, itemsMap);
final JLabel label = new JLabel("");
if (mnemonic != -1) {
label.setText(mnemonic + ".");
label.setDisplayedMnemonicIndex(0);
}
label.setPreferredSize(new JLabel("8.").getPreferredSize());
final JComponent leftRenderer = (JComponent)component.getComponents()[0];
component.remove(leftRenderer);
panelWithMnemonic.setBorder(BorderFactory.createEmptyBorder(0, 7, 0, 0));
panelWithMnemonic.setBackground(leftRenderer.getBackground());
label.setBackground(leftRenderer.getBackground());
panelWithMnemonic.add(label, BorderLayout.WEST);
panelWithMnemonic.add(leftRenderer, BorderLayout.CENTER);
component.add(panelWithMnemonic);
return component;
}
};
final ListPopupImpl popup = new ListPopupImpl(new BaseListPopupStep<Object>(title, Arrays.asList(elements)) {
@Override
public boolean isSpeedSearchEnabled() {
return true;
}
@Override
public String getIndexedString(Object value) {
if (value instanceof GotoRelatedItem) {
//noinspection ConstantConditions
return ((GotoRelatedItem)value).getCustomName();
}
final PsiElement element = (PsiElement)value;
return renderer.getElementText(element) + " " + renderer.getContainerText(element, null);
}
@Override
public PopupStep onChosen(Object selectedValue, boolean finalChoice) {
processor.process(selectedValue);
return super.onChosen(selectedValue, finalChoice);
}
}) {
};
popup.getList().setCellRenderer(new PopupListElementRenderer(popup) {
Map<Object, String> separators = new HashMap<Object, String>();
{
final ListModel model = popup.getList().getModel();
String current = null;
boolean hasTitle = false;
for (int i = 0; i < model.getSize(); i++) {
final Object element = model.getElementAt(i);
final GotoRelatedItem item = itemsMap.get(element);
if (item != null && !StringUtil.equals(current, item.getGroup())) {
current = item.getGroup();
separators.put(element, current);
if (!hasTitle && !StringUtil.isEmpty(current)) {
hasTitle = true;
}
}
}
if (!hasTitle) {
separators.remove(model.getElementAt(0));
}
}
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
final Component component = renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
final String separator = separators.get(value);
if (separator != null) {
JPanel panel = new JPanel(new BorderLayout());
panel.add(component, BorderLayout.CENTER);
final SeparatorWithText sep = new SeparatorWithText() {
@Override
protected void paintComponent(Graphics g) {
g.setColor(new JBColor(Color.WHITE, UIUtil.getSeparatorColor()));
g.fillRect(0,0,getWidth(), getHeight());
super.paintComponent(g);
}
};
sep.setCaption(separator);
panel.add(sep, BorderLayout.NORTH);
return panel;
}
return component;
}
});
popup.setMinimumSize(new Dimension(200, -1));
for (Object item : elements) {
final int mnemonic = getMnemonic(item, itemsMap);
if (mnemonic != -1) {
final Action action = createNumberAction(mnemonic, popup, itemsMap, processor);
popup.registerAction(mnemonic + "Action", KeyStroke.getKeyStroke(String.valueOf(mnemonic)), action);
popup.registerAction(mnemonic + "Action", KeyStroke.getKeyStroke("NUMPAD" + String.valueOf(mnemonic)), action);
hasMnemonic.set(true);
}
}
return popup;
}
@NotNull
public static List<GotoRelatedItem> getItems(@NotNull PsiFile psiFile, @Nullable Editor editor, @Nullable DataContext dataContext) {
PsiElement contextElement = psiFile;
if (editor != null) {
PsiElement element = psiFile.findElementAt(editor.getCaretModel().getOffset());
if (element != null) {
contextElement = element;
}
}
Set<GotoRelatedItem> items = new LinkedHashSet<GotoRelatedItem>();
for (GotoRelatedProvider provider : Extensions.getExtensions(GotoRelatedProvider.EP_NAME)) {
items.addAll(provider.getItems(contextElement));
if (dataContext != null) {
items.addAll(provider.getItems(dataContext));
}
}
sortByGroupNames(items);
return new ArrayList<GotoRelatedItem>(items);
}
private static void sortByGroupNames(Set<GotoRelatedItem> items) {
Map<String, List<GotoRelatedItem>> map = new HashMap<String, List<GotoRelatedItem>>();
for (GotoRelatedItem item : items) {
final String key = item.getGroup();
if (!map.containsKey(key)) {
map.put(key, new ArrayList<GotoRelatedItem>());
}
map.get(key).add(item);
}
final List<String> keys = new ArrayList<String>(map.keySet());
Collections.sort(keys, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return StringUtil.isEmpty(o1) ? 1 : StringUtil.isEmpty(o2) ? -1 : o1.compareTo(o2);
}
});
items.clear();
for (String key : keys) {
items.addAll(map.get(key));
}
}
@Override
public void update(AnActionEvent e) {
e.getPresentation().setEnabled(LangDataKeys.PSI_FILE.getData(e.getDataContext()) != null);
}
private static Action createNumberAction(final int mnemonic,
final ListPopupImpl listPopup,
final Map<PsiElement, GotoRelatedItem> itemsMap,
final Processor<Object> processor) {
return new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
for (final Object item : listPopup.getListStep().getValues()) {
if (getMnemonic(item, itemsMap) == mnemonic) {
listPopup.setFinalRunnable(new Runnable() {
@Override
public void run() {
processor.process(item);
}
});
listPopup.closeOk(null);
}
}
}
};
}
private static int getMnemonic(Object item, Map<PsiElement, GotoRelatedItem> itemsMap) {
return (item instanceof GotoRelatedItem ? (GotoRelatedItem)item : itemsMap.get((PsiElement)item)).getMnemonic();
}
}