| /* |
| * Copyright 2000-2012 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.android.designer.propertyTable; |
| |
| import com.android.SdkConstants; |
| import com.android.resources.ResourceType; |
| import com.android.tools.idea.rendering.ResourceNameValidator; |
| import com.android.tools.lint.detector.api.LintUtils; |
| import com.intellij.android.designer.model.IdManager; |
| import com.intellij.android.designer.model.RadModelBuilder; |
| import com.intellij.android.designer.model.RadViewComponent; |
| import com.intellij.android.designer.propertyTable.editors.ResourceEditor; |
| import com.intellij.designer.model.PropertiesContainer; |
| import com.intellij.designer.model.Property; |
| import com.intellij.designer.model.PropertyContext; |
| import com.intellij.designer.propertyTable.InplaceContext; |
| import com.intellij.designer.propertyTable.PropertyEditor; |
| import com.intellij.designer.propertyTable.PropertyRenderer; |
| import com.intellij.designer.propertyTable.renderers.LabelPropertyRenderer; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.DialogBuilder; |
| import com.intellij.openapi.ui.DialogWrapper; |
| import com.intellij.openapi.ui.Messages; |
| import com.intellij.psi.xml.XmlAttribute; |
| import com.intellij.psi.xml.XmlAttributeValue; |
| import com.intellij.psi.xml.XmlTag; |
| import com.intellij.refactoring.rename.RenameProcessor; |
| import com.intellij.ui.components.JBCheckBox; |
| import com.intellij.usageView.UsageInfo; |
| import org.jetbrains.android.dom.attrs.AttributeDefinition; |
| import org.jetbrains.android.dom.attrs.AttributeFormat; |
| 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.util.Arrays; |
| import java.util.List; |
| import java.util.Set; |
| |
| import static com.android.SdkConstants.*; |
| |
| /** |
| * Customized renderer and property editors for the ID property |
| */ |
| public class IdProperty extends AttributeProperty { |
| private static final int REFACTOR_ASK = 0; |
| private static final int REFACTOR_NO = 1; |
| private static final int REFACTOR_YES = 2; |
| private static int ourRefactoringChoice = REFACTOR_ASK; |
| |
| public static final Property INSTANCE = new IdProperty(); |
| |
| private IdProperty() { |
| this(ATTR_ID, new AttributeDefinition(ATTR_ID, null, Arrays.asList(AttributeFormat.Reference))); |
| setImportant(true); |
| } |
| |
| public IdProperty(@NotNull String name, @NotNull AttributeDefinition definition) { |
| super(name, definition); |
| } |
| |
| public IdProperty(@Nullable Property parent, @NotNull String name, @NotNull AttributeDefinition definition) { |
| super(parent, name, definition); |
| } |
| |
| @Override |
| public Property<RadViewComponent> createForNewPresentation(@Nullable Property parent, @NotNull String name) { |
| return new IdProperty(parent, name, myDefinition); |
| } |
| |
| @Override |
| public void setValue(@NotNull final RadViewComponent component, final Object value) throws Exception { |
| final String newId = value != null ? value.toString() : ""; |
| |
| final String oldId = component.getId(); |
| |
| if (ourRefactoringChoice != REFACTOR_NO |
| && oldId != null |
| && !oldId.isEmpty() |
| && !newId.isEmpty() |
| && !oldId.equals(newId) |
| && component.getTag().isValid()) { |
| // Offer rename refactoring? |
| XmlTag tag = component.getTag(); |
| XmlAttribute attribute = tag.getAttribute(SdkConstants.ATTR_ID, SdkConstants.ANDROID_URI); |
| if (attribute != null) { |
| Module module = RadModelBuilder.getModule(component); |
| if (module != null) { |
| XmlAttributeValue valueElement = attribute.getValueElement(); |
| if (valueElement != null && valueElement.isValid()) { |
| final Project project = module.getProject(); |
| // Exact replace only, no comment/text occurrence changes since it is non-interactive |
| RenameProcessor processor = new RenameProcessor(project, valueElement, newId, false /*comments*/, false /*text*/); |
| processor.setPreviewUsages(false); |
| // Do a quick usage search to see if we need to ask about renaming |
| UsageInfo[] usages = processor.findUsages(); |
| if (usages.length > 0) { |
| int choice = ourRefactoringChoice; |
| if (choice == REFACTOR_ASK) { |
| DialogBuilder builder = new DialogBuilder(project); |
| builder.setTitle("Update Usages?"); |
| JPanel panel = new JPanel(new BorderLayout()); // UGH! |
| JLabel label = new JLabel("<html>" + |
| "Update usages as well?<br>" + |
| "This will update all XML references and Java R field references.<br>" + |
| "<br>" + |
| "</html>"); |
| panel.add(label, BorderLayout.CENTER); |
| JBCheckBox checkBox = new JBCheckBox("Don't ask again during this session"); |
| panel.add(checkBox, BorderLayout.SOUTH); |
| builder.setCenterPanel(panel); |
| builder.setDimensionServiceKey("idPropertyDimension"); |
| builder.removeAllActions(); |
| |
| DialogBuilder.CustomizableAction yesAction = builder.addOkAction(); |
| yesAction.setText(Messages.YES_BUTTON); |
| |
| builder.addActionDescriptor(new DialogBuilder.ActionDescriptor() { |
| @Override |
| public Action getAction(final DialogWrapper dialogWrapper) { |
| return new AbstractAction(Messages.NO_BUTTON) { |
| @Override |
| public void actionPerformed(ActionEvent actionEvent) { |
| dialogWrapper.close(DialogWrapper.NEXT_USER_EXIT_CODE); |
| } |
| }; |
| } |
| }); |
| builder.addCancelAction(); |
| int exitCode = builder.show(); |
| |
| choice = exitCode == DialogWrapper.OK_EXIT_CODE ? REFACTOR_YES : |
| exitCode == DialogWrapper.NEXT_USER_EXIT_CODE ? REFACTOR_NO : ourRefactoringChoice; |
| |
| if (!checkBox.isSelected()) { |
| ourRefactoringChoice = REFACTOR_ASK; |
| } else { |
| ourRefactoringChoice = choice; |
| } |
| |
| if (exitCode == DialogWrapper.CANCEL_EXIT_CODE) { |
| return; |
| } |
| } |
| |
| if (choice == REFACTOR_YES) { |
| processor.run(); |
| // Fall through to also set the value in the layout editor property; otherwise we'll be out of sync |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| //noinspection ConstantConditions |
| super.setValue(component, value); |
| } |
| |
| @Override |
| public boolean availableFor(List<PropertiesContainer> components) { |
| return false; |
| } |
| |
| private final PropertyRenderer myRenderer = new IdPropertyRenderer(); |
| private final IdPropertyEditor myEditor = new IdPropertyEditor(); |
| |
| @NotNull |
| @Override |
| public PropertyRenderer getRenderer() { |
| return myRenderer; |
| } |
| |
| @Override |
| public PropertyEditor getEditor() { |
| return myEditor; |
| } |
| |
| /** |
| * Customized such that we can filter out the @+id/@id prefixes |
| * <p> |
| * Also, no need to include a "..." button; you pretty much never want to link your declaration to an existing id |
| */ |
| private static class IdPropertyRenderer extends LabelPropertyRenderer { |
| public IdPropertyRenderer() { |
| super(null); |
| } |
| |
| // Strip the @id/@+id/ prefix |
| @Override |
| @NotNull |
| public JComponent getComponent(@Nullable PropertiesContainer container, |
| PropertyContext context, |
| @Nullable Object value, |
| boolean selected, |
| boolean hasFocus) { |
| String s = value != null ? value.toString() : ""; |
| value = IdManager.getIdName(s); |
| return super.getComponent(container, context, value, selected, hasFocus); |
| } |
| } |
| |
| /** |
| * Customized such that we can filter out (and on edit, put back) the @+id/@id prefixes |
| * <p> |
| * Also, no need to include a "..." button; you pretty much never want to link your declaration to an existing id |
| */ |
| private static class IdPropertyEditor extends PropertyEditor { |
| private final JTextField myEditor = new JTextField(); |
| |
| private IdPropertyEditor() { |
| JTextField textField = myEditor; |
| textField.addActionListener(new ActionListener() { |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| fireValueCommitted(false, true); |
| } |
| }); |
| ResourceEditor.selectTextOnFocusGain(textField); |
| } |
| |
| @NotNull |
| @Override |
| public JComponent getComponent(@Nullable PropertiesContainer container, |
| @Nullable PropertyContext context, |
| Object value, |
| @Nullable InplaceContext inplaceContext) { |
| myEditor.setText(value != null ? IdManager.getIdName(value.toString()) : ""); |
| preferredSizeChanged(); |
| return myEditor; |
| } |
| |
| @Nullable |
| @Override |
| public Object getValue() throws Exception { |
| String text = myEditor.getText().trim(); |
| if (!text.startsWith(PREFIX_RESOURCE_REF)) { |
| text = NEW_ID_PREFIX + text; |
| } |
| |
| String name = LintUtils.stripIdPrefix(text); |
| if (name.length() > 0) { |
| ResourceNameValidator validator = ResourceNameValidator.create(false, (Set<String>)null, ResourceType.ID); |
| String errorText = validator.getErrorText(name); |
| if (errorText != null) { |
| throw new IllegalArgumentException(errorText); |
| } |
| } |
| |
| return text; |
| } |
| |
| @Override |
| public void updateUI() { |
| SwingUtilities.updateComponentTreeUI(myEditor); |
| } |
| } |
| } |