com.android.tools.idea.ui.resourcechooser.ChooseResourceDialog.java Source code

Java tutorial

Introduction

Here is the source code for com.android.tools.idea.ui.resourcechooser.ChooseResourceDialog.java

Source

/*
 * Copyright 2000-2013 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.android.tools.idea.ui.resourcechooser;

import com.android.ide.common.rendering.api.ItemResourceValue;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.res2.ResourceItem;
import com.android.ide.common.resources.ResourceResolver;
import com.android.ide.common.resources.ResourceUrl;
import com.android.resources.ResourceFolderType;
import com.android.resources.ResourceType;
import com.android.sdklib.IAndroidTarget;
import com.android.tools.adtui.treegrid.TreeGrid;
import com.android.tools.idea.configurations.Configuration;
import com.android.tools.idea.configurations.ConfigurationManager;
import com.android.tools.idea.editors.theme.*;
import com.android.tools.idea.editors.theme.attributes.editors.DrawableRendererEditor;
import com.android.tools.idea.editors.theme.attributes.editors.GraphicalResourceRendererEditor;
import com.android.tools.idea.editors.theme.ui.ResourceComponent;
import com.android.tools.idea.javadoc.AndroidJavaDocRenderer;
import com.android.tools.idea.rendering.HtmlBuilderHelper;
import com.android.tools.idea.rendering.RenderTask;
import com.android.tools.idea.rendering.webp.WebpImageReaderSpi;
import com.android.tools.idea.rendering.webp.WebpMetadata;
import com.android.tools.idea.res.AppResourceRepository;
import com.android.tools.idea.res.ProjectResourceRepository;
import com.android.tools.idea.res.ResourceHelper;
import com.android.tools.idea.res.ResourceNameValidator;
import com.android.tools.idea.ui.SearchField;
import com.android.tools.lint.checks.IconDetector;
import com.android.tools.swing.ui.SwatchComponent;
import com.android.utils.HtmlBuilder;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.intellij.icons.AllIcons;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.ide.util.treeView.AbstractTreeStructure;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.impl.SimpleDataContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.OnePixelDivider;
import com.intellij.openapi.ui.ValidationInfo;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.ListPopup;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.ui.*;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.table.JBTable;
import com.intellij.util.PlatformIcons;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.ui.*;
import com.intellij.util.ui.accessibility.ScreenReader;
import icons.AndroidIcons;
import org.jetbrains.android.actions.CreateResourceFileAction;
import org.jetbrains.android.actions.CreateXmlResourceDialog;
import org.jetbrains.android.actions.CreateXmlResourcePanel;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.refactoring.AndroidBaseLayoutRefactoringAction;
import org.jetbrains.android.refactoring.AndroidExtractStyleAction;
import org.jetbrains.android.sdk.AndroidTargetData;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.android.util.AndroidResourceUtil;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.AbstractBorder;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.List;

import static com.android.SdkConstants.*;

/**
 * Resource Chooser, with previews. Based on ResourceDialog in the android-designer.
 * <P>
 * TODO: Perform validation (such as cyclic layout resource detection for layout selection)
 */
public class ChooseResourceDialog extends DialogWrapper {
    private static final String TYPE_KEY = "ResourceType";
    private static final String FOLDER_TYPE_KEY = "ResourceFolderType";
    private static final String GRID_MODE_KEY = "ResourceChooserGridMode";
    public static final String APP_NAMESPACE_LABEL = "Project";
    private static final int GRID_ICON_SIZE = JBUI.scale(50);
    private static final int GRID_CHECK_SIZE = JBUI.scale(8);
    private static final int GRID_CELL_SIZE = JBUI.scale(120);
    private static final int LIST_ICON_SIZE = JBUI.scale(28);
    private static final int LIST_CHECK_SIZE = JBUI.scale(5);
    private static final int LIST_CELL_HEIGHT = JBUI.scale(40);
    static final int TABLE_CELL_HEIGHT = JBUI.scale(30);
    private static final JBColor LIST_DIVIDER_COLOR = new JBColor(Gray._245, Gray._80);
    private static final JBInsets LIST_PADDING = JBUI.insets(7, 6);
    public static final JBDimension PANEL_PREFERRED_SIZE = JBUI.size(850, 620);
    static final SimpleTextAttributes SEARCH_MATCH_ATTRIBUTES = new SimpleTextAttributes(null, null, null,
            SimpleTextAttributes.STYLE_SEARCH_MATCH);

    @NotNull
    private final Module myModule;
    @NotNull
    private final AndroidFacet myFacet;
    @NotNull
    private final Configuration myConfiguration;
    @Nullable
    private final XmlTag myTag;

    private final JComponent myContentPanel;
    private final JTabbedPane myTabbedPane;
    private final JPanel myAltPane; // Alternative used instead of tabbed pane if there is only one resource type
    private final JComponent myViewOption;
    private final String myResourceNameSuggestion;
    private final EnumSet<ResourceType> myTypes;
    private final String myInitialValue;
    private final boolean myInitialValueIsFramework;
    private final ResourceNameVisibility myResourceNameVisibility;
    private boolean myGridMode = PropertiesComponent.getInstance().getBoolean(GRID_MODE_KEY, false);

    // if we are picking a resource that can't be a color, then all these are null
    @Nullable
    private ResourceEditorTab myColorPickerPanel;
    @Nullable
    private ColorPicker myColorPicker;

    // we can ONLY ever have the state-list picker in {@link ResourceType#COLOR} or {@link ResourceType#DRAWABLE} mode.
    // We only ever need one stateList picker because Android can never allow picking both types for any attribute.
    @Nullable
    private ResourceEditorTab myStateListPickerPanel;
    @Nullable
    private StateListPicker myStateListPicker;
    @Nullable
    private ResourcePickerListener myResourcePickerListener;
    @NotNull
    private ImmutableMap<String, Color> myContrastColorsWithDescription = ImmutableMap.of();
    private boolean myIsBackgroundColor;
    private final SearchField mySearchField;
    private final boolean myHideLeftSideActions;
    private boolean myAllowCreateResource = true;
    private String myResultResourceName;
    private boolean myUseGlobalUndo;
    private RenderTask myRenderTask;
    private final MultiMap<ResourceType, String> myThemAttributes;

    /** Creates a builder for a new resource chooser dialog */
    @NotNull
    public static Builder builder() {
        return new Builder();
    }

    /** Builder class for constructing a resource chooser */
    public static class Builder {
        private Module myModule;
        private Configuration myConfiguration;
        private XmlTag myTag;
        private XmlFile myFile;
        private boolean myIsFrameworkValue;
        private String myCurrentValue;
        private EnumSet<ResourceType> myTypes;
        private ResourceNameVisibility myResourceNameVisibility = ResourceNameVisibility.SHOW;
        private String myResourceNameSuggestion;
        private boolean myHideLeftSideActions;

        public Builder() {
        }

        public Builder setModule(@NotNull Module module) {
            myModule = module;
            return this;
        }

        public Builder setTag(@Nullable XmlTag tag) {
            myTag = tag;
            if (myTag != null && myFile == null) {
                myFile = (XmlFile) myTag.getContainingFile();
            }
            return this;
        }

        public Builder setFile(@Nullable XmlFile file) {
            myFile = file;
            return this;
        }

        public Builder setIsFrameworkValue(boolean frameworkValue) {
            myIsFrameworkValue = frameworkValue;
            return this;
        }

        public Builder setCurrentValue(@Nullable String currentValue) {
            myCurrentValue = currentValue;
            return this;
        }

        public Builder setTypes(@NotNull EnumSet<ResourceType> types) {
            myTypes = types;
            return this;
        }

        public Builder setResourceNameVisibility(@NotNull ResourceNameVisibility resourceNameVisibility) {
            myResourceNameVisibility = resourceNameVisibility;
            return this;
        }

        public Builder setResourceNameSuggestion(@Nullable String resourceNameSuggestion) {
            if (resourceNameSuggestion != null && (resourceNameSuggestion.startsWith(PREFIX_RESOURCE_REF)
                    || resourceNameSuggestion.startsWith(PREFIX_THEME_REF)
                    || resourceNameSuggestion.startsWith("#"))) {
                throw new IllegalArgumentException("invalid name suggestion " + resourceNameSuggestion);
            }

            myResourceNameSuggestion = resourceNameSuggestion;
            return this;
        }

        public Builder setHideLeftSideActions(boolean hideLeftSideActions) {
            myHideLeftSideActions = hideLeftSideActions;
            return this;
        }

        public Builder setConfiguration(@Nullable Configuration configuration) {
            myConfiguration = configuration;
            return this;
        }

        public ChooseResourceDialog build() {
            Configuration configuration = myConfiguration;
            AndroidFacet facet = AndroidFacet.getInstance(myModule);
            assert facet != null;

            if (configuration == null) {
                if (myFile != null && myFile.getVirtualFile() != null) {
                    ConfigurationManager configurationManager = facet.getConfigurationManager();
                    configuration = configurationManager.getConfiguration(myFile.getVirtualFile());
                }

                if (configuration == null) {
                    configuration = ThemeEditorUtils.getConfigurationForModule(myModule);
                }
            }

            return new ChooseResourceDialog(facet, configuration, myTag, myTypes, myCurrentValue,
                    myIsFrameworkValue, myResourceNameVisibility, myResourceNameSuggestion, myHideLeftSideActions);
        }
    }

    public interface ResourcePickerListener {
        void resourceChanged(@Nullable String resource);
    }

