| /* |
| * Copyright (C) 2013 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.intellij.android.designer.designSurface; |
| |
| import com.android.tools.idea.configurations.RenderContext; |
| import com.android.tools.idea.configurations.RenderOptionsMenuBuilder; |
| import com.android.tools.idea.rendering.IncludeReference; |
| import com.android.tools.idea.rendering.RefreshRenderAction; |
| import com.android.tools.idea.rendering.RenderResult; |
| import com.android.tools.idea.rendering.SaveScreenshotAction; |
| import com.google.common.collect.Sets; |
| import com.intellij.android.designer.model.RadViewComponent; |
| import com.intellij.android.designer.model.RadViewLayout; |
| import com.intellij.designer.actions.DesignerActionPanel; |
| import com.intellij.designer.designSurface.DesignerEditorPanel; |
| import com.intellij.designer.designSurface.EditableArea; |
| import com.intellij.designer.designSurface.ZoomType; |
| import com.intellij.designer.model.RadComponent; |
| import com.intellij.designer.model.RadLayout; |
| import com.intellij.icons.AllIcons; |
| import com.intellij.openapi.actionSystem.*; |
| import com.intellij.openapi.fileTypes.StdFileTypes; |
| import com.intellij.openapi.keymap.KeymapUtil; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.PsiReference; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.xml.XmlFile; |
| import com.intellij.refactoring.psi.SearchUtils; |
| import com.intellij.ui.IdeBorderFactory; |
| import com.intellij.ui.SideBorder; |
| import com.intellij.util.PsiNavigateUtil; |
| import icons.AndroidIcons; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.awt.*; |
| import java.util.*; |
| import java.util.List; |
| |
| public class AndroidDesignerActionPanel extends DesignerActionPanel { |
| public AndroidDesignerActionPanel(DesignerEditorPanel designer, JComponent shortcuts) { |
| super(designer, shortcuts); |
| |
| DefaultActionGroup popupGroup = getPopupGroup(); |
| popupGroup.addSeparator(); |
| popupGroup.add(new SaveScreenshotAction((AndroidDesignerEditorPanel)myDesigner)); |
| } |
| |
| @Override |
| protected JComponent createToolbar() { |
| JPanel panel = new JPanel(new BorderLayout()); |
| panel.setBorder(IdeBorderFactory.createBorder(SideBorder.BOTTOM)); |
| |
| // Create a layout where there are three toolbars: |
| // +----------------------------------------------------------------------------+ |
| // | Normal toolbar, minus dynamic actions | |
| // +---------------------------------------------+------------------------------+ |
| // | Dynamic layout actions | Zoom actions and file status | |
| // +---------------------------------------------+------------------------------+ |
| |
| ActionManager actionManager = ActionManager.getInstance(); |
| ActionToolbar actionToolbar = actionManager.createActionToolbar(TOOLBAR, getActionGroup(), true); |
| actionToolbar.setLayoutPolicy(ActionToolbar.WRAP_LAYOUT_POLICY); |
| panel.add(actionToolbar.getComponent(), BorderLayout.NORTH); |
| |
| ActionToolbar layoutToolBar = actionManager.createActionToolbar(TOOLBAR, getDynamicActionGroup(), true); |
| layoutToolBar.setLayoutPolicy(ActionToolbar.WRAP_LAYOUT_POLICY); |
| |
| // The default toolbar layout adds too much spacing between the buttons. Switch to mini mode, |
| // but also set a minimum size which will add *some* padding for our 16x16 icons. |
| layoutToolBar.setMiniMode(true); |
| layoutToolBar.setMinimumButtonSize(new Dimension(22, 24)); |
| |
| ActionToolbar zoomToolBar = actionManager.createActionToolbar(TOOLBAR, getRhsActions(), true); |
| JPanel bottom = new JPanel(new BorderLayout()); |
| bottom.add(layoutToolBar.getComponent(), BorderLayout.WEST); |
| RenderContext context = (AndroidDesignerEditorPanel)myDesigner; |
| RenderOptionsMenuBuilder optionsMenuBuilder = RenderOptionsMenuBuilder.create(context); |
| ActionToolbar optionsToolBar = optionsMenuBuilder.addPreferXmlOption().addDeviceFrameOption().addRetinaOption().build(); |
| JPanel combined = new JPanel(new BorderLayout()); |
| combined.add(zoomToolBar.getComponent(), BorderLayout.WEST); |
| combined.add(optionsToolBar.getComponent(), BorderLayout.EAST); |
| bottom.add(combined, BorderLayout.EAST); |
| |
| panel.add(bottom, BorderLayout.SOUTH); |
| |
| return panel; |
| } |
| |
| @Override |
| protected DefaultActionGroup createActionGroup() { |
| // Like super implementation, but only adding the static group; the dynamic |
| // actions are added to a separate toolbar |
| DefaultActionGroup group = new DefaultActionGroup(); |
| group.add(getActionGroup()); |
| return group; |
| } |
| |
| @Override |
| public void update() { |
| // Overriding super; we don't want to dynamically hide or show the toolbar; it's there all the time |
| } |
| |
| private ActionGroup getRhsActions() { |
| DefaultActionGroup group = new DefaultActionGroup(); |
| |
| group.add(new ToggleAction(null, "Zoom to Fit (0)", AndroidIcons.ZoomFit) { |
| @Override |
| public boolean isSelected(AnActionEvent e) { |
| return ((AndroidDesignerEditorPanel)myDesigner).isZoomToFit(); |
| } |
| |
| @Override |
| public void setSelected(AnActionEvent e, boolean state) { |
| myDesigner.zoom(ZoomType.FIT); |
| } |
| }); |
| group.add(new AnAction(null, "Reset Zoom to 100% (1)", AndroidIcons.ZoomActual) { |
| @Override |
| public void actionPerformed(AnActionEvent e) { myDesigner.zoom(ZoomType.ACTUAL); } |
| }); |
| group.addSeparator(); |
| group.add(new AnAction(null, "Zoom In (+)", AndroidIcons.ZoomIn) { |
| @Override |
| public void actionPerformed(AnActionEvent e) { myDesigner.zoom(ZoomType.IN); } |
| }); |
| group.add(new AnAction(null, "Zoom Out (-)", AndroidIcons.ZoomOut) { |
| @Override |
| public void actionPerformed(AnActionEvent e) { myDesigner.zoom(ZoomType.OUT); } |
| }); |
| |
| String description = "Jump to Source"; |
| KeyboardShortcut shortcut = ActionManager.getInstance().getKeyboardShortcut(IdeActions.ACTION_GOTO_DECLARATION); |
| if (shortcut != null) { |
| description += " (" + KeymapUtil.getShortcutText(shortcut) + ")"; |
| } |
| // Use FilesTypes.Text rather than FileTypes.Xml here to avoid having the icon from |
| // the tab bar replicated right below it |
| group.add(new AnAction(null, description, AllIcons.FileTypes.Text) { |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| List<RadComponent> selection = myDesigner.getSurfaceArea().getSelection(); |
| if (!selection.isEmpty()) { |
| RadViewComponent component = (RadViewComponent)selection.get(0); |
| PsiNavigateUtil.navigate(component.getTag()); |
| } |
| } |
| |
| @Override |
| public void update(AnActionEvent e) { |
| List<RadComponent> selection = myDesigner.getSurfaceArea().getSelection(); |
| e.getPresentation().setEnabled(!selection.isEmpty()); |
| } |
| }); |
| |
| AndroidDesignerEditorPanel designer = (AndroidDesignerEditorPanel)myDesigner; |
| group.add(new RefreshRenderAction(designer)); |
| |
| return group; |
| } |
| |
| @Override |
| protected void addSelectionActions(List<RadComponent> selection, DefaultActionGroup group) { |
| if (!selection.isEmpty()) { |
| // Segment the selected components into lists of siblings |
| Map<RadComponent, List<RadComponent>> siblingLists = RadComponent.groupSiblings(selection); |
| for (Map.Entry<RadComponent, List<RadComponent>> entry : siblingLists.entrySet()) { |
| @Nullable RadComponent parent = entry.getKey(); |
| @NotNull List<RadComponent> children = entry.getValue(); |
| |
| if (parent != null) { |
| RadLayout layout = parent.getLayout(); |
| if (layout instanceof RadViewLayout) { |
| ((RadViewLayout) layout).addContainerSelectionActions(myDesigner, group, RadViewComponent.getViewComponents(children)); |
| } else { |
| layout.addSelectionActions(myDesigner, group, getShortcuts(), children); |
| } |
| } else if (selection.size() == 1) { |
| // If you select a root layout, offer selection actions on it as well |
| RadLayout selected = selection.get(0).getLayout(); |
| if (selected instanceof RadViewLayout) { |
| ((RadViewLayout) selected).addContainerSelectionActions(myDesigner, group, RadViewComponent.getViewComponents(children)); |
| } |
| } |
| } |
| |
| for (RadComponent component : selection) { |
| component.addSelectionActions(myDesigner, group, getShortcuts(), Collections.singletonList(component)); |
| } |
| } |
| } |
| |
| private DefaultActionGroup myPopupGroup; |
| private final DefaultActionGroup myDynamicBeforeGroup = new DefaultActionGroup(); |
| private final DefaultActionGroup myDynamicAfterGroup = new DefaultActionGroup(); |
| |
| @Override |
| public ActionGroup getPopupActions(EditableArea area) { |
| ActionGroup superActions = super.getPopupActions(area); |
| |
| if (myPopupGroup == null) { |
| myPopupGroup = new DefaultActionGroup(); |
| myPopupGroup.add(myDynamicBeforeGroup); |
| myPopupGroup.add(superActions); |
| myPopupGroup.add(myDynamicAfterGroup); |
| } |
| |
| if (myDynamicBeforeGroup.getChildrenCount() > 0) { |
| myDynamicBeforeGroup.removeAll(); |
| } |
| if (myDynamicAfterGroup.getChildrenCount() > 0) { |
| myDynamicAfterGroup.removeAll(); |
| } |
| |
| addShowIncludedInContextMenuActions(); |
| addSelectionContextMenuActions(area); |
| |
| return myPopupGroup; |
| } |
| |
| private void addSelectionContextMenuActions(EditableArea area) { |
| // Insert selection actions |
| List<RadComponent> selection = area.getSelection(); |
| if (selection.size() == 1) { |
| // Add specific actions |
| RadComponent selected = selection.get(0); |
| if (selected instanceof RadViewComponent) { |
| RadViewComponent viewComponent = (RadViewComponent)selected; |
| AndroidDesignerEditorPanel designer = (AndroidDesignerEditorPanel)myDesigner; |
| viewComponent.addPopupActions(designer, myDynamicBeforeGroup, myDynamicAfterGroup, designer, selection); |
| |
| // Add in actions from the parents |
| RadComponent current = selected.getParent(); |
| while (current != null && current instanceof RadViewComponent) { |
| DefaultActionGroup container = new DefaultActionGroup(((RadViewComponent)current).getId(), true); |
| if (((RadViewComponent)current).addPopupActions(designer, container, container, designer, selection)) { |
| myDynamicAfterGroup.add(container); |
| } |
| current = current.getParent(); |
| } |
| } |
| } |
| } |
| |
| private void addShowIncludedInContextMenuActions() { |
| DefaultActionGroup targetGroup = myDynamicBeforeGroup; |
| AndroidDesignerEditorPanel designer = (AndroidDesignerEditorPanel)myDesigner; |
| XmlFile xmlFile = designer.getXmlFile(); |
| GlobalSearchScope useScope = GlobalSearchScope.projectScope(designer.getProject()); |
| GlobalSearchScope scope = GlobalSearchScope.getScopeRestrictedByFileTypes(useScope, StdFileTypes.XML); |
| Iterable<PsiReference> allReferences = SearchUtils.findAllReferences(xmlFile, scope); |
| Iterator<PsiReference> iterator = allReferences.iterator(); |
| |
| if (iterator.hasNext()) { |
| boolean needSeparator = false; |
| |
| |
| VirtualFile skip = null; |
| String includingLayout = IncludeReference.getIncludingLayout(designer.getXmlFile()); |
| if (includingLayout != null) { |
| targetGroup.add(new ShowIncludedIn(designer, null, "Hide Including Layout")); |
| targetGroup.addSeparator(); |
| RenderResult lastResult = designer.getLastRenderResult(); |
| if (lastResult != null) { |
| IncludeReference includedWithin = lastResult.getIncludedWithin(); |
| if (includedWithin != null && includedWithin != IncludeReference.NONE) { |
| skip = includedWithin.getFromFile(); |
| } |
| } |
| } |
| |
| Set<VirtualFile> files = Sets.newHashSet(); |
| while (iterator.hasNext()) { |
| PsiReference reference = iterator.next(); |
| PsiElement referenceElement = reference.getElement(); |
| if (referenceElement != null) { |
| PsiFile file = referenceElement.getContainingFile(); |
| if (file != null && file != xmlFile) { |
| VirtualFile fromFile = file.getVirtualFile(); |
| if (fromFile != null && fromFile != skip) { |
| files.add(fromFile); |
| } |
| } |
| } |
| } |
| |
| if (!files.isEmpty()) { |
| List<VirtualFile> sorted = new ArrayList<VirtualFile>(files); |
| Collections.sort(sorted, new Comparator<VirtualFile>() { |
| @Override |
| public int compare(VirtualFile f1, VirtualFile f2) { |
| VirtualFile p1 = f1.getParent(); |
| VirtualFile p2 = f2.getParent(); |
| if (p1 != null && p2 != null) { |
| return p1.getName().compareTo(p2.getName()); |
| } |
| return f1.getName().compareTo(f2.getName()); |
| } |
| }); |
| for (VirtualFile fromFile : sorted) { |
| VirtualFile toFile = designer.getVirtualFile(); |
| IncludeReference includeReference = IncludeReference.create(designer.getModule(), fromFile, toFile); |
| String title = String.format("Show Included In %1$s", includeReference.getFromDisplayName()); |
| targetGroup.add(new ShowIncludedIn(designer, includeReference, title)); |
| needSeparator = true; |
| } |
| |
| if (needSeparator) { |
| targetGroup.addSeparator(); |
| } |
| } |
| } |
| } |
| |
| private static class ShowIncludedIn extends AnAction { |
| private final IncludeReference myReference; |
| private final AndroidDesignerEditorPanel myPanel; |
| |
| private ShowIncludedIn(@NotNull AndroidDesignerEditorPanel panel, @Nullable IncludeReference reference, @NotNull String title) { |
| super(title); |
| myPanel = panel; |
| myReference = reference; |
| } |
| |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| String layout = myReference != null ? myReference.getFromResourceUrl() : null; |
| IncludeReference.setIncludingLayout(myPanel.getProject(), myPanel.getXmlFile(), layout); |
| myPanel.requestRender(); |
| } |
| } |
| } |