FlowLatencyDialog.java

/**
 * Copyright (c) 2004-2025 Carnegie Mellon University and others. (see Contributors file).
 * All Rights Reserved.
 *
 * NO WARRANTY. ALL MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY
 * KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE
 * OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT
 * MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
 *
 * This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 * SPDX-License-Identifier: EPL-2.0
 *
 * Created, in part, with funding and support from the United States Government. (see Acknowledgments file).
 *
 * This program includes and/or can make use of certain third party source code, object code, documentation and other
 * files ("Third Party Software"). The Third Party Software that is used by this program is dependent upon your system
 * configuration. By using this program, You agree to comply with any and all relevant Third Party Software terms and
 * conditions contained in any such Third Party Software or separate license file distributed with such Third Party
 * Software. The parties who own the Third Party Software ("Third Party Licensors") are intended third party benefici-
 * aries to this license with respect to the terms applicable to their Third Party Software. Third Party Software li-
 * censes only apply to the Third Party Software and not any other portion of this program or this program as a whole.
 */
package org.osate.analysis.flows.dialogs;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.jface.preference.IPersistentPreferenceStore;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.osate.analysis.flows.preferences.Constants;
import org.osate.ui.dialogs.Dialog;

public final class FlowLatencyDialog extends TitleAreaDialog {
	private static final String[] PREF_IDS = { Constants.ASYNCHRONOUS_SYSTEM, Constants.PARTITONING_POLICY,
			Constants.WORST_CASE_DEADLINE, Constants.BESTCASE_EMPTY_QUEUE, Constants.DISABLE_QUEUING_LATENCY };
	private static final String[] LAST_USED_PREF_IDS = { Constants.ASYNCHRONOUS_SYSTEM_LAST_USED,
			Constants.PARTITONING_POLICY_LAST_USED, Constants.WORST_CASE_DEADLINE_LAST_USED,
			Constants.BESTCASE_EMPTY_QUEUE_LAST_USED, Constants.DISABLE_QUEUING_LATENCY_LAST_USED };

	private final IPreferenceStore latencyPrefs;
	private boolean dontShowDialog = false;
	public final Map<String, String> localValues = new HashMap<>(); // indexed by LAST_USED_PREF_IDS
	private final Map<String, Button> valueToButton = new HashMap<>(); // index by the preference value strings

	public FlowLatencyDialog(Shell parentShell, final IPreferenceStore prefs) {
		super(parentShell);
		latencyPrefs = prefs;
		for (String prefId : LAST_USED_PREF_IDS) {
			localValues.put(prefId, latencyPrefs.getString(prefId));
		}
	}

	public boolean dontShowDialog() {
		return latencyPrefs.getBoolean(Constants.DONT_SHOW_DIALOG);
	}

	@Override
	protected void configureShell(final Shell newShell) {
		super.configureShell(newShell);
		newShell.setText("Flow Latency Analysis");
	}

	@Override
	public void create() {
		super.create();
		setTitle("Check Flow Latency");
		setMessage(
				"Configure the settings for the flow latency analysis.  Clicking \"Ok\" runs the analysis and causes "
						+ "the settings to be used the next time the analysis is executed.  Clicking \"Cancel\" "
						+ "leaves the remembered settings as they were.");
	}

