diff --git a/app/display/actions/src/main/java/org/csstudio/display/actions/Messages.java b/app/display/actions/src/main/java/org/csstudio/display/actions/Messages.java index 3361c61929..6052dea50d 100644 --- a/app/display/actions/src/main/java/org/csstudio/display/actions/Messages.java +++ b/app/display/actions/src/main/java/org/csstudio/display/actions/Messages.java @@ -22,6 +22,7 @@ public class Messages { public static String ActionCloseDisplay, ActionExecuteCommand, ActionExecuteScript, + ActionOpenDataBrowser, ActionOpenDisplay, ActionOpenFile, ActionOpenWebPage, diff --git a/app/display/actions/src/main/java/org/csstudio/display/actions/OpenDataBrowserAction.java b/app/display/actions/src/main/java/org/csstudio/display/actions/OpenDataBrowserAction.java new file mode 100644 index 0000000000..6198802837 --- /dev/null +++ b/app/display/actions/src/main/java/org/csstudio/display/actions/OpenDataBrowserAction.java @@ -0,0 +1,81 @@ +package org.csstudio.display.actions; + +import javafx.scene.image.Image; +import org.csstudio.display.builder.model.persist.ModelReader; +import org.csstudio.display.builder.model.persist.ModelWriter; +import org.csstudio.display.builder.model.persist.XMLTags; +import org.csstudio.display.builder.model.properties.ActionInfoBase; +import org.csstudio.display.builder.representation.javafx.actionsdialog.ActionsDialog; +import org.phoebus.framework.persistence.XMLUtil; +import org.phoebus.ui.javafx.ImageCache; +import org.w3c.dom.Element; + +import javax.xml.stream.XMLStreamWriter; + +public class OpenDataBrowserAction extends ActionInfoBase { + + public static final String OPEN_DATA_BROWSER = "open_data_browser"; + private static final Integer PRIORITY = 10; + private String pvs = "$(pv_name)"; + private String timeframe = "1 hour"; + + @SuppressWarnings("unused") + /** + * Do not remove, needed by SPI framework. + */ + public OpenDataBrowserAction() { + this.description = Messages.ActionOpenDataBrowser; + this.type = OPEN_DATA_BROWSER; + } + + public OpenDataBrowserAction(String description, String pvs, String timeframe) { + this.pvs = pvs; + this.timeframe = timeframe; + this.description = description; + this.type = OPEN_DATA_BROWSER; + } + + @Override + public Image getImage() { + return ImageCache.getImage(ActionsDialog.class, "/icons/databrowser.png"); + } + + @Override + public Integer getPriority() { + return PRIORITY; + } + + @Override + public void readFromXML(ModelReader modelReader, Element actionXml) { + pvs = XMLUtil.getChildString(actionXml, XMLTags.PV_NAME).orElse(""); + timeframe = XMLUtil.getChildString(actionXml, XMLTags.TIME_FRAME).orElse(""); + + if (description.isEmpty()) + description = Messages.ActionOpenDataBrowser; + } + + @Override + public void writeToXML(ModelWriter modelWriter, XMLStreamWriter writer) throws Exception { + writer.writeAttribute(XMLTags.TYPE, OPEN_DATA_BROWSER); + writeDescriptionToXML(writer, description); + writer.writeStartElement(XMLTags.PV_NAME); + writer.writeCharacters(pvs); + writer.writeEndElement(); + writer.writeStartElement(XMLTags.TIME_FRAME); + writer.writeCharacters(timeframe); + writer.writeEndElement(); + } + + public String getPVs() { + return pvs; + } + + public String getTimeframe() { + return timeframe; + } + + @Override + public boolean matchesAction(String actionId) { + return actionId.equalsIgnoreCase(OPEN_DATA_BROWSER); + } +} diff --git a/app/display/actions/src/main/java/org/csstudio/display/actions/OpenDataBrowserActionController.java b/app/display/actions/src/main/java/org/csstudio/display/actions/OpenDataBrowserActionController.java new file mode 100644 index 0000000000..111e82cbca --- /dev/null +++ b/app/display/actions/src/main/java/org/csstudio/display/actions/OpenDataBrowserActionController.java @@ -0,0 +1,45 @@ +package org.csstudio.display.actions; + +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.fxml.FXML; +import javafx.scene.control.TextField; +import org.csstudio.display.builder.model.ActionControllerBase; +import org.csstudio.display.builder.model.Widget; +import org.csstudio.display.builder.model.spi.ActionInfo; + +/** + * FXML Controller for the open data browser action editor + */ +public class OpenDataBrowserActionController extends ActionControllerBase { + + @FXML + private TextField pvNames; + + @FXML + private TextField timeframe; + + private final StringProperty pvNameProperty = new SimpleStringProperty(); + private final StringProperty timeframeProperty = new SimpleStringProperty(); + + public OpenDataBrowserActionController(OpenDataBrowserAction openDataBrowserActionInfo){ + descriptionProperty.set(openDataBrowserActionInfo.getDescription()); + pvNameProperty.setValue(openDataBrowserActionInfo.getPVs()); + timeframeProperty.setValue(openDataBrowserActionInfo.getTimeframe()); + } + + /** + * Init + */ + @FXML + @Override + public void initialize() { + super.initialize(); + pvNames.textProperty().bindBidirectional(pvNameProperty); + timeframe.textProperty().bindBidirectional(timeframeProperty); + } + + public ActionInfo getActionInfo(){ + return new OpenDataBrowserAction(descriptionProperty.get(), pvNameProperty.get(), timeframeProperty.get()); + } +} diff --git a/app/display/actions/src/main/java/org/csstudio/display/actions/OpenDataBrowserActionEditor.java b/app/display/actions/src/main/java/org/csstudio/display/actions/OpenDataBrowserActionEditor.java new file mode 100644 index 0000000000..e5d02434b1 --- /dev/null +++ b/app/display/actions/src/main/java/org/csstudio/display/actions/OpenDataBrowserActionEditor.java @@ -0,0 +1,60 @@ +package org.csstudio.display.actions; + +import javafx.fxml.FXMLLoader; +import javafx.scene.Node; +import org.csstudio.display.builder.model.Widget; +import org.csstudio.display.builder.model.spi.ActionEditor; +import org.csstudio.display.builder.model.spi.ActionInfo; +import org.phoebus.framework.nls.NLS; + +import java.io.IOException; +import java.util.ResourceBundle; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Editor for {@link OpenDataBrowserAction}. + */ +public class OpenDataBrowserActionEditor implements ActionEditor { + + private OpenDataBrowserActionController openDataBrowserActionController; + private Node openDataBowserEditorUi; + + @Override + public boolean matchesAction(String type) { + return OpenDataBrowserAction.OPEN_DATA_BROWSER.equalsIgnoreCase(type); + } + + @Override + public ActionInfo getActionInfo() { + return openDataBrowserActionController.getActionInfo(); + } + + @Override + public Node getEditorUi() { + return openDataBowserEditorUi; + } + + @Override + public void configure(Widget widget, ActionInfo actionInfo) { + FXMLLoader fxmlLoader = new FXMLLoader(); + fxmlLoader.setResources(NLS.getMessages(Messages.class)); + fxmlLoader.setLocation(this.getClass().getResource("OpenDataBrowserAction.fxml")); + fxmlLoader.setControllerFactory(clazz -> { + try { + return clazz.getConstructor(OpenDataBrowserAction.class).newInstance(actionInfo); + } catch (Exception e) { + Logger.getLogger(OpenDataBrowserActionEditor.class.getName()).log(Level.SEVERE, "Failed to construct OpenDataBrowserActionController", e); + } + return null; + }); + + try { + openDataBowserEditorUi = fxmlLoader.load(); + openDataBrowserActionController = fxmlLoader.getController(); + } catch (IOException e) { + Logger.getLogger(OpenDataBrowserActionEditor.class.getName()).log(Level.SEVERE, "Failed to load the OpenDataBrowserAction UI", e); + throw new RuntimeException(e); + } + } +} diff --git a/app/display/actions/src/main/resources/META-INF/services/org.csstudio.display.builder.model.spi.ActionEditor b/app/display/actions/src/main/resources/META-INF/services/org.csstudio.display.builder.model.spi.ActionEditor index b04d508548..4c7fa91c2b 100644 --- a/app/display/actions/src/main/resources/META-INF/services/org.csstudio.display.builder.model.spi.ActionEditor +++ b/app/display/actions/src/main/resources/META-INF/services/org.csstudio.display.builder.model.spi.ActionEditor @@ -5,3 +5,4 @@ org.csstudio.display.actions.OpenDisplayActionEditor org.csstudio.display.actions.OpenFileActionEditor org.csstudio.display.actions.OpenWebPageActionEditor org.csstudio.display.actions.CloseDisplayActionEditor +org.csstudio.display.actions.OpenDataBrowserActionEditor diff --git a/app/display/actions/src/main/resources/META-INF/services/org.csstudio.display.builder.model.spi.ActionInfo b/app/display/actions/src/main/resources/META-INF/services/org.csstudio.display.builder.model.spi.ActionInfo index c05adaf60e..08898016b1 100644 --- a/app/display/actions/src/main/resources/META-INF/services/org.csstudio.display.builder.model.spi.ActionInfo +++ b/app/display/actions/src/main/resources/META-INF/services/org.csstudio.display.builder.model.spi.ActionInfo @@ -4,4 +4,5 @@ org.csstudio.display.actions.ExecuteScriptAction org.csstudio.display.actions.ExecuteCommandAction org.csstudio.display.actions.OpenFileAction org.csstudio.display.actions.OpenWebPageAction -org.csstudio.display.actions.CloseDisplayAction \ No newline at end of file +org.csstudio.display.actions.CloseDisplayAction +org.csstudio.display.actions.OpenDataBrowserAction \ No newline at end of file diff --git a/app/display/actions/src/main/resources/org/csstudio/display/actions/OpenDataBrowserAction.fxml b/app/display/actions/src/main/resources/org/csstudio/display/actions/OpenDataBrowserAction.fxml new file mode 100644 index 0000000000..86e4dc7f48 --- /dev/null +++ b/app/display/actions/src/main/resources/org/csstudio/display/actions/OpenDataBrowserAction.fxml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/display/actions/src/main/resources/org/csstudio/display/actions/messages.properties b/app/display/actions/src/main/resources/org/csstudio/display/actions/messages.properties index f0efc8e1f4..f00d1f43e8 100644 --- a/app/display/actions/src/main/resources/org/csstudio/display/actions/messages.properties +++ b/app/display/actions/src/main/resources/org/csstudio/display/actions/messages.properties @@ -1,6 +1,7 @@ ActionCloseDisplay=Close Display ActionExecuteCommand=Execute Command ActionExecuteScript=Execute Script +ActionOpenDataBrowser=Open Data Browser ActionOpenDisplay=Open Display ActionsDialog_Description=Description: ActionOpenFile=Open File @@ -14,8 +15,12 @@ ActionsDialog_ExecuteAll=Execute all actions as one ActionsDialog_FilePath=File Path: ActionsDialog_Info=Configure actions which open displays, write PVs etc. ActionsDialog_PVName=PV Name: +ActionsDialog_PVNames=PV Names: +ActionsDialog_PVNames_Help=List names of PVs separated by a space (e.g. 'PV:1 PV:2') ActionsDialog_ScriptPath=Script File: ActionsDialog_ScriptText=Embedded Script Text: +ActionsDialog_Timeframe=Timeframe Start: +ActionsDialog_Timeframe_Help=Format: temporal (e.g. '1 hour', '1 day') or absolute (e.g. 'yyyy-MM-dd HH:mm:ss') ActionsDialog_URL=URL: ActionsDialog_Value=Value: Add=Add diff --git a/app/display/model/src/main/java/org/csstudio/display/builder/model/persist/XMLTags.java b/app/display/model/src/main/java/org/csstudio/display/builder/model/persist/XMLTags.java index 2c6c95b12d..fd383c2b9b 100644 --- a/app/display/model/src/main/java/org/csstudio/display/builder/model/persist/XMLTags.java +++ b/app/display/model/src/main/java/org/csstudio/display/builder/model/persist/XMLTags.java @@ -37,6 +37,7 @@ public interface XMLTags SCRIPT = "script", TARGET = "target", TEXT = "text", + TIME_FRAME = "timeframe", TRIGGER = "trigger", TYPE = "type", URL = "url", diff --git a/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/actionhandlers/OpenDataBrowserActionHandler.java b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/actionhandlers/OpenDataBrowserActionHandler.java new file mode 100644 index 0000000000..05434471f9 --- /dev/null +++ b/app/display/runtime/src/main/java/org/csstudio/display/builder/runtime/app/actionhandlers/OpenDataBrowserActionHandler.java @@ -0,0 +1,102 @@ +package org.csstudio.display.builder.runtime.app.actionhandlers; + +import org.csstudio.display.actions.OpenDataBrowserAction; +import org.csstudio.display.builder.model.DisplayModel; +import org.csstudio.display.builder.model.Widget; +import org.csstudio.display.builder.model.spi.ActionHandler; +import org.csstudio.display.builder.model.spi.ActionInfo; +import org.csstudio.display.builder.representation.ToolkitRepresentation; +import org.csstudio.trends.databrowser3.DataBrowserApp; +import org.csstudio.trends.databrowser3.DataBrowserInstance; +import org.csstudio.trends.databrowser3.model.PVItem; +import org.phoebus.framework.macros.MacroHandler; +import org.phoebus.framework.macros.MacroValueProvider; +import org.phoebus.framework.util.ResourceParser; +import org.phoebus.framework.workbench.ApplicationService; +import org.phoebus.util.time.TimeParser; +import org.phoebus.util.time.TimeRelativeInterval; +import org.phoebus.util.time.TimestampFormats; + +import java.time.Instant; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class OpenDataBrowserActionHandler implements ActionHandler { + + private final Logger logger = Logger.getLogger(OpenDataBrowserActionHandler.class.getName()); + + /** + * Expand macros that may exist in the PV name + * @param name PV name + * @param macros Macros available + * @return PV name with macros replaced + */ + private String expandMacros(String name, MacroValueProvider macros) { + try { + name = MacroHandler.replace(macros, name); + } catch (Exception ignore) { + // NOP + } + return name; + } + + @Override + public void handleAction(Widget sourceWidget, ActionInfo pluggableActionInfo) { + OpenDataBrowserAction action = (OpenDataBrowserAction) pluggableActionInfo; + + try + { + final DisplayModel model = sourceWidget.getTopDisplayModel(); + final ToolkitRepresentation toolkit = ToolkitRepresentation.getToolkit(model); + final MacroValueProvider macros = sourceWidget.getMacrosOrProperties(); + final String pvNames = action.getPVs().strip(); + final String timeframe = action.getTimeframe().strip(); + + String[] pvs = pvNames.split(" "); + for (int i = 0; i < pvs.length; i++) { + pvs[i] = expandMacros(pvs[i], macros); + } + // Build URI from list of PVs + String pvURI = "pv://?" + String.join("&", pvs); + + // Set timeframe + TimeRelativeInterval timeInterval; + if (timeframe.contains(":") || timeframe.contains("-")){ + // An absolute time has been provided + Instant ints = TimestampFormats.parse(timeframe); + timeInterval = TimeRelativeInterval.of(ints, + TimeParser.parseTemporalAmount(TimeParser.NOW)); + } else { + // Temporal timeframe + timeInterval = TimeRelativeInterval.of(TimeParser.parseTemporalAmount(timeframe), + TimeParser.parseTemporalAmount(TimeParser.NOW)); + } + + toolkit.submit(() -> + { // Create databrowser instance + DataBrowserInstance instance = ApplicationService.createInstance(DataBrowserApp.NAME, + ResourceParser.createResourceURI(pvURI)); + + // Set the default archiver + for (String pv: pvs) { + PVItem pvItem = (PVItem) instance.getModel().getItem(pv); + pvItem.useDefaultArchiveDataSources(); + } + + // Set timeframe + instance.getModel().setTimerange(timeInterval); + + return null; + }); + } + catch (Exception ex) + { + logger.log(Level.WARNING, ex, () -> action + " failed. Cannot open data browser"); + } + } + + @Override + public boolean matches(ActionInfo pluggableActionInfo) { + return pluggableActionInfo.getType().equalsIgnoreCase(OpenDataBrowserAction.OPEN_DATA_BROWSER); + } +} diff --git a/app/display/runtime/src/main/resources/META-INF/services/org.csstudio.display.builder.model.spi.ActionHandler b/app/display/runtime/src/main/resources/META-INF/services/org.csstudio.display.builder.model.spi.ActionHandler index 805f05da1d..4898f0b72c 100644 --- a/app/display/runtime/src/main/resources/META-INF/services/org.csstudio.display.builder.model.spi.ActionHandler +++ b/app/display/runtime/src/main/resources/META-INF/services/org.csstudio.display.builder.model.spi.ActionHandler @@ -5,3 +5,4 @@ org.csstudio.display.builder.runtime.app.actionhandlers.ExecuteCommandActionHand org.csstudio.display.builder.runtime.app.actionhandlers.OpenFileActionHandler org.csstudio.display.builder.runtime.app.actionhandlers.OpenWebPageActionHandler org.csstudio.display.builder.runtime.app.actionhandlers.CloseDisplayActionHandler +org.csstudio.display.builder.runtime.app.actionhandlers.OpenDataBrowserActionHandler