blob: fb0932405430e108cfe9cde9c475cb5e7df409cb [file] [log] [blame]
/*
* 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.uibuilder.editor;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.tools.idea.AndroidPsiUtils;
import com.android.tools.idea.configurations.*;
import com.android.tools.idea.rendering.RenderResult;
import com.android.tools.idea.rendering.RenderedViewHierarchy;
import com.android.tools.idea.rendering.multi.RenderPreviewManager;
import com.android.tools.idea.uibuilder.api.DragType;
import com.android.tools.idea.uibuilder.api.InsertType;
import com.android.tools.idea.uibuilder.api.ViewGroupHandler;
import com.android.tools.idea.uibuilder.api.ViewHandler;
import com.android.tools.idea.uibuilder.handlers.ViewHandlerManager;
import com.android.tools.idea.uibuilder.model.*;
import com.android.tools.idea.uibuilder.surface.DesignSurface;
import com.android.tools.idea.uibuilder.surface.ScreenView;
import com.intellij.designer.DesignerEditorPanelFacade;
import com.intellij.designer.LightFillLayout;
import com.intellij.ide.CopyProvider;
import com.intellij.ide.CutProvider;
import com.intellij.ide.DeleteProvider;
import com.intellij.ide.PasteProvider;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ThreeComponentsSplitter;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.xml.XmlFile;
import com.intellij.ui.IdeBorderFactory;
import com.intellij.ui.SideBorder;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.annotations.NonNls;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.List;
/**
* Assembles a designer editor from various components
*/
public class NlEditorPanel extends JPanel implements DesignerEditorPanelFacade, DataProvider {
private final DesignSurface mySurface;
private final ThreeComponentsSplitter myContentSplitter;
public NlEditorPanel(@NonNull NlEditor editor, @NonNull AndroidFacet facet, @NonNull VirtualFile file) {
super(new BorderLayout());
setOpaque(true);
final Project project = facet.getModule().getProject();
XmlFile xmlFile = (XmlFile)AndroidPsiUtils.getPsiFileSafely(project, file);
assert xmlFile != null : file;
mySurface = new DesignSurface(project);
NlModel model = NlModel.create(mySurface, editor, facet, xmlFile);
mySurface.setModel(model);
myContentSplitter = new ThreeComponentsSplitter();
// The {@link LightFillLayout} provides the UI for the minimized forms of the {@link LightToolWindow}
// used for the palette and the structure/properties panes.
JPanel contentPanel = new JPanel(new LightFillLayout());
JComponent toolbar = createToolbar(mySurface);
contentPanel.add(toolbar);
contentPanel.add(mySurface);
myContentSplitter.setDividerWidth(0);
myContentSplitter.setDividerMouseZoneSize(Registry.intValue("ide.splitter.mouseZone"));
myContentSplitter.setInnerComponent(contentPanel);
add(myContentSplitter, BorderLayout.CENTER);
// When you're opening the layout editor we don't want to delay anything
model.requestRenderAsap();
}
static JComponent createToolbar(@NonNull DesignSurface surface) {
RenderContext context = new NlRenderContext(surface);
ActionGroup group = createActions(context, surface);
ActionManager actionManager = ActionManager.getInstance();
ActionToolbar actionToolbar = actionManager.createActionToolbar("NeleToolbarId", group, true);
actionToolbar.setLayoutPolicy(ActionToolbar.WRAP_LAYOUT_POLICY);
JComponent editorToolbar = actionToolbar.getComponent();
editorToolbar.setBorder(IdeBorderFactory.createBorder(SideBorder.BOTTOM));
return editorToolbar;
}
private static DefaultActionGroup createActions(RenderContext configurationHolder, DesignSurface surface) {
DefaultActionGroup group = new DefaultActionGroup();
OrientationMenuAction orientationAction = new OrientationMenuAction(configurationHolder);
group.add(orientationAction);
group.addSeparator();
DeviceMenuAction deviceAction = new DeviceMenuAction(configurationHolder);
group.add(deviceAction);
TargetMenuAction targetMenuAction = new TargetMenuAction(configurationHolder);
group.add(targetMenuAction);
ThemeMenuAction themeAction = new ThemeMenuAction(configurationHolder);
group.add(themeAction);
LocaleMenuAction localeAction = new LocaleMenuAction(configurationHolder);
group.add(localeAction);
ConfigurationMenuAction configAction = new ConfigurationMenuAction(configurationHolder);
group.add(configAction);
ZoomMenuAction zoomAction = new ZoomMenuAction(surface);
group.add(zoomAction);
return group;
}
@Nullable
public JComponent getPreferredFocusedComponent() {
return mySurface.getPreferredFocusedComponent();
}
public void dispose() {
NlPaletteManager.get(mySurface.getProject()).dispose(this);
NlStructureManager.get(mySurface.getProject()).dispose(this);
Disposer.dispose(myContentSplitter);
}
public void activate() {
mySurface.activate();
}
public void deactivate() {
mySurface.deactivate();
}
public DesignSurface getSurface() {
return mySurface;
}
@Override
public Object getData(@NonNls String dataId) {
if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId) ||
PlatformDataKeys.CUT_PROVIDER.is(dataId) ||
PlatformDataKeys.COPY_PROVIDER.is(dataId) ||
PlatformDataKeys.PASTE_PROVIDER.is(dataId)) {
return new ActionHandler(this);
}
return null;
}
@Override
public ThreeComponentsSplitter getContentSplitter() {
return myContentSplitter;
}
private static class ActionHandler implements DeleteProvider, CutProvider, CopyProvider, PasteProvider {
private final NlEditorPanel myEditor;
public ActionHandler(NlEditorPanel panel) {
myEditor = panel;
}
@Override
public void performCopy(@NonNull DataContext dataContext) {
ScreenView screenView = myEditor.getSurface().getCurrentScreenView();
if (screenView == null) {
return;
}
CopyPasteManager.getInstance().setContents(screenView.getModel().getSelectionAsTransferable());
}
@Override
public boolean isCopyEnabled(@NonNull DataContext dataContext) {
return hasNonEmptySelection();
}
@Override
public boolean isCopyVisible(@NonNull DataContext dataContext) {
return true;
}
@Override
public void performCut(@NonNull DataContext dataContext) {
performCopy(dataContext);
deleteElement(dataContext);
}
@Override
public boolean isCutEnabled(@NonNull DataContext dataContext) {
return hasNonEmptySelection();
}
@Override
public boolean isCutVisible(@NonNull DataContext dataContext) {
return true;
}
@Override
public void deleteElement(@NonNull DataContext dataContext) {
DesignSurface surface = myEditor.getSurface();
ScreenView screenView = surface.getCurrentScreenView();
if (screenView == null) {
return;
}
SelectionModel selectionModel = screenView.getSelectionModel();
NlModel model = screenView.getModel();
model.delete(selectionModel.getSelection());
model.requestRender();
}
@Override
public boolean canDeleteElement(@NonNull DataContext dataContext) {
return hasNonEmptySelection();
}
@Override
public void performPaste(@NonNull DataContext dataContext) {
pasteOperation(false /* check and perform the actual paste */);
}
@Override
public boolean isPastePossible(@NonNull DataContext dataContext) {
return true;
}
@Override
public boolean isPasteEnabled(@NonNull DataContext dataContext) {
return pasteOperation(true /* check only */);
}
private boolean hasNonEmptySelection() {
ScreenView screenView = myEditor.getSurface().getCurrentScreenView();
return screenView != null && !screenView.getSelectionModel().isEmpty();
}
private boolean pasteOperation(boolean checkOnly) {
ScreenView screenView = myEditor.getSurface().getCurrentScreenView();
if (screenView == null) {
return false;
}
List<NlComponent> selection = screenView.getSelectionModel().getSelection();
if (selection.size() != 1) {
return false;
}
NlComponent receiver = selection.get(0);
NlComponent before;
NlModel model = screenView.getModel();
ViewHandlerManager handlerManager = ViewHandlerManager.get(model.getProject());
ViewHandler handler = handlerManager.getHandler(receiver);
if (handler instanceof ViewGroupHandler) {
before = receiver.getChild(0);
} else {
before = receiver.getNextSibling();
receiver = receiver.getParent();
if (receiver == null) {
return false;
}
}
DnDTransferItem item = getClipboardData();
if (item == null) {
return false;
}
InsertType insertType = model.determineInsertType(DragType.PASTE, item, checkOnly);
List<NlComponent> pasted = model.createComponents(screenView, item, insertType);
if (!model.canAddComponents(pasted, receiver, before)) {
return false;
}
if (checkOnly) {
return true;
}
model.addComponents(pasted, receiver, before, insertType);
return true;
}
@Nullable
private static DnDTransferItem getClipboardData() {
try {
Object data = CopyPasteManager.getInstance().getContents(ItemTransferable.DESIGNER_FLAVOR);
if (!(data instanceof DnDTransferItem)) {
return null;
}
return (DnDTransferItem)data;
}
catch (Exception e) {
return null;
}
}
}
/** <b>Temporary</b> bridge to older Configuration actions. When we can ditch the old layout preview
* and old layout editors, we no longer needs this level of indirection to let the configuration actions
* talk to multiple different editor implementations, and the render actions can directly address DesignSurface.
*/
private static class NlRenderContext implements RenderContext {
@NonNull private DesignSurface mySurface;
public NlRenderContext(@NonNull DesignSurface surface) {
mySurface = surface;
}
@Nullable
@Override
public Configuration getConfiguration() {
return mySurface.getConfiguration();
}
@Override
public void setConfiguration(@NonNull Configuration configuration) {
throw new UnsupportedOperationException();
}
@Override
public void requestRender() {
if (mySurface.getCurrentScreenView() != null) {
mySurface.getCurrentScreenView().getModel().requestRenderAsap();
}
}
@NonNull
@Override
public UsageType getType() {
return UsageType.LAYOUT_EDITOR;
}
@Nullable
@Override
public XmlFile getXmlFile() {
final Configuration configuration = mySurface.getConfiguration();
if (configuration != null) {
return (XmlFile)configuration.getPsiFile();
}
return null;
}
@Nullable
@Override
public VirtualFile getVirtualFile() {
final Configuration configuration = mySurface.getConfiguration();
if (configuration != null) {
return configuration.getFile();
}
return null;
}
@Nullable
@Override
public Module getModule() {
final Configuration configuration = mySurface.getConfiguration();
if (configuration != null) {
return configuration.getModule();
}
return null;
}
@Override
public boolean hasAlphaChannel() {
return false;
}
@NonNull
@Override
public Component getComponent() {
return mySurface;
}
@NonNull
@Override
public Dimension getFullImageSize() {
throw new UnsupportedOperationException();
}
@NonNull
@Override
public Dimension getScaledImageSize() {
throw new UnsupportedOperationException();
}
@NonNull
@Override
public Rectangle getClientArea() {
throw new UnsupportedOperationException();
}
@Override
public boolean supportsPreviews() {
return false;
}
@Nullable
@Override
public RenderPreviewManager getPreviewManager(boolean createIfNecessary) {
return null;
}
@Override
public void setMaxSize(int width, int height) {
}
@Override
public void zoomFit(boolean onlyZoomOut, boolean allowZoomIn) {
mySurface.zoomToFit();
}
@Override
public void updateLayout() {
}
@Override
public void setDeviceFramesEnabled(boolean on) {
}
@Nullable
@Override
public BufferedImage getRenderedImage() {
throw new UnsupportedOperationException();
}
@Nullable
@Override
public RenderResult getLastResult() {
return null;
}
@Nullable
@Override
public RenderedViewHierarchy getViewHierarchy() {
return null;
}
}
}