blob: 68256da00f8b62ce4f0d05e591bc74f15d6aec1d [file] [log] [blame]
/*
* 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();
}
}
}