blob: 98dde86e06426a439550dc123dc20f9db7a4fdc9 [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
*
* 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.ide.eclipse.adt.internal.editors.layout.gle2;
import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_DEVICE;
import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_DEVICE_STATE;
import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.MASK_ALL;
import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils.SHADOW_SIZE;
import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils.SMALL_SHADOW_SIZE;
import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreview.LARGE_SHADOWS;
import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode.CUSTOM;
import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode.NONE;
import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode.SCREENS;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.api.Rect;
import com.android.ide.common.rendering.api.Capability;
import com.android.ide.common.resources.configuration.DensityQualifier;
import com.android.ide.common.resources.configuration.DeviceConfigHelper;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.common.resources.configuration.LocaleQualifier;
import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationDescription;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Locale;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.NestedConfiguration;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.VaryingConfiguration;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.resources.Density;
import com.android.resources.ScreenSize;
import com.android.sdklib.devices.Device;
import com.android.sdklib.devices.Screen;
import com.android.sdklib.devices.State;
import com.google.common.collect.Lists;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.ide.IDE;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* Manager for the configuration previews, which handles layout computations,
* managing the image buffer cache, etc
*/
public class RenderPreviewManager {
private static double sScale = 1.0;
private static final int RENDER_DELAY = 150;
private static final int PREVIEW_VGAP = 18;
private static final int PREVIEW_HGAP = 12;
private static final int MAX_WIDTH = 200;
private static final int MAX_HEIGHT = MAX_WIDTH;
private static final int ZOOM_ICON_WIDTH = 16;
private static final int ZOOM_ICON_HEIGHT = 16;
private @Nullable List<RenderPreview> mPreviews;
private @Nullable RenderPreviewList mManualList;
private final @NonNull LayoutCanvas mCanvas;
private final @NonNull CanvasTransform mVScale;
private final @NonNull CanvasTransform mHScale;
private int mPrevCanvasWidth;
private int mPrevCanvasHeight;
private int mPrevImageWidth;
private int mPrevImageHeight;
private @NonNull RenderPreviewMode mMode = NONE;
private @Nullable RenderPreview mActivePreview;
private @Nullable ScrollBarListener mListener;
private int mLayoutHeight;
/** Last seen state revision in this {@link RenderPreviewManager}. If less
* than {@link #sRevision}, the previews need to be updated on next exposure */
private static int mRevision;
/** Current global revision count */
private static int sRevision;
private boolean mNeedLayout;
private boolean mNeedRender;
private boolean mNeedZoom;
private SwapAnimation mAnimation;
/**
* Creates a {@link RenderPreviewManager} associated with the given canvas
*
* @param canvas the canvas to manage previews for
*/
public RenderPreviewManager(@NonNull LayoutCanvas canvas) {
mCanvas = canvas;
mHScale = canvas.getHorizontalTransform();
mVScale = canvas.getVerticalTransform();
}
/**
* Revise the global state revision counter. This will cause all layout
* preview managers to refresh themselves to the latest revision when they
* are next exposed.
*/
public static void bumpRevision() {
sRevision++;
}
/**
* Returns the associated chooser
*
* @return the associated chooser
*/
@NonNull
ConfigurationChooser getChooser() {
GraphicalEditorPart editor = mCanvas.getEditorDelegate().getGraphicalEditor();
return editor.getConfigurationChooser();
}
/**
* Returns the associated canvas
*
* @return the canvas
*/
@NonNull
public LayoutCanvas getCanvas() {
return mCanvas;
}
/** Zooms in (grows all previews) */
public void zoomIn() {
sScale = sScale * (1 / 0.9);
if (Math.abs(sScale-1.0) < 0.0001) {
sScale = 1.0;
}
updatedZoom();
}
/** Zooms out (shrinks all previews) */
public void zoomOut() {
sScale = sScale * (0.9 / 1);
if (Math.abs(sScale-1.0) < 0.0001) {
sScale = 1.0;
}
updatedZoom();
}
/** Zooms to 100 (resets zoom) */
public void zoomReset() {
sScale = 1.0;
updatedZoom();
mNeedZoom = mNeedLayout = true;
mCanvas.redraw();
}
private void updatedZoom() {
if (hasPreviews()) {
for (RenderPreview preview : mPreviews) {
preview.disposeThumbnail();
}
RenderPreview preview = mCanvas.getPreview();
if (preview != null) {
preview.disposeThumbnail();
}
}
mNeedLayout = mNeedRender = true;
mCanvas.redraw();
}
static int getMaxWidth() {
return (int) (sScale * MAX_WIDTH);
}
static int getMaxHeight() {
return (int) (sScale * MAX_HEIGHT);
}
static double getScale() {
return sScale;
}
/**
* Returns whether there are any manual preview items (provided the current
* mode is manual previews
*
* @return true if there are items in the manual preview list
*/
public boolean hasManualPreviews() {
assert mMode == CUSTOM;
return mManualList != null && !mManualList.isEmpty();
}
/** Delete all the previews */
public void deleteManualPreviews() {
disposePreviews();
selectMode(NONE);
mCanvas.setFitScale(true /* onlyZoomOut */, true /*allowZoomIn*/);
if (mManualList != null) {
mManualList.delete();
}
}
/** Dispose all the previews */
public void disposePreviews() {
if (mPreviews != null) {
List<RenderPreview> old = mPreviews;
mPreviews = null;
for (RenderPreview preview : old) {
preview.dispose();
}
}
}
/**
* Deletes the given preview
*
* @param preview the preview to be deleted
*/
public void deletePreview(RenderPreview preview) {
mPreviews.remove(preview);
preview.dispose();
layout(true);
mCanvas.redraw();
if (mManualList != null) {
mManualList.remove(preview);
saveList();
}
}
/**
* Compute the total width required for the previews, including internal padding
*
* @return total width in pixels
*/
public int computePreviewWidth() {
int maxPreviewWidth = 0;
if (hasPreviews()) {
for (RenderPreview preview : mPreviews) {
maxPreviewWidth = Math.max(maxPreviewWidth, preview.getWidth());
}
if (maxPreviewWidth > 0) {
maxPreviewWidth += 2 * PREVIEW_HGAP; // 2x for left and right side
maxPreviewWidth += LARGE_SHADOWS ? SHADOW_SIZE : SMALL_SHADOW_SIZE;
}
return maxPreviewWidth;
}
return 0;
}
/**
* Layout Algorithm. This sets the {@link RenderPreview#getX()} and
* {@link RenderPreview#getY()} coordinates of all the previews. It also
* marks previews as visible or invisible via
* {@link RenderPreview#setVisible(boolean)} according to their position and
* the current visible view port in the layout canvas. Finally, it also sets
* the {@code mLayoutHeight} field, such that the scrollbars can compute the
* right scrolled area, and that scrolling can cause render refreshes on
* views that are made visible.
* <p>
* This is not a traditional bin packing problem, because the objects to be
* packaged do not have a fixed size; we can scale them up and down in order
* to provide an "optimal" size.
* <p>
* See http://en.wikipedia.org/wiki/Packing_problem See
* http://en.wikipedia.org/wiki/Bin_packing_problem
*/
void layout(boolean refresh) {
mNeedLayout = false;
if (mPreviews == null || mPreviews.isEmpty()) {
return;
}
int scaledImageWidth = mHScale.getScaledImgSize();
int scaledImageHeight = mVScale.getScaledImgSize();
Rectangle clientArea = mCanvas.getClientArea();
if (!refresh &&
(scaledImageWidth == mPrevImageWidth
&& scaledImageHeight == mPrevImageHeight
&& clientArea.width == mPrevCanvasWidth
&& clientArea.height == mPrevCanvasHeight)) {
// No change
return;
}
mPrevImageWidth = scaledImageWidth;
mPrevImageHeight = scaledImageHeight;
mPrevCanvasWidth = clientArea.width;
mPrevCanvasHeight = clientArea.height;
if (mListener == null) {
mListener = new ScrollBarListener();
mCanvas.getVerticalBar().addSelectionListener(mListener);
}
beginRenderScheduling();
mLayoutHeight = 0;
if (previewsHaveIdenticalSize() || fixedOrder()) {
// If all the preview boxes are of identical sizes, or if the order is predetermined,
// just lay them out in rows.
rowLayout();
} else if (previewsFit()) {
layoutFullFit();
} else {
rowLayout();
}
mCanvas.updateScrollBars();
}
/**
* Performs a simple layout where the views are laid out in a row, wrapping
* around the top left canvas image.
*/
private void rowLayout() {
// TODO: Separate layout heuristics for portrait and landscape orientations (though
// it also depends on the dimensions of the canvas window, which determines the
// shape of the leftover space)
int scaledImageWidth = mHScale.getScaledImgSize();
int scaledImageHeight = mVScale.getScaledImgSize();
Rectangle clientArea = mCanvas.getClientArea();
int availableWidth = clientArea.x + clientArea.width - getX();
int availableHeight = clientArea.y + clientArea.height - getY();
int maxVisibleY = clientArea.y + clientArea.height;
int bottomBorder = scaledImageHeight;
int rightHandSide = scaledImageWidth + PREVIEW_HGAP;
int nextY = 0;
// First lay out images across the top right hand side
int x = rightHandSide;
int y = 0;
boolean wrapped = false;
int vgap = PREVIEW_VGAP;
for (RenderPreview preview : mPreviews) {
// If we have forked previews, double the vgap to allow space for two labels
if (preview.isForked()) {
vgap *= 2;
break;
}
}
List<RenderPreview> aspectOrder;
if (!fixedOrder()) {
aspectOrder = new ArrayList<RenderPreview>(mPreviews);
Collections.sort(aspectOrder, RenderPreview.INCREASING_ASPECT_RATIO);
} else {
aspectOrder = mPreviews;
}
for (RenderPreview preview : aspectOrder) {
if (x > 0 && x + preview.getWidth() > availableWidth) {
x = rightHandSide;
int prevY = y;
y = nextY;
if ((prevY <= bottomBorder ||
y <= bottomBorder)
&& Math.max(nextY, y + preview.getHeight()) > bottomBorder) {
// If there's really no visible room below, don't bother
// Similarly, don't wrap individually scaled views
if (bottomBorder < availableHeight - 40 && preview.getScale() < 1.2) {
// If it's closer to the top row than the bottom, just
// mark the next row for left justify instead
if (bottomBorder - y > y + preview.getHeight() - bottomBorder) {
rightHandSide = 0;
wrapped = true;
} else if (!wrapped) {
y = nextY = Math.max(nextY, bottomBorder + vgap);
x = rightHandSide = 0;
wrapped = true;
}
}
}
}
if (x > 0 && y <= bottomBorder
&& Math.max(nextY, y + preview.getHeight()) > bottomBorder) {
if (clientArea.height - bottomBorder < preview.getHeight()) {
// No room below the device on the left; just continue on the
// bottom row
} else if (preview.getScale() < 1.2) {
if (bottomBorder - y > y + preview.getHeight() - bottomBorder) {
rightHandSide = 0;
wrapped = true;
} else {
y = nextY = Math.max(nextY, bottomBorder + vgap);
x = rightHandSide = 0;
wrapped = true;
}
}
}
preview.setPosition(x, y);
if (y > maxVisibleY && maxVisibleY > 0) {
preview.setVisible(false);
} else if (!preview.isVisible()) {
preview.setVisible(true);
}
x += preview.getWidth();
x += PREVIEW_HGAP;
nextY = Math.max(nextY, y + preview.getHeight() + vgap);
}
mLayoutHeight = nextY;
}
private boolean fixedOrder() {
return mMode == SCREENS;
}
/** Returns true if all the previews have the same identical size */
private boolean previewsHaveIdenticalSize() {
if (!hasPreviews()) {
return true;
}
Iterator<RenderPreview> iterator = mPreviews.iterator();
RenderPreview first = iterator.next();
int width = first.getWidth();
int height = first.getHeight();
while (iterator.hasNext()) {
RenderPreview preview = iterator.next();
if (width != preview.getWidth() || height != preview.getHeight()) {
return false;
}
}
return true;
}
/** Returns true if all the previews can fully fit in the available space */
private boolean previewsFit() {
int scaledImageWidth = mHScale.getScaledImgSize();
int scaledImageHeight = mVScale.getScaledImgSize();
Rectangle clientArea = mCanvas.getClientArea();
int availableWidth = clientArea.x + clientArea.width - getX();
int availableHeight = clientArea.y + clientArea.height - getY();
int bottomBorder = scaledImageHeight;
int rightHandSide = scaledImageWidth + PREVIEW_HGAP;
// First see if we can fit everything; if so, we can try to make the layouts
// larger such that they fill up all the available space
long availableArea = rightHandSide * bottomBorder +
availableWidth * (Math.max(0, availableHeight - bottomBorder));
long requiredArea = 0;
for (RenderPreview preview : mPreviews) {
// Note: This does not include individual preview scale; the layout
// algorithm itself may be tweaking the scales to fit elements within
// the layout
requiredArea += preview.getArea();
}
return requiredArea * sScale < availableArea;
}
private void layoutFullFit() {
int scaledImageWidth = mHScale.getScaledImgSize();
int scaledImageHeight = mVScale.getScaledImgSize();
Rectangle clientArea = mCanvas.getClientArea();
int availableWidth = clientArea.x + clientArea.width - getX();
int availableHeight = clientArea.y + clientArea.height - getY();
int maxVisibleY = clientArea.y + clientArea.height;
int bottomBorder = scaledImageHeight;
int rightHandSide = scaledImageWidth + PREVIEW_HGAP;
int minWidth = Integer.MAX_VALUE;
int minHeight = Integer.MAX_VALUE;
for (RenderPreview preview : mPreviews) {
minWidth = Math.min(minWidth, preview.getWidth());
minHeight = Math.min(minHeight, preview.getHeight());
}
BinPacker packer = new BinPacker(minWidth, minHeight);
// TODO: Instead of this, just start with client area and occupy scaled image size!
// Add in gap on right and bottom since we'll add that requirement on the width and
// height rectangles too (for spacing)
packer.addSpace(new Rect(rightHandSide, 0,
availableWidth - rightHandSide + PREVIEW_HGAP,
availableHeight + PREVIEW_VGAP));
if (maxVisibleY > bottomBorder) {
packer.addSpace(new Rect(0, bottomBorder + PREVIEW_VGAP,
availableWidth + PREVIEW_HGAP, maxVisibleY - bottomBorder + PREVIEW_VGAP));
}
// TODO: Sort previews first before attempting to position them?
ArrayList<RenderPreview> aspectOrder = new ArrayList<RenderPreview>(mPreviews);
Collections.sort(aspectOrder, RenderPreview.INCREASING_ASPECT_RATIO);
for (RenderPreview preview : aspectOrder) {
int previewWidth = preview.getWidth();
int previewHeight = preview.getHeight();
previewHeight += PREVIEW_VGAP;
if (preview.isForked()) {
previewHeight += PREVIEW_VGAP;
}
previewWidth += PREVIEW_HGAP;
// title height? how do I account for that?
Rect position = packer.occupy(previewWidth, previewHeight);
if (position != null) {
preview.setPosition(position.x, position.y);
preview.setVisible(true);
} else {
// Can't fit: give up and do plain row layout
rowLayout();
return;
}
}
mLayoutHeight = availableHeight;
}
/**
* Paints the configuration previews
*
* @param gc the graphics context to paint into
*/
void paint(GC gc) {
if (hasPreviews()) {
// Ensure up to date at all times; consider moving if it's too expensive
layout(mNeedLayout);
if (mNeedRender) {
renderPreviews();
}
if (mNeedZoom) {
boolean allowZoomIn = true /*mMode == NONE*/;
mCanvas.setFitScale(false /*onlyZoomOut*/, allowZoomIn);
mNeedZoom = false;
}
int rootX = getX();
int rootY = getY();
for (RenderPreview preview : mPreviews) {
if (preview.isVisible()) {
int x = rootX + preview.getX();
int y = rootY + preview.getY();
preview.paint(gc, x, y);
}
}
RenderPreview preview = mCanvas.getPreview();
if (preview != null) {
String displayName = null;
Configuration configuration = preview.getConfiguration();
if (configuration instanceof VaryingConfiguration) {
// Use override flags from stashed preview, but configuration
// data from live (not varying) configured configuration
VaryingConfiguration cfg = (VaryingConfiguration) configuration;
int flags = cfg.getAlternateFlags() | cfg.getOverrideFlags();
displayName = NestedConfiguration.computeDisplayName(flags,
getChooser().getConfiguration());
} else if (configuration instanceof NestedConfiguration) {
int flags = ((NestedConfiguration) configuration).getOverrideFlags();
displayName = NestedConfiguration.computeDisplayName(flags,
getChooser().getConfiguration());
} else {
displayName = configuration.getDisplayName();
}
if (displayName != null) {
CanvasTransform hi = mHScale;
CanvasTransform vi = mVScale;
int destX = hi.translate(0);
int destY = vi.translate(0);
int destWidth = hi.getScaledImgSize();
int destHeight = vi.getScaledImgSize();
int x = destX + destWidth / 2 - preview.getWidth() / 2;
int y = destY + destHeight;
preview.paintTitle(gc, x, y, false /*showFile*/, displayName);
}
}
// Zoom overlay
int x = getZoomX();
if (x > 0) {
int y = getZoomY();
int oldAlpha = gc.getAlpha();
// Paint background oval rectangle behind the zoom and close icons
gc.setBackground(gc.getDevice().getSystemColor(SWT.COLOR_GRAY));
gc.setAlpha(128);
int padding = 3;
int arc = 5;
gc.fillRoundRectangle(x - padding, y - padding,
ZOOM_ICON_WIDTH + 2 * padding,
4 * ZOOM_ICON_HEIGHT + 2 * padding, arc, arc);
gc.setAlpha(255);
IconFactory iconFactory = IconFactory.getInstance();
Image zoomOut = iconFactory.getIcon("zoomminus"); //$NON-NLS-1$);
Image zoomIn = iconFactory.getIcon("zoomplus"); //$NON-NLS-1$);
Image zoom100 = iconFactory.getIcon("zoom100"); //$NON-NLS-1$);
Image close = iconFactory.getIcon("close"); //$NON-NLS-1$);
gc.drawImage(zoomIn, x, y);
y += ZOOM_ICON_HEIGHT;
gc.drawImage(zoomOut, x, y);
y += ZOOM_ICON_HEIGHT;
gc.drawImage(zoom100, x, y);
y += ZOOM_ICON_HEIGHT;
gc.drawImage(close, x, y);
y += ZOOM_ICON_HEIGHT;
gc.setAlpha(oldAlpha);
}
} else if (mMode == CUSTOM) {
int rootX = getX();
rootX += mHScale.getScaledImgSize();
rootX += 2 * PREVIEW_HGAP;
int rootY = getY();
rootY += 20;
gc.setFont(mCanvas.getFont());
gc.setForeground(mCanvas.getDisplay().getSystemColor(SWT.COLOR_BLACK));
gc.drawText("Add previews with \"Add as Thumbnail\"\nin the configuration menu",
rootX, rootY, true);
}
if (mAnimation != null) {
mAnimation.tick(gc);
}
}
private void addPreview(@NonNull RenderPreview preview) {
if (mPreviews == null) {
mPreviews = Lists.newArrayList();
}
mPreviews.add(preview);
}
/** Adds the current configuration as a new configuration preview */
public void addAsThumbnail() {
ConfigurationChooser chooser = getChooser();
String name = chooser.getConfiguration().getDisplayName();
if (name == null || name.isEmpty()) {
name = getUniqueName();
}
InputDialog d = new InputDialog(
AdtPlugin.getShell(),
"Add as Thumbnail Preview", // title
"Name of thumbnail:",
name,
null);
if (d.open() == Window.OK) {
selectMode(CUSTOM);
String newName = d.getValue();
// Create a new configuration from the current settings in the composite
Configuration configuration = Configuration.copy(chooser.getConfiguration());
configuration.setDisplayName(newName);
RenderPreview preview = RenderPreview.create(this, configuration);
addPreview(preview);
layout(true);
beginRenderScheduling();
scheduleRender(preview);
mCanvas.setFitScale(true /* onlyZoomOut */, false /*allowZoomIn*/);
if (mManualList == null) {
loadList();
}
if (mManualList != null) {
mManualList.add(preview);
saveList();
}
}
}
/**
* Computes a unique new name for a configuration preview that represents
* the current, default configuration
*
* @return a unique name
*/
private String getUniqueName() {
if (mPreviews == null || mPreviews.isEmpty()) {
// NO, not for the first preview!
return "Config1";
}
Set<String> names = new HashSet<String>(mPreviews.size());
for (RenderPreview preview : mPreviews) {
names.add(preview.getDisplayName());
}
int index = 2;
while (true) {
String name = String.format("Config%1$d", index);
if (!names.contains(name)) {
return name;
}
index++;
}
}
/** Generates a bunch of default configuration preview thumbnails */
public void addDefaultPreviews() {
ConfigurationChooser chooser = getChooser();
Configuration parent = chooser.getConfiguration();
if (parent instanceof NestedConfiguration) {
parent = ((NestedConfiguration) parent).getParent();
}
if (mCanvas.getImageOverlay().getImage() != null) {
// Create Language variation
createLocaleVariation(chooser, parent);
// Vary screen size
// TODO: Be smarter here: Pick a screen that is both as differently as possible
// from the current screen as well as also supported. So consider
// things like supported screens, targetSdk etc.
createScreenVariations(parent);
// Vary orientation
createStateVariation(chooser, parent);
// Vary render target
createRenderTargetVariation(chooser, parent);
}
// Also add in include-context previews, if any
addIncludedInPreviews();
// Make a placeholder preview for the current screen, in case we switch from it
RenderPreview preview = RenderPreview.create(this, parent);
mCanvas.setPreview(preview);
sortPreviewsByOrientation();
}
private void createRenderTargetVariation(ConfigurationChooser chooser, Configuration parent) {
/* This is disabled for now: need to load multiple versions of layoutlib.
When I did this, there seemed to be some drug interactions between
them, and I would end up with NPEs in layoutlib code which normally works.
VaryingConfiguration configuration =
VaryingConfiguration.create(chooser, parent);
configuration.setAlternatingTarget(true);
configuration.syncFolderConfig();
addPreview(RenderPreview.create(this, configuration));
*/
}
private void createStateVariation(ConfigurationChooser chooser, Configuration parent) {
State currentState = parent.getDeviceState();
State nextState = parent.getNextDeviceState(currentState);
if (nextState != currentState) {
VaryingConfiguration configuration =
VaryingConfiguration.create(chooser, parent);
configuration.setAlternateDeviceState(true);
configuration.syncFolderConfig();
addPreview(RenderPreview.create(this, configuration));
}
}
private void createLocaleVariation(ConfigurationChooser chooser, Configuration parent) {
LocaleQualifier currentLanguage = parent.getLocale().qualifier;
for (Locale locale : chooser.getLocaleList()) {
LocaleQualifier qualifier = locale.qualifier;
if (!qualifier.getLanguage().equals(currentLanguage.getLanguage())) {
VaryingConfiguration configuration =
VaryingConfiguration.create(chooser, parent);
configuration.setAlternateLocale(true);
configuration.syncFolderConfig();
addPreview(RenderPreview.create(this, configuration));
break;
}
}
}
private void createScreenVariations(Configuration parent) {
ConfigurationChooser chooser = getChooser();
VaryingConfiguration configuration;
configuration = VaryingConfiguration.create(chooser, parent);
configuration.setVariation(0);
configuration.setAlternateDevice(true);
configuration.syncFolderConfig();
addPreview(RenderPreview.create(this, configuration));
configuration = VaryingConfiguration.create(chooser, parent);
configuration.setVariation(1);
configuration.setAlternateDevice(true);
configuration.syncFolderConfig();
addPreview(RenderPreview.create(this, configuration));
}
/**
* Returns the current mode as seen by this {@link RenderPreviewManager}.
* Note that it may not yet have been synced with the global mode kept in
* {@link AdtPrefs#getRenderPreviewMode()}.
*
* @return the current preview mode
*/
@NonNull
public RenderPreviewMode getMode() {
return mMode;
}
/**
* Update the set of previews for the current mode
*
* @param force force a refresh even if the preview type has not changed
* @return true if the views were recomputed, false if the previews were
* already showing and the mode not changed
*/
public boolean recomputePreviews(boolean force) {
RenderPreviewMode newMode = AdtPrefs.getPrefs().getRenderPreviewMode();
if (newMode == mMode && !force
&& (mRevision == sRevision
|| mMode == NONE
|| mMode == CUSTOM)) {
return false;
}
RenderPreviewMode oldMode = mMode;
mMode = newMode;
mRevision = sRevision;
sScale = 1.0;
disposePreviews();
switch (mMode) {
case DEFAULT:
addDefaultPreviews();
break;
case INCLUDES:
addIncludedInPreviews();
break;
case LOCALES:
addLocalePreviews();
break;
case SCREENS:
addScreenSizePreviews();
break;
case VARIATIONS:
addVariationPreviews();
break;
case CUSTOM:
addManualPreviews();
break;
case NONE:
// Can't just set mNeedZoom because with no previews, the paint
// method does nothing
mCanvas.setFitScale(false /*onlyZoomOut*/, true /*allowZoomIn*/);
break;
default:
assert false : mMode;
}
// We schedule layout for the next redraw rather than process it here immediately;
// not only does this let us avoid doing work for windows where the tab is in the
// background, but when a file is opened we may not know the size of the canvas
// yet, and the layout methods need it in order to do a good job. By the time
// the canvas is painted, we have accurate bounds.
mNeedLayout = mNeedRender = true;
mCanvas.redraw();
if (oldMode != mMode && (oldMode == NONE || mMode == NONE)) {
// If entering or exiting preview mode: updating padding which is compressed
// only in preview mode.
mCanvas.getHorizontalTransform().refresh();
mCanvas.getVerticalTransform().refresh();
}
return true;
}
/**
* Sets the new render preview mode to use
*
* @param mode the new mode
*/
public void selectMode(@NonNull RenderPreviewMode mode) {
if (mode != mMode) {
AdtPrefs.getPrefs().setPreviewMode(mode);
recomputePreviews(false);
}
}
/** Similar to {@link #addDefaultPreviews()} but for locales */
public void addLocalePreviews() {
ConfigurationChooser chooser = getChooser();
List<Locale> locales = chooser.getLocaleList();
Configuration parent = chooser.getConfiguration();
for (Locale locale : locales) {
if (!locale.hasLanguage() && !locale.hasRegion()) {
continue;
}
NestedConfiguration configuration = NestedConfiguration.create(chooser, parent);
configuration.setOverrideLocale(true);
configuration.setLocale(locale, false);
String displayName = ConfigurationChooser.getLocaleLabel(chooser, locale, false);
assert displayName != null; // it's never non null when locale is non null
configuration.setDisplayName(displayName);
addPreview(RenderPreview.create(this, configuration));
}
// Make a placeholder preview for the current screen, in case we switch from it
Configuration configuration = parent;
Locale locale = configuration.getLocale();
String label = ConfigurationChooser.getLocaleLabel(chooser, locale, false);
if (label == null) {
label = "default";
}
configuration.setDisplayName(label);
RenderPreview preview = RenderPreview.create(this, parent);
if (preview != null) {
mCanvas.setPreview(preview);
}
// No need to sort: they should all be identical
}
/** Similar to {@link #addDefaultPreviews()} but for screen sizes */
public void addScreenSizePreviews() {
ConfigurationChooser chooser = getChooser();
Collection<Device> devices = chooser.getDevices();
Configuration configuration = chooser.getConfiguration();
boolean canScaleNinePatch = configuration.supports(Capability.FIXED_SCALABLE_NINE_PATCH);
// Rearrange the devices a bit such that the most interesting devices bubble
// to the front
// 10" tablet, 7" tablet, reference phones, tiny phone, and in general the first
// version of each seen screen size
List<Device> sorted = new ArrayList<Device>(devices);
Set<ScreenSize> seenSizes = new HashSet<ScreenSize>();
State currentState = configuration.getDeviceState();
String currentStateName = currentState != null ? currentState.getName() : "";
for (int i = 0, n = sorted.size(); i < n; i++) {
Device device = sorted.get(i);
boolean interesting = false;
State state = device.getState(currentStateName);
if (state == null) {
state = device.getAllStates().get(0);
}
if (device.getName().startsWith("Nexus ") //$NON-NLS-1$
|| device.getName().endsWith(" Nexus")) { //$NON-NLS-1$
// Not String#contains("Nexus") because that would also pick up all the generic
// entries ("3.7in WVGA (Nexus One)") so we'd have them duplicated
interesting = true;
}
FolderConfiguration c = DeviceConfigHelper.getFolderConfig(state);
if (c != null) {
ScreenSizeQualifier sizeQualifier = c.getScreenSizeQualifier();
if (sizeQualifier != null) {
ScreenSize size = sizeQualifier.getValue();
if (!seenSizes.contains(size)) {
seenSizes.add(size);
interesting = true;
}
}
// Omit LDPI, not really used anymore
DensityQualifier density = c.getDensityQualifier();
if (density != null) {
Density d = density.getValue();
if (d == Density.LOW) {
interesting = false;
}
if (!canScaleNinePatch && d == Density.TV) {
interesting = false;
}
}
}
if (interesting) {
NestedConfiguration screenConfig = NestedConfiguration.create(chooser,
configuration);
screenConfig.setOverrideDevice(true);
screenConfig.setDevice(device, true);
screenConfig.syncFolderConfig();
screenConfig.setDisplayName(ConfigurationChooser.getDeviceLabel(device, true));
addPreview(RenderPreview.create(this, screenConfig));
}
}
// Sorted by screen size, in decreasing order
sortPreviewsByScreenSize();
}
/**
* Previews this layout as included in other layouts
*/
public void addIncludedInPreviews() {
ConfigurationChooser chooser = getChooser();
IProject project = chooser.getProject();
if (project == null) {
return;
}
IncludeFinder finder = IncludeFinder.get(project);
final List<Reference> includedBy = finder.getIncludedBy(chooser.getEditedFile());
if (includedBy == null || includedBy.isEmpty()) {
// TODO: Generate some useful defaults, such as including it in a ListView
// as the list item layout?
return;
}
for (final Reference reference : includedBy) {
String title = reference.getDisplayName();
Configuration config = Configuration.create(chooser.getConfiguration(),
reference.getFile());
RenderPreview preview = RenderPreview.create(this, config);
preview.setDisplayName(title);
preview.setIncludedWithin(reference);
addPreview(preview);
}
sortPreviewsByOrientation();
}
/**
* Previews this layout as included in other layouts
*/
public void addVariationPreviews() {
ConfigurationChooser chooser = getChooser();
IFile file = chooser.getEditedFile();
List<IFile> variations = AdtUtils.getResourceVariations(file, false /*includeSelf*/);
// Sort by parent folder
Collections.sort(variations, new Comparator<IFile>() {
@Override
public int compare(IFile file1, IFile file2) {
return file1.getParent().getName().compareTo(file2.getParent().getName());
}
});
Configuration currentConfig = chooser.getConfiguration();
for (IFile variation : variations) {
String title = variation.getParent().getName();
Configuration config = Configuration.create(chooser.getConfiguration(), variation);
config.setTheme(currentConfig.getTheme());
config.setActivity(currentConfig.getActivity());
RenderPreview preview = RenderPreview.create(this, config);
preview.setDisplayName(title);
preview.setAlternateInput(variation);
addPreview(preview);
}
sortPreviewsByOrientation();
}
/**
* Previews this layout using a custom configured set of layouts
*/
public void addManualPreviews() {
if (mManualList == null) {
loadList();
} else {
mPreviews = mManualList.createPreviews(mCanvas);
}
}
private void loadList() {
IProject project = getChooser().getProject();
if (project == null) {
return;
}
if (mManualList == null) {
mManualList = RenderPreviewList.get(project);
}
try {
mManualList.load(getChooser().getDevices());
mPreviews = mManualList.createPreviews(mCanvas);
} catch (IOException e) {
AdtPlugin.log(e, null);
}
}
private void saveList() {
if (mManualList != null) {
try {
mManualList.save();
} catch (IOException e) {
AdtPlugin.log(e, null);
}
}
}
void rename(ConfigurationDescription description, String newName) {
IProject project = getChooser().getProject();
if (project == null) {
return;
}
if (mManualList == null) {
mManualList = RenderPreviewList.get(project);
}
description.displayName = newName;
saveList();
}
/**
* Notifies that the main configuration has changed.
*
* @param flags the change flags, a bitmask corresponding to the
* {@code CHANGE_} constants in {@link ConfigurationClient}
*/
public void configurationChanged(int flags) {
// Similar to renderPreviews, but only acts on incomplete previews
if (hasPreviews()) {
// Do zoomed images first
beginRenderScheduling();
for (RenderPreview preview : mPreviews) {
if (preview.getScale() > 1.2) {
preview.configurationChanged(flags);
}
}
for (RenderPreview preview : mPreviews) {
if (preview.getScale() <= 1.2) {
preview.configurationChanged(flags);
}
}
RenderPreview preview = mCanvas.getPreview();
if (preview != null) {
preview.configurationChanged(flags);
preview.dispose();
}
mNeedLayout = true;
mCanvas.redraw();
}
}
/** Updates the configuration preview thumbnails */
public void renderPreviews() {
if (hasPreviews()) {
beginRenderScheduling();
// Process in visual order
ArrayList<RenderPreview> visualOrder = new ArrayList<RenderPreview>(mPreviews);
Collections.sort(visualOrder, RenderPreview.VISUAL_ORDER);
// Do zoomed images first
for (RenderPreview preview : visualOrder) {
if (preview.getScale() > 1.2 && preview.isVisible()) {
scheduleRender(preview);
}
}
// Non-zoomed images
for (RenderPreview preview : visualOrder) {
if (preview.getScale() <= 1.2 && preview.isVisible()) {
scheduleRender(preview);
}
}
}
mNeedRender = false;
}
private int mPendingRenderCount;
/**
* Reset rendering scheduling. The next render request will be scheduled
* after a single delay unit.
*/
public void beginRenderScheduling() {
mPendingRenderCount = 0;
}
/**
* Schedule rendering the given preview. Each successive call will add an additional
* delay unit to the schedule from the previous {@link #scheduleRender(RenderPreview)}
* call, until {@link #beginRenderScheduling()} is called again.
*
* @param preview the preview to render
*/
public void scheduleRender(@NonNull RenderPreview preview) {
mPendingRenderCount++;
preview.render(mPendingRenderCount * RENDER_DELAY);
}
/**
* Switch to the given configuration preview
*
* @param preview the preview to switch to
*/
public void switchTo(@NonNull RenderPreview preview) {
IFile input = preview.getAlternateInput();
if (input != null) {
IWorkbenchPartSite site = mCanvas.getEditorDelegate().getEditor().getSite();
try {
// This switches to the given file, but the file might not have
// an identical configuration to what was shown in the preview.
// For example, while viewing a 10" layout-xlarge file, it might
// show a preview for a 5" version tied to the default layout. If
// you click on it, it will open the default layout file, but it might
// be using a different screen size; any of those that match the
// default layout, say a 3.8".
//
// Thus, we need to also perform a screen size sync first
Configuration configuration = preview.getConfiguration();
boolean setSize = false;
if (configuration instanceof NestedConfiguration) {
NestedConfiguration nestedConfig = (NestedConfiguration) configuration;
setSize = nestedConfig.isOverridingDevice();
if (configuration instanceof VaryingConfiguration) {
VaryingConfiguration c = (VaryingConfiguration) configuration;
setSize |= c.isAlternatingDevice();
}
if (setSize) {
ConfigurationChooser chooser = getChooser();
IFile editedFile = chooser.getEditedFile();
if (editedFile != null) {
chooser.syncToVariations(CFG_DEVICE|CFG_DEVICE_STATE,
editedFile, configuration, false, false);
}
}
}
IDE.openEditor(site.getWorkbenchWindow().getActivePage(), input,
CommonXmlEditor.ID);
} catch (PartInitException e) {
AdtPlugin.log(e, null);
}
return;
}
GraphicalEditorPart editor = mCanvas.getEditorDelegate().getGraphicalEditor();
ConfigurationChooser chooser = editor.getConfigurationChooser();
Configuration originalConfiguration = chooser.getConfiguration();
// The new configuration is the configuration which will become the configuration
// in the layout editor's chooser
Configuration previewConfiguration = preview.getConfiguration();
Configuration newConfiguration = previewConfiguration;
if (newConfiguration instanceof NestedConfiguration) {
// Should never use a complementing configuration for the main
// rendering's configuration; instead, create a new configuration
// with a snapshot of the configuration's current values
newConfiguration = Configuration.copy(previewConfiguration);
// Remap all the previews to be parented to this new copy instead
// of the old one (which is no longer controlled by the chooser)
for (RenderPreview p : mPreviews) {
Configuration configuration = p.getConfiguration();
if (configuration instanceof NestedConfiguration) {
NestedConfiguration nested = (NestedConfiguration) configuration;
nested.setParent(newConfiguration);
}
}
}
// Make a preview for the configuration which *was* showing in the
// chooser up until this point:
RenderPreview newPreview = mCanvas.getPreview();
if (newPreview == null) {
newPreview = RenderPreview.create(this, originalConfiguration);
}
// Update its configuration such that it is complementing or inheriting
// from the new chosen configuration
if (previewConfiguration instanceof VaryingConfiguration) {
VaryingConfiguration varying = VaryingConfiguration.create(
(VaryingConfiguration) previewConfiguration,
newConfiguration);
varying.updateDisplayName();
originalConfiguration = varying;
newPreview.setConfiguration(originalConfiguration);
} else if (previewConfiguration instanceof NestedConfiguration) {
NestedConfiguration nested = NestedConfiguration.create(
(NestedConfiguration) previewConfiguration,
originalConfiguration,
newConfiguration);
nested.setDisplayName(nested.computeDisplayName());
originalConfiguration = nested;
newPreview.setConfiguration(originalConfiguration);
}
// Replace clicked preview with preview of the formerly edited main configuration
// This doesn't work yet because the image overlay has had its image
// replaced by the configuration previews! I should make a list of them
//newPreview.setFullImage(mImageOverlay.getAwtImage());
for (int i = 0, n = mPreviews.size(); i < n; i++) {
if (preview == mPreviews.get(i)) {
mPreviews.set(i, newPreview);
break;
}
}
// Stash the corresponding preview (not active) on the canvas so we can
// retrieve it if clicking to some other preview later
mCanvas.setPreview(preview);
preview.setVisible(false);
// Switch to the configuration from the clicked preview (though it's
// most likely a copy, see above)
chooser.setConfiguration(newConfiguration);
editor.changed(MASK_ALL);
// Scroll to the top again, if necessary
mCanvas.getVerticalBar().setSelection(mCanvas.getVerticalBar().getMinimum());
mNeedLayout = mNeedZoom = true;
mCanvas.redraw();
mAnimation = new SwapAnimation(preview, newPreview);
}
/**
* Gets the preview at the given location, or null if none. This is
* currently deeply tied to where things are painted in onPaint().
*/
RenderPreview getPreview(ControlPoint mousePos) {
if (hasPreviews()) {
int rootX = getX();
if (mousePos.x < rootX) {
return null;
}
int rootY = getY();
for (RenderPreview preview : mPreviews) {
int x = rootX + preview.getX();
int y = rootY + preview.getY();
if (mousePos.x >= x && mousePos.x <= x + preview.getWidth()) {
if (mousePos.y >= y && mousePos.y <= y + preview.getHeight()) {
return preview;
}
}
}
}
return null;
}
private int getX() {
return mHScale.translate(0);
}
private int getY() {
return mVScale.translate(0);
}
private int getZoomX() {
Rectangle clientArea = mCanvas.getClientArea();
int x = clientArea.x + clientArea.width - ZOOM_ICON_WIDTH;
if (x < mHScale.getScaledImgSize() + PREVIEW_HGAP) {
// No visible previews because the main image is zoomed too far
return -1;
}
return x - 6;
}
private int getZoomY() {
Rectangle clientArea = mCanvas.getClientArea();
return clientArea.y + 5;
}
/**
* Returns the height of the layout
*
* @return the height
*/
public int getHeight() {
return mLayoutHeight;
}
/**
* Notifies that preview manager that the mouse cursor has moved to the
* given control position within the layout canvas
*
* @param mousePos the mouse position, relative to the layout canvas
*/
public void moved(ControlPoint mousePos) {
RenderPreview hovered = getPreview(mousePos);
if (hovered != mActivePreview) {
if (mActivePreview != null) {
mActivePreview.setActive(false);
}
mActivePreview = hovered;
if (mActivePreview != null) {
mActivePreview.setActive(true);
}
mCanvas.redraw();
}
}
/**
* Notifies that preview manager that the mouse cursor has entered the layout canvas
*
* @param mousePos the mouse position, relative to the layout canvas
*/
public void enter(ControlPoint mousePos) {
moved(mousePos);
}
/**
* Notifies that preview manager that the mouse cursor has exited the layout canvas
*
* @param mousePos the mouse position, relative to the layout canvas
*/
public void exit(ControlPoint mousePos) {
if (mActivePreview != null) {
mActivePreview.setActive(false);
}
mActivePreview = null;
mCanvas.redraw();
}
/**
* Process a mouse click, and return true if it was handled by this manager
* (e.g. the click was on a preview)
*
* @param mousePos the mouse position where the click occurred
* @return true if the click occurred over a preview and was handled, false otherwise
*/
public boolean click(ControlPoint mousePos) {
// Clicked zoom?
int x = getZoomX();
if (x > 0) {
if (mousePos.x >= x && mousePos.x <= x + ZOOM_ICON_WIDTH) {
int y = getZoomY();
if (mousePos.y >= y && mousePos.y <= y + 4 * ZOOM_ICON_HEIGHT) {
if (mousePos.y < y + ZOOM_ICON_HEIGHT) {
zoomIn();
} else if (mousePos.y < y + 2 * ZOOM_ICON_HEIGHT) {
zoomOut();
} else if (mousePos.y < y + 3 * ZOOM_ICON_HEIGHT) {
zoomReset();
} else {
selectMode(NONE);
}
return true;
}
}
}
RenderPreview preview = getPreview(mousePos);
if (preview != null) {
boolean handled = preview.click(mousePos.x - getX() - preview.getX(),
mousePos.y - getY() - preview.getY());
if (handled) {
// In case layout was performed, there could be a new preview
// under this coordinate now, so make sure it's hover etc
// shows up
moved(mousePos);
return true;
}
}
return false;
}
/**
* Returns true if there are thumbnail previews
*
* @return true if thumbnails are being shown
*/
public boolean hasPreviews() {
return mPreviews != null && !mPreviews.isEmpty();
}
private void sortPreviewsByScreenSize() {
if (mPreviews != null) {
Collections.sort(mPreviews, new Comparator<RenderPreview>() {
@Override
public int compare(RenderPreview preview1, RenderPreview preview2) {
Configuration config1 = preview1.getConfiguration();
Configuration config2 = preview2.getConfiguration();
Device device1 = config1.getDevice();
Device device2 = config1.getDevice();
if (device1 != null && device2 != null) {
Screen screen1 = device1.getDefaultHardware().getScreen();
Screen screen2 = device2.getDefaultHardware().getScreen();
if (screen1 != null && screen2 != null) {
double delta = screen1.getDiagonalLength()
- screen2.getDiagonalLength();
if (delta != 0.0) {
return (int) Math.signum(delta);
} else {
if (screen1.getPixelDensity() != screen2.getPixelDensity()) {
return screen1.getPixelDensity().compareTo(
screen2.getPixelDensity());
}
}
}
}
State state1 = config1.getDeviceState();
State state2 = config2.getDeviceState();
if (state1 != state2 && state1 != null && state2 != null) {
return state1.getName().compareTo(state2.getName());
}
return preview1.getDisplayName().compareTo(preview2.getDisplayName());
}
});
}
}
private void sortPreviewsByOrientation() {
if (mPreviews != null) {
Collections.sort(mPreviews, new Comparator<RenderPreview>() {
@Override
public int compare(RenderPreview preview1, RenderPreview preview2) {
Configuration config1 = preview1.getConfiguration();
Configuration config2 = preview2.getConfiguration();
State state1 = config1.getDeviceState();
State state2 = config2.getDeviceState();
if (state1 != state2 && state1 != null && state2 != null) {
return state1.getName().compareTo(state2.getName());
}
return preview1.getDisplayName().compareTo(preview2.getDisplayName());
}
});
}
}
/**
* Vertical scrollbar listener which updates render previews which are not
* visible and triggers a redraw
*/
private class ScrollBarListener implements SelectionListener {
@Override
public void widgetSelected(SelectionEvent e) {
if (mPreviews == null) {
return;
}
ScrollBar bar = mCanvas.getVerticalBar();
int selection = bar.getSelection();
int thumb = bar.getThumb();
int maxY = selection + thumb;
beginRenderScheduling();
for (RenderPreview preview : mPreviews) {
if (!preview.isVisible() && preview.getY() <= maxY) {
preview.setVisible(true);
}
}
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
}
/** Animation overlay shown briefly after swapping two previews */
private class SwapAnimation implements Runnable {
private long begin;
private long end;
private static final long DURATION = 400; // ms
private Rect initialRect1;
private Rect targetRect1;
private Rect initialRect2;
private Rect targetRect2;
private RenderPreview preview;
SwapAnimation(RenderPreview preview1, RenderPreview preview2) {
begin = System.currentTimeMillis();
end = begin + DURATION;
initialRect1 = new Rect(preview1.getX(), preview1.getY(),
preview1.getWidth(), preview1.getHeight());
CanvasTransform hi = mCanvas.getHorizontalTransform();
CanvasTransform vi = mCanvas.getVerticalTransform();
initialRect2 = new Rect(hi.translate(0), vi.translate(0),
hi.getScaledImgSize(), vi.getScaledImgSize());
preview = preview2;
}
void tick(GC gc) {
long now = System.currentTimeMillis();
if (now > end || mCanvas.isDisposed()) {
mAnimation = null;
return;
}
CanvasTransform hi = mCanvas.getHorizontalTransform();
CanvasTransform vi = mCanvas.getVerticalTransform();
if (targetRect1 == null) {
targetRect1 = new Rect(hi.translate(0), vi.translate(0),
hi.getScaledImgSize(), vi.getScaledImgSize());
}
double portion = (now - begin) / (double) DURATION;
Rect rect1 = new Rect(
(int) (portion * (targetRect1.x - initialRect1.x) + initialRect1.x),
(int) (portion * (targetRect1.y - initialRect1.y) + initialRect1.y),
(int) (portion * (targetRect1.w - initialRect1.w) + initialRect1.w),
(int) (portion * (targetRect1.h - initialRect1.h) + initialRect1.h));
if (targetRect2 == null) {
targetRect2 = new Rect(preview.getX(), preview.getY(),
preview.getWidth(), preview.getHeight());
}
portion = (now - begin) / (double) DURATION;
Rect rect2 = new Rect(
(int) (portion * (targetRect2.x - initialRect2.x) + initialRect2.x),
(int) (portion * (targetRect2.y - initialRect2.y) + initialRect2.y),
(int) (portion * (targetRect2.w - initialRect2.w) + initialRect2.w),
(int) (portion * (targetRect2.h - initialRect2.h) + initialRect2.h));
gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_GRAY));
gc.drawRectangle(rect1.x, rect1.y, rect1.w, rect1.h);
gc.drawRectangle(rect2.x, rect2.y, rect2.w, rect2.h);
mCanvas.getDisplay().timerExec(5, this);
}
@Override
public void run() {
mCanvas.redraw();
}
}
/**
* Notifies the {@linkplain RenderPreviewManager} that the configuration used
* in the main chooser has been changed. This may require updating parent references
* in the preview configurations inheriting from it.
*
* @param oldConfiguration the previous configuration
* @param newConfiguration the new configuration in the chooser
*/
public void updateChooserConfig(
@NonNull Configuration oldConfiguration,
@NonNull Configuration newConfiguration) {
if (hasPreviews()) {
for (RenderPreview preview : mPreviews) {
Configuration configuration = preview.getConfiguration();
if (configuration instanceof NestedConfiguration) {
NestedConfiguration nestedConfig = (NestedConfiguration) configuration;
if (nestedConfig.getParent() == oldConfiguration) {
nestedConfig.setParent(newConfiguration);
}
}
}
}
}
}