/* * Copyright (C) 2014 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.avdmanager; import com.android.resources.*; import com.android.sdklib.devices.*; import com.android.tools.idea.wizard.DynamicWizardStepWithHeaderAndDescription; import com.android.tools.idea.wizard.LabelWithEditLink; import com.android.tools.idea.wizard.ScopedStateStore; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.intellij.openapi.Disposable; import com.intellij.openapi.ui.ComboBox; import com.intellij.ui.EnumComboBoxModel; import com.intellij.ui.components.JBLabel; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.text.Document; import java.awt.event.ItemListener; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import static com.android.tools.idea.avdmanager.AvdWizardConstants.*; /** * UI for configuring a Device Hardware Profile. */ public class ConfigureDeviceOptionsStep extends DynamicWizardStepWithHeaderAndDescription { @Nullable private final Device myTemplateDevice; private final boolean myForceCreation; private DeviceDefinitionPreview myDeviceDefinitionPreview; private JTextField myDiagonalScreenSize; private JTextField myScreenResolutionWidth; private StorageField myRamField; private JCheckBox mySupportsLandscape; private JTextField myScreenResolutionHeight; private JCheckBox myHasBackFacingCamera; private JCheckBox myHasFrontFacingCamera; private JCheckBox myHasAccelerometer; private JCheckBox myHasGyroscope; private JCheckBox myHasGps; private JCheckBox myHasProximitySensor; private JCheckBox mySupportsPortrait; private JComboBox myNavigationControlsCombo; private JPanel myRootPanel; private JCheckBox myHasHardwareButtons; private JCheckBox myHasHardwareKeyboard; private LabelWithEditLink myDeviceName; private JBLabel myHelpAndErrorLabel; /** * This contains the Software for the device. Since it has no effect on the * emulator whatsoever, we just use a single instance with reasonable * defaults. */ private final Software mySoftware; private final Camera myFrontCamera = new Camera(CameraLocation.FRONT, true, true); private final Camera myBackCamera = new Camera(CameraLocation.BACK, true, true); private Device.Builder myBuilder = new Device.Builder(); public ConfigureDeviceOptionsStep(@Nullable Device templateDevice, boolean forceCreation, @Nullable Disposable parentDisposable) { super("Configure Hardware Profile", null, null, parentDisposable); myTemplateDevice = templateDevice; myForceCreation = forceCreation; setBodyComponent(myRootPanel); if (templateDevice == null) { setDescriptionText("Create a new hardware profile by selecting hardware features below."); } else { setDescriptionText(templateDevice.getDisplayName() + " (Edited)"); } mySoftware = new Software(); mySoftware.setLiveWallpaperSupport(true); mySoftware.setGlVersion("2.0"); } private void createUIComponents() { myNavigationControlsCombo = new ComboBox(new EnumComboBoxModel<Navigation>(Navigation.class)); } @Override public void init() { super.init(); registerComponents(); if (myTemplateDevice == null) { initDefaultParams(); } else { initFromDevice(); } myBuilder.setManufacturer("User"); myBuilder.addSoftware(mySoftware); invokeUpdate(null); } @Override protected JLabel getDescriptionText() { return myHelpAndErrorLabel; } /** * Initialize our state to match the device we've been given as a starting point */ private void initFromDevice() { assert myTemplateDevice != null; if (myForceCreation) { myState.put(DEVICE_NAME_KEY, DeviceManagerConnection.getUniqueId(myTemplateDevice.getDisplayName() + " (Edited)")); } else { myState.put(DEVICE_NAME_KEY, myTemplateDevice.getDisplayName()); } myBuilder.setTagId(myTemplateDevice.getTagId()); for (Map.Entry<String, String> entry : myTemplateDevice.getBootProps().entrySet()) { myBuilder.addBootProp(entry.getKey(), entry.getValue()); } Hardware defaultHardware = myTemplateDevice.getDefaultHardware(); Screen screen = defaultHardware.getScreen(); myState.put(DIAGONAL_SCREENSIZE_KEY, screen.getDiagonalLength()); myState.put(RESOLUTION_WIDTH_KEY, screen.getXDimension()); myState.put(RESOLUTION_HEIGHT_KEY, screen.getYDimension()); myState.put(RAM_STORAGE_KEY, defaultHardware.getRam()); myState.put(HAS_HARDWARE_BUTTONS_KEY, defaultHardware.getButtonType() == ButtonType.HARD); myState.put(HAS_HARDWARE_KEYBOARD_KEY, defaultHardware.getKeyboard() != Keyboard.NOKEY); myState.put(NAVIGATION_KEY, defaultHardware.getNav()); final List<State> states = myTemplateDevice.getAllStates(); myState.put(SUPPORTS_PORTRAIT_KEY, Iterables.any(states, new Predicate<State>() { @Override public boolean apply(State input) { return input.getOrientation().equals(ScreenOrientation.PORTRAIT); } })); myState.put(SUPPORTS_LANDSCAPE_KEY, Iterables.any(states, new Predicate<State>() { @Override public boolean apply(State input) { return input.getOrientation().equals(ScreenOrientation.LANDSCAPE); } })); myState.put(HAS_FRONT_CAMERA_KEY, defaultHardware.getCamera(CameraLocation.FRONT) != null); myState.put(HAS_BACK_CAMERA_KEY, defaultHardware.getCamera(CameraLocation.BACK) != null); myState.put(HAS_ACCELEROMETER_KEY, defaultHardware.getSensors().contains(Sensor.ACCELEROMETER)); myState.put(HAS_GYROSCOPE_KEY, defaultHardware.getSensors().contains(Sensor.GYROSCOPE)); myState.put(HAS_GPS_KEY, defaultHardware.getSensors().contains(Sensor.GPS)); myState.put(HAS_PROXIMITY_SENSOR_KEY, defaultHardware.getSensors().contains(Sensor.PROXIMITY_SENSOR)); } /** * Initialize a reasonable set of default values (based on the Nexus 5) */ private void initDefaultParams() { myState.put(DEVICE_NAME_KEY, DeviceManagerConnection.getUniqueId(null)); myState.put(DIAGONAL_SCREENSIZE_KEY, 5.0); myState.put(RESOLUTION_WIDTH_KEY, 1080); myState.put(RESOLUTION_HEIGHT_KEY, 1920); myState.put(RAM_STORAGE_KEY, new Storage(2, Storage.Unit.GiB)); myState.put(HAS_HARDWARE_BUTTONS_KEY, false); myState.put(HAS_HARDWARE_KEYBOARD_KEY, false); myState.put(NAVIGATION_KEY, Navigation.NONAV); myState.put(SUPPORTS_PORTRAIT_KEY, true); myState.put(SUPPORTS_LANDSCAPE_KEY, true); myState.put(HAS_FRONT_CAMERA_KEY, true); myState.put(HAS_BACK_CAMERA_KEY, true); myState.put(HAS_ACCELEROMETER_KEY, true); myState.put(HAS_GYROSCOPE_KEY, true); myState.put(HAS_GPS_KEY, true); myState.put(HAS_PROXIMITY_SENSOR_KEY, true); } @Override public boolean validate() { setErrorHtml(""); if (myState.get(DIAGONAL_SCREENSIZE_KEY) == null) { setErrorHtml("Please enter a positive floating point value for the screen size"); return false; } if (myState.get(RESOLUTION_WIDTH_KEY) == null || myState.get(RESOLUTION_HEIGHT_KEY) == null) { setErrorHtml("Please enter positive integer values for the screen resolution (width x height)"); return false; } if (myState.get(RAM_STORAGE_KEY) == null) { setErrorHtml("Please specify a non-zero amount of RAM"); return false; } Double dpi = myState.get(WIP_SCREEN_DPI_KEY); if (dpi == null || dpi <= 0) { setErrorHtml("The given resolution and screensize specified have a DPI that is too low."); return false; } Boolean supportsLandscape = myState.get(SUPPORTS_LANDSCAPE_KEY); supportsLandscape = supportsLandscape == null ? false : supportsLandscape; Boolean supportsPortrait = myState.get(SUPPORTS_PORTRAIT_KEY); supportsPortrait = supportsPortrait == null ? false : supportsPortrait; if (!(supportsLandscape || supportsPortrait)) { setErrorHtml("A device must support at least one orientation (Portrait or Landscape)"); return false; } return myState.get(DEVICE_DEFINITION_KEY) != null; } @Override public void deriveValues(Set<ScopedStateStore.Key> modified) { boolean refreshAll = modified == null; if (refreshAll) { modified = ImmutableSet.of(); } if (refreshAll || modified.contains(DEVICE_NAME_KEY)) { String name = myState.get(DEVICE_NAME_KEY); myBuilder.setName(name == null ? "" : name); } if (!myState.containsKey(WIP_SCREEN_KEY)) { Screen s = createDefaultScreen(); myState.put(WIP_SCREEN_KEY, s); } Screen screen = myState.get(WIP_SCREEN_KEY); assert screen != null; if (refreshAll || !Collections.disjoint(modified, ImmutableSet.of(DIAGONAL_SCREENSIZE_KEY, RESOLUTION_WIDTH_KEY, RESOLUTION_HEIGHT_KEY))) { Double diagonalLength = myState.get(DIAGONAL_SCREENSIZE_KEY); if (diagonalLength != null) { screen.setDiagonalLength(diagonalLength); screen.setSize(getScreenSize(diagonalLength)); } else { diagonalLength = -1.0; } Integer resolutionWidth = myState.get(RESOLUTION_WIDTH_KEY); if (resolutionWidth != null) { screen.setXDimension(resolutionWidth); } else { resolutionWidth = 0; } Integer resolutionHeight = myState.get(RESOLUTION_HEIGHT_KEY); if (resolutionHeight != null) { screen.setYDimension(resolutionHeight); } else { resolutionHeight = 0; } // The diagonal DPI will be somewhere in between the X and Y dpi if // they differ double dpi = Math.sqrt(resolutionWidth * resolutionWidth + resolutionHeight * resolutionHeight) / diagonalLength; // The diagonal DPI should keep only two digits precision. if (dpi >= 0) { dpi = Math.round(dpi * 100) / 100.0; myState.put(WIP_SCREEN_DPI_KEY, dpi); screen.setYdpi(dpi); screen.setXdpi(dpi); screen.setRatio(getScreenRatio(resolutionWidth, resolutionHeight)); screen.setPixelDensity(getDensity(dpi)); } } if (!myState.containsKey(WIP_HARDWARE_KEY)) { Hardware h = createDefaultHardware(); myState.put(WIP_HARDWARE_KEY, h); } Hardware hardware = myState.get(WIP_HARDWARE_KEY); assert hardware != null; if (refreshAll || modified.contains(HAS_ACCELEROMETER_KEY)) { Boolean hasAccelerometer = myState.get(HAS_ACCELEROMETER_KEY); if (hasAccelerometer != null && hasAccelerometer) { hardware.addSensor(Sensor.ACCELEROMETER); } else { hardware.getSensors().remove(Sensor.ACCELEROMETER); } } if (refreshAll || modified.contains(HAS_GYROSCOPE_KEY)) { Boolean hasGyroscope = myState.get(HAS_GYROSCOPE_KEY); if (hasGyroscope != null && hasGyroscope) { hardware.addSensor(Sensor.GYROSCOPE); } else { hardware.getSensors().remove(Sensor.GYROSCOPE); } } if (refreshAll || modified.contains(HAS_GPS_KEY)) { Boolean hasGps = myState.get(HAS_GPS_KEY); if (hasGps != null && hasGps) { hardware.addSensor(Sensor.GPS); } else { hardware.getSensors().remove(Sensor.GPS); } } if (refreshAll || modified.contains(HAS_PROXIMITY_SENSOR_KEY)) { Boolean hasProximitySensor = myState.get(HAS_PROXIMITY_SENSOR_KEY); if (hasProximitySensor != null && hasProximitySensor) { hardware.addSensor(Sensor.PROXIMITY_SENSOR); } else { hardware.getSensors().remove(Sensor.PROXIMITY_SENSOR); } } if (refreshAll || modified.contains(HAS_FRONT_CAMERA_KEY)) { Boolean hasFrontCamera = myState.get(HAS_FRONT_CAMERA_KEY); if (hasFrontCamera != null && hasFrontCamera) { hardware.addCamera(myFrontCamera); } else { hardware.getCameras().remove(myFrontCamera); } } if (refreshAll || modified.contains(HAS_BACK_CAMERA_KEY)) { Boolean hasBackCamera = myState.get(HAS_BACK_CAMERA_KEY); if (hasBackCamera != null && hasBackCamera) { hardware.addCamera(myBackCamera); } else { hardware.getCameras().remove(myBackCamera); } } Boolean hasHardwareKeyboard = myState.get(HAS_HARDWARE_KEYBOARD_KEY); if (refreshAll || modified.contains(HAS_HARDWARE_KEYBOARD_KEY)) { if (hasHardwareKeyboard != null && hasHardwareKeyboard) { hardware.setKeyboard(Keyboard.QWERTY); } else { hardware.setKeyboard(Keyboard.NOKEY); } } if (hasHardwareKeyboard == null) { hasHardwareKeyboard = false; } if (refreshAll || modified.contains(HAS_HARDWARE_BUTTONS_KEY)) { Boolean hasHardwareButtons = myState.get(HAS_HARDWARE_BUTTONS_KEY); if (hasHardwareButtons != null && hasHardwareButtons) { hardware.setButtonType(ButtonType.HARD); } else { hardware.setButtonType(ButtonType.SOFT); } } if (refreshAll || modified.contains(NAVIGATION_KEY)) { Navigation nav = myState.get(NAVIGATION_KEY); if (nav != null) { hardware.setNav(nav); } } if (refreshAll || modified.contains(RAM_STORAGE_KEY)) { Storage ram = myState.get(RAM_STORAGE_KEY); if (ram != null) { hardware.setRam(ram); } } // Add the screen to the hardware definition hardware.setScreen(screen); // Set up our states setStates(hardware, hasHardwareKeyboard); myState.remove(DEVICE_DEFINITION_KEY); try { Device d = myBuilder.build(); myDeviceDefinitionPreview.setDevice(d); myState.put(DEVICE_DEFINITION_KEY, d); } catch (Exception e) { // Pass } } /** * Add the proper device states based on keyboard availability and orientation support. */ private void setStates(Hardware hardware, Boolean hasHardwareKeyboard) { Boolean supportsLandscape = myState.get(SUPPORTS_LANDSCAPE_KEY); supportsLandscape = supportsLandscape == null ? false : supportsLandscape; Boolean supportsPortrait = myState.get(SUPPORTS_PORTRAIT_KEY); supportsPortrait = supportsPortrait == null ? false : supportsPortrait; boolean defaultSelected = false; if (supportsPortrait) { State state = new State(); state.setName("Portrait"); state.setDescription("The device in portrait orientation"); state.setOrientation(ScreenOrientation.PORTRAIT); if (hardware.getNav().equals(Navigation.NONAV)) { state.setNavState(NavigationState.HIDDEN); } else { state.setNavState(NavigationState.EXPOSED); } if (hardware.getKeyboard().equals(Keyboard.NOKEY)) { state.setKeyState(KeyboardState.SOFT); } else { state.setKeyState(KeyboardState.HIDDEN); } state.setHardware(hardware); state.setDefaultState(true); defaultSelected = true; myBuilder.removeState(state.getName()); myBuilder.addState(state); } if (supportsLandscape) { State state = new State(); state.setName("Landscape"); state.setDescription("The device in landscape orientation"); state.setOrientation(ScreenOrientation.LANDSCAPE); if (hardware.getNav().equals(Navigation.NONAV)) { state.setNavState(NavigationState.HIDDEN); } else { state.setNavState(NavigationState.EXPOSED); } if (hardware.getKeyboard().equals(Keyboard.NOKEY)) { state.setKeyState(KeyboardState.SOFT); } else { state.setKeyState(KeyboardState.HIDDEN); } state.setHardware(hardware); if (!defaultSelected) { state.setDefaultState(true); defaultSelected = true; } myBuilder.removeState(state.getName()); myBuilder.addState(state); } if (hasHardwareKeyboard) { if (supportsPortrait) { State state = new State(); state.setName("Portrait with keyboard"); state.setDescription("The device in portrait orientation with a keyboard open"); state.setOrientation(ScreenOrientation.LANDSCAPE); if (hardware.getNav().equals(Navigation.NONAV)) { state.setNavState(NavigationState.HIDDEN); } else { state.setNavState(NavigationState.EXPOSED); } state.setKeyState(KeyboardState.EXPOSED); state.setHardware(hardware); if (!defaultSelected) { state.setDefaultState(true); defaultSelected = true; } myBuilder.removeState(state.getName()); myBuilder.addState(state); } if (supportsLandscape) { State state = new State(); state.setName("Landscape with keyboard"); state.setDescription("The device in landscape orientation with a keyboard open"); state.setOrientation(ScreenOrientation.LANDSCAPE); if (hardware.getNav().equals(Navigation.NONAV)) { state.setNavState(NavigationState.HIDDEN); } else { state.setNavState(NavigationState.EXPOSED); } state.setKeyState(KeyboardState.EXPOSED); state.setHardware(hardware); if (!defaultSelected) { state.setDefaultState(true); } myBuilder.removeState(state.getName()); myBuilder.addState(state); } } } /** * Create a screen with a reasonable set of defaults. */ private static Screen createDefaultScreen() { Screen s = new Screen(); s.setXdpi(316); s.setYdpi(316); s.setMultitouch(Multitouch.JAZZ_HANDS); s.setMechanism(TouchScreen.FINGER); s.setScreenType(ScreenType.CAPACITIVE); return s; } /** * Create a hardware profile with a reasonable set of defaults. */ private static Hardware createDefaultHardware() { Hardware h = new Hardware(); h.addNetwork(Network.BLUETOOTH); h.addNetwork(Network.WIFI); h.addNetwork(Network.NFC); h.addSensor(Sensor.BAROMETER); h.addSensor(Sensor.COMPASS); h.addSensor(Sensor.LIGHT_SENSOR); h.setHasMic(true); h.addInternalStorage(new Storage(4, Storage.Unit.GiB)); h.setCpu("Generic CPU"); h.setGpu("Generic GPU"); h.addSupportedAbi(Abi.ARMEABI); h.addSupportedAbi(Abi.ARMEABI_V7A); h.addSupportedAbi(Abi.MIPS); h.addSupportedAbi(Abi.X86); h.setChargeType(PowerType.BATTERY); return h; } /** * Get the resource bucket value that corresponds to the given size in inches. */ @NotNull private static ScreenSize getScreenSize(@Nullable Double diagonalSize) { if (diagonalSize == null) { return ScreenSize.NORMAL; } double diagonalDp = 160.0 * diagonalSize; // Set the Screen Size if (diagonalDp >= 1200) { return ScreenSize.XLARGE; } else if (diagonalDp >= 800) { return ScreenSize.LARGE; } else if (diagonalDp >= 568) { return ScreenSize.NORMAL; } else { return ScreenSize.SMALL; } } /** * Calculate the screen ratio. Beyond a 5:3 ratio is considered "long" */ @NotNull private static ScreenRatio getScreenRatio(int width, int height) { int longSide = Math.max(width, height); int shortSide = Math.min(width, height); // Above a 5:3 ratio is "long" if (longSide * 1.0 / shortSide >= 5.0 / 3) { return ScreenRatio.LONG; } else { return ScreenRatio.NOTLONG; } } /** * Calculate the density resource bucket for the given dots-per-inch */ @NotNull private static Density getDensity(double dpi) { double difference = Double.MAX_VALUE; Density bucket = Density.MEDIUM; for (Density d : Density.values()) { if (Math.abs(d.getDpiValue() - dpi) < difference) { difference = Math.abs(d.getDpiValue() - dpi); bucket = d; } } return bucket; } /** * Bind our controls to their state store keys */ private void registerComponents() { register(DEVICE_NAME_KEY, myDeviceName, LABEL_WITH_EDIT_LINK_COMPONENT_BINDING); setControlDescription(myDeviceName, "Actual Android Virtual Device size of the screen, measured as the screen's diagonal"); register(DIAGONAL_SCREENSIZE_KEY, myDiagonalScreenSize, DOUBLE_BINDING); setControlDescription(myDeviceName, "Actual Android Virtual Device size of the screen, measured as the screen's diagonal"); register(RESOLUTION_WIDTH_KEY, myScreenResolutionWidth, INT_BINDING); setControlDescription(myScreenResolutionWidth, "The total number of physical pixels on a screen. " + "When adding support for multiple screens, applications do not work directly " + "with resolution; applications should be concerned only with screen size and density," + " as specified by the generalized size and density groups.\n" + "\n" + "Width in pixels\n"); register(RESOLUTION_HEIGHT_KEY, myScreenResolutionHeight, INT_BINDING); setControlDescription(myScreenResolutionHeight, "The total number of physical pixels on a screen. " + "When adding support for multiple screens, applications do not work directly " + "with resolution; applications should be concerned only with screen size and density," + " as specified by the generalized size and density groups.\n" + "<br>" + "Height in pixels\n"); register(RAM_STORAGE_KEY, myRamField, myRamField.getBinding()); setControlDescription(myRamField, "The amount of physical RAM on the device."); register(HAS_HARDWARE_BUTTONS_KEY, myHasHardwareButtons); setControlDescription(myHasHardwareButtons, "Enables hardware navigation button support in Android Virtual Device"); register(HAS_HARDWARE_KEYBOARD_KEY, myHasHardwareKeyboard); setControlDescription(myHasHardwareKeyboard, "Enables hardware keyboard support in Android Virtual Device"); register(NAVIGATION_KEY, myNavigationControlsCombo, NAVIGATION_BINDING); setControlDescription(myNavigationControlsCombo, "No Navigation - No navigational controls \n" + "<br>" + "Directional Pad - Enables direction pad support in emulator\n" + "<br>" + "Trackball - Enables trackball support in emulator"); register(SUPPORTS_LANDSCAPE_KEY, mySupportsLandscape); setControlDescription(mySupportsLandscape, "Enables the landscape device screen state in emulator "); register(SUPPORTS_PORTRAIT_KEY, mySupportsPortrait); setControlDescription(mySupportsPortrait, "Enables the portrait device screen state in emulator "); register(HAS_BACK_CAMERA_KEY, myHasBackFacingCamera); setControlDescription(myHasBackFacingCamera, "Enables back-facing camera support in emulator"); register(HAS_FRONT_CAMERA_KEY, myHasFrontFacingCamera); setControlDescription(myHasFrontFacingCamera, "Enables front- facing camera support in emulator"); register(HAS_ACCELEROMETER_KEY, myHasAccelerometer); setControlDescription(myHasAccelerometer, "Enables accelerometer support in emulator"); register(HAS_GYROSCOPE_KEY, myHasGyroscope); setControlDescription(myHasGyroscope, "Enables gyroscope support in emulator"); register(HAS_GPS_KEY, myHasGps); setControlDescription(myHasGps, "Enables GPS (global positioning support) support in emulator"); register(HAS_PROXIMITY_SENSOR_KEY, myHasProximitySensor); setControlDescription(myHasProximitySensor, "Enables proximity sensor support in emulator"); } private static final ComponentBinding<Double, JTextField> DOUBLE_BINDING = new ComponentBinding<Double, JTextField>() { @Override public void setValue(@Nullable Double newValue, @NotNull JTextField component) { if (newValue != null) { component.setText(Double.toString(newValue)); } } @Nullable @Override public Double getValue(@NotNull JTextField component) { String text = component.getText(); if (text != null) { try { return Double.parseDouble(text); } catch (NumberFormatException e) { // Pass, will return null below } } return null; } @Nullable @Override public Document getDocument(@NotNull JTextField component) { return component.getDocument(); } }; private static final ComponentBinding<Integer, JTextField> INT_BINDING = new ComponentBinding<Integer, JTextField>() { @Override public void setValue(@Nullable Integer newValue, @NotNull JTextField component) { if (newValue != null) { component.setText(Integer.toString(newValue)); } } @Nullable @Override public Integer getValue(@NotNull JTextField component) { String text = component.getText(); if (text != null) { try { return Integer.parseInt(text); } catch (NumberFormatException e) { // Pass, will return null below } } return null; } @Nullable @Override public Document getDocument(@NotNull JTextField component) { return component.getDocument(); } }; private static final ComponentBinding<Navigation, JComboBox> NAVIGATION_BINDING = new ComponentBinding<Navigation, JComboBox>() { @Override public void setValue(@Nullable Navigation newValue, @NotNull JComboBox component) { component.setSelectedItem(newValue); } @Nullable @Override public Navigation getValue(@NotNull JComboBox component) { return (Navigation)component.getSelectedItem(); } @Override public void addItemListener(@NotNull ItemListener listener, @NotNull JComboBox component) { component.addItemListener(listener); } }; public static final ComponentBinding<String, LabelWithEditLink> LABEL_WITH_EDIT_LINK_COMPONENT_BINDING = new ComponentBinding<String, LabelWithEditLink>() { @Override public void setValue(@Nullable String newValue, @NotNull LabelWithEditLink component) { newValue = newValue == null ? "" : newValue; component.setText(newValue); } @Nullable @Override public String getValue(@NotNull LabelWithEditLink component) { return component.getText(); } @Nullable @Override public Document getDocument(@NotNull LabelWithEditLink component) { return component.getDocument(); } }; @NotNull @Override public String getStepName() { return "Configure Device Profile"; } @Override public JComponent getPreferredFocusedComponent() { return null; } }