    private ChooseResourceDialog(@NotNull AndroidFacet facet, @NotNull Configuration configuration,
            @Nullable XmlTag tag, @NotNull EnumSet<ResourceType> types, @Nullable String value,
            boolean isFrameworkValue, @NotNull ResourceNameVisibility resourceNameVisibility,
            @Nullable String resourceNameSuggestion, boolean hideLeftSideActions) {
        super(facet.getModule().getProject());
        myModule = facet.getModule();
        myFacet = facet;
        myConfiguration = configuration;
        myTag = tag;
        myInitialValue = value;
        myInitialValueIsFramework = isFrameworkValue;
        myResourceNameVisibility = resourceNameVisibility;

        // Treat mipmaps as a type of drawable
        types = types.clone();
        if (types.contains(ResourceType.MIPMAP)) {
            types.add(ResourceType.DRAWABLE);
            types.remove(ResourceType.MIPMAP);
        }

        // You can specify a color in place of a drawable
        if (types.contains(ResourceType.DRAWABLE)
                || types.contains(ResourceType.MIPMAP) && !types.contains(ResourceType.COLOR)) {
            types.add(ResourceType.COLOR);
        }

        myTypes = types;

        myHideLeftSideActions = hideLeftSideActions;
        myResourceNameSuggestion = resourceNameSuggestion;

        ResourceResolver resolver = configuration.getResourceResolver();
        assert resolver != null;

        myThemAttributes = initializeThemeAttributes(configuration, resolver);

        ResourceValue resValue = null;
        if (value != null) {
            resValue = resolver.findResValue(value, isFrameworkValue);
        }

        myViewOption = createViewOptions();
        myTabbedPane = initializeTabbedPane();
        if (myTabbedPane == null) {
            myAltPane = new JPanel(new BorderLayout());
            myAltPane.setPreferredSize(PANEL_PREFERRED_SIZE);
            myAltPane.setBorder(IdeBorderFactory.createEmptyBorder(0, JBUI.scale(12), 0, 0));
        } else {
            myAltPane = null;
        }

        myContentPanel = new JPanel(new BorderLayout());
        myContentPanel.add(myTabbedPane != null ? myTabbedPane : myAltPane);
        mySearchField = createSearchField();
        myContentPanel.add(createToolbar(), BorderLayout.NORTH);

        setTitle("Resources");
        setupViewOptions();
        init();

        selectResourceValue(resValue);

        // we need to trigger this once before the window is made visible to update any extra labels
        doValidate();
    }

    @NotNull
    public Module getModule() {
        return myModule;
    }

    @NotNull
    public AndroidFacet geFacet() {
        return myFacet;
    }

    private void selectResourceValue(@Nullable ResourceValue resValue) {
        // initial selection
        if (resValue != null) {
            ResourcePanel panel = getSelectedPanel();
            if (panel.select(resValue)) {
                return;
            }

            // Selection not found in the current panel: switch tab to show it

            ResourceType type;
            if (resValue instanceof ItemResourceValue) { // type is always null for ItemResourceValue
                type = ResolutionUtils.getAttrType((ItemResourceValue) resValue, myConfiguration);
            } else {
                type = resValue.getResourceType();
            }
            // panel is null if the reference is incorrect, e.g. "@sdfgsdfgs" (user error).
            if (type != null) {
                panel = getPanel(myTabbedPane, type);
                if (panel != null) {
                    if (myTabbedPane != null) {
                        myTabbedPane.setSelectedComponent(panel.myComponent.getParent());
                    }
                    if (!panel.select(resValue) && type == ResourceType.COLOR) {
                        // You might have selected a private framework color; we *can* edit these
                        panel.showPreview(null, true);
                    }
                }
            }
        }
    }

    private static MultiMap<ResourceType, String> initializeThemeAttributes(@NotNull Configuration configuration,
            @NotNull ResourceResolver resolver) {
        MultiMap<ResourceType, String> attrs = new MultiMap<>();
        String themeName = configuration.getTheme();
        assert themeName != null;
        for (ItemResourceValue item : ResolutionUtils.getThemeAttributes(resolver, themeName)) {
            ResourceType type = ResolutionUtils.getAttrType(item, configuration);
            if (type != null) {
                attrs.putValue(type, ResolutionUtils.getQualifiedItemName(item));
            }
        }

        return attrs;
    }

    private boolean allowColors() {
        return myTypes.contains(ResourceType.COLOR);
    }

    private boolean allowDrawables() {
        return myTypes.contains(ResourceType.DRAWABLE) || myTypes.contains(ResourceType.MIPMAP);
    }

    @NotNull
    private JComponent createToolbar() {
        JComponent toolbar = Box.createHorizontalBox();
        toolbar.add(mySearchField);
        toolbar.add(Box.createHorizontalStrut(JBUI.scale(20)));
        toolbar.add(myViewOption);

        toolbar.add(Box.createHorizontalGlue());
        JBLabel addNew = new JBLabel("Add new resource");
        addNew.setIcon(PlatformIcons.COMBOBOX_ARROW_ICON);
        addNew.setHorizontalTextPosition(SwingConstants.LEFT);
        addNew.setIconTextGap(0);
        if (ScreenReader.isActive()) {
            addNew.setFocusable(true);
        }
        toolbar.add(addNew);
        MyAddNewResourceLabelListener listener = new MyAddNewResourceLabelListener();
        addNew.addMouseListener(listener);
        addNew.addKeyListener(listener);

        toolbar.setBorder(new CompoundBorder(JBUI.Borders.customLine(OnePixelDivider.BACKGROUND, 0, 0, 1, 0),
                JBUI.Borders.empty(8)));
        return toolbar;
    }

