Java tutorial
/* * Copyright (C) 2016 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.mockup.editor; import com.android.resources.ScreenOrientation; import com.android.sdklib.devices.Device; import com.android.sdklib.devices.State; import com.android.sdklib.internal.avd.AvdInfo; import com.android.sdklib.internal.avd.AvdManager; import com.android.tools.idea.avdmanager.AvdManagerConnection; import com.android.tools.idea.avdmanager.AvdWizardUtils; import com.android.tools.idea.configurations.Configuration; import com.android.tools.idea.configurations.ConfigurationManager; import com.android.tools.idea.ddms.screenshot.DeviceArtPainter; import com.android.tools.idea.wizard.model.ModelWizardDialog; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.ui.JBColor; import com.intellij.util.ui.UIUtil; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.text.SimpleAttributeSet; import javax.swing.text.StyleConstants; import javax.swing.text.StyledDocument; import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static com.android.ide.common.rendering.HardwareConfigHelper.*; /** * Popup that let the user choose a device for the screen view that matches the * image ratio. */ @SuppressWarnings("unchecked") public class DeviceSelectionPopup extends DialogWrapper { private static final String OK_BUTTON_TEXT = "OK"; private static final String TITLE = "Change Device to Match Mockup"; private static final String PROMPT_HELP_TEXT = "Only displaying devices with same aspect ratio as the image."; private static final JBColor ERROR_COLOR = JBColor.RED; private static final String NO_DEVICE_MESSAGE = "No device or AVD matching the image dimension has been found.\n" + "Create a new AVD with the same aspect ratio as the image or choose another image for better result."; private static final Color MESSAGE_COLOR = JBColor.foreground(); private static final Logger LOGGER = Logger.getInstance(DeviceSelectionPopup.class); private final Configuration myConfiguration; private final Dimension myImageSize; private final BufferedImage myImage; private JPanel myPanel; private JComboBox myDevicesComboBox; private JButton myNewDeviceButton; private JLabel promptLabel; private JPanel myPreviewPanel; private JTextPane myMessage; private List<Device> myMatchingDevices; @Nullable private Device mySelectedDevice; private ScreenOrientation myScreenOrientation; private BufferedImage myDeviceFrame; private Map<String, BufferedImage> myImageCache; private boolean myNoMatchingDevice; @Nullable @Override protected String getHelpId() { return null; } public DeviceSelectionPopup(Project project, Configuration configuration, BufferedImage image) { super(project, true); myConfiguration = configuration; myImage = image; myMatchingDevices = new ArrayList<>(); myImageCache = new HashMap<>(); myImageSize = new Dimension(image.getWidth(), image.getHeight()); mySelectedDevice = configuration.getDevice(); setTitle(TITLE); setHorizontalStretch(1.2f); myHelpAction.setEnabled(false); setOKButtonText(OK_BUTTON_TEXT); init(); } /** * Fill the device combo box by looking first for device matching the same size * as the image. If none are found, try to find device with the same aspect ratio. * If none are found again, tries to find avd matching size or ratio. * Otherwise, use the current selected device as the only item in the combo box * * @param configuration */ private void initDeviceList(Configuration configuration) { myNoMatchingDevice = false; if (!myMatchingDevices.isEmpty()) { myMatchingDevices.clear(); } if (myDevicesComboBox.getItemCount() > 0) { myDevicesComboBox.removeAllItems(); } int lastNexusIndex = 0; int lastMatchIndex = 0; int lastNexusRatioIndex = 0; ConfigurationManager configurationManager = configuration.getConfigurationManager(); List<Device> deviceList = configurationManager.getDevices(); final double imageRatio = myImageSize.width / (double) myImageSize.height; myScreenOrientation = myImageSize.width <= myImageSize.height ? ScreenOrientation.PORTRAIT : ScreenOrientation.LANDSCAPE; for (Device device : deviceList) { final Dimension screenSize = device.getScreenSize(myScreenOrientation); if (screenSize == null) { continue; } if (myImageSize.equals(screenSize)) { if (isNexus(device)) { myMatchingDevices.add(lastNexusIndex++, device); } else if (isGeneric(device)) { myMatchingDevices.add(lastNexusIndex + lastMatchIndex++, device); } } else { if (ratioAlmostEqual(imageRatio, screenSize)) { if (isNexus(device)) { myMatchingDevices.add(lastNexusIndex + lastMatchIndex + lastNexusRatioIndex++, device); } else { myMatchingDevices.add(device); } } } } // if not physical device match the size of the mockup // Try to find on in the Avd if (myMatchingDevices.isEmpty()) { if (findMatchingAvd(configurationManager, imageRatio)) { return; } // If there is still no device matching the size or ratio of the mockup, // use the current selected device as best matching device. myMatchingDevices.add(configuration.getDevice()); myNoMatchingDevice = true; } for (Device device : myMatchingDevices) { String deviceLabel; if (isNexus(device)) { deviceLabel = getNexusLabel(device); } else if (isGeneric(device)) { deviceLabel = getGenericLabel(device); } else { deviceLabel = device.getId(); } if (device == myConfiguration.getDevice()) { // Set a special label for the current device // and display it on top if it matches the image ratio deviceLabel = String.format("* %s (current)", deviceLabel); } myDevicesComboBox.addItem(deviceLabel); } mySelectedDevice = myMatchingDevices.get(0); myDevicesComboBox.setSelectedIndex(0); updateDevicePreview(); } /** * Find a avd matching the image size or ratio. * * @param configurationManager configuration managet to get the Android Facet * @param imageRatio ratio of the imgage * @return true if a matching avd has been found */ private boolean findMatchingAvd(ConfigurationManager configurationManager, double imageRatio) { AndroidFacet facet = AndroidFacet.getInstance(configurationManager.getModule()); if (facet == null) { // Unlikely, but has happened - see http://b.android.com/68091 return false; } final AvdManager avdManager = facet.getAvdManagerSilently(); if (avdManager != null) { final AvdInfo[] allAvds = avdManager.getAllAvds(); for (AvdInfo avd : allAvds) { Device device = configurationManager.createDeviceForAvd(avd); if (device != null) { final Dimension screenSize = device.getScreenSize(myScreenOrientation); if (myImageSize.equals(screenSize) || ratioAlmostEqual(imageRatio, screenSize)) { String avdName = "AVD: " + avd.getName(); myDevicesComboBox.addItem(avdName); myMatchingDevices.add(device); } } } } return !myMatchingDevices.isEmpty(); } private static boolean ratioAlmostEqual(double imageRatio, Dimension screenSize) { return Math.abs(screenSize.width / (double) screenSize.height - imageRatio) < 0.01; } @Override @NotNull protected Action[] createActions() { return new Action[] { getOKAction(), getCancelAction(), getHelpAction() }; } @Override public JComponent getPreferredFocusedComponent() { return myDevicesComboBox; } @Override protected JComponent createCenterPanel() { initDeviceList(myConfiguration); initMessage(); initDeviceComboBox(); initNewButton(); return myPanel; } private void initNewButton() { myNewDeviceButton.addActionListener(e -> { ModelWizardDialog dialog = AvdWizardUtils.createAvdWizard(getContentPane(), null); if (dialog.showAndGet()) { AvdManagerConnection.getDefaultAvdManagerConnection().getAvds(true); initDeviceList(myConfiguration); } }); } private void initDeviceComboBox() { myDevicesComboBox.addActionListener(event -> updateDevicePreview()); } private void initMessage() { myMessage.setEditable(false); myMessage.setFont(promptLabel.getFont()); myMessage.setBackground(myPanel.getBackground()); StyledDocument doc = myMessage.getStyledDocument(); SimpleAttributeSet center = new SimpleAttributeSet(); StyleConstants.setAlignment(center, StyleConstants.ALIGN_CENTER); doc.setParagraphAttributes(0, doc.getLength(), center, false); if (myNoMatchingDevice) { myMessage.setText(NO_DEVICE_MESSAGE); myMessage.setForeground(ERROR_COLOR); } else { myMessage.setText(PROMPT_HELP_TEXT); myMessage.setForeground(MESSAGE_COLOR); } } /** * Update the preview image withe the selected device */ private void updateDevicePreview() { int selectedIndex = myDevicesComboBox.getSelectedIndex(); if (selectedIndex < 0) { selectedIndex = 0; myDevicesComboBox.setSelectedIndex(0); } mySelectedDevice = myMatchingDevices.get(selectedIndex); if (mySelectedDevice == null) { myDeviceFrame = null; return; } if (myImageCache.containsKey(mySelectedDevice.getId())) { myDeviceFrame = myImageCache.get(mySelectedDevice.getId()); myPreviewPanel.repaint(); } else { myDeviceFrame = null; myPreviewPanel.repaint(); final DeviceArtPainter deviceArtPainter = DeviceArtPainter.getInstance(); if (deviceArtPainter.hasDeviceFrame(mySelectedDevice)) { new Thread(() -> { try { myDeviceFrame = deviceArtPainter.createFrame(myImage, mySelectedDevice, myScreenOrientation, true /*show effect*/, 1, null); } catch (IllegalStateException artDescriptorException) { LOGGER.warn(artDescriptorException); myDeviceFrame = myImage; } finally { UIUtil.invokeLaterIfNeeded(() -> myPreviewPanel.repaint()); } }).start(); } else { myDeviceFrame = myImage; } } } @Override protected void doOKAction() { super.doOKAction(); if (mySelectedDevice != null) { setConfigurationDevice(mySelectedDevice); } } private void setConfigurationDevice(@NotNull Device selectedDevice) { final State state = selectedDevice.getDefaultState().deepCopy(); myConfiguration.setDeviceStateName(state.getName()); myConfiguration.getConfigurationManager().selectDevice(selectedDevice); } private void createUIComponents() { myPreviewPanel = new MyPreviewPanel(); } private class MyPreviewPanel extends JPanel { AffineTransform myTransform = new AffineTransform(); public MyPreviewPanel() { setPreferredSize(new Dimension(100, 600)); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); if (myDeviceFrame == null || mySelectedDevice == null) { if (mySelectedDevice == null) { drawCenteredString(g, "No device selected", getBounds(), g.getFont()); } else { drawCenteredString(g, "Loading...", getBounds(), g.getFont()); } return; } final Dimension screenSize = mySelectedDevice.getScreenSize(myScreenOrientation); if (screenSize != null) { int sw = getWidth(); int sh = getHeight(); int iw = myDeviceFrame.getWidth(); int ih = myDeviceFrame.getHeight(); float scale = Math.min(sw / (float) iw, sh / (float) ih); myTransform.setToIdentity(); myTransform.translate((sw - iw * scale) / 2f, (sh - ih * scale) / 2f); myTransform.scale(scale, scale); final Graphics2D g2d = (Graphics2D) g; final AffineTransform tx = g2d.getTransform(); g2d.transform(myTransform); g2d.drawImage(myDeviceFrame, 0, 0, iw, ih, null); g2d.setTransform(tx); } } /** * Draw a String centered in the middle of rect. * * @param g The Graphics instance. * @param text The String to draw. * @param rect The Rectangle to center the text in. */ public void drawCenteredString(Graphics g, String text, Rectangle rect, Font font) { FontMetrics metrics = g.getFontMetrics(font); int x = (rect.width - metrics.stringWidth(text)) / 2; // Determine the Y coordinate for the text (note we add the ascent, as in java 2d 0 is top of the screen) int y = ((rect.height - metrics.getHeight()) / 2) + metrics.getAscent(); g.setFont(font); g.drawString(text, x, y); g.dispose(); } } }