diff --git a/base/uk.ac.stfc.isis.ibex.configserver.tests/src/uk/ac/stfc/isis/ibex/configserver/tests/internal/ImportConverterTest.java b/base/uk.ac.stfc.isis.ibex.configserver.tests/src/uk/ac/stfc/isis/ibex/configserver/tests/internal/ImportConverterTest.java new file mode 100644 index 0000000000..9c36959bf2 --- /dev/null +++ b/base/uk.ac.stfc.isis.ibex.configserver.tests/src/uk/ac/stfc/isis/ibex/configserver/tests/internal/ImportConverterTest.java @@ -0,0 +1,121 @@ +/* +* This file is part of the ISIS IBEX application. +* Copyright (C) 2012-2023 Science & Technology Facilities Council. +* All rights reserved. +* +* This program is distributed in the hope that it will be useful. +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License v1.0 which accompanies this distribution. +* EXCEPT AS EXPRESSLY SET FORTH IN THE ECLIPSE PUBLIC LICENSE V1.0, THE PROGRAM +* AND ACCOMPANYING MATERIALS ARE PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES +* OR CONDITIONS OF ANY KIND. See the Eclipse Public License v1.0 for more details. +* +* You should have received a copy of the Eclipse Public License v1.0 +* along with this program; if not, you can obtain a copy from +* https://www.eclipse.org/org/documents/epl-v10.php or +* http://opensource.org/licenses/eclipse-1.0.php +*/ + +package uk.ac.stfc.isis.ibex.configserver.tests.internal; + +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import org.junit.BeforeClass; +import org.junit.Test; + +import uk.ac.stfc.isis.ibex.configserver.configuration.Block; +import uk.ac.stfc.isis.ibex.configserver.configuration.Configuration; +import uk.ac.stfc.isis.ibex.configserver.configuration.Group; +import uk.ac.stfc.isis.ibex.configserver.configuration.Ioc; +import uk.ac.stfc.isis.ibex.configserver.configuration.Macro; +import uk.ac.stfc.isis.ibex.configserver.configuration.Macro.HasDefault; +import uk.ac.stfc.isis.ibex.configserver.configuration.PVDefaultValue; +import uk.ac.stfc.isis.ibex.configserver.configuration.PVSet; +import uk.ac.stfc.isis.ibex.configserver.editing.EditableConfiguration; +import uk.ac.stfc.isis.ibex.configserver.editing.EditableIoc; +import uk.ac.stfc.isis.ibex.configserver.internal.ImportConverter; + +public class ImportConverterTest { + + private final static String SOURCE_PREFIX = "SOURCE"; + private final static String DESTINATION_PREFIX = "DESTINATION"; + private final static String COMPONENT_NAME = "COMP"; + private final static String COMPONENT_DESC = "DESC"; + private final static String IOC_NAME = "IOC_01"; + private final static boolean IOC_AUTOSTART = true; + private final static boolean IOC_RESTART = true; + + private final static Macro MACRO = new Macro("MACRO", "10", "DESC", "pattern", "1", HasDefault.YES); + private final static PVDefaultValue PV = new PVDefaultValue(SOURCE_PREFIX.concat(":PV"), "10"); + private final static PVSet PV_SET = new PVSet(SOURCE_PREFIX.concat(":PV_SET"), true); + private final static Block BLOCK = new Block("BLOCK", SOURCE_PREFIX.concat(":PV"), true, true); + private final static Group GROUP = new Group("GROUP", Arrays.asList("BLOCK"), null); + + private static Collection editableIocs; + private static Configuration source; + private static Configuration empty; + private static EditableConfiguration destination; + + + @BeforeClass + public static void setUp() { + editableIocs = new ArrayList(); + EditableIoc editableIoc = new EditableIoc(IOC_NAME); + editableIoc.setAutostart(IOC_AUTOSTART); + editableIoc.setRestart(IOC_RESTART); + editableIoc.setMacros(Arrays.asList(MACRO)); + editableIoc.setPvs(Arrays.asList(PV)); + editableIoc.setPvSets(Arrays.asList(PV_SET)); + editableIocs.add(editableIoc); + + source = new Configuration(COMPONENT_NAME, COMPONENT_DESC, null, Arrays.asList(new Ioc(editableIoc)), Arrays.asList(BLOCK), + Arrays.asList(GROUP), Collections.emptyList(), Collections.emptyList(), false, false, false); + empty = new Configuration("EMPTY", "EMTPY DESC", null, Collections.emptyList(), Collections.emptyList(), + Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), false, false, false); + destination = new EditableConfiguration(empty, Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + + ImportConverter.convert(source, destination, SOURCE_PREFIX, DESTINATION_PREFIX, editableIocs); + } + + @Test + public void GIVEN_ioc_WHEN_component_converted_THEN_ioc_values_converted_correctly() { + var ioc = destination.getAddedIocs().stream().findFirst().get(); + assertTrue(ioc.getName().equals(IOC_NAME)); + assertTrue(ioc.getAutostart() == IOC_AUTOSTART); + assertTrue(ioc.getRestart() == IOC_RESTART); + assertTrue(ioc.getMacros().contains(MACRO)); + + var pv = ioc.getPvs().stream().findFirst().get(); + assertTrue(pv.getName().equals(DESTINATION_PREFIX.concat(":PV"))); + assertTrue(pv.getValue().equals(PV.getValue())); + + var pvSet = ioc.getPvSets().stream().findFirst().get(); + assertTrue(pvSet.getName().equals(DESTINATION_PREFIX.concat(":PV_SET"))); + assertTrue(pvSet.getEnabled() == PV_SET.getEnabled()); + } + + @Test + public void GIVEN_block_WHEN_component_converted_THEN_block_values_converted_correctly() { + var block = destination.getAllBlocks().stream().findFirst().get(); + assertTrue(block.getName().equals(BLOCK.getName())); + assertTrue(block.getPV().equals(DESTINATION_PREFIX.concat(":PV"))); + assertTrue(block.getIsLocal()); + assertTrue(block.getIsVisible()); + } + + @Test + public void GIVEN_group_with_block_WHEN_component_converted_THEN_group_values_converted_correctly() { + var group = destination.getEditableGroups().stream().findFirst().get(); + assertTrue(group.getName().equals(GROUP.getName())); + assertTrue(group.getBlocks().stream().findFirst().get().equals(BLOCK.getName())); + + var block = group.getSelectedBlocks().stream().findFirst().get(); + assertTrue(block.getName().equals(block.getName())); + assertTrue(block.getPV().equals(DESTINATION_PREFIX.concat(":PV"))); + } +} diff --git a/base/uk.ac.stfc.isis.ibex.configserver/META-INF/MANIFEST.MF b/base/uk.ac.stfc.isis.ibex.configserver/META-INF/MANIFEST.MF index 6c59481b1a..8d243d2860 100644 --- a/base/uk.ac.stfc.isis.ibex.configserver/META-INF/MANIFEST.MF +++ b/base/uk.ac.stfc.isis.ibex.configserver/META-INF/MANIFEST.MF @@ -41,6 +41,7 @@ Export-Package: uk.ac.stfc.isis.ibex.configserver; uk.ac.stfc.isis.ibex.configserver, uk.ac.stfc.isis.ibex.configserver.configuration, uk.ac.stfc.isis.ibex.epics.observing", - uk.ac.stfc.isis.ibex.configserver.internal + uk.ac.stfc.isis.ibex.configserver.internal, + uk.ac.stfc.isis.ibex.configserver.json Import-Package: org.eclipse.wb.swt Automatic-Module-Name: uk.ac.stfc.isis.ibex.configserver diff --git a/base/uk.ac.stfc.isis.ibex.configserver/src/uk/ac/stfc/isis/ibex/configserver/internal/ImportConverter.java b/base/uk.ac.stfc.isis.ibex.configserver/src/uk/ac/stfc/isis/ibex/configserver/internal/ImportConverter.java new file mode 100644 index 0000000000..f1ae5ad804 --- /dev/null +++ b/base/uk.ac.stfc.isis.ibex.configserver/src/uk/ac/stfc/isis/ibex/configserver/internal/ImportConverter.java @@ -0,0 +1,137 @@ +/* + * This file is part of the ISIS IBEX application. + * Copyright (C) 2012-2023 Science & Technology Facilities Council. + * All rights reserved. + * + * This program is distributed in the hope that it will be useful. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution. + * EXCEPT AS EXPRESSLY SET FORTH IN THE ECLIPSE PUBLIC LICENSE V1.0, THE PROGRAM + * AND ACCOMPANYING MATERIALS ARE PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND. See the Eclipse Public License v1.0 for more details. + * + * You should have received a copy of the Eclipse Public License v1.0 + * along with this program; if not, you can obtain a copy from + * https://www.eclipse.org/org/documents/epl-v10.php or + * http://opensource.org/licenses/eclipse-1.0.php + */ + +package uk.ac.stfc.isis.ibex.configserver.internal; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.stream.Collectors; + +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.swt.widgets.Display; + +import uk.ac.stfc.isis.ibex.configserver.configuration.Configuration; +import uk.ac.stfc.isis.ibex.configserver.configuration.PVDefaultValue; +import uk.ac.stfc.isis.ibex.configserver.configuration.PVSet; +import uk.ac.stfc.isis.ibex.configserver.editing.DuplicateBlockNameException; +import uk.ac.stfc.isis.ibex.configserver.editing.EditableBlock; +import uk.ac.stfc.isis.ibex.configserver.editing.EditableConfiguration; +import uk.ac.stfc.isis.ibex.configserver.editing.EditableIoc; +import uk.ac.stfc.isis.ibex.logger.IsisLog; +import uk.ac.stfc.isis.ibex.logger.LoggerUtils; + +/** + * Handles the changes to be done when importing a component from another instrument. + */ +public final class ImportConverter { + + private ImportConverter() { } + + /** + * Adds the IOCs, blocks, and groups of the imported component to the local editable component. + * + * @param source The imported component. + * @param destination The editable component that will be added. + * @param sourcePrefix The remote instrument prefix. + * @param destinationPrefix The local instrument prefix. + * @param editableIocs The local IOCs. + */ + public static void convert(Configuration source, EditableConfiguration destination, String sourcePrefix, String destinationPrefix, Collection editableIocs) { + destination.setName(source.getName()); + destination.setDescription(source.description()); + destination.setIsProtected(source.isProtected()); + destination.setIsDynamic(source.isDynamic()); + + convertIocs(source, destination, sourcePrefix, destinationPrefix, editableIocs); + convertBlocks(source, destination, sourcePrefix, destinationPrefix); + convertGroups(source, destination); + } + + private static void convertIocs(Configuration source, EditableConfiguration destination, String sourcePrefix, String destinationPrefix, Collection editableIocs) { + for (var ioc : source.getIocs()) { + for (var editableIoc : editableIocs) { + if (ioc.getName().equals(editableIoc.getName())) { + editableIoc.setAutostart(ioc.getAutostart()); + editableIoc.setRestart(ioc.getRestart()); + + editableIoc.setMacros(ioc.getMacros()); + + editableIoc.setPvs(ioc.getPvs().stream() + .map(e -> new PVDefaultValue(e.getName().replace(sourcePrefix, destinationPrefix), e.getValue())) + .collect(Collectors.toList())); + + editableIoc.setPvSets(ioc.getPvSets().stream() + .map(e -> new PVSet(e.getName().replace(sourcePrefix, destinationPrefix), e.getEnabled())) + .collect(Collectors.toList())); + + destination.addIoc(editableIoc); + break; + } + } + } + } + + private static void convertBlocks(Configuration source, EditableConfiguration destination, String sourcePrefix, String destinationPrefix) { + for (var block : source.getBlocks()) { + var editableBlock = new EditableBlock(block); + + if (editableBlock.getIsLocal()) { + editableBlock.setPV(editableBlock.getPV().replace(sourcePrefix, destinationPrefix)); + } + + try { + destination.addNewBlock(editableBlock); + } catch (DuplicateBlockNameException e) { + LoggerUtils.logErrorWithStackTrace(IsisLog.getLogger(ImportConverter.class), e.getMessage(), e); + new MessageDialog(Display.getCurrent().getActiveShell(), "Error", null, + "Failed to add block " + block.getName() + ":\nBlock with this name already exists.", + MessageDialog.ERROR, new String[] {"OK"}, 0).open(); + } + } + } + + private static void convertGroups(Configuration source, EditableConfiguration destination) { + // After blocks have been added to the destination configuration, add them to their respective groups. + // We need to use the newly added Editable Blocks when we toggle the selection for each new Editable Group. + + var destinationBlocks = destination.getAllBlocks(); + var sourceGroups = source.getGroups() + .stream() + .filter(g -> DisplayUtils.filterNoneGroup(g.getName())) + .collect(Collectors.toList()); + + // Create the destination groups. + for (var sourceGroup : sourceGroups) { + var newGroup = destination.addNewGroup(); + newGroup.setName(sourceGroup.getName()); + + // Get the toggled blocks. + var blocksToToggle = new ArrayList(); + for (var sourceBlockName : sourceGroup.getBlocks()) { + for (var destinationBlock : destinationBlocks) { + if (destinationBlock.getName().equals(sourceBlockName)) { + blocksToToggle.add(destinationBlock); + break; + } + } + } + + newGroup.toggleSelection(blocksToToggle); + } + } +} diff --git a/base/uk.ac.stfc.isis.ibex.e4.client/Application.e4xmi b/base/uk.ac.stfc.isis.ibex.e4.client/Application.e4xmi index d6f3fa54b9..a65016fea1 100644 --- a/base/uk.ac.stfc.isis.ibex.e4.client/Application.e4xmi +++ b/base/uk.ac.stfc.isis.ibex.e4.client/Application.e4xmi @@ -20,6 +20,7 @@ + @@ -98,6 +99,7 @@ + @@ -612,6 +614,7 @@ + diff --git a/base/uk.ac.stfc.isis.ibex.ui.configserver.tests/src/uk/ac/stfc/isis/ibex/ui/configserver/editing/components/tests/ComponentNameSearchTest.java b/base/uk.ac.stfc.isis.ibex.ui.configserver.tests/src/uk/ac/stfc/isis/ibex/ui/configserver/editing/components/tests/ComponentNameSearchTest.java new file mode 100644 index 0000000000..5d124a592a --- /dev/null +++ b/base/uk.ac.stfc.isis.ibex.ui.configserver.tests/src/uk/ac/stfc/isis/ibex/ui/configserver/editing/components/tests/ComponentNameSearchTest.java @@ -0,0 +1,74 @@ +/* +* This file is part of the ISIS IBEX application. +* Copyright (C) 2012-2023 Science & Technology Facilities Council. +* All rights reserved. +* +* This program is distributed in the hope that it will be useful. +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License v1.0 which accompanies this distribution. +* EXCEPT AS EXPRESSLY SET FORTH IN THE ECLIPSE PUBLIC LICENSE V1.0, THE PROGRAM +* AND ACCOMPANYING MATERIALS ARE PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES +* OR CONDITIONS OF ANY KIND. See the Eclipse Public License v1.0 for more details. +* +* You should have received a copy of the Eclipse Public License v1.0 +* along with this program; if not, you can obtain a copy from +* https://www.eclipse.org/org/documents/epl-v10.php or +* http://opensource.org/licenses/eclipse-1.0.php +*/ + +package uk.ac.stfc.isis.ibex.ui.configserver.editing.components.tests; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.junit.Before; +import org.junit.Test; + +import uk.ac.stfc.isis.ibex.configserver.configuration.Configuration; +import uk.ac.stfc.isis.ibex.ui.configserver.editing.components.ComponentNameSearch; + +public class ComponentNameSearchTest { + + private ComponentNameSearch search; + + private final String COMPONENT_NAME = "COMP_1"; + private final Configuration component = mockComponent(COMPONENT_NAME); + + private Configuration mockComponent(String name) { + Configuration component = mock(Configuration.class); + when(component.getName()).thenReturn(name); + return component; + } + + + @Before + public void setUp() { + search = new ComponentNameSearch(); + } + + @Test + public void GIVEN_empty_match_WHEN_search_THEN_match() { + search.setSearchText(""); + assertTrue(search.select(null, null, component)); + } + + @Test + public void GIVEN_full_match_WHEN_search_THEN_match() { + search.setSearchText(COMPONENT_NAME); + assertTrue(search.select(null, null, component)); + } + + @Test + public void GIVEN_partial_match_WHEN_search_THEN_match() { + search.setSearchText(COMPONENT_NAME.substring(0, 2)); + assertTrue(search.select(null, null, component)); + } + + @Test + public void GIVEN_no_match_WHEN_search_THEN_no_match() { + search.setSearchText("CONFIG_1"); + assertFalse(search.select(null, null, component)); + } +} diff --git a/base/uk.ac.stfc.isis.ibex.ui.configserver/META-INF/MANIFEST.MF b/base/uk.ac.stfc.isis.ibex.ui.configserver/META-INF/MANIFEST.MF index 52507a1416..f7bd73f7a0 100644 --- a/base/uk.ac.stfc.isis.ibex.ui.configserver/META-INF/MANIFEST.MF +++ b/base/uk.ac.stfc.isis.ibex.ui.configserver/META-INF/MANIFEST.MF @@ -26,8 +26,10 @@ Require-Bundle: org.eclipse.ui, uk.ac.stfc.isis.ibex.logger, uk.ac.stfc.isis.ibex.managermode, org.csstudio.csdata;bundle-version="3.2.0", - org.csstudio.ui.util;bundle-version="4.0.2" + org.csstudio.ui.util;bundle-version="4.0.2", + uk.ac.stfc.isis.ibex.instrument Bundle-RequiredExecutionEnvironment: JavaSE-17 Bundle-ActivationPolicy: lazy Automatic-Module-Name: uk.ac.stfc.isis.ibex.ui.configserver -Import-Package: uk.ac.stfc.isis.ibex.ui.ioccontrol +Import-Package: uk.ac.stfc.isis.ibex.ui.ioccontrol, + uk.ac.stfc.isis.ibex.ui.mainmenu.instrument diff --git a/base/uk.ac.stfc.isis.ibex.ui.configserver/src/uk/ac/stfc/isis/ibex/ui/configserver/ImportVariables.java b/base/uk.ac.stfc.isis.ibex.ui.configserver/src/uk/ac/stfc/isis/ibex/ui/configserver/ImportVariables.java new file mode 100644 index 0000000000..779bb1b709 --- /dev/null +++ b/base/uk.ac.stfc.isis.ibex.ui.configserver/src/uk/ac/stfc/isis/ibex/ui/configserver/ImportVariables.java @@ -0,0 +1,149 @@ +/* + * This file is part of the ISIS IBEX application. + * Copyright (C) 2012-2023 Science & Technology Facilities Council. + * All rights reserved. + * + * This program is distributed in the hope that it will be useful. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution. + * EXCEPT AS EXPRESSLY SET FORTH IN THE ECLIPSE PUBLIC LICENSE V1.0, THE PROGRAM + * AND ACCOMPANYING MATERIALS ARE PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND. See the Eclipse Public License v1.0 for more details. + * + * You should have received a copy of the Eclipse Public License v1.0 + * along with this program; if not, you can obtain a copy from + * https://www.eclipse.org/org/documents/epl-v10.php or + * http://opensource.org/licenses/eclipse-1.0.php + */ + +package uk.ac.stfc.isis.ibex.ui.configserver; + +import java.util.Collection; + +import uk.ac.stfc.isis.ibex.configserver.configuration.Configuration; +import uk.ac.stfc.isis.ibex.configserver.internal.Converters; +import uk.ac.stfc.isis.ibex.epics.observing.BaseObserver; +import uk.ac.stfc.isis.ibex.epics.observing.ClosableObservable; +import uk.ac.stfc.isis.ibex.epics.observing.ForwardingObservable; +import uk.ac.stfc.isis.ibex.instrument.Instrument; +import uk.ac.stfc.isis.ibex.instrument.InstrumentUtils; +import uk.ac.stfc.isis.ibex.instrument.channels.CompressedCharWaveformChannel; +import uk.ac.stfc.isis.ibex.instrument.channels.StringChannel; +import uk.ac.stfc.isis.ibex.model.ModelObject; + +/** + * The import variables. Contains observables from a remote instrument. + */ +public class ImportVariables extends ModelObject { + + private static final String VERSION_ADDRESS = "CS:VERSION:SVN:REV"; + private static final String COMPONENTS_ADDRESS = "CS:BLOCKSERVER:ALL_COMPONENT_DETAILS"; + + private final Converters converters; + private String localPrefix; + private String remotePrefix; + + private ClosableObservable localConfigVersionObservable; + private ClosableObservable remoteConfigVersionObservable; + private ForwardingObservable> componentsObservable; + + private boolean connected = false; + private boolean versionMatch = false; + private Collection components = null; + + /** + * Constructor. + * + * @param converters + */ + public ImportVariables(Converters converters) { + this.converters = converters; + this.localPrefix = Instrument.getInstance().getPvPrefix(); + localConfigVersionObservable = new StringChannel().reader(localPrefix.concat(VERSION_ADDRESS)); + } + + /** + * Select the remote instrument to observe. + * + * @param remotePrefix The remote instrument PV prefix. + */ + public void selectInstrument(String remotePrefix) { + if (remoteConfigVersionObservable != null) { + remoteConfigVersionObservable.close(); + } + if (componentsObservable != null) { + componentsObservable.close(); + } + + this.remotePrefix = remotePrefix; + + remoteConfigVersionObservable = new StringChannel().reader(remotePrefix.concat(VERSION_ADDRESS)); + remoteConfigVersionObservable.subscribe(new BaseObserver() { + @Override + public void onValue(String value) { + var localVersion = localConfigVersionObservable.getValue(); + // Always allow copying if either block server is on a development version. + var match = value.equals(localVersion) || value.startsWith("0.0.0") || localVersion.startsWith("0.0.0"); + firePropertyChange("status", versionMatch, versionMatch = match); + } + }); + + componentsObservable = InstrumentUtils.convert(new CompressedCharWaveformChannel().reader(remotePrefix.concat(COMPONENTS_ADDRESS)), converters.toConfigList()); + componentsObservable.subscribe(new BaseObserver>() { + @Override + public void onValue(Collection value) { + firePropertyChange("components", components, components = value); + } + + @Override + public void onConnectionStatus(boolean isConnected) { + firePropertyChange("status", null, connected = isConnected); + + if (!isConnected) { + firePropertyChange("components", components, components = null); + } + } + }); + } + + /** + * @return The remote instrument prefix. + */ + public String getRemotePrefix() { + return remotePrefix; + } + + /** + * @return If the remote instrument Block Server is running. + */ + public boolean connected() { + return connected; + } + + /** + * @return If the remote instrument and local instrument are on the same configuration version. + */ + public boolean versionMatch() { + return versionMatch; + } + + /** + * @return The remote instrument components. + */ + public Collection getComponents() { + return components; + } + + /** + * @return String depending on the status or null if no error. + */ + public String getErrorMessage() { + if (!connected()) { + return "Selected instrument's Block Server is offline."; + } else if (!versionMatch()) { + return "Selected instrument is on a different version."; + } else { + return null; + } + } +} diff --git a/base/uk.ac.stfc.isis.ibex.ui.configserver/src/uk/ac/stfc/isis/ibex/ui/configserver/commands/ImportComponentHandler.java b/base/uk.ac.stfc.isis.ibex.ui.configserver/src/uk/ac/stfc/isis/ibex/ui/configserver/commands/ImportComponentHandler.java new file mode 100644 index 0000000000..2a39ef819a --- /dev/null +++ b/base/uk.ac.stfc.isis.ibex.ui.configserver/src/uk/ac/stfc/isis/ibex/ui/configserver/commands/ImportComponentHandler.java @@ -0,0 +1,87 @@ +/* + * This file is part of the ISIS IBEX application. + * Copyright (C) 2012-2023 Science & Technology Facilities Council. + * All rights reserved. + * + * This program is distributed in the hope that it will be useful. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution. + * EXCEPT AS EXPRESSLY SET FORTH IN THE ECLIPSE PUBLIC LICENSE V1.0, THE PROGRAM + * AND ACCOMPANYING MATERIALS ARE PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND. See the Eclipse Public License v1.0 for more details. + * + * You should have received a copy of the Eclipse Public License v1.0 + * along with this program; if not, you can obtain a copy from + * https://www.eclipse.org/org/documents/epl-v10.php or + * http://opensource.org/licenses/eclipse-1.0.php + */ + +package uk.ac.stfc.isis.ibex.ui.configserver.commands; + +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.widgets.Shell; + +import uk.ac.stfc.isis.ibex.configserver.configuration.Configuration; +import uk.ac.stfc.isis.ibex.configserver.internal.ImportConverter; +import uk.ac.stfc.isis.ibex.configserver.json.JsonConverters; +import uk.ac.stfc.isis.ibex.instrument.Instrument; +import uk.ac.stfc.isis.ibex.logger.IsisLog; +import uk.ac.stfc.isis.ibex.logger.LoggerUtils; +import uk.ac.stfc.isis.ibex.ui.configserver.ConfigurationServerUI; +import uk.ac.stfc.isis.ibex.ui.configserver.ConfigurationViewModels; +import uk.ac.stfc.isis.ibex.ui.configserver.ImportVariables; +import uk.ac.stfc.isis.ibex.ui.configserver.dialogs.EditConfigDialog; +import uk.ac.stfc.isis.ibex.ui.configserver.dialogs.ImportComponentDialog; + +/** + * Handles the selection of the import component menu item. + */ +public class ImportComponentHandler extends DisablingConfigHandler { + + private static final String TITLE = "Component Selector"; + private static final String SUB_TITLE = "Select Instrument and Component to Import"; + private static final String EDIT_TITLE = "Import Component"; + private static final String EDIT_SUB_TITLE = "Editing an Import Component"; + + /** + * The constructor. + */ + public ImportComponentHandler() { + super(SERVER.saveAs()); + } + + /** + * Open an import component dialogue. + * + * @param shell shell to use + * @throws TimeoutException If getting the blank config fails. + */ + @Override + public void safeExecute(Shell shell) throws TimeoutException { + ImportVariables importVariables = new ImportVariables(new JsonConverters()); + ImportComponentDialog importConfigDialog = new ImportComponentDialog(shell, TITLE, SUB_TITLE, importVariables); + if (importConfigDialog.open() == Window.OK && importConfigDialog.getSelectedComponent() != null) { + ConfigurationViewModels configurationViewModels = ConfigurationServerUI.getDefault().configurationViewModels(); + var blank = configurationViewModels.getBlankConfig(); + var editableIocs = SERVER.iocs().getValue(); + ImportConverter.convert(importConfigDialog.getSelectedComponent(), blank, importVariables.getRemotePrefix(), Instrument.getInstance().getPvPrefix(), editableIocs); + + blank.setIsComponent(true); + EditConfigDialog editDialog = new EditConfigDialog(shell, EDIT_TITLE, EDIT_SUB_TITLE, blank, true, configurationViewModels, false); + if (editDialog.open() == Window.OK) { + try { + SERVER.saveAsComponent().write(editDialog.getComponent()); + } catch (IOException e) { + LoggerUtils.logErrorWithStackTrace(IsisLog.getLogger(getClass()), e.getMessage(), e); + new MessageDialog(shell, "Error", null, + "Failed to save import component '%s'.".formatted(editDialog.getComponent().getName()), + MessageDialog.ERROR, new String[] {"OK"}, 0).open(); + } + } + } + } +} diff --git a/base/uk.ac.stfc.isis.ibex.ui.configserver/src/uk/ac/stfc/isis/ibex/ui/configserver/dialogs/ImportComponentDialog.java b/base/uk.ac.stfc.isis.ibex.ui.configserver/src/uk/ac/stfc/isis/ibex/ui/configserver/dialogs/ImportComponentDialog.java new file mode 100644 index 0000000000..272cab75d7 --- /dev/null +++ b/base/uk.ac.stfc.isis.ibex.ui.configserver/src/uk/ac/stfc/isis/ibex/ui/configserver/dialogs/ImportComponentDialog.java @@ -0,0 +1,243 @@ +/* + * This file is part of the ISIS IBEX application. + * Copyright (C) 2012-2023 Science & Technology Facilities Council. + * All rights reserved. + * + * This program is distributed in the hope that it will be useful. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution. + * EXCEPT AS EXPRESSLY SET FORTH IN THE ECLIPSE PUBLIC LICENSE V1.0, THE PROGRAM + * AND ACCOMPANYING MATERIALS ARE PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND. See the Eclipse Public License v1.0 for more details. + * + * You should have received a copy of the Eclipse Public License v1.0 + * along with this program; if not, you can obtain a copy from + * https://www.eclipse.org/org/documents/epl-v10.php or + * http://opensource.org/licenses/eclipse-1.0.php + */ + +package uk.ac.stfc.isis.ibex.ui.configserver.dialogs; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.stream.Collectors; + +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.TitleAreaDialog; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import uk.ac.stfc.isis.ibex.configserver.configuration.Configuration; +import uk.ac.stfc.isis.ibex.instrument.Instrument; +import uk.ac.stfc.isis.ibex.instrument.InstrumentInfo; +import uk.ac.stfc.isis.ibex.ui.LinkWrapper; +import uk.ac.stfc.isis.ibex.ui.configserver.ImportVariables; +import uk.ac.stfc.isis.ibex.ui.configserver.editing.components.ComponentTable; +import uk.ac.stfc.isis.ibex.ui.mainmenu.instrument.InstrumentTable; + +/** + * Dialog for importing a component. + */ +public class ImportComponentDialog extends TitleAreaDialog { + + private static final int WIDTH = 800; + private static final int HEIGHT = 800; + private static final String HELP_LINK = "https://github.com/ISISComputingGroup/ibex_user_manual/wiki/Create-and-Manage-Components#id3"; + + private final String title; + private final String subTitle; + private InstrumentTable instrumentTable; + private ComponentTable componentTable; + private Button importButton; + + private ImportVariables importVariables; + private Configuration selectedComponent = null; + + + /** + * Constructor. + * + * @param parentShell parent shell to run dialogue + * @param title title of dialogue + * @param subTitle action being taken + * @param importVariables variables used for importing + */ + public ImportComponentDialog(Shell parentShell, String title, String subTitle, ImportVariables importVariables) { + super(parentShell); + this.title = title; + this.subTitle = subTitle; + this.importVariables = importVariables; + } + + @Override + protected void configureShell(Shell shell) { + super.configureShell(shell); + shell.setText(title); + } + + @Override + protected Point getInitialSize() { + return new Point(WIDTH, HEIGHT); + } + + @Override + protected Control createDialogArea(Composite parent) { + setTitle(subTitle); + Composite container = new Composite(parent, SWT.FILL); + container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); + container.setLayout(new GridLayout(2, true)); + + LinkWrapper link = new LinkWrapper(container, "Help", HELP_LINK); + link.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 2, 1)); + + createIntrumentGroup(container); + createComponentGroup(container); + createCustomGroup(container); + + importVariables.addUiThreadPropertyChangeListener("components", new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + componentTable.setComponents(importVariables.getComponents()); + } + }); + + importVariables.addUiThreadPropertyChangeListener("status", new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + componentTable.enabled(importVariables.connected() && importVariables.versionMatch()); + setErrorMessage(importVariables.getErrorMessage()); + } + }); + + return container; + } + + @Override + protected void createButtonsForButtonBar(Composite parent) { + importButton = createButton(parent, IDialogConstants.OK_ID, "Import", true); + createButton(parent, IDialogConstants.CANCEL_ID, "Cancel", false); + importButton.setEnabled(false); + } + + private void createIntrumentGroup(Composite parent) { + Group group = new Group(parent, SWT.NONE); + group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); + group.setText("Instruments"); + group.setLayout(new GridLayout(2, false)); + + Label label = new Label(group, SWT.RIGHT); + label.setText("Instrument:"); + label.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1)); + + Text searchText = new Text(group, SWT.SEARCH); + searchText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + searchText.addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + instrumentTable.setSearch(searchText.getText()); + } + }); + + var currentInstrumentName = Instrument.getInstance().name().getValue(); + var instruments = Instrument.getInstance().getInstruments().stream().filter(i -> !i.name().equals(currentInstrumentName)).collect(Collectors.toList()); + instrumentTable = new InstrumentTable(group, SWT.NONE, SWT.FULL_SELECTION, instruments); + instrumentTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); + instrumentTable.addSelectionChangedListener(new ISelectionChangedListener() { + @Override + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection selection = (IStructuredSelection) event.getSelection(); + if (selection.size() > 0) { + InstrumentInfo instrument = (InstrumentInfo) selection.getFirstElement(); + importVariables.selectInstrument(instrument.pvPrefix()); + } + } + }); + } + + private void createComponentGroup(Composite parent) { + Group group = new Group(parent, SWT.NONE); + group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); + group.setText("Components"); + group.setLayout(new GridLayout(2, false)); + + Label label = new Label(group, SWT.RIGHT); + label.setText("Component:"); + label.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1)); + + Text searchText = new Text(group, SWT.SEARCH); + searchText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + searchText.addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + componentTable.setSearch(searchText.getText()); + } + }); + + componentTable = new ComponentTable(group, SWT.NONE, SWT.FULL_SELECTION); + componentTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); + componentTable.addSelectionChangedListener(new ISelectionChangedListener() { + @Override + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection selection = (IStructuredSelection) event.getSelection(); + if (selection.size() > 0) { + selectedComponent = (Configuration) selection.getFirstElement(); + importButton.setEnabled(true); + } else { + importButton.setEnabled(false); + } + } + }); + } + + private void createCustomGroup(Composite parent) { + final int columns = 3; + Group group = new Group(parent, SWT.NONE); + group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 2, 1)); + group.setText("Custom Instrument Selection"); + group.setLayout(new GridLayout(columns, false)); + + String warningString = "Configure a PV prefix for an unknown instrument. (e.g. \"IN:NAME:\")"; + Label warning = new Label(group, SWT.LEFT); + warning.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, columns, 1)); + warning.setText(warningString); + + Label label = new Label(group, SWT.RIGHT); + label.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1)); + label.setText("PV Prefix:"); + + Text text = new Text(group, SWT.BORDER); + text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + + Button button = new Button(group, SWT.NONE); + button.setText("Select"); + button.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1)); + button.addListener(SWT.Selection, new Listener() { + @Override + public void handleEvent(Event event) { + importVariables.selectInstrument(text.getText()); + } + }); + } + + /** + * @return The currently selected component. + */ + public Configuration getSelectedComponent() { + return selectedComponent; + } +} diff --git a/base/uk.ac.stfc.isis.ibex.ui.configserver/src/uk/ac/stfc/isis/ibex/ui/configserver/editing/components/ComponentNameSearch.java b/base/uk.ac.stfc.isis.ibex.ui.configserver/src/uk/ac/stfc/isis/ibex/ui/configserver/editing/components/ComponentNameSearch.java new file mode 100644 index 0000000000..8946e35cdd --- /dev/null +++ b/base/uk.ac.stfc.isis.ibex.ui.configserver/src/uk/ac/stfc/isis/ibex/ui/configserver/editing/components/ComponentNameSearch.java @@ -0,0 +1,49 @@ +/* + * This file is part of the ISIS IBEX application. + * Copyright (C) 2012-2023 Science & Technology Facilities Council. + * All rights reserved. + * + * This program is distributed in the hope that it will be useful. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution. + * EXCEPT AS EXPRESSLY SET FORTH IN THE ECLIPSE PUBLIC LICENSE V1.0, THE PROGRAM + * AND ACCOMPANYING MATERIALS ARE PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND. See the Eclipse Public License v1.0 for more details. + * + * You should have received a copy of the Eclipse Public License v1.0 + * along with this program; if not, you can obtain a copy from + * https://www.eclipse.org/org/documents/epl-v10.php or + * http://opensource.org/licenses/eclipse-1.0.php + */ + +package uk.ac.stfc.isis.ibex.ui.configserver.editing.components; + +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerFilter; + +import uk.ac.stfc.isis.ibex.configserver.configuration.Configuration; + +/** + * A filter for searching by component name. + * Case insensitive and the search text matches to any part of the component name. + */ +public class ComponentNameSearch extends ViewerFilter { + private String searchString = ""; + + /** + * Sets the text to search for. + * @param s the text to search for + */ + public void setSearchText(String s) { + searchString = s; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean select(Viewer viewer, Object parentElement, Object element) { + Configuration component = (Configuration) element; + return searchString == null || component.getName().toUpperCase().contains(searchString.toUpperCase()); + } +} diff --git a/base/uk.ac.stfc.isis.ibex.ui.configserver/src/uk/ac/stfc/isis/ibex/ui/configserver/editing/components/ComponentTable.java b/base/uk.ac.stfc.isis.ibex.ui.configserver/src/uk/ac/stfc/isis/ibex/ui/configserver/editing/components/ComponentTable.java new file mode 100644 index 0000000000..a7dc4a51a9 --- /dev/null +++ b/base/uk.ac.stfc.isis.ibex.ui.configserver/src/uk/ac/stfc/isis/ibex/ui/configserver/editing/components/ComponentTable.java @@ -0,0 +1,149 @@ +/* + * This file is part of the ISIS IBEX application. + * Copyright (C) 2012-2023 Science & Technology Facilities Council. + * All rights reserved. + * + * This program is distributed in the hope that it will be useful. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution. + * EXCEPT AS EXPRESSLY SET FORTH IN THE ECLIPSE PUBLIC LICENSE V1.0, THE PROGRAM + * AND ACCOMPANYING MATERIALS ARE PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND. See the Eclipse Public License v1.0 for more details. + * + * You should have received a copy of the Eclipse Public License v1.0 + * along with this program; if not, you can obtain a copy from + * https://www.eclipse.org/org/documents/epl-v10.php or + * http://opensource.org/licenses/eclipse-1.0.php + */ + +package uk.ac.stfc.isis.ibex.ui.configserver.editing.components; + +import java.util.Collection; + +import org.eclipse.jface.layout.TableColumnLayout; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.ColumnWeightData; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; + +import uk.ac.stfc.isis.ibex.configserver.configuration.Configuration; + +/** + * A table of components. Can be filtered by component name. + */ +public class ComponentTable extends Composite { + private TableViewer viewer; + private TableColumnLayout tableColumnLayout; + private ComponentNameSearch search; + + /** + * Creates the table. + * @param parent the parent composite + * @param style the SWT style flags + * @param tableStyle the SWT style flags for the underlying table + */ + public ComponentTable(Composite parent, int style, int tableStyle) { + super(parent, SWT.NONE); + + GridLayout compositeLayout = new GridLayout(1, false); + compositeLayout.marginHeight = 0; + compositeLayout.marginWidth = 0; + setLayout(compositeLayout); + setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); + + createTable(this, tableStyle); + + search = new ComponentNameSearch(); + viewer.addFilter(search); + } + + /** + * Adds a listener for selection changes to the instrument table viewer. + * + * @param listener a selection changed listener + */ + public void addSelectionChangedListener(ISelectionChangedListener listener) { + viewer.addSelectionChangedListener(listener); + } + + /** + * Removes the given selection change listener from the instrument table viewer. + * + * @param listener a selection changed listener + */ + public void removeSelectionChangedListener(ISelectionChangedListener listener) { + viewer.removeSelectionChangedListener(listener); + } + + /** + * Sets the text to search for and refreshes the table. + * + * @param searchText the text to search for + */ + public void setSearch(String searchText) { + search.setSearchText(searchText); + viewer.refresh(); + } + + /** + * Sets the components to display. + * + * @param collection The collection of components. + */ + public void setComponents(Collection collection) { + viewer.setInput(collection); + } + + /** + * Enable or disable component selection. + * + * @param state The new enabled state. + */ + public void enabled(boolean state) { + viewer.getControl().setEnabled(state); + } + + private void createTable(Composite parent, int tableStyle) { + Composite tableComposite = new Composite(parent, SWT.NONE); + tableComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); + + viewer = new TableViewer(tableComposite, tableStyle); + viewer.getControl().setEnabled(false); + viewer.setContentProvider(ArrayContentProvider.getInstance()); + + Table table = viewer.getTable(); + table.setHeaderVisible(true); + table.setLinesVisible(true); + + tableColumnLayout = new TableColumnLayout(); + tableComposite.setLayout(tableColumnLayout); + + createColumn(); + viewer.refresh(); + } + + private void createColumn() { + TableViewerColumn nameColViewer = new TableViewerColumn(viewer, SWT.LEFT); + TableColumn nameColumn = nameColViewer.getColumn(); + nameColumn.setText("Name"); + nameColumn.setResizable(false); + nameColViewer.setLabelProvider(new ColumnLabelProvider() { + @Override + public String getText(Object element) { + Configuration component = (Configuration) element; + return component.getDisplayName(); + } + }); + + final int weight = 100; + tableColumnLayout.setColumnData(nameColumn, new ColumnWeightData(weight, false)); + } +} diff --git a/base/uk.ac.stfc.isis.ibex.ui.mainmenu/META-INF/MANIFEST.MF b/base/uk.ac.stfc.isis.ibex.ui.mainmenu/META-INF/MANIFEST.MF index 02c2d9dc6e..000498b96d 100644 --- a/base/uk.ac.stfc.isis.ibex.ui.mainmenu/META-INF/MANIFEST.MF +++ b/base/uk.ac.stfc.isis.ibex.ui.mainmenu/META-INF/MANIFEST.MF @@ -29,3 +29,4 @@ Import-Package: uk.ac.stfc.isis.ibex.epics.switching, uk.ac.stfc.isis.ibex.model, uk.ac.stfc.isis.ibex.ui.alarm Automatic-Module-Name: uk.ac.stfc.isis.ibex.ui.mainmenu +Export-Package: uk.ac.stfc.isis.ibex.ui.mainmenu.instrument diff --git a/base/uk.ac.stfc.isis.ibex.ui/src/uk/ac/stfc/isis/ibex/ui/LinkWrapper.java b/base/uk.ac.stfc.isis.ibex.ui/src/uk/ac/stfc/isis/ibex/ui/LinkWrapper.java new file mode 100644 index 0000000000..408322ed9b --- /dev/null +++ b/base/uk.ac.stfc.isis.ibex.ui/src/uk/ac/stfc/isis/ibex/ui/LinkWrapper.java @@ -0,0 +1,51 @@ +package uk.ac.stfc.isis.ibex.ui; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Link; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; + +import uk.ac.stfc.isis.ibex.logger.IsisLog; +import uk.ac.stfc.isis.ibex.logger.LoggerUtils; + +/** + * Helper class that wraps a {@link org.eclipse.swt.widgets.Link}. + */ +public class LinkWrapper { + + private Link link; + + /** + * @param parent Parent element. + * @param text The text for the link. + * @param link The link string. + */ + public LinkWrapper(Composite parent, String text, String link) { + this.link = new Link(parent, SWT.NONE); + this.link.setText("%s".formatted(link, text)); + this.link.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + try { + PlatformUI.getWorkbench().getBrowserSupport().getExternalBrowser().openURL(new URL(e.text)); + } catch (PartInitException | MalformedURLException ex) { + LoggerUtils.logErrorWithStackTrace(IsisLog.getLogger(getClass()), ex.getMessage(), ex); + } + } + }); + } + + /** + * @param layoutData the new layout data for the receiver. + * @see org.eclipse.swt.widgets.Control#setLayoutData(Object) + */ + public void setLayoutData(Object layoutData) { + link.setLayoutData(layoutData); + } +}