/*
* Copyright (C) 2012 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.sdkuilib.internal.repository.ui;
import com.android.sdklib.devices.Device;
import com.android.sdklib.devices.DeviceManager;
import com.android.sdklib.devices.DeviceManager.DevicesChangedListener;
import com.android.sdklib.devices.Hardware;
import com.android.sdklib.devices.Screen;
import com.android.sdklib.devices.Storage;
import com.android.sdklib.devices.Storage.Unit;
import com.android.sdklib.internal.avd.AvdInfo;
import com.android.sdkuilib.internal.repository.UpdaterData;
import com.android.sdkuilib.internal.repository.icons.ImageFactory;
import com.android.sdkuilib.internal.widgets.AvdCreationDialog;
import com.android.sdkuilib.internal.widgets.AvdSelector;
import com.android.sdkuilib.internal.widgets.DeviceCreationDialog;
import com.android.sdkuilib.repository.ISdkChangeListener;
import com.android.sdkuilib.ui.GridDataBuilder;
import com.android.sdkuilib.ui.GridLayoutBuilder;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.Resource;
import org.eclipse.swt.graphics.TextLayout;
import org.eclipse.swt.graphics.TextStyle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A page displaying Device Manager entries.
* <p/>
* This is displayed as a second tab in the AVD Manager window.
* The layout purposely matches the one from {@link AvdManagerPage} and {@link AvdSelector}
* so that there's a good consistency when switching tabs.
* The table displays a few properties of each device as well as actions to edit/add/delete
* devices and a button to create an AVD from a given device.
*
* Non-goals: this tries to keep it simple for a first iteration. Possible enhancements:
* - a way to sort the device list by name, manufacturer or screen size.
* - possibly a tree organized by manufacturer.
* - a filter box to do a string search on any part of the display.
*/
public class DeviceManagerPage extends Composite
implements ISdkChangeListener, DevicesChangedListener, DisposeListener {
public interface IAvdCreatedListener {
public void onAvdCreated(AvdInfo createdAvdInfo);
}
private final UpdaterData mUpdaterData;
private final DeviceManager mDeviceManager;
private Table mTable;
private Button mNewButton;
private Button mEditButton;
private Button mDeleteButton;
private Button mNewAvdButton;
private Button mRefreshButton;
private ImageFactory mImageFactory;
private Image mUserImage;
private Image mGenericImage;
private Image mOtherImage;
private int mImageWidth;
private boolean mDisableRefresh;
private IAvdCreatedListener mAvdCreatedListener;
/**
* Create the composite.
* @param parent The parent of the composite.
* @param updaterData An instance of {@link UpdaterData}.
*/
public DeviceManagerPage(Composite parent,
int swtStyle,
UpdaterData updaterData,
DeviceManager deviceManager) {
super(parent, swtStyle);
mUpdaterData = updaterData;
mUpdaterData.addListeners(this);
mDeviceManager = deviceManager;
mDeviceManager.registerListener(this);
createContents(this);
postCreate(); //$hide$
}
public void setAvdCreatedListener(IAvdCreatedListener avdCreatedListener) {
mAvdCreatedListener = avdCreatedListener;
}
private void createContents(Composite parent) {
// get some bitmaps.
mImageFactory = new ImageFactory(parent.getDisplay());
mUserImage = mImageFactory.getImageByName("devman_user_16.png");
mGenericImage = mImageFactory.getImageByName("devman_generic_16.png");
mOtherImage = mImageFactory.getImageByName("devman_manufacturer_16.png");
mImageWidth = Math.max(mGenericImage.getImageData().width,
Math.max(mUserImage.getImageData().width,
mOtherImage.getImageData().width));
// Layout has 2 columns
GridLayoutBuilder.create(parent).columns(2);
// Insert a top label explanation. This matches the design in AvdManagerPage so
// that the table starts at the same height on both tabs.
Label label = new Label(parent, SWT.NONE);
label.setText("List of known device definitions. This can later be used to create Android Virtual Devices.");
GridDataBuilder.create(label).hSpan(2);
// Device table.
mTable = new Table(parent, SWT.FULL_SELECTION | SWT.SINGLE | SWT.BORDER);
mTable.setHeaderVisible(true);
mTable.setLinesVisible(true);
mTable.setFont(parent.getFont());
setTableHeightHint(30);
// Buttons on the side.
Composite buttons = new Composite(parent, SWT.NONE);
GridLayoutBuilder.create(buttons).columns(1).noMargins();
GridDataBuilder.create(buttons).vFill();
buttons.setFont(parent.getFont());
mNewButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
mNewButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mNewButton.setText("New Device...");
mNewButton.setToolTipText("Creates a new user device definition.");
mNewButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
onNewDevice();
}
});
mEditButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
mEditButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mEditButton.setText("Edit...");
mEditButton.setToolTipText("Edit an existing device definition.");
mEditButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
onEditDevice();
}
});
mDeleteButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
mDeleteButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mDeleteButton.setText("Delete...");
mDeleteButton.setToolTipText("Deletes the selected AVD.");
mDeleteButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
onDeleteDevice();
}
});
@SuppressWarnings("unused")
Label spacing = new Label(buttons, SWT.NONE);
mNewAvdButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
mNewAvdButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mNewAvdButton.setText("Create AVD...");
mNewAvdButton.setToolTipText("Creates a new AVD based on this device definition.");
mNewAvdButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
onCreateAvd();
}
});
Composite padding = new Composite(buttons, SWT.NONE);
padding.setLayoutData(new GridData(GridData.FILL_VERTICAL));
mRefreshButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
mRefreshButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mRefreshButton.setText("Refresh");
mRefreshButton.setToolTipText("Reloads the list of devices.");
mRefreshButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
onRefresh();
}
});
// Legend at the bottom.
// This matches the one on AvdSelector so that the table height in the tab be similar.
Composite legend = new Composite(parent, SWT.NONE);
GridLayoutBuilder.create(legend).columns(4).noMargins();
GridDataBuilder.create(legend).hFill().vTop().hGrab().hSpan(2);
legend.setFont(parent.getFont());
new Label(legend, SWT.NONE).setImage(mUserImage);
new Label(legend, SWT.NONE).setText("A user-created device definition.");
new Label(legend, SWT.NONE).setImage(mGenericImage);
new Label(legend, SWT.NONE).setText("A generic device definition.");
Label icon = new Label(legend, SWT.NONE);
icon.setImage(mOtherImage);
Label l = new Label(legend, SWT.NONE);
l.setText("A manufacturer-specific device definition.");
GridData gd;
l.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalSpan = 3;
icon.setVisible(false);
l.setVisible(false);
// create the table columns
final TableColumn column0 = new TableColumn(mTable, SWT.NONE);
column0.setText("Device");
adjustColumnsWidth(mTable, column0);
setupSelectionListener(mTable);
fillTable(mTable);
updateButtonStates();
setEnabled(true);
}
private void adjustColumnsWidth(final Table table, final TableColumn column0) {
// Add a listener to resize the column to the full width of the table
table.addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
Rectangle r = table.getClientArea();
column0.setWidth(r.width * 100 / 100 - 1); // 100%
}
});
}
private void setupSelectionListener(Table table) {
// TODO Auto-generated method stub
}
/**
* Sets the table grid layout data.
*
* @param heightHint If > 0, the height hint is set to the requested value.
*/
public void setTableHeightHint(int heightHint) {
GridData data = new GridData();
if (heightHint > 0) {
data.heightHint = heightHint;
}
data.grabExcessVerticalSpace = true;
data.grabExcessHorizontalSpace = true;
data.horizontalAlignment = GridData.FILL;
data.verticalAlignment = GridData.FILL;
mTable.setLayoutData(data);
}
@Override
public void widgetDisposed(DisposeEvent e) {
dispose();
}
@Override
public void dispose() {
mUpdaterData.removeListener(this);
mDeviceManager.unregisterListener(this);
super.dispose();
}
@Override
protected void checkSubclass() {
// Disable the check that prevents subclassing of SWT components
}
// -- Start of internal part ----------
// Hide everything down-below from SWT designer
//$hide>>$
/**
* Called by the constructor right after {@link #createContents(Composite)}.
*/
private void postCreate() {
// nothing to be done for now.
}
// -------
private static class CellInfo {
final boolean mIsUser;
final Device mDevice;
final TextLayout mWidget;
Rectangle mBounds;
CellInfo(boolean isUser, Device device, TextLayout widget) {
mIsUser = isUser;
mDevice = device;
mWidget = widget;
}
}
private void fillTable(final Table table) {
table.removeAll();
disposeTableResources(table.getData("disposeResources"));
final List<Resource> disposables = new ArrayList<Resource>();
Font boldFont = getBoldFont(table);
if (boldFont != null) {
disposables.add(boldFont);
} else {
boldFont = table.getFont();
}
try {
mDisableRefresh = true;
disposables.addAll(fillDevices(table, boldFont, true,
mDeviceManager.getDevices(DeviceManager.USER_DEVICES)));
disposables.addAll(fillDevices(table, boldFont, false,
mDeviceManager.getDevices(DeviceManager.DEFAULT_DEVICES |
DeviceManager.VENDOR_DEVICES)));
} finally {
mDisableRefresh = false;
}
table.setData("disposeResources", disposables);
if (!Boolean.TRUE.equals(table.getData("createdTableListeners"))) {
table.addListener(SWT.PaintItem, new Listener() {
@Override
public void handleEvent(Event event) {
if (event.item != null) {
Object info = event.item.getData();
if (info instanceof CellInfo) {
((CellInfo) info).mWidget.draw(event.gc, event.x, event.y + 1);
}
}
}
});
table.addListener(SWT.MeasureItem, new Listener() {
@Override
public void handleEvent(Event event) {
if (event.item != null) {
Object info = event.item.getData();
if (info instanceof CellInfo) {
CellInfo ci = (CellInfo) info;
Rectangle bounds = ci.mBounds;
if (bounds == null) {
// TextLayout.getBounds() seems expensive, so let's cache it.
ci.mBounds = bounds = ci.mWidget.getBounds();
}
event.width = bounds.width + 2;
event.height = bounds.height + 4;
}
}
}
});
table.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent event) {
disposeTableResources(table.getData("disposeResources"));
}
});
table.addSelectionListener(new SelectionListener() {
/** Handles single clicks on a row. */
@Override
public void widgetSelected(SelectionEvent event) {
updateButtonStates();
}
/** Handles double click on a row. */
@Override
public void widgetDefaultSelected(SelectionEvent event) {
// FIXME: should double-click be to edit a device or create a new AVD?
onEditDevice();
}
});
}
if (table.getItemCount() == 0) {
table.setEnabled(true);
TableItem item = new TableItem(table, SWT.NONE);
item.setData(null);
item.setText(0, "No devices available");
return;
}
table.setData("createdTableListeners", Boolean.TRUE);
}
private void disposeTableResources(Object disposablesList) {
if (disposablesList instanceof List<?>) {
for (Object obj : (List<?>) disposablesList) {
if (obj instanceof Resource) {
((Resource) obj).dispose();
}
}
}
}
private Font getBoldFont(Table table) {
Display display = table.getDisplay();
FontData[] fds = table.getFont().getFontData();
if (fds != null && fds.length > 0) {
fds[0].setStyle(SWT.BOLD);
return new Font(display, fds[0]);
}
return null;
}
private List<Resource> fillDevices(
Table table,
Font boldFont,
boolean isUser,
List<Device> devices) {
List<Resource> disposables = new ArrayList<Resource>();
Display display = table.getDisplay();
TextStyle boldStyle = new TextStyle();
boldStyle.font = boldFont;
// We need the list to be be modifiable so that we can sort it.
devices = new ArrayList<Device>(devices);
if (isUser) {
// Just sort user devices by alphabetical name. They will show up at the top.
Collections.sort(devices, new Comparator<Device>() {
@Override
public int compare(Device d1, Device d2) {
String s1 = d1 == null ? "" : d1.getName();
String s2 = d2 == null ? "" : d2.getName();
return s1.compareTo(s2);
}});
} else {
// Sort non-user devices by descending "pretty name"
// TODO revisit. Doesn't perform as well as expected.
Collections.sort(devices, new Comparator<Device>() {
@Override
public int compare(Device d1, Device d2) {
String s1 = getPrettyName(d1, true /*leadZeroes*/);
String s2 = getPrettyName(d2, true /*leadZeroes*/);
return s2.compareTo(s1);
}});
}
// Generate a list of the AVD names using these devices
Map<Device, List<String>> device2avdMap = new HashMap<Device, List<String>>();
for (AvdInfo avd : mUpdaterData.getAvdManager().getAllAvds()) {
String n = avd.getDeviceName();
String m = avd.getDeviceManufacturer();
if (n == null || m == null || n.isEmpty() || m.isEmpty()) {
continue;
}
for (Device device : devices) {
if (m.equals(device.getManufacturer()) && n.equals(device.getName())) {
List<String> list = device2avdMap.get(device);
if (list == null) {
list = new LinkedList<String>();
device2avdMap.put(device, list);
}
list.add(avd.getName());
}
}
}
final String prefix = "\n ";
for (Device device : devices) {
TableItem item = new TableItem(table, SWT.NONE);
TextLayout widget = new TextLayout(display);
CellInfo ci = new CellInfo(isUser, device, widget);
item.setData(ci);
widget.setIndent(mImageWidth * 2);
widget.setFont(table.getFont());
StringBuilder sb = new StringBuilder();
String name = getPrettyName(device, false /*leadZeroes*/);
sb.append(name);
int pos1 = sb.length();
String manufacturer = device.getManufacturer();
String manu = manufacturer;
if (isUser) {
item.setImage(mUserImage);
} else if (GENERIC.equals(manu)) {
item.setImage(mGenericImage);
} else {
item.setImage(mOtherImage);
if (!manufacturer.contains(NEXUS)) {
sb.append(" by ").append(manufacturer);
}
}
Hardware hw = device.getDefaultHardware();
Screen screen = hw.getScreen();
sb.append(prefix);
sb.append(String.format(java.util.Locale.US,
"Screen: %1$.1f\", %2$d \u00D7 %3$d, %4$s %5$s", // U+00D7: Unicode multiplication sign
screen.getDiagonalLength(),
screen.getXDimension(),
screen.getYDimension(),
screen.getSize().getShortDisplayValue(),
screen.getPixelDensity().getResourceValue()
));
Storage sto = hw.getRam();
Unit unit = sto.getSizeAsUnit(Unit.GiB) > 1 ? Unit.GiB : Unit.MiB;
sb.append(prefix);
sb.append(String.format(java.util.Locale.US,
"RAM: %1$d %2$s",
sto.getSizeAsUnit(unit),
unit));
List<String> avdList = device2avdMap.get(device);
if (avdList != null && !avdList.isEmpty()) {
sb.append(prefix);
sb.append("Used by: ");
boolean first = true;
for (String avd : avdList) {
if (!first) {
sb.append(", ");
}
sb.append(avd);
first = false;
}
}
widget.setText(sb.toString());
widget.setStyle(boldStyle, 0, pos1);
}
return disposables;
}
// Constants extracted from DeviceMenuListerner -- TODO refactor somewhere else.
private static final String NEXUS = "Nexus"; //$NON-NLS-1$
private static final String GENERIC = "Generic"; //$NON-NLS-1$
private static Pattern PATTERN = Pattern.compile(
"(\\d+\\.?\\d*)in (.+?)( \\(.*Nexus.*\\))?"); //$NON-NLS-1$
/**
* Returns a pretty name for the device.
*
* Extracted from DeviceMenuListener.
* Modified to remove the leading space insertion as it doesn't render
* neatly in the avd manager. Instead added the option to add leading
* zeroes to make the string names sort properly.
*
* Replace "'in'" with '"' (e.g. 2.7" QVGA instead of 2.7in QVGA)
* Use the same precision for all devices (all but one specify decimals)
* Add in screen resolution and density
*/
private static String getPrettyName(Device d, boolean leadZeroes) {
if (d == null) {
return "";
}
String name = d.getName();
if (name.equals("3.7 FWVGA slider")) { //$NON-NLS-1$
// Fix metadata: this one entry doesn't have "in" like the rest of them
name = "3.7in FWVGA slider"; //$NON-NLS-1$
}
Matcher matcher = PATTERN.matcher(name);
if (matcher.matches()) {
String size = matcher.group(1);
String n = matcher.group(2);
int dot = size.indexOf('.');
if (dot == -1) {
size = size + ".0";
dot = size.length() - 2;
}
if (leadZeroes && dot < 3) {
// Pad to have at least 3 digits before the dot, for sorting purposes.
// We can revisit this once we get devices that are more than 999 inches wide.
size = "000".substring(dot) + size;
}
name = size + "\" " + n;
}
return name;
}
/**
* Returns the currently selected cell info in the table or null
*/
private CellInfo getTableSelection() {
if (mTable.isDisposed()) {
return null;
}
int selIndex = mTable.getSelectionIndex();
if (selIndex >= 0) {
return (CellInfo) mTable.getItem(selIndex).getData();
}
return null;
}
private void updateButtonStates() {
CellInfo ci = getTableSelection();
mNewButton.setEnabled(true);
mEditButton.setEnabled(ci != null);
mEditButton.setText((ci != null && !ci.mIsUser) ? "Clone..." : "Edit...");
mDeleteButton.setEnabled(ci != null && ci.mIsUser);
mNewAvdButton.setEnabled(ci != null);
mRefreshButton.setEnabled(true);
}
private void onNewDevice() {
DeviceCreationDialog dlg = new DeviceCreationDialog(
getShell(),
mDeviceManager,
mUpdaterData.getImageFactory(),
null /*device*/);
if (dlg.open() == Window.OK) {
onRefresh();
// Select the new device, if any.
selectCellByDevice(dlg.getCreatedDevice());
updateButtonStates();
}
}
private void onEditDevice() {
CellInfo ci = getTableSelection();
if (ci == null || ci.mDevice == null) {
return;
}
DeviceCreationDialog dlg = new DeviceCreationDialog(
getShell(),
mDeviceManager,
mUpdaterData.getImageFactory(),
ci.mDevice);
if (dlg.open() == Window.OK) {
onRefresh();
// Select the new device, if any.
selectCellByDevice(dlg.getCreatedDevice());
updateButtonStates();
}
}
private void onDeleteDevice() {
CellInfo ci = getTableSelection();
if (ci == null || ci.mDevice == null || !ci.mIsUser) {
return;
}
final String name = getPrettyName(ci.mDevice, false /*leadZeroes*/);
final AtomicBoolean result = new AtomicBoolean(false);
getDisplay().syncExec(new Runnable() {
@Override
public void run() {
Shell shell = getDisplay().getActiveShell();
boolean ok = MessageDialog.openQuestion(shell,
"Delete Device Definition",
String.format(
"Please confirm that you want to delete the device definition named '%s'. This operation cannot be reverted.",
name));
result.set(ok);
}
});
if (result.get()) {
mDeviceManager.removeUserDevice(ci.mDevice);
mDeviceManager.saveUserDevices();
onRefresh();
}
}
private void onCreateAvd() {
CellInfo ci = getTableSelection();
if (ci == null || ci.mDevice == null) {
return;
}
final AvdCreationDialog dlg = new AvdCreationDialog(mTable.getShell(),
mUpdaterData.getAvdManager(),
mImageFactory,
mUpdaterData.getSdkLog(),
null);
dlg.selectInitialDevice(ci.mDevice);
if (dlg.open() == Window.OK) {
onRefresh();
if (mAvdCreatedListener != null) {
mAvdCreatedListener.onAvdCreated(dlg.getCreatedAvd());
}
}
}
private void onRefresh() {
if (mDisableRefresh || mTable.isDisposed()) {
return;
}
int selIndex = mTable.getSelectionIndex();
CellInfo selected = getTableSelection();
fillTable(mTable);
if (selected != null) {
if (selectCellByName(selected)) {
updateButtonStates();
return;
}
}
// If not found by name, use the position if available.
if (selIndex >= 0 && selIndex < mTable.getItemCount()) {
mTable.select(selIndex);
}
}
private boolean selectCellByName(CellInfo selected) {
if (mTable.isDisposed() || selected == null || selected.mDevice == null) {
return false;
}
String name = selected.mDevice.getName();
for (int n = mTable.getItemCount() - 1; n >= 0; n--) {
TableItem item = mTable.getItem(n);
Object data = item.getData();
if (data instanceof CellInfo) {
CellInfo ci = (CellInfo) data;
if (ci != null && ci.mDevice != null && name.equals(ci.mDevice.getName())) {
// Same cell object. Select it.
mTable.select(n);
return true;
}
}
}
return false;
}
private boolean selectCellByDevice(Device selected) {
if (mTable.isDisposed() || selected == null) {
return false;
}
for (int n = mTable.getItemCount() - 1; n >= 0; n--) {
TableItem item = mTable.getItem(n);
Object data = item.getData();
if (data instanceof CellInfo) {
CellInfo ci = (CellInfo) data;
if (ci != null && ci.mDevice == selected) {
// Same device object. Select it.
mTable.select(n);
return true;
}
}
}
return false;
}
// -------
// --- Implementation of ISdkChangeListener ---
@Override
public void onSdkLoaded() {
onSdkReload();
}
@Override
public void onSdkReload() {
onRefresh();
}
@Override
public void preInstallHook() {
// nothing to be done for now.
}
@Override
public void postInstallHook() {
// nothing to be done for now.
}
// --- Implementation of DevicesChangeListener
@Override
public void onDevicesChanged() {
onRefresh();
}
// End of hiding from SWT Designer
//$hide<<$
}