	@Override
	protected Control createDialogArea(final Composite parent) {
		setHelpAvailable(true);
		PlatformUI.getWorkbench().getHelpSystem().setHelp(getShell(), "org.osate.analysis.flows.flow_latency_dialog");

		final Composite root = (Composite) super.createDialogArea(parent);

		final ScrolledComposite scroller = new ScrolledComposite(root, SWT.H_SCROLL | SWT.V_SCROLL);
		scroller.setLayoutData(new GridData(GridData.FILL_BOTH));

		final Composite myWorkArea = new Composite(scroller, SWT.NONE);
		scroller.setContent(myWorkArea);
		myWorkArea.setLayout(new GridLayout(2, true));

		createGroup(myWorkArea, "System type", Constants.ASYNCHRONOUS_SYSTEM_LAST_USED,
				new String[] { "Asynchronous system (AS)", "Synchronous system (SS)" },
				new String[] { Constants.ASYNCHRONOUS_SYSTEM_YES, Constants.ASYNCHRONOUS_SYSTEM_NO });
		createGroup(myWorkArea, "Partition output policy", Constants.PARTITONING_POLICY_LAST_USED,
				new String[] { "Partition end (PE)", "Major frame delayed (MF)" },
				new String[] { Constants.PARTITIONING_POLICY_PARTITION_END_STR,
						Constants.PARTITIONING_POLICY_MAJOR_FRAME_DELAYED_STR });
		createGroup(myWorkArea, "For worst-case processing time use", Constants.WORST_CASE_DEADLINE_LAST_USED,
				new String[] { "Deadline (DL)", "Maximum compute execution time (ET)" },
				new String[] { Constants.WORST_CASE_DEADLINE_YES, Constants.WORST_CASE_DEADLINE_NO });
		createGroup(myWorkArea, "For best-case queuing latency on incoming ports",
				Constants.BESTCASE_EMPTY_QUEUE_LAST_USED,
				new String[] { "Assume an empty queue (EQ)", "Assume a full queue (FQ)" },
				new String[] { Constants.BESTCASE_EMPTY_QUEUE_YES, Constants.BESTCASE_EMPTY_QUEUE_NO });
		createGroup(myWorkArea, "Disable queuing latency in the results", Constants.DISABLE_QUEUING_LATENCY_LAST_USED,
				new String[] { "Disable", "Enable" },
				new String[] { Constants.DISABLE_QUEUING_LATENCY_YES, Constants.DISABLE_QUEUING_LATENCY_NO });

		/*
		 * If we are showing the dialog, then the value of DONT_SHOW_DIALOG is false.
		 */
		dontShowDialog = false;
		final Button hide = new Button(myWorkArea, SWT.CHECK);
		hide.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING));
		hide.setText("Don't show this dialog again");
		hide.setToolTipText("Check this button to hide this dialog in the future.  Use the" + System.lineSeparator()
				+ "OSATE > Analysis > Flow Latency preference pane to bring it back.");
		hide.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(final SelectionEvent event) {
				dontShowDialog = hide.getSelection();
			}
		});

		final Composite prefButtons = new Composite(myWorkArea, SWT.NONE);
		prefButtons.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END));
		prefButtons.setLayout(new RowLayout(SWT.HORIZONTAL));

		final Button defaults = new Button(prefButtons, SWT.PUSH);
		defaults.setText("Restore Defaults");
		defaults.setToolTipText("Restore the setting above to the defaults from the " + System.lineSeparator()
				+ "OSATE > Analysis > Flow Latency preference pane.");
		defaults.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(final SelectionEvent event) {
				/*
				 * Clear all selections first, then set them because setSelection()
				 * doesn't understand the other radio buttons in the same group.
				 */
				for (final String value : valueToButton.keySet()) {
					valueToButton.get(value).setSelection(false);
				}

				for (int i = 0; i < PREF_IDS.length; i++) {
					final String prefValue = latencyPrefs.getString(PREF_IDS[i]);
					valueToButton.get(prefValue).setSelection(true);
					localValues.put(LAST_USED_PREF_IDS[i], prefValue);
				}
			}
		});

		/*
		 * Don't bother having a "save preferences" button if the preferences aren't persistable
		 */
		if (latencyPrefs instanceof IPersistentPreferenceStore) {
			final Button save = new Button(prefButtons, SWT.PUSH);
			save.setText("Save as Defaults");
			save.setToolTipText("Make the above settings the defaults as in the "
					+ "\"OSATE > Analysis > Flow Latency\" preference pane.");
			save.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(final SelectionEvent event) {
					for (int i = 0; i < PREF_IDS.length; i++) {
						latencyPrefs.setValue(PREF_IDS[i], localValues.get(LAST_USED_PREF_IDS[i]));
					}
					savePreferences();
				}
			});
		}

		myWorkArea.layout();
		final Point workAreaSizeOriginal = myWorkArea.computeSize(SWT.DEFAULT, SWT.DEFAULT);
		myWorkArea.setSize(workAreaSizeOriginal);

		/*
		 * This is sleazy. We only need the scroller because on Windows and Linux (but not OS X it seems) fiddling
		 * with the system magnification/dpi/zoom settings can cause the contents of the window to be enlarged, but
		 * for some reason the bounds of the dialog remain sized at 100%. So we want to put out "work area" in
		 * the a scroller for this case. But we need to size the work area based on the size of the dialog so
		 * it doesn't look funny. Just using its computed size (see immediately above) is not sufficient, because
		 * it might be too narrow in the overall dialog and look strange. So we now wait for the scroller itself
		 * to be resized when the dialog/shell/window sets its initial size and then set the size of the
		 * "work area" to the minimum of its existing size and the new size. Then we can remove the listener.
		 */
		scroller.addControlListener(new ControlListener() {
			@Override
			public void controlResized(ControlEvent e) {
				final Point newSize = scroller.getSize();
				final int newWidth = workAreaSizeOriginal.x < newSize.x ? newSize.x : workAreaSizeOriginal.x;
				final int newHeight = workAreaSizeOriginal.y < newSize.y ? newSize.y : workAreaSizeOriginal.y;
				myWorkArea.setSize(new Point(newWidth, newHeight));
				// Don't need the listener any more
				scroller.removeControlListener(this);
			}

			@Override
			public void controlMoved(ControlEvent e) {
				// don't care
			}
		});

		return root;
	}

	@Override
	protected boolean isResizable() {
		// TODO Auto-generated method stub
		return true;
	}

	@Override
	protected void okPressed() {
		// Update the last used preferences
		for (int i = 0; i < LAST_USED_PREF_IDS.length; i++) {
			latencyPrefs.setValue(LAST_USED_PREF_IDS[i], localValues.get(LAST_USED_PREF_IDS[i]));
		}
		// Save the show dialog pref -- maybe
		// Note, we can only go from false to true here becuse if don't show was true, the dialog wouldn't have been shown
		if (dontShowDialog) {
			if (MessageDialog.openQuestion(getShell(), "Confirm change",
					"This options dialog will be hidden in the future.  You can restore it by going "
							+ "to the \"OSATE > Analysis > Flow Latency\" preference pane.  Do you wish to make this change?")) {
				latencyPrefs.setValue(Constants.DONT_SHOW_DIALOG, dontShowDialog);
			}
		}

		savePreferences();
		super.okPressed();
	}

	private void savePreferences() {
		if (latencyPrefs.needsSaving()) {
			final Job saveJob = Job.create("Save latency prefences", monitor -> {
				try {
					((IPersistentPreferenceStore) latencyPrefs).save();
				} catch (final IOException e) {
					Display.getDefault().asyncExec(() -> {
						Dialog.showError("Error", "There was a problem saving the preferences: " + e.getMessage());
					});
				}
			});
			saveJob.schedule();
		}
	}

	private Group createGroup(final Composite parent, final String title, final String prefId, final String[] labels,
			final String[] values) {
		final Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
		group.setLayout(new RowLayout(SWT.VERTICAL));
		final GridData layoutData = new GridData(GridData.FILL_HORIZONTAL);
		layoutData.horizontalSpan = 2;
		group.setLayoutData(layoutData);
		group.setText(title);

		final String prefValue = latencyPrefs.getString(prefId);
		for (int i = 0; i < labels.length; i++) {
			final Button radio = new Button(group, SWT.RADIO);
			radio.setText(labels[i]);
			final String choiceValue = values[i];
			radio.setSelection(prefValue.equalsIgnoreCase(choiceValue));
			radio.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(final SelectionEvent event) {
					if (radio.getSelection()) {
						localValues.put(prefId, choiceValue);
					}
				}
			});
			valueToButton.put(choiceValue, radio);
		}

		return group;
	}
}