Java tutorial
/* * Copyright (C) 2015 The Android Open Source Project * * 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.ResourceValue; import com.android.ide.common.resources.ResourceRepository; 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.idea.configurations.Configuration; import com.android.tools.idea.editors.theme.ThemeEditorUtils; 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.rendering.RenderTask; import com.android.tools.idea.res.ResourceHelper; import com.android.tools.swing.ui.SwatchComponent; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.intellij.icons.AllIcons; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.editor.event.DocumentAdapter; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.JBPopupMenu; import com.intellij.openapi.ui.ValidationInfo; import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.components.JBLabel; import com.intellij.util.ui.JBUI; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.android.sdk.AndroidTargetData; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.util.EnumSet; import java.util.List; public class StateListPicker extends JPanel { private static final EnumSet<ResourceType> DIMENSIONS_ONLY = EnumSet.of(ResourceType.DIMEN); private final Module myModule; private final Configuration myConfiguration; private @Nullable ResourceHelper.StateList myStateList; private List<StateComponent> myStateComponents; private final @NotNull RenderTask myRenderTask; private boolean myIsBackgroundStateList; /** If not empty, it contains colors to compare with the state list items colors to find out any possible contrast problems, * and descriptions to use in case there is a problem. */ private @NotNull ImmutableMap<String, Color> myContrastColorsWithDescription = ImmutableMap.of(); public StateListPicker(@Nullable ResourceHelper.StateList stateList, @NotNull Module module, @NotNull Configuration configuration) { myModule = module; myConfiguration = configuration; myRenderTask = DrawableRendererEditor.configureRenderTask(module, configuration); setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); if (stateList != null) { setStateList(stateList); } } public void setStateList(@NotNull ResourceHelper.StateList stateList) { myStateList = stateList; myStateComponents = Lists.newArrayListWithCapacity(myStateList.getStates().size()); removeAll(); if (myStateList.getStates().isEmpty()) { add(new JLabel("Empty " + myStateList.getType() + " StateList")); } for (final ResourceHelper.StateListState state : myStateList.getStates()) { final StateComponent stateComponent = createStateComponent(state); add(stateComponent); } revalidate(); repaint(); } @NotNull private StateComponent createStateComponent(@NotNull ResourceHelper.StateListState state) { final StateComponent stateComponent = new StateComponent(myModule.getProject()); myStateComponents.add(stateComponent); String stateValue = state.getValue(); String alphaValue = state.getAlpha(); stateComponent.addValueActionListener(new ValueActionListener(state, stateComponent)); stateComponent.addAlphaActionListener(new AlphaActionListener(state, stateComponent)); stateComponent.setValueText(stateValue); stateComponent.setAlphaValue(alphaValue); stateComponent.setAlphaVisible(!StringUtil.isEmpty(alphaValue)); stateComponent.setNameText(state.getDescription()); stateComponent.setComponentPopupMenu(createAlphaPopupMenu(state, stateComponent)); AndroidFacet facet = AndroidFacet.getInstance(myModule); assert facet != null; assert myStateList != null; List<String> completionStrings = ResourceHelper.getCompletionFromTypes(facet, myStateList.getFolderType() == ResourceFolderType.COLOR ? GraphicalResourceRendererEditor.COLORS_ONLY : GraphicalResourceRendererEditor.DRAWABLES_ONLY); stateComponent.getResourceComponent().setCompletionStrings(completionStrings); stateComponent.getAlphaComponent() .setCompletionStrings(ResourceHelper.getCompletionFromTypes(facet, DIMENSIONS_ONLY)); return stateComponent; } @NotNull private JBPopupMenu createAlphaPopupMenu(@NotNull final ResourceHelper.StateListState state, @NotNull final StateComponent stateComponent) { JBPopupMenu popupMenu = new JBPopupMenu(); final JMenuItem deleteAlpha = new JMenuItem("Delete alpha"); popupMenu.add(deleteAlpha); deleteAlpha.setVisible(!StringUtil.isEmpty(state.getAlpha())); final JMenuItem createAlpha = new JMenuItem("Create alpha"); popupMenu.add(createAlpha); createAlpha.setVisible(StringUtil.isEmpty(state.getAlpha())); deleteAlpha.addActionListener(e -> { stateComponent.getAlphaComponent().setVisible(false); stateComponent.setAlphaValue(null); state.setAlpha(null); updateIcon(stateComponent); deleteAlpha.setVisible(false); createAlpha.setVisible(true); }); createAlpha.addActionListener(e -> { AlphaActionListener listener = stateComponent.getAlphaActionListener(); if (listener == null) { return; } listener.actionPerformed( new ActionEvent(stateComponent.getAlphaComponent(), ActionEvent.ACTION_PERFORMED, null)); if (!StringUtil.isEmpty(state.getAlpha())) { stateComponent.getAlphaComponent().setVisible(true); createAlpha.setVisible(false); deleteAlpha.setVisible(true); } }); return popupMenu; } /** * Returns a {@link ValidationInfo} in the case one of the state list state has a value that does not resolve to a valid resource, * or a value that is a private framework value. or if one of the state list component requires an API level higher than minApi. */ @Nullable /*if there is no error*/ public ValidationInfo doValidate(int minApi) { IAndroidTarget target = myConfiguration.getRealTarget(); assert target != null; final AndroidTargetData androidTargetData = AndroidTargetData.getTargetData(target, myModule); assert androidTargetData != null; ResourceRepository frameworkResources = myConfiguration.getFrameworkResources(); assert frameworkResources != null; for (StateComponent component : myStateComponents) { ValidationInfo error = component.doValidate(minApi, androidTargetData); if (error != null) { return error; } } return null; } public void setContrastParameters(@NotNull ImmutableMap<String, Color> contrastColorsWithDescription, boolean isBackgroundStateList) { myContrastColorsWithDescription = contrastColorsWithDescription; myIsBackgroundStateList = isBackgroundStateList; } @Nullable public ResourceHelper.StateList getStateList() { return myStateList; } class ValueActionListener extends DocumentAdapter implements ActionListener { private final ResourceHelper.StateListState myState; private final StateComponent myComponent; public ValueActionListener(ResourceHelper.StateListState state, StateComponent stateComponent) { myState = state; myComponent = stateComponent; } /** * @see AlphaActionListener#documentChanged(DocumentEvent) */ @Override public void documentChanged(DocumentEvent e) { myState.setValue(myComponent.getResourceValue()); // 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(() -> { updateIcon(myComponent); myComponent.repaint(); }, ModalityState.any()); } @Override public void actionPerformed(ActionEvent e) { ResourceComponent resourceComponent = myComponent.getResourceComponent(); final String attributeValue = resourceComponent.getValueText(); ResourceUrl attributeValueUrl = ResourceUrl.parse(attributeValue); boolean isFrameworkValue = attributeValueUrl != null && attributeValueUrl.framework; String nameSuggestion = attributeValueUrl != null ? attributeValueUrl.name : attributeValue; EnumSet<ResourceType> allowedTypes; assert myStateList != null; if (myStateList.getFolderType() == ResourceFolderType.COLOR) { allowedTypes = GraphicalResourceRendererEditor.COLORS_ONLY; } else { allowedTypes = GraphicalResourceRendererEditor.DRAWABLES_ONLY; } ChooseResourceDialog.ResourceNameVisibility resourceNameVisibility = ChooseResourceDialog.ResourceNameVisibility.FORCE; if (nameSuggestion.startsWith("#")) { nameSuggestion = null; resourceNameVisibility = ChooseResourceDialog.ResourceNameVisibility.SHOW; } ChooseResourceDialog dialog = ChooseResourceDialog.builder().setModule(myModule).setTypes(allowedTypes) .setCurrentValue(attributeValue).setIsFrameworkValue(isFrameworkValue) .setResourceNameVisibility(resourceNameVisibility).setResourceNameSuggestion(nameSuggestion) .setConfiguration(myConfiguration).build(); if (!myContrastColorsWithDescription.isEmpty()) { dialog.setContrastParameters(myContrastColorsWithDescription, myIsBackgroundStateList, !myStateList.getDisabledStates().contains(myState)); } dialog.show(); if (dialog.isOK()) { String resourceName = dialog.getResourceName(); myComponent.setValueText(resourceName); // If a resource was overridden, it may affect several states of the state list. // Thus we need to repaint all components. repaintAllComponents(); } } } private class AlphaActionListener extends DocumentAdapter implements ActionListener { private final ResourceHelper.StateListState myState; private final StateComponent myComponent; public AlphaActionListener(ResourceHelper.StateListState state, StateComponent stateComponent) { myState = state; myComponent = stateComponent; } /** * @see ValueActionListener#documentChanged(DocumentEvent) */ @Override public void documentChanged(DocumentEvent e) { myState.setAlpha(myComponent.getAlphaValue()); // 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(() -> { updateIcon(myComponent); myComponent.repaint(); }, ModalityState.any()); } @Override public void actionPerformed(ActionEvent e) { SwatchComponent source = myComponent.getAlphaComponent(); String itemValue = source.getText(); ResourceResolver resourceResolver = myConfiguration.getResourceResolver(); assert resourceResolver != null; ResourceValue resValue = resourceResolver.findResValue(itemValue, false); String resolvedResource = resValue != null ? resourceResolver.resolveResValue(resValue).getName() : itemValue; ChooseResourceDialog dialog = ChooseResourceDialog.builder().setModule(myModule) .setTypes(DIMENSIONS_ONLY).setCurrentValue(resolvedResource).setConfiguration(myConfiguration) .build(); dialog.show(); if (dialog.isOK()) { String resourceName = dialog.getResourceName(); myState.setAlpha(resourceName); myComponent.setAlphaValue(resourceName); // If a resource was overridden, it may affect several states of the state list. // Thus we need to repaint all components. repaintAllComponents(); } } } private void updateIcon(@NotNull StateComponent component) { component.showAlphaError(false); ResourceResolver resourceResolver = myConfiguration.getResourceResolver(); assert resourceResolver != null; component.setValueIcon(getSwatchIcon(component.getResourceValue(), resourceResolver, myRenderTask)); String alphaValue = component.getAlphaValue(); if (!StringUtil.isEmpty(alphaValue)) { try { float alpha = Float.parseFloat(ResourceHelper.resolveStringValue(resourceResolver, alphaValue)); Font iconFont = JBUI.Fonts.smallFont().asBold(); component.getAlphaComponent() .setSwatchIcon(new SwatchComponent.TextIcon(String.format("%.2f", alpha), iconFont)); } catch (NumberFormatException e) { component.showAlphaError(true); component.getAlphaComponent().setSwatchIcon(SwatchComponent.WARNING_ICON); } } else { Font iconFont = JBUI.Fonts.smallFont().asBold(); component.getAlphaComponent().setSwatchIcon(new SwatchComponent.TextIcon("1.00", iconFont)); } } @NotNull public static SwatchComponent.SwatchIcon getSwatchIcon(@Nullable String resourceName, @NotNull ResourceResolver resourceResolver, @NotNull RenderTask renderTask) { ResourceValue resValue = resourceResolver.findResValue(resourceName, false); resValue = resourceResolver.resolveResValue(resValue); if (resValue == null || resValue.getResourceType() == ResourceType.COLOR) { final List<Color> colors = ResourceHelper.resolveMultipleColors(resourceResolver, resValue, renderTask.getModule().getProject()); SwatchComponent.SwatchIcon icon; if (colors.isEmpty()) { Color colorValue = ResourceHelper.parseColor(resourceName); if (colorValue != null) { icon = new SwatchComponent.ColorIcon(colorValue); } else { icon = SwatchComponent.WARNING_ICON; } } else { icon = new SwatchComponent.ColorIcon(Iterables.getLast(colors)); icon.setIsStack(colors.size() > 1); } return icon; } List<BufferedImage> images = renderTask.renderDrawableAllStates(resValue); SwatchComponent.SwatchIcon icon; if (images.isEmpty()) { icon = SwatchComponent.WARNING_ICON; } else { icon = new SwatchComponent.SquareImageIcon(Iterables.getLast(images)); icon.setIsStack(images.size() > 1); } return icon; } private void repaintAllComponents() { for (StateComponent component : myStateComponents) { updateIcon(component); component.repaint(); } } private class StateComponent extends Box { private final ResourceComponent myResourceComponent; private final SwatchComponent myAlphaComponent; private final JBLabel myAlphaErrorLabel; private AlphaActionListener myAlphaActionListener; public StateComponent(@NotNull Project project) { super(BoxLayout.PAGE_AXIS); myResourceComponent = new ResourceComponent(project, true); add(myResourceComponent); myAlphaComponent = new SwatchComponent(project, true); add(myAlphaComponent); Font font = StateListPicker.this.getFont(); setFont(ThemeEditorUtils.scaleFontForAttribute(font)); Box alphaErrorComponent = new Box(BoxLayout.LINE_AXIS); myAlphaErrorLabel = new JBLabel("This value does not resolve to a floating-point number.", AllIcons.General.BalloonWarning, SwingConstants.LEADING); myAlphaErrorLabel.setVisible(false); alphaErrorComponent.add(myAlphaErrorLabel); alphaErrorComponent.add(Box.createHorizontalGlue()); add(alphaErrorComponent); } @NotNull public ResourceComponent getResourceComponent() { return myResourceComponent; } @NotNull public SwatchComponent getAlphaComponent() { return myAlphaComponent; } public void showAlphaError(boolean hasError) { myAlphaErrorLabel.setVisible(hasError); myAlphaComponent.setWarningBorder(hasError); } public void setNameText(@NotNull String name) { myResourceComponent.setNameText(name); } public void setValueText(@NotNull String value) { myResourceComponent.setValueText(value); updateIcon(this); } public void setAlphaValue(@Nullable String alphaValue) { myAlphaComponent.setText(Strings.nullToEmpty(alphaValue)); } public void setAlphaVisible(boolean isVisible) { myAlphaComponent.setVisible(isVisible); } public void setValueIcon(@NotNull SwatchComponent.SwatchIcon icon) { myResourceComponent.setSwatchIcon(icon); } @NotNull public String getResourceValue() { return myResourceComponent.getValueText(); } @Nullable public String getAlphaValue() { return myAlphaComponent.getText(); } public void addValueActionListener(@NotNull ValueActionListener listener) { myResourceComponent.addSwatchListener(listener); myResourceComponent.addTextDocumentListener(listener); } public void addAlphaActionListener(@NotNull AlphaActionListener listener) { myAlphaComponent.addSwatchListener(listener); myAlphaComponent.addTextDocumentListener(listener); myAlphaActionListener = listener; } @Nullable public AlphaActionListener getAlphaActionListener() { return myAlphaActionListener; } @Override public void setComponentPopupMenu(JPopupMenu popup) { super.setComponentPopupMenu(popup); myResourceComponent.setComponentPopupMenu(popup); myAlphaComponent.setComponentPopupMenu(popup); } @Override public void setFont(Font font) { super.setFont(font); if (myResourceComponent != null) { myResourceComponent.setFont(font); } if (myAlphaComponent != null) { myAlphaComponent.setFont(font); } } @Nullable /*if there is no error*/ public ValidationInfo doValidate(int minApi, @NotNull AndroidTargetData androidTargetData) { ValidationInfo error = getResourceComponent().doValidate(minApi, androidTargetData); if (error == null && getAlphaValue() != null) { error = getAlphaComponent().doValidate(minApi, androidTargetData); } return error; } } }