    private JComponent createViewOptions() {
        ToggleAction listView = createListViewAction();
        ToggleAction gridView = createGridViewAction();
        DefaultActionGroup group = new DefaultActionGroup(listView, gridView);
        JComponent component = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true)
                .getComponent();
        component.setBorder(null);
        component.setMaximumSize(new Dimension(JBUI.scale(100), component.getMaximumSize().height));
        return component;
    }

    @Nullable
    private JTabbedPane initializeTabbedPane() {
        if (myTypes.size() <= 1) {
            return null;
        }

        //noinspection UndesirableClassUsage We install our own special UI, intellij stuff will break it
        JTabbedPane pane = new JTabbedPane(SwingConstants.LEFT);
        pane.setName("ResourceTypeTabs"); // for UI tests
        pane.setUI(new SimpleTabUI());

        List<ResourceType> sorted = Lists.newArrayList(myTypes);
        // Sort drawables above colors
        Collections.sort(sorted, (t1, t2) -> typeRank(t1) - typeRank(t2));

        for (ResourceType type : sorted) {
            // only show color state lists if we are not showing drawables
            JPanel container = new JPanel(new BorderLayout());
            container.setPreferredSize(PANEL_PREFERRED_SIZE);
            container.putClientProperty(ResourceType.class, type);
            pane.addTab(type.getDisplayName(), container);
        }

        pane.addChangeListener(e -> handleTabChange());
        return pane;
    }

    private static int typeRank(ResourceType type) {
        switch (type) {
        case DRAWABLE:
            return 0;
        case COLOR:
            return 1;
        default:
            return type.ordinal() + 2;
        }
    }

    private void handleTabChange() {
        ResourcePanel panel = getSelectedPanel();
        panel.configureList(myGridMode);
        updateFilter();
        setupViewOptions();
    }

    @NotNull
    private ResourcePanel getSelectedPanel() {
        if (myTabbedPane != null) {
            JPanel selectedComponent = (JPanel) myTabbedPane.getSelectedComponent();
            ResourceType type = (ResourceType) selectedComponent.getClientProperty(ResourceType.class);
            return getPanel(myTabbedPane, type);
        } else {
            // Just one type
            return getPanel(null, myTypes.iterator().next());
        }
    }

    private Map<ResourceType, ResourcePanel> myTypeToPanels = Maps.newEnumMap(ResourceType.class);

    private ResourcePanel getPanel(@Nullable JTabbedPane tabbedPane, @NotNull ResourceType type) {
        // All ResourceType requests for MIPMAP should be converted into a drawable instead
        if (type == ResourceType.MIPMAP) { // mipmaps are treated as drawables
            type = ResourceType.DRAWABLE;
        }

        ResourcePanel panel = myTypeToPanels.get(type);
        if (panel == null) {
            panel = new ResourcePanel(type, type != ResourceType.COLOR || !allowDrawables(),
                    myThemAttributes.get(type));
            panel.expandAll();

            JPanel container = myAltPane;
            if (container == null && tabbedPane != null) {
                for (int i = 0, n = tabbedPane.getComponentCount(); i < n; i++) {
                    JPanel tab = (JPanel) tabbedPane.getComponentAt(i);
                    if (tab.getClientProperty(ResourceType.class) == type) {
                        container = tab;
                        break;
                    }
                }
            }
            if (container != null) {
                container.add(panel.myComponent, BorderLayout.CENTER);
                myTypeToPanels.put(type, panel);
            }
        }

        return panel;
    }

    @NotNull
    private ToggleAction createGridViewAction() {
        return new ToggleAction(null, "grid", AndroidIcons.Views.GridView) {
            @Override
            public boolean isSelected(AnActionEvent e) {
                return myGridMode;
            }

            @Override
            public void setSelected(AnActionEvent e, boolean state) {
                setGridMode(state);
            }
        };
    }

    @NotNull
    private ToggleAction createListViewAction() {
        return new ToggleAction(null, "list", AndroidIcons.Views.ListView) {
            @Override
            public boolean isSelected(AnActionEvent e) {
                return !myGridMode;
            }

            @Override
            public void setSelected(AnActionEvent e, boolean state) {
                setGridMode(!state);
            }
        };
    }

    @NotNull
    private AnAction createNewResourceValueAction() {
        return new AnAction() {
            @Override
            public void actionPerformed(AnActionEvent e) {
                ResourceType type = (ResourceType) getTemplatePresentation().getClientProperty(TYPE_KEY);
                createNewResourceValue(type);
            }
        };
    }

    @NotNull
    private AnAction createNewResourceFileAction() {
        return new AnAction() {
            @Override
            public void actionPerformed(AnActionEvent e) {
                ResourceFolderType type = (ResourceFolderType) getTemplatePresentation()
                        .getClientProperty(FOLDER_TYPE_KEY);
                createNewResourceFile(type);
            }
        };
    }

    @NotNull
    private AbstractAction createNewResourceAction() {
        return new AbstractAction("New Resource", AllIcons.General.ComboArrowDown) {
            @Override
            public void actionPerformed(ActionEvent e) {
                JComponent component = (JComponent) e.getSource();
                ActionPopupMenu popupMenu = createNewResourcePopupMenu();
                popupMenu.getComponent().show(component, 0, component.getHeight());
            }
        };
    }

    @NotNull
    private AnAction createExtractStyleAction() {
        return new AnAction("Extract Style...") {
            @Override
            public void actionPerformed(AnActionEvent e) {
                extractStyle();
            }
        };
    }

    @NotNull
    private AnAction createNewResourceReferenceAction() {
        return new AnAction() {
            @Override
            public void actionPerformed(AnActionEvent e) {
                ResourcePanel panel = getSelectedPanel();
                panel.showNewResource(panel.myReferencePanel);
            }
        };
    }

    @NotNull
    private SearchField createSearchField() {
        SearchField searchField = new SearchField(false) { // no history: interferes with arrow down to jump into the list
            @Override
            protected void showPopup() {
                // Turn off search popup; we're overriding the Down key to jump into the list instead
            }
        };
        searchField.getTextEditor().addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                // Allow arrow down to jump directly into the list
                if (e.getKeyCode() == KeyEvent.VK_DOWN) {
                    e.consume();
                    getSelectedPanel().selectFirst();
                }
            }
        });
        searchField.setMaximumSize(new Dimension(JBUI.scale(300), searchField.getMaximumSize().height));
        searchField.addDocumentListener(new DocumentAdapter() {
            @Override
            protected void textChanged(DocumentEvent e) {
                updateFilter();
            }
        });

        return searchField;
    }

    private void updateFilter() {
        ResourcePanel panel = getSelectedPanel();
        final String text = mySearchField.getText();
        if (text.isEmpty()) {
            if (!panel.isFiltered()) {
                return;
            }
            panel.setFilter(null);
            return;
        }
        if (panel.getType() == ResourceType.COLOR) {
            Condition<ResourceChooserItem> colorCondition = null;
            if (text.startsWith("#")) {
                final Color color = ResourceHelper.parseColor(text);
                if (color != null) {
                    colorCondition = item -> {
                        assert item.getType() == ResourceType.COLOR; // we don't want to search non-colors
                        return ResourceHelper.resolveMultipleColors(getResourceResolver(), item.getResourceValue(),
                                myModule.getProject()).contains(color);
                    };
                }
            }
            if (colorCondition != null) {
                panel.setFilter(colorCondition);
                return;
            }
        }

        Condition<ResourceChooserItem> condition = item -> {
            if (item.getType() == ResourceType.STRING) {
                // TODO: Cache on item!
                String string = ResourceHelper.resolveStringValue(getResourceResolver(), item.getResourceUrl());
                if (StringUtil.containsIgnoreCase(string, text)) {
                    return true;
                }
            }
            return StringUtil.containsIgnoreCase(item.getName(), text);
        };

        panel.setFilter(condition);
    }

    private void initializeColorPicker(@Nullable String value,
            @NotNull final ResourceNameVisibility resourceNameVisibility, ResourceResolver resolver,
            ResourceValue resValue) {
        Color color = null;
        if (resValue != null) {
            color = ResourceHelper.resolveColor(resolver, resValue, myModule.getProject());
        }

        if (color == null) {
            color = ResourceHelper.parseColor(value);
        }
        myColorPicker = new ColorPicker(myDisposable, color, true, new ColorPickerListener() {
            @Override
            public void colorChanged(Color color) {
                notifyResourcePickerListeners(ResourceHelper.colorToString(color));
            }

            @Override
            public void closed(@Nullable Color color) {
            }
        });
        myColorPicker.pickARGB();

        myColorPickerPanel = new ResourceEditorTab(myModule, "Color", myColorPicker, resourceNameVisibility, false,
                ResourceFolderType.VALUES, true, ResourceType.COLOR) {
            @NotNull
            @Override
            public String doSave() {
                String value = ResourceHelper.colorToString(myColorPicker.getColor());
                if (getResourceNameVisibility() == ResourceNameVisibility.FORCE
                        || (getResourceNameVisibility() == ResourceNameVisibility.SHOW
                                && !getSelectedPanel().myEditorPanel.getResourceName().isEmpty())) {
                    value = saveValuesResource(getSelectedPanel().myEditorPanel.getResourceName(), value,
                            getLocationSettings());
                }
                // else we use the value we got at the start of the method
                return value;
            }
        };
    }

    private void ensurePickersInitialized() {
        boolean allowDrawables = allowDrawables();
        boolean allowColors = allowColors();

        if (allowColors || allowDrawables) {
            if (myStateListPicker != null || myColorPicker != null) {
                return;
            }
            Configuration configuration = getConfiguration();
            ResourceResolver resolver = configuration.getResourceResolver();
            assert resolver != null;

            ResourceValue resValue = null;
            if (myInitialValue != null) {
                resValue = resolver.findResValue(myInitialValue, myInitialValueIsFramework);
            }

            final ResourceType stateListType;
            final ResourceFolderType stateListFolderType;
            if (allowDrawables) {
                stateListType = ResourceType.DRAWABLE;
                stateListFolderType = ResourceFolderType.DRAWABLE;
            } else {
                stateListType = ResourceType.COLOR;
                stateListFolderType = ResourceFolderType.COLOR;
            }

            initializeStateListPicker(configuration, resolver, resValue, stateListType, stateListFolderType);
            initializeColorPicker(myInitialValue, myResourceNameVisibility, resolver, resValue);
        }
    }

    private void initializeStateListPicker(@NotNull Configuration configuration, ResourceResolver resolver,
            ResourceValue resValue, final ResourceType stateListType,
            final ResourceFolderType stateListFolderType) {
        ResourceHelper.StateList stateList = null;
        if (resValue != null) {
            stateList = ResourceHelper.resolveStateList(resolver, resValue, myModule.getProject());
            if (stateList != null && stateList.getType() != stateListType) {
                // this is very strange, this means we have asked to open the resource picker to allow drawables but with a color state-list
                // or to 'not allow drawables', but with a drawables state-list, must be a user error, this should not normally happen.
                Logger.getInstance(ChooseResourceDialog.class)
                        .warn("StateList type mismatch " + stateList.getType() + " " + stateListType);
                stateList = null;
            }
        }

        myStateListPicker = new StateListPicker(stateList, myModule, configuration);
        myStateListPickerPanel = new ResourceEditorTab(myModule, "Statelist", myStateListPicker,
                ResourceNameVisibility.FORCE, true, stateListFolderType, false, stateListType) {
            @Override
            @Nullable
            public ValidationInfo doValidate() {
                ValidationInfo error = super.doValidate();
                if (error == null) {
                    int minDirectoriesApi = ThemeEditorUtils.getMinFolderApi(getLocationSettings().getDirNames(),
                            myModule);
                    error = myStateListPicker.doValidate(minDirectoriesApi);
                }
                return error;
            }

            @NotNull
            @Override
            public String doSave() {
                String stateListName = getSelectedPanel().myEditorPanel.getResourceName();
                Module module = getSelectedModule();
                VirtualFile resDir = getResourceDirectory();
                List<String> dirNames = getLocationSettings().getDirNames();
                ResourceFolderType resourceFolderType = ResourceFolderType.getFolderType(dirNames.get(0));
                ResourceType resourceType = ResourceType.getEnum(resourceFolderType.getName());

                Project project = module.getProject();
                List<VirtualFile> files = null;
                if (resDir == null) {
                    AndroidUtils.reportError(project,
                            AndroidBundle.message("check.resource.dir.error", module.getName()));
                } else {
                    if (resourceType != null) {
                        files = AndroidResourceUtil.findOrCreateStateListFiles(project, resDir, resourceFolderType,
                                resourceType, stateListName, dirNames);
                    }
                }
                if (files != null) {
                    assert myStateListPicker != null;
                    ResourceHelper.StateList stateList1 = myStateListPicker.getStateList();
                    assert stateList1 != null;
                    AndroidResourceUtil.updateStateList(project, stateList1, files);
                }

                if (resourceFolderType == ResourceFolderType.COLOR) {
                    return COLOR_RESOURCE_PREFIX + stateListName;
                }
                assert resourceFolderType == ResourceFolderType.DRAWABLE;
                return DRAWABLE_PREFIX + stateListName;
            }
        };
    }

    @NotNull
    Configuration getConfiguration() {
        return myConfiguration;
    }

    private void setupViewOptions() {
        myViewOption.setVisible(getSelectedPanel().supportsGridMode());
    }

    @NotNull
    @Override
    protected DialogStyle getStyle() {
        // will draw the line between the main panel and the action buttons.
        return DialogStyle.COMPACT;
    }

    public void setContrastParameters(@NotNull ImmutableMap<String, Color> contrastColorsWithDescription,
            boolean isBackground, boolean displayWarning) {
        ensurePickersInitialized();
        if (myColorPicker != null) {
            myColorPicker.setContrastParameters(contrastColorsWithDescription, isBackground, displayWarning);
        }
        if (myStateListPicker != null) {
            myStateListPicker.setContrastParameters(contrastColorsWithDescription, isBackground);
        }
        myContrastColorsWithDescription = contrastColorsWithDescription;
        myIsBackgroundColor = isBackground;
    }

    @Override
    protected boolean postponeValidation() {
        return false;
    }

    public enum ResourceNameVisibility {
        /**
         * Show field, but do not force name to be used.
         */
        SHOW,

        /**
         * Force creation of named color.
         */
        FORCE
    }

    @Override
    @Nullable
    protected ValidationInfo doValidate() {
        return getSelectedPanel().doValidate();
    }

    public void setResourcePickerListener(@Nullable ResourcePickerListener resourcePickerListener) {
        myResourcePickerListener = resourcePickerListener;
    }

    private void notifyResourcePickerListeners(@Nullable String resource) {
        if (myResourcePickerListener != null) {
            myResourcePickerListener.resourceChanged(resource);
        }
    }

    public void generateColorSuggestions(@NotNull Color primaryColor, @NotNull String attributeName) {
        List<Color> suggestedColors = null;
        switch (attributeName) {
        case MaterialColors.PRIMARY_MATERIAL_ATTR:
            suggestedColors = MaterialColorUtils.suggestPrimaryColors();
            break;
        case MaterialColors.PRIMARY_DARK_MATERIAL_ATTR:
            suggestedColors = MaterialColorUtils.suggestPrimaryDarkColors(primaryColor);
            break;
        case MaterialColors.ACCENT_MATERIAL_ATTR:
            suggestedColors = MaterialColorUtils.suggestAccentColors(primaryColor);
            break;
        }
        if (suggestedColors != null) {
            ensurePickersInitialized();
            assert myColorPicker != null;
            myColorPicker.setRecommendedColors(suggestedColors);
        }
    }

    private ActionPopupMenu createNewResourcePopupMenu() {
        ActionManager actionManager = ActionManager.getInstance();
        DefaultActionGroup actionGroup = new DefaultActionGroup();

        ResourcePanel panel = getSelectedPanel();
        ResourceType resourceType = panel.getType();

        ResourceFolderType folderType = AndroidResourceUtil.XML_FILE_RESOURCE_TYPES.get(resourceType);
        if (folderType != null) {
            AnAction newFileAction = createNewResourceFileAction();
            newFileAction.getTemplatePresentation().setText("New " + folderType.getName() + " File...");
            newFileAction.getTemplatePresentation().putClientProperty(FOLDER_TYPE_KEY, folderType);
            actionGroup.add(newFileAction);
        }
        if (AndroidResourceUtil.VALUE_RESOURCE_TYPES.contains(resourceType)) {
            String title = "New " + resourceType + " Value...";
            if (resourceType == ResourceType.LAYOUT) {
                title = "New Layout Alias";
            }
            AnAction newValueAction = createNewResourceValueAction();
            newValueAction.getTemplatePresentation().setText(title);
            newValueAction.getTemplatePresentation().putClientProperty(TYPE_KEY, resourceType);
            actionGroup.add(newValueAction);
        }
        if (myTag != null && ResourceType.STYLE.equals(resourceType)) {
            final boolean enabled = AndroidBaseLayoutRefactoringAction.getLayoutViewElement(myTag) != null
                    && AndroidExtractStyleAction.doIsEnabled(myTag);
            AnAction extractStyleAction = createExtractStyleAction();
            extractStyleAction.getTemplatePresentation().setEnabled(enabled);
            actionGroup.add(extractStyleAction);
        }
        if (GraphicalResourceRendererEditor.COLORS_AND_DRAWABLES.contains(resourceType)) {
            AnAction newReferenceAction = createNewResourceReferenceAction();
            newReferenceAction.getTemplatePresentation().setText("New " + resourceType + " Reference...");
            actionGroup.add(newReferenceAction);
        }

        return actionManager.createActionPopupMenu(ActionPlaces.UNKNOWN, actionGroup);
    }

    private void createNewResourceValue(ResourceType resourceType) {
        ensurePickersInitialized();
        if (resourceType == ResourceType.COLOR && myColorPickerPanel != null) {
            getSelectedPanel().showNewResource(myColorPickerPanel);
            return;
        }

        CreateXmlResourceDialog dialog = new CreateXmlResourceDialog(myModule, resourceType, null, null, true, null,
                null);
        dialog.setTitle("New " + StringUtil.capitalize(resourceType.getDisplayName()) + " Value Resource");
        if (!dialog.showAndGet()) {
            return;
        }

        Project project = myModule.getProject();
        final VirtualFile resDir = dialog.getResourceDirectory();
        if (resDir == null) {
            AndroidUtils.reportError(project, AndroidBundle.message("check.resource.dir.error", myModule));
            return;
        }

        String fileName = dialog.getFileName();
        List<String> dirNames = dialog.getDirNames();
        String resValue = dialog.getValue();
        String resName = dialog.getResourceName();
        if (!AndroidResourceUtil.createValueResource(project, resDir, resName, resourceType, fileName, dirNames,
                resValue)) {
            return;
        }

        PsiDocumentManager.getInstance(myModule.getProject()).commitAllDocuments();

        myResultResourceName = "@" + resourceType.getName() + "/" + resName;
        close(OK_EXIT_CODE);
    }

    private void createNewResourceFile(ResourceFolderType folderType) {
        ensurePickersInitialized();

        // if we are not showing the stateList picker, and we do have a stateList in it, then we can open it to allow the user to edit it.
        if (myStateListPicker != null && myStateListPicker.getStateList() != null
                && folderType == myStateListPicker.getStateList().getFolderType()) {
            assert myStateListPickerPanel != null;
            getSelectedPanel().showNewResource(myStateListPickerPanel);
            return;
        }

        XmlFile newFile = CreateResourceFileAction.createFileResource(myFacet, folderType, null, null, null, true,
                null, null, null);
        if (newFile != null) {
            String name = newFile.getName();
            int index = name.lastIndexOf('.');
            if (index != -1) {
                name = name.substring(0, index);
            }
            myResultResourceName = "@" + folderType.getName() + "/" + name;
            close(OK_EXIT_CODE);
        }
    }

    private void extractStyle() {
        assert myTag != null;
        final String resName = AndroidExtractStyleAction.doExtractStyle(myModule, myTag, false, null);
        if (resName == null) {
            return;
        }
        myResultResourceName = "@style/" + resName;
        close(OK_EXIT_CODE);
    }

    @Override
    public JComponent getPreferredFocusedComponent() {
        return mySearchField;
    }

    @Override
    protected JComponent createCenterPanel() {
        return myContentPanel;
    }

    // These actions are placed in the "Add new resource" label's menu instead of in the dialog's own
    // actions (by overriding createLeftSideActions() with the below method body) such that they're not listed redundantly.
    @NotNull
    protected Action[] getCreateActions() {
        return myAllowCreateResource && !myHideLeftSideActions ? new Action[] { createNewResourceAction() }
                : new Action[0];
    }

    public ChooseResourceDialog setAllowCreateResource(boolean allowCreateResource) {
        myAllowCreateResource = allowCreateResource;
        return this;
    }

    public boolean getAllowCreateResource() {
        return myAllowCreateResource;
    }

    /**
     * Expands the location settings panel
     */
    public void openLocationSettings() {
        ensurePickersInitialized();
        if (myColorPickerPanel != null) {
            myColorPickerPanel.setLocationSettingsOpen(true);
        }
        if (myStateListPickerPanel != null) {
            myStateListPickerPanel.setLocationSettingsOpen(true);
        }
        getSelectedPanel().myReferencePanel.setLocationSettingsOpen(true);
    }

    public String getResourceName() {
        return myResultResourceName;
    }

    @Override
    protected void doOKAction() {
        ResourcePanel resourcePanel = getSelectedPanel();
        ResourceEditorTab editor = resourcePanel.getCurrentResourceEditor();

        // we are about to close, and potentially create/edit resources, that may cause all sorts of refreshes, so lets clear any live preview values.
        notifyResourcePickerListeners(null);

        if (editor != null) {
            myResultResourceName = editor.doSave();
        } else {
            ResourceChooserItem item = resourcePanel.getSelectedItem();
            myResultResourceName = item != null ? item.getResourceUrl() : null;
        }
        super.doOKAction();
    }

    private void setGridMode(boolean gridMode) {
        if (gridMode != myGridMode) {
            myGridMode = gridMode;
            getSelectedPanel().configureList(myGridMode);
            PropertiesComponent.getInstance().setValue(GRID_MODE_KEY, gridMode, false);
        }
    }

    public void setUseGlobalUndo(boolean useGlobalUndo) {
        myUseGlobalUndo = useGlobalUndo;
    }

    @Nullable
    Icon getIcon(@NotNull ResourceChooserItem item, int size, int checkerboardSize) {
        Icon icon = item.getIcon();
        if (icon != null && (size == icon.getIconWidth())) {
            return icon;
        }

        switch (item.getType()) {
        case COLOR:
        case DRAWABLE:
        case MIPMAP:
            icon = createIcon(size, checkerboardSize, true, item.getPath(), item.getResourceValue(),
                    item.getType());
            if (icon == null) {
                //noinspection UndesirableClassUsage
                icon = new ImageIcon(new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB));
            }
            break;
        default:
            icon = null;
        }

        // Cache for next time
        item.setIcon(icon);

        return icon;
    }

    @Nullable
    Icon createIcon(int size, int checkerboardSize, boolean interpolate, @Nullable String path,
            @NotNull ResourceValue resourceValue, @NotNull ResourceType type) {
        if (path != null && IconDetector.isDrawableFile(path) && !path.endsWith(DOT_XML)) {
            // WebP images for unknown reasons don't load via ImageIcon(path)
            if (path.endsWith(DOT_WEBP)) {
                try {
                    BufferedImage image = ImageIO.read(new File(path));
                    if (image != null) {
                        return new ResourceChooserIcon(size, image, checkerboardSize, interpolate);
                    }
                } catch (IOException ignore) {
                }
            }

            return new ResourceChooserIcon(size, new ImageIcon(path).getImage(), checkerboardSize, interpolate);
        } else if (type == ResourceType.DRAWABLE || type == ResourceType.MIPMAP) {
            // TODO: Attempt to guess size for XML drawables since at least for vectors, we have attributes
            // like android:width, android:height, android:viewportWidth and android:viewportHeight
            // which we can use to get a suitable aspect ratio
            //noinspection UnnecessaryLocalVariable
            int width = size;
            //noinspection UnnecessaryLocalVariable
            int height = size;

            RenderTask renderTask = getRenderTask();
            renderTask.setOverrideRenderSize(width, height);
            renderTask.setMaxRenderSize(width, height);
            BufferedImage image = renderTask.renderDrawable(resourceValue);
            if (image != null) {
                return new ResourceChooserIcon(size, image, checkerboardSize, interpolate);
            }
            // TODO maybe have a different icon for state list drawable
        } else if (type == ResourceType.COLOR) {
            Color color = ResourceHelper.resolveColor(getResourceResolver(), resourceValue, myModule.getProject());
            if (color != null) { // maybe null for invalid color
                return new ColorIcon(size, color);
            }
            // TODO maybe have a different icon when the resource points to more then 1 color
        }

        return null;
    }

    @NotNull
    private SwatchComponent.SwatchIcon getSwatchIcon(@Nullable String name) {
        return StateListPicker.getSwatchIcon(name, getResourceResolver(), getRenderTask());
    }

    @NotNull
    private ResourceResolver getResourceResolver() {
        Configuration config = getConfiguration();
        ResourceResolver resolver = config.getResourceResolver();
        assert resolver != null;
        return resolver;
    }

    @NotNull
    private RenderTask getRenderTask() {
        if (myRenderTask == null) {
            myRenderTask = DrawableRendererEditor.configureRenderTask(myModule, getConfiguration());
            myRenderTask.setMaxRenderSize(150, 150); // don't make huge images here
        }
        return myRenderTask;
    }

    /**
     * Saves any value that can be saved into the values.xml file and does not require its own file.
     * @param value of the resource being edited to be saved
     * @return the value that is returned by the resource chooser.
     */
    @NotNull
    private String saveValuesResource(@NotNull String name, @NotNull String value,
            @NotNull CreateXmlResourcePanel locationSettings) {
        ResourceType type = locationSettings.getType();
        String fileName = locationSettings.getFileName();
        List<String> dirNames = locationSettings.getDirNames();

        Project project = myModule.getProject();
        final VirtualFile resDir = locationSettings.getResourceDirectory();
        if (resDir == null) {
            AndroidUtils.reportError(project,
                    AndroidBundle.message("check.resource.dir.error", myModule.getName()));
        } else {
            if (!AndroidResourceUtil.changeValueResource(project, resDir, name, type, value, fileName, dirNames,
                    myUseGlobalUndo)) {
                // Changing value resource has failed, one possible reason is that resource isn't defined in the project.
                // Trying to create the resource instead.
                AndroidResourceUtil.createValueResource(project, resDir, name, type, fileName, dirNames, value);
            }
        }
        return PREFIX_RESOURCE_REF + type + "/" + name;
    }

    @NotNull
    private static EnumSet<ResourceType> getAllowedTypes(@NotNull ResourceType type) {
        switch (type) {
        case COLOR:
            return GraphicalResourceRendererEditor.COLORS_ONLY;
        case DRAWABLE:
            return GraphicalResourceRendererEditor.DRAWABLES_ONLY;
        default:
            return EnumSet.of(type);
        }
    }

    private class ResourcePanel {

        private static final String NONE = "None";
        private static final String TEXT = "Text";
        private static final String EDITOR = "Editor";
        private static final String DRAWABLE = "Bitmap";
        private static final String TABLE = "Table";

        @NotNull
        public final JBSplitter myComponent;
        @Nullable
        private TreeGrid<ResourceChooserItem> myList;
        @Nullable
        private JBTable myTable;
        @NotNull
        private final JPanel myPreviewPanel;

        private JLabel myNoPreviewComponent;
        private JTextPane myHtmlTextArea;
        private EditResourcePanel myEditorPanel;
        @Nullable
        private ResourceDrawablePanel myDrawablePanel;
        @Nullable
        private ResourceTablePanel myTablePanel;

        private ResourceComponent myReferenceComponent;
        private ResourceEditorTab myReferencePanel;

        @NotNull
        private final ResourceChooserGroup[] myGroups;
        @NotNull
        private final ResourceType myType;

        public ResourcePanel(@NotNull ResourceType type, boolean includeFileResources,
                @NotNull Collection<String> attrs) {
            myType = type;

            List<ResourceChooserGroup> groups = Lists.newArrayListWithCapacity(3);
            ResourceChooserGroup projectItems = new ResourceChooserGroup(APP_NAMESPACE_LABEL, type, myFacet, false,
                    includeFileResources);
            if (!projectItems.isEmpty()) {
                groups.add(projectItems);
            }
            ResourceChooserGroup frameworkItems = new ResourceChooserGroup(ANDROID_NS_NAME, type, myFacet, true,
                    includeFileResources);
            if (!frameworkItems.isEmpty()) {
                groups.add(frameworkItems);
            }
            ResourceChooserGroup themeItems = new ResourceChooserGroup("Theme attributes", myType, myFacet, attrs);
            if (!themeItems.isEmpty()) {
                groups.add(themeItems);
            }
            myGroups = groups.toArray(new ResourceChooserGroup[0]);

            myComponent = new JBSplitter(false, 0.5f);
            myComponent.setSplitterProportionKey("android.resource_dialog_splitter");

            JComponent firstComponent = createListPanel();
            firstComponent.setPreferredSize(JBUI.size(200, 600));

            myComponent.setFirstComponent(firstComponent);

            myPreviewPanel = new JPanel(new CardLayout());
            myPreviewPanel.setPreferredSize(JBUI.size(400, 600));
            myComponent.setSecondComponent(myPreviewPanel);

            showPreview(null);
        }

        @NotNull
        private JComponent createListPanel() {
            JComponent component;
            if (myType == ResourceType.DRAWABLE || myType == ResourceType.COLOR || myType == ResourceType.MIPMAP
            // Styles and IDs: no "values" to show
                    || myType == ResourceType.STYLE || myType == ResourceType.ID) {
                AbstractTreeStructure treeContentProvider = new ResourceTreeContentProvider(myGroups);
                TreeGrid<ResourceChooserItem> list = new TreeGrid<>(treeContentProvider);
                list.addListSelectionListener(e -> {
                    showPreview(getSelectedItem());
                    notifyResourcePickerListeners(getValueForLivePreview());
                });
                component = myList = list;
                // setup default list look and feel
                configureList(myGridMode);
            } else {
                // Table view (strings, dimensions, etc
                final AbstractTableModel model = new ResourceTableContentProvider(myGroups);

                FilteringTableModel<ResourceChooserItem> tableModel = new FilteringTableModel<>(
                        new AbstractTableModel() {
                            @Override
                            public int getRowCount() {
                                return model.getRowCount();
                            }

                            @Override
                            public int getColumnCount() {
                                return model.getColumnCount();
                            }

                            @Override
                            public Object getValueAt(int rowIndex, int columnIndex) {
                                return model.getValueAt(rowIndex, columnIndex);
                            }
                        }, ResourceChooserItem.class);
                tableModel.refilter(); // Needed as otherwise the filtered list does not show any content.

                component = myTable = new JBTable(tableModel);
                component.setName("nameTable"); // for tests
                myTable.setFillsViewportHeight(true);
                myTable.setTableHeader(null);
                myTable.setBorder(null);
                TableColumnModel columnModel = myTable.getColumnModel();
                columnModel.getColumn(0).setHeaderValue("Key");
                columnModel.getColumn(1).setHeaderValue("Default Value");
                columnModel.getColumn(0).setCellRenderer(new ColoredTableCellRenderer() {
                    @Override
                    protected void customizeCellRenderer(JTable table, Object value, boolean isSelected,
                            boolean hasFocus, int row, int column) {
                        boolean isHeader = false;
                        if (value instanceof ResourceChooserItem) {
                            ResourceChooserItem item = (ResourceChooserItem) value;
                            String string = item.getName();
                            String filter = mySearchField.getText();
                            if (!filter.isEmpty()) {
                                int match = StringUtil.indexOfIgnoreCase(string, filter, 0);
                                if (match != -1) {
                                    append(string.substring(0, match));
                                    append(string.substring(match, match + filter.length()),
                                            SEARCH_MATCH_ATTRIBUTES);
                                    append(string.substring(match + filter.length()));
                                } else {
                                    append(string);
                                }
                            } else {
                                append(string);
                            }
                        } else {
                            isHeader = true;
                            append(value.toString());
                        }

                        if (isHeader) {
                            setFont(UIUtil.getLabelFont().deriveFont(Font.BOLD));
                            if (!isSelected) {
                                setBackground(UIUtil.getLabelBackground());
                            }
                        } else {
                            setFont(UIUtil.getLabelFont());
                            if (!isSelected) {
                                setBackground(table.getBackground());
                            }
                        }
                    }
                });
                columnModel.getColumn(1).setCellRenderer(new DefaultTableCellRenderer() {
                    @Override
                    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
                            boolean hasFocus, int row, int column) {
                        if (value instanceof ResourceChooserItem) {
                            value = ((ResourceChooserItem) value).getDefaultValue();
                            setBackground(table.getBackground());
                        } else {
                            // Header node
                            setBackground(UIUtil.getLabelBackground());
                            value = "";
                        }
                        return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
                    }
                });
                myTable.setRowHeight(TABLE_CELL_HEIGHT);
                myTable.setStriped(false);
                myTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

                myTable.getSelectionModel().addListSelectionListener(e -> {
                    showPreview(getSelectedItem());
                    notifyResourcePickerListeners(getValueForLivePreview());
                });
                myTable.setBorder(BorderFactory.createEmptyBorder());

                TableSpeedSearch speedSearch = new TableSpeedSearch(myTable);
                speedSearch.setClearSearchOnNavigateNoMatch(true);
            }

            new DoubleClickListener() {
                @Override
                protected boolean onDoubleClick(MouseEvent e) {
                    ResourceChooserItem selected = getSelectedItem();
                    if (selected != null) {
                        myResultResourceName = selected.getResourceUrl();
                        close(OK_EXIT_CODE);
                        return true;
                    }
                    return false;
                }
            }.installOn(component);

            JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(component,
                    ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
            scrollPane.setBorder(BorderFactory.createEmptyBorder());
            scrollPane.setViewportBorder(BorderFactory.createEmptyBorder());
            scrollPane.getVerticalScrollBar().setUnitIncrement(JBUI.scale(16));
            return scrollPane;
        }

        boolean isFiltered() {
            if (myList != null) {
                return myList.isFiltered();
            } else if (myTable != null) {
                // Not tracking this yet; err on the side of caution
                return true;
            } else {
                return false;
            }
        }

        void setFilter(@Nullable Condition<ResourceChooserItem> condition) {
            if (myList != null) {
                myList.setFilter(condition);
                if (condition != null) {
                    // Select the only single item after filtering, if any
                    myList.selectIfUnique();
                }
            } else if (myTable != null) {
                //noinspection unchecked
                ((FilteringTableModel<ResourceChooserItem>) myTable.getModel()).setFilter(condition);
                if (condition != null) {
                    TableModel model = myTable.getModel();
                    ResourceChooserItem single = null;
                    for (int row = 0, rowCount = model.getRowCount(); row < rowCount; row++) {
                        Object value = model.getValueAt(row, 0);
                        if (value instanceof ResourceChooserItem) {
                            if (single == null) {
                                single = (ResourceChooserItem) value;
                            } else {
                                single = null;
                                break;
                            }
                        }
                    }
                    if (single != null) {
                        setSelectedItem(single);
                    }
                }
            }
        }

        void selectFirst() {
            if (myList != null) {
                myList.selectFirst();
            } else if (myTable != null) {
                List<ResourceChooserItem> first = myGroups[0].getItems();
                if (first.size() > 0) {
                    setSelectedItem(first.get(0));
                    myTable.requestFocus();
                }
            }
        }

        private void showDrawableItem(ResourceChooserItem item) {
            if (myDrawablePanel == null) {
                myDrawablePanel = new ResourceDrawablePanel(ChooseResourceDialog.this);
                myPreviewPanel.add(myDrawablePanel, DRAWABLE);
            }
            CardLayout layout = (CardLayout) myPreviewPanel.getLayout();
            myDrawablePanel.select(item);
            layout.show(myPreviewPanel, DRAWABLE);
        }

        private void showTableItem(ResourceChooserItem item) {
            if (myTablePanel == null) {
                myTablePanel = new ResourceTablePanel(ChooseResourceDialog.this);
                myPreviewPanel.add(myTablePanel.getPanel(), TABLE);
            } else {
                // Without this, selecting different tables (e.g. keep arrow down pressed) causes the splitter
                // to keep recomputing the allocations based on the preferred sizes of the children instead
                // of sticking with the current proportion
                myComponent.skipNextLayouting();
            }
            CardLayout layout = (CardLayout) myPreviewPanel.getLayout();
            myTablePanel.select(item);
            layout.show(myPreviewPanel, TABLE);
        }

        private void showEditorPanel() {
            if (myEditorPanel == null) {
                myReferenceComponent = new ResourceComponent(myModule.getProject(), true);
                myReferenceComponent.addSwatchListener(e -> {
                    String attributeValue = myReferenceComponent.getValueText();
                    ResourceUrl attributeValueUrl = ResourceUrl.parse(attributeValue);
                    boolean isFrameworkValue = attributeValueUrl != null && attributeValueUrl.framework;
                    String nameSuggestion = attributeValueUrl != null ? attributeValueUrl.name : null;

                    ChooseResourceDialog dialog = builder().setModule(myReferencePanel.getSelectedModule())
                            .setTypes(getAllowedTypes(myType)).setCurrentValue(attributeValue)
                            .setIsFrameworkValue(isFrameworkValue)
                            .setResourceNameVisibility(ResourceNameVisibility.FORCE)
                            .setResourceNameSuggestion(nameSuggestion).setConfiguration(getConfiguration()).build();

                    if (myResourcePickerListener != null) {
                        dialog.setResourcePickerListener(myResourcePickerListener);
                    }
                    if (!myContrastColorsWithDescription.isEmpty()) {
                        dialog.setContrastParameters(myContrastColorsWithDescription, myIsBackgroundColor, true);
                    }
                    dialog.show();

                    if (dialog.isOK()) {
                        String resourceName = dialog.getResourceName();
                        myReferenceComponent.setValueText(resourceName);
                        myReferenceComponent.repaint();
                    } else {
                        // reset live preview to original value
                        notifyResourcePickerListeners(myReferenceComponent.getValueText());
                    }
                });
                myReferenceComponent
                        .addTextDocumentListener(new com.intellij.openapi.editor.event.DocumentAdapter() {
                            @Override
                            public void documentChanged(com.intellij.openapi.editor.event.DocumentEvent e) {
                                // This is run inside a WriteAction and updateIcon may need an APP_RESOURCES_LOCK from AndroidFacet.
                                // To prevent a potential deadlock, we call updateIcon in another thread.
                                ApplicationManager.getApplication().invokeLater(() -> {
                                    updateReferenceSwatchIcon();
                                    notifyResourcePickerListeners(myReferenceComponent.getValueText());
                                }, ModalityState.any());
                            }
                        });
                // TODO, what if we change module in the resource editor, we should update the auto complete to match
                myReferenceComponent.setCompletionStrings(
                        ResourceHelper.getCompletionFromTypes(myFacet, getAllowedTypes(myType)));

                Box referenceComponentPanel = new Box(BoxLayout.Y_AXIS);
                referenceComponentPanel.setName("ReferenceEditor"); // for UI tests
                referenceComponentPanel.add(myReferenceComponent);
                referenceComponentPanel.add(Box.createVerticalGlue());
                myReferencePanel = new ResourceEditorTab(myModule, "Reference", referenceComponentPanel,
                        ResourceNameVisibility.FORCE, false, ResourceFolderType.VALUES, true, myType) {
                    @Override
                    @Nullable
                    public ValidationInfo doValidate() {
                        ValidationInfo error = super.doValidate();
                        if (error == null) {
                            int minDirectoriesApi = ThemeEditorUtils
                                    .getMinFolderApi(getLocationSettings().getDirNames(), myModule);
                            IAndroidTarget target = getConfiguration().getRealTarget();
                            assert target != null;
                            final AndroidTargetData androidTargetData = AndroidTargetData.getTargetData(target,
                                    myModule);
                            assert androidTargetData != null;
                            error = myReferenceComponent.doValidate(minDirectoriesApi, androidTargetData);
                        }
                        return error;
                    }

                    @NotNull
                    @Override
                    public String doSave() {
                        return saveValuesResource(myEditorPanel.getResourceName(),
                                myReferenceComponent.getValueText(), getLocationSettings());
                    }
                };

                myEditorPanel = new EditResourcePanel(myResourceNameSuggestion);
                myEditorPanel.addVariantActionListener(e -> {
                    // user has selected a different variant for the current resource, so we need to display it
                    getSelectedPanel().editResourceItem(myEditorPanel.getSelectedVariant());
                });

                myEditorPanel.addTab(myReferencePanel);
                ensurePickersInitialized();
                if (myType == ResourceType.COLOR) {
                    assert myColorPickerPanel != null;
                    myEditorPanel.addTab(myColorPickerPanel);
                }
                if (myStateListPicker != null && myStateListPicker.getStateList() != null
                        && myType == myStateListPicker.getStateList().getType()) {
                    assert myStateListPickerPanel != null;
                    myEditorPanel.addTab(myStateListPickerPanel);
                }
                myPreviewPanel.add(myEditorPanel, EDITOR);
            }
            CardLayout layout = (CardLayout) myPreviewPanel.getLayout();
            layout.show(myPreviewPanel, EDITOR);
        }

        private void updateReferenceSwatchIcon() {
            SwatchComponent.SwatchIcon icon = getSwatchIcon(myReferenceComponent.getValueText());
            if (icon instanceof SwatchComponent.ColorIcon) {
                SwatchComponent.ColorIcon colorIcon = (SwatchComponent.ColorIcon) icon;
                myReferenceComponent.setWarning(ColorUtils.getContrastWarningMessage(
                        myContrastColorsWithDescription, colorIcon.getColor(), myIsBackgroundColor));
            } else {
                myReferenceComponent.setWarning(null);
            }
            myReferenceComponent.setSwatchIcon(icon);
            myReferenceComponent.repaint();
        }

        private void showNoPreview() {
            if (myNoPreviewComponent == null) {
                myNoPreviewComponent = new JLabel("No Preview");
                myNoPreviewComponent.setHorizontalAlignment(SwingConstants.CENTER);
                myNoPreviewComponent.setVerticalAlignment(SwingConstants.CENTER);
                myPreviewPanel.add(myNoPreviewComponent, NONE);
            }

            CardLayout layout = (CardLayout) myPreviewPanel.getLayout();
            layout.show(myPreviewPanel, NONE);
        }

        private void showHtml(String doc) {
            if (myHtmlTextArea == null) {
                myHtmlTextArea = new JTextPane();
                myHtmlTextArea.setEditable(false);
                myHtmlTextArea.setContentType(UIUtil.HTML_MIME);
                myHtmlTextArea.setMargin(JBUI.insets(8, 8, 8, 8));
                JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myHtmlTextArea, true);
                myPreviewPanel.add(scrollPane, TEXT);
            }

            myHtmlTextArea.setText(doc);
            HtmlBuilderHelper.fixFontStyles(myHtmlTextArea);
            myHtmlTextArea.getCaret().setDot(0);

            CardLayout layout = (CardLayout) myPreviewPanel.getLayout();
            layout.show(myPreviewPanel, TEXT);
        }

        @NotNull
        public ResourceType getType() {
            return myType;
        }

        /** Determines if the given item is something we can edit (vs just select) */
        private boolean allowEditing(@Nullable ResourceChooserItem item) {
            if (item == null) {
                return false;
            }

            // Determine whether we allow editing. We allow editing if
            //  (1) it's a project item, and
            //  (2) it's a "complex" type (e.g. a state list) or a color.
            //  (3) it's not an attribute
            //
            // You can't edit bitmaps, framework resources, etc.
            if (item.isFramework() || item.isAttr()) {
                return false;
            }

            // Checking for is-framework isn't enough: we don't let you edit resources
            // from libraries (such as appcompat) either
            ProjectResourceRepository repository = ProjectResourceRepository.getProjectResources(myModule, true);
            assert repository != null;
            if (!repository.hasResourceItem(item.getType(), item.getName())) {
                return false;
            }

            ResourceType type = item.getType();
            if (type == ResourceType.COLOR) {
                // All (project) colors can be edited
                return true;
            }

            // Some drawables can be edited (e.g. state lists). No other resource types for now.
            if (type == ResourceType.DRAWABLE) {
                if (item.isReference()) {
                    return true;
                }
                Project project = myModule.getProject();
                ResourceHelper.StateList stateList = ResourceHelper.resolveStateList(getResourceResolver(),
                        item.getResourceValue(), project);
                if (stateList != null) { // if this is not a state list, it may be just a normal color
                    return true;
                } else {
                    return false;
                }
            }

            return false;
        }

        public void showPreview(@Nullable ResourceChooserItem item) {
            showPreview(item, allowEditing(item));
        }

        public void showPreview(@Nullable ResourceChooserItem element, boolean allowEditor) {
            // TODO maybe have a element of "new Color" and "new StateList"

            if (element != null && element.isAttr()) {
                ResourceUrl url = ResourceUrl.parse(element.getResourceUrl());
                assert url != null;
                String doc = AndroidJavaDocRenderer.render(myModule, getConfiguration(), url);
                showHtml(doc);
                return;
            }

            if (allowEditor) {
                if ((myType == ResourceType.COLOR || myType == ResourceType.DRAWABLE
                        || myType == ResourceType.MIPMAP) && element != null) {
                    ProjectResourceRepository repository = ProjectResourceRepository.getProjectResources(myModule,
                            true);
                    assert repository != null;
                    boolean inProject = repository.hasResourceItem(element.getType(), element.getName());
                    if (inProject) {
                        showEditorPanel();
                        myEditorPanel.setResourceName(element.getName());
                        ResourceItem defaultValue = setupVariants();
                        if (defaultValue != null) {
                            editResourceItem(defaultValue);
                            return;
                        }
                    }
                }

                ensurePickersInitialized();
                if (element == null && myStateListPicker != null && myStateListPicker.getStateList() != null
                        && myStateListPicker.getStateList().getType() == myType) {
                    assert myStateListPickerPanel != null;
                    showEditorPanel();
                    myEditorPanel.setSelectedTab(myStateListPickerPanel);
                    return;
                }

                if (element == null && myType == ResourceType.COLOR) {
                    assert myColorPickerPanel != null;
                    showEditorPanel();
                    myEditorPanel.setSelectedTab(myColorPickerPanel);
                    return;
                }
            }

            if (element == null || element.getType() == ResourceType.ID) {
                showNoPreview();
                return;
            }

            switch (myType) {
            case DRAWABLE:
            case MIPMAP:
            case COLOR:
                showDrawableItem(element);
                return;
            case STRING:
            case DIMEN:
            case BOOL:
                // TODO which other ones?
                showTableItem(element);
                return;
            default:
                // fall through to just do plain doc-pane rendering
            }

            String doc = AndroidJavaDocRenderer.render(myModule, getConfiguration(), element.getType(),
                    element.getName(), element.isFramework());
            if (doc != null) {
                showHtml(doc);
            } else {
                showNoPreview();
            }
        }

        public void editResourceItem(@NotNull ResourceItem selected) {
            ResourceValue resourceValue = selected.getResourceValue(false);
            assert resourceValue != null;

            @NotNull
            ResourceEditorTab resourceEditorTab;
            String value = resourceValue.getValue();
            if (value != null && (value.startsWith(PREFIX_RESOURCE_REF) || value.startsWith(PREFIX_THEME_REF))) {
                myReferenceComponent.setValueText(value);
                updateReferenceSwatchIcon();
                resourceEditorTab = myReferencePanel;
            } else {
                ResourceHelper.StateList stateList = ResourceHelper.resolveStateList(getResourceResolver(),
                        resourceValue, myModule.getProject());
                if (stateList != null) { // if this is not a state list, it may be just a normal color
                    ensurePickersInitialized();
                    assert myStateListPickerPanel != null;
                    assert myStateListPicker != null;

                    if (stateList.getType() != myStateListPickerPanel.getLocationSettings().getType()) {
                        Logger.getInstance(ChooseResourceDialog.class)
                                .warn("StateList type mismatch " + stateList.getType() + " "
                                        + myStateListPickerPanel.getLocationSettings().getType());
                        showPreview(getSelectedItem(), false);
                        return;
                    }
                    myStateListPicker.setStateList(stateList);
                    if (myStateListPickerPanel.getFullPanel().getParent() == null) {
                        myEditorPanel.addTab(myStateListPickerPanel);
                    }
                    resourceEditorTab = myStateListPickerPanel;
                } else {
                    Color color = ResourceHelper.parseColor(resourceValue.getValue());
                    if (color != null) { // if invalid color because of user error or a reference to another color
                        ensurePickersInitialized();
                        assert myColorPickerPanel != null;
                        assert myColorPicker != null;
                        myColorPicker.setColor(color);
                        resourceEditorTab = myColorPickerPanel;
                    } else {
                        // we are an actual image, so we need to just display it.
                        showPreview(getSelectedItem(), false);
                        return;
                    }
                }
            }

            myEditorPanel.setSelectedTab(resourceEditorTab);

            setLocationFromResourceItem(selected);
        }

        @Nullable
        public ValidationInfo doValidate() {
            final boolean okActionEnabled;
            ValidationInfo error = null;

            ResourceEditorTab editor = getCurrentResourceEditor();
            if (editor != null) {
                String overwriteResource = "";

                // if name is hidden, then we allow any value
                if (editor.getResourceNameVisibility() == ResourceNameVisibility.FORCE
                        || (editor.getResourceNameVisibility() == ResourceNameVisibility.SHOW
                                && !myEditorPanel.getResourceName().isEmpty())) {
                    ResourceNameValidator validator = editor.getValidator();
                    String enteredName = myEditorPanel.getResourceName();
                    if (validator.doesResourceExist(enteredName)) {
                        ResourceType type = getSelectedPanel().getType();
                        overwriteResource = String.format("Saving this %1$s will override existing resource %2$s.",
                                type.getDisplayName().toLowerCase(Locale.US), enteredName);
                    } else {
                        String errorText = validator.getErrorText(enteredName);
                        if (errorText != null) {
                            error = new ValidationInfo(errorText, myEditorPanel.getResourceNameField());
                        }
                    }

                    // the name of the resource must have changed, lets re-load the variants.
                    if (!overwriteResource.equals(myEditorPanel.getResourceNameMessage())) {
                        ResourceItem defaultResourceItem = setupVariants();
                        if (defaultResourceItem != null) {
                            setLocationFromResourceItem(defaultResourceItem);
                        }
                    }
                }

                if (!overwriteResource.equals(myEditorPanel.getResourceNameMessage())) {
                    myEditorPanel.setResourceNameMessage(overwriteResource);
                }

                if (error == null) {
                    error = editor.doValidate();
                }

                okActionEnabled = error == null;
            } else {
                okActionEnabled = getSelectedItem() != null;
            }

            // Need to always manually update the setOKActionEnabled as the DialogWrapper
            // only updates it if we go from having a error string to not having one
            // or the other way round, but not if the error string state has not changed.
            setOKActionEnabled(okActionEnabled);

            return error;
        }

        private void setLocationFromResourceItem(@NotNull ResourceItem item) {
            VirtualFile file = LocalFileSystem.getInstance().findFileByIoFile(item.getFile());
            assert file != null;
            // TODO as we only show variants that are specific to the folderType, and we have different folderTypes for different Editor tabs, reset does not always work.
            // TODO CreateXmlResourcePanel should show all variants irrespective of folderType and we should have just 1 CreateXmlResourcePanel per EditResourcePanel.
            for (ResourceEditorTab editor : myEditorPanel.getAllTabs()) {
                editor.getLocationSettings().resetFromFile(file, myModule.getProject());
            }
        }

        /**
         * @return the default value for this config, otherwise the first value.
         */
        @Nullable
        private ResourceItem setupVariants() {
            List<ResourceItem> resources = AppResourceRepository.getAppResources(myFacet, true)
                    .getResourceItem(myType, myEditorPanel.getResourceName());
            assert resources != null;
            ResourceItem defaultValue = getConfiguration().getFullConfig().findMatchingConfigurable(resources);
            if (defaultValue == null && !resources.isEmpty()) {
                // we may not have ANY value that works in current config, then just pick the first one
                defaultValue = resources.get(0);
            }
            myEditorPanel.setVariant(resources, defaultValue);
            return defaultValue;
        }

        @Nullable
        public ResourceEditorTab getCurrentResourceEditor() {
            return myEditorPanel != null && myEditorPanel.isVisible() ? myEditorPanel.getSelectedTab() : null;
        }

        private boolean supportsGridMode() {
            return myType == ResourceType.COLOR || myType == ResourceType.DRAWABLE || myType == ResourceType.MIPMAP;
        }

        private void configureList(boolean gridView) {
            if (myList == null) {
                return;
            }
            if (gridView && supportsGridMode()) {
                // Using a DefaultListCellRenderer instead of a SimpleColoredComponent here because we want
                // to use HTML labels in order to handle line breaking with <nobr> and <br> tags
                ListCellRenderer gridRenderer = new DefaultListCellRenderer() {
                    {
                        setHorizontalTextPosition(SwingConstants.CENTER);
                        setVerticalTextPosition(SwingConstants.BOTTOM);
                        setHorizontalAlignment(SwingConstants.CENTER);
                    }

                    private final int CHAR_WIDTH = getFontMetrics(getFont()).charWidth('x'); // it's a monospace font;
                    private final int CHARS_PER_CELL = GRID_CELL_SIZE / CHAR_WIDTH;

                    @Override
                    public Component getListCellRendererComponent(JList list, Object value, int index,
                            boolean isSelected, boolean cellHasFocus) {
                        DefaultListCellRenderer component = (DefaultListCellRenderer) super.getListCellRendererComponent(
                                list, value, index, isSelected, false);

                        final Border border = component.getBorder();
                        component.setBorder(new AbstractBorder() {
                            @Override
                            public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
                                border.paintBorder(c, g, x, y, width, height);
                            }
                        });

                        // TODO show deprecated resources with a strikeout
                        ResourceChooserItem rItem = (ResourceChooserItem) value;
                        setIcon(ChooseResourceDialog.this.getIcon(rItem, GRID_ICON_SIZE, GRID_CHECK_SIZE));
                        String name = rItem.getName();

                        String filter = mySearchField.getText();
                        int match = -1;
                        if (!filter.isEmpty()) {
                            match = StringUtil.indexOfIgnoreCase(name, filter, 0);
                        }

                        int breakPoint = -1;
                        if (name.length() > CHARS_PER_CELL) {
                            breakPoint = name.indexOf('_', CHARS_PER_CELL / 2);
                            if (breakPoint == -1 || breakPoint >= CHARS_PER_CELL
                                    || name.length() - breakPoint >= CHARS_PER_CELL) {
                                breakPoint = CHARS_PER_CELL;
                            } else {
                                breakPoint++;
                            }
                        }

                        if (match != -1 || breakPoint != -1) {
                            HtmlBuilder builder = new HtmlBuilder();
                            builder.openHtmlBody();
                            builder.beginNoBr();
                            if (match == -1) {
                                // Just a breakpoint:
                                builder.add(name, 0, breakPoint);
                                builder.newline();
                                builder.add(name, breakPoint, name.length());
                            } else if (breakPoint == -1) {
                                // Just a match
                                builder.add(name, 0, match);
                                builder.beginColor(JBColor.BLUE);
                                builder.beginBold();
                                builder.add(name, match, match + filter.length());
                                builder.endBold();
                                builder.endColor();
                                builder.add(name, match + filter.length(), name.length());
                            } else {
                                // Both:
                                if (breakPoint < match) {
                                    builder.add(name, 0, breakPoint);
                                    builder.newline();
                                    builder.add(name, breakPoint, match);
                                } else {
                                    builder.add(name, 0, match);
                                }
                                builder.beginColor(JBColor.BLUE);
                                builder.beginBold();
                                builder.add(name, match, match + filter.length());
                                builder.endBold();
                                builder.endColor();
                                // We don't show a breakpoint inside the matched region, we'll
                                // put it right after if that's where it appeared
                                if (breakPoint >= match && breakPoint < match + filter.length()) {
                                    builder.newline();
                                    builder.add(name, match + filter.length(), name.length());
                                } else if (match < breakPoint) {
                                    builder.add(name, match + filter.length(), breakPoint);
                                    builder.newline();
                                    builder.add(name, breakPoint, name.length());
                                }
                            }
                            builder.endNoBr();
                            builder.closeHtmlBody();
                            component.setText(builder.getHtml());
                        }
                        return component;
                    }
                };
                myList.setFixedCellWidth(GRID_CELL_SIZE);
                myList.setFixedCellHeight(GRID_CELL_SIZE);
                //noinspection unchecked
                myList.setCellRenderer(gridRenderer);
                myList.setLayoutOrientation(JList.HORIZONTAL_WRAP);
            } else {
                ColoredListCellRenderer<ResourceChooserItem> listRenderer = new ColoredListCellRenderer<ResourceChooserItem>() {
                    @Override
                    protected void customizeCellRenderer(@NotNull JList list, ResourceChooserItem value, int index,
                            boolean selected, boolean hasFocus) {
                        if (!hasFocus) {
                            setBorder(new AbstractBorder() {
                                @Override
                                public void paintBorder(Component c, Graphics g, int x, int y, int width,
                                        int height) {
                                    Color oldColor = g.getColor();
                                    g.setColor(LIST_DIVIDER_COLOR);
                                    int thickness = 1;
                                    g.fillRect(x, y + height - thickness, width, thickness);
                                    g.setColor(oldColor);
                                }
                            });
                        } else {
                            // Delegate, but mess with insets!
                            final Border border = getBorder();
                            setBorder(new AbstractBorder() {
                                @Override
                                public void paintBorder(Component c, Graphics g, int x, int y, int width,
                                        int height) {
                                    border.paintBorder(c, g, x, y, width, height);
                                }
                            });
                        }
                        setIpad(LIST_PADDING);

                        // TODO: show deprecated resources with a strikeout
                        // TODO: show private resources in a different way (and offer copy to project)
                        setIcon(ChooseResourceDialog.this.getIcon(value, LIST_ICON_SIZE, LIST_CHECK_SIZE));

                        String string = value.toString();
                        String filter = mySearchField.getText();
                        if (!filter.isEmpty()) {
                            int match = StringUtil.indexOfIgnoreCase(string, filter, 0);
                            if (match != -1) {
                                append(string.substring(0, match));
                                append(string.substring(match, match + filter.length()), SEARCH_MATCH_ATTRIBUTES);
                                append(string.substring(match + filter.length()));
                            } else {
                                append(string);
                            }
                        } else {
                            append(string);
                        }
                    }
                };
                // we use ANY fixed value here, as the width will stretch anyway, but we don't want the list to have to calculate it.
                myList.setFixedCellWidth(10);
                myList.setFixedCellHeight(LIST_CELL_HEIGHT);
                //noinspection unchecked
                myList.setCellRenderer(listRenderer);
                myList.setLayoutOrientation(JList.VERTICAL);
            }
        }

        private void showNewResource(@NotNull ResourceEditorTab tab) {
            setSelectedItem(null);
            showEditorPanel();
            myEditorPanel.setSelectedTab(tab);
            myEditorPanel.setResourceName("");
            for (ResourceEditorTab editor : myEditorPanel.getAllTabs()) {
                editor.getLocationSettings().resetToDefault();
            }
        }

        /**
         * @param value can also be an instance of {@link ItemResourceValue} for ?attr/ values
         */
        private boolean select(@NotNull ResourceValue value) {
            boolean isAttr = value instanceof ItemResourceValue;
            for (ResourceChooserGroup group : myGroups) {
                for (ResourceChooserItem item : group.getItems()) {
                    if (isAttr) {
                        if (item.isAttr() && ((ItemResourceValue) value).isFrameworkAttr() == item.isFramework()
                                && value.getName().equals(item.getName())) {
                            setSelectedItem(item);
                            return true;
                        }
                    } else {
                        if (!item.isAttr() && value.isFramework() == item.isFramework()
                                && value.getName().equals(item.getName())) {
                            setSelectedItem(item);
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        public void expandAll() {
            if (myList != null) {
                myList.expandAll();
            }
        }

        public ResourceChooserItem getSelectedItem() {
            if (myList != null) {
                return myList.getSelectedElement();
            } else if (myTable != null) {
                int index = myTable.getSelectionModel().getLeadSelectionIndex();
                if (index != -1) {
                    Object selected = myTable.getValueAt(index, 0);
                    if (selected instanceof ResourceChooserItem) {
                        return (ResourceChooserItem) selected;
                    }
                }
            }
            return null;
        }

        public void setSelectedItem(@Nullable ResourceChooserItem item) {
            if (myList != null) {
                myList.setSelectedElement(item);
            } else if (myTable != null) {
                TableModel model = myTable.getModel();
                for (int row = 0, rowCount = model.getRowCount(); row < rowCount; row++) {
                    Object object = model.getValueAt(row, 0);
                    if (object == item) {
                        myTable.getSelectionModel().setSelectionInterval(row, row);
                        Rectangle cellRect = myTable.getCellRect(row, 0, true);
                        myTable.scrollRectToVisible(cellRect);
                        break;
                    }
                }
            }
        }

        @Nullable
        public String getValueForLivePreview() {
            if (myType == ResourceType.COLOR && myColorPicker != null && myColorPicker.isShowing()) {
                return ResourceHelper.colorToString(myColorPicker.getColor());
            }
            ResourceChooserItem item = getSelectedItem();
            return item != null ? item.getResourceUrl() : null;
        }
    }

    private class MyAddNewResourceLabelListener extends MouseAdapter implements KeyListener {
        @Override
        public void mouseClicked(MouseEvent e) {
            handle(e);
        }

        public void handle(InputEvent e) {
            DefaultActionGroup group = new DefaultActionGroup();
            Component source = (Component) e.getSource();
            DataContext context = SimpleDataContext.getSimpleContext(PlatformDataKeys.CONTEXT_COMPONENT.getName(),
                    source, null);

            Action[] actions = getCreateActions();
            for (final Action action : actions) {
                final AnAction anAction = new AnAction() {
                    @Override
                    public void actionPerformed(AnActionEvent e) {
                        action.actionPerformed(new ActionEvent(source, 0, ""));
                    }

                    @Override
                    public void update(AnActionEvent e) {
                        Presentation presentation = e.getPresentation();
                        String name = (String) action.getValue(Action.NAME);
                        if (name != null) {
                            presentation.setText(name);
                        }
                        super.update(e);
                    }
                };

                if (actions.length == 1) {
                    // One action: perform it immediately
                    AnActionEvent actionEvent = AnActionEvent.createFromInputEvent(e,
                            ChooseResourceDialog.class.getSimpleName(), new Presentation(), context);
                    anAction.actionPerformed(actionEvent);
                    return;
                } else {
                    group.add(anAction);
                }
            }

            // Post menu
            JBPopupFactory factory = JBPopupFactory.getInstance();
            ListPopup popup = factory.createActionGroupPopup(null, group, context,
                    JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, true, null, 10);
            popup.showUnderneathOf(source);
        }

        @Override
        public void keyTyped(KeyEvent e) {

        }

        @Override
        public void keyPressed(KeyEvent e) {
            if (e.getModifiers() == 0 && e.getKeyCode() == KeyEvent.VK_SPACE) {
                handle(e);
            }
        }

        @Override
        public void keyReleased(KeyEvent e) {

        }
    }
}