package jalview.ws2.gui;
import static java.lang.String.format;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletionStage;
import java.util.function.Consumer;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.ToolTipManager;
import jalview.datamodel.Alignment;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.AlignmentOrder;
import jalview.datamodel.AlignmentView;
import jalview.datamodel.HiddenColumns;
import jalview.gui.AlignFrame;
import jalview.gui.AlignViewport;
import jalview.gui.Desktop;
import jalview.gui.JvOptionPane;
import jalview.gui.JvSwingUtils;
import jalview.gui.WebserviceInfo;
import jalview.gui.WsJobParameters;
import jalview.util.MathUtils;
import jalview.util.MessageManager;
import jalview.ws.params.ArgumentI;
import jalview.ws.params.ParamDatastoreI;
import jalview.ws.params.WsParamSetI;
import jalview.ws2.MenuEntryProviderI;
import jalview.ws2.PollingTaskExecutor;
import jalview.ws2.WSJob;
import jalview.ws2.WebServiceInfoUpdater;
import jalview.ws2.WebServiceWorkerI;
import jalview.ws2.WebServiceWorkerListener;
import jalview.ws2.operations.AlignmentOperation;
import jalview.ws2.operations.AlignmentOperation.AlignmentResult;
import jalview.ws2.operations.AlignmentOperation.AlignmentWorker;
public class AlignmentMenuBuilder implements MenuEntryProviderI
{
AlignmentOperation operation;
public AlignmentMenuBuilder(AlignmentOperation operation)
{
this.operation = operation;
}
public void buildMenu(JMenu parent, AlignFrame frame)
{
if (operation.canSubmitGaps())
{
var alignSubmenu = new JMenu(operation.getName());
buildMenu(alignSubmenu, frame, false);
parent.add(alignSubmenu);
var realignSubmenu = new JMenu(MessageManager.formatMessage(
"label.realign_with_params", operation.getName()));
realignSubmenu.setToolTipText(MessageManager
.getString("label.align_sequences_to_existing_alignment"));
buildMenu(realignSubmenu, frame, true);
parent.add(realignSubmenu);
}
else
{
buildMenu(parent, frame, false);
}
}
protected void buildMenu(JMenu parent, AlignFrame frame,
boolean submitGaps)
{
final String action = submitGaps ? "Align" : "Realign";
final var calcName = operation.getName();
{
var item = new JMenuItem(MessageManager.formatMessage(
"label.calcname_with_default_settings", calcName));
item.setToolTipText(MessageManager
.formatMessage("label.action_with_default_settings", action));
item.addActionListener((event) -> {
final AlignmentView msa = frame.gatherSequencesForAlignment();
if (msa != null)
{
startWorker(frame, msa, Collections.emptyList(), submitGaps);
}
});
parent.add(item);
}
if (operation.hasParameters())
{
var item = new JMenuItem(
MessageManager.getString("label.edit_settings_and_run"));
item.setToolTipText(MessageManager.getString(
"label.view_and_change_parameters_before_alignment"));
item.addActionListener((event) -> {
AlignmentView msa = frame.gatherSequencesForAlignment();
if (msa != null)
{
openEditParamsDialog(operation.getParamStore(), null, null)
.thenAcceptAsync((arguments) -> {
if (arguments != null)
{
startWorker(frame, msa, arguments, submitGaps);
}
});
}
});
parent.add(item);
}
var presets = operation.getParamStore().getPresets();
if (presets != null && presets.size() > 0)
{
final var presetList = new JMenu(MessageManager
.formatMessage("label.run_with_preset_params", calcName));
final var showToolTipFor = ToolTipManager.sharedInstance()
.getDismissDelay();
for (final var preset : presets)
{
var item = new JMenuItem(preset.getName());
final int QUICK_TOOLTIP = 1500;
item.addMouseListener(new MouseAdapter()
{
@Override
public void mouseEntered(MouseEvent e)
{
ToolTipManager.sharedInstance().setDismissDelay(QUICK_TOOLTIP);
}
@Override
public void mouseExited(MouseEvent e)
{
ToolTipManager.sharedInstance().setDismissDelay(showToolTipFor);
}
});
String tooltip = JvSwingUtils.wrapTooltip(true,
format("%s
%s",
MessageManager.getString(
preset.isModifiable() ? "label.user_preset"
: "label.service_preset"),
preset.getDescription()));
item.setToolTipText(tooltip);
item.addActionListener((event) -> {
AlignmentView msa = frame.gatherSequencesForAlignment();
startWorker(frame, msa, preset.getArguments(), submitGaps);
});
presetList.add(item);
}
parent.add(presetList);
}
}
private CompletionStage> openEditParamsDialog(
ParamDatastoreI paramStore, WsParamSetI preset,
List arguments)
{
WsJobParameters jobParams;
if (preset == null && arguments != null && arguments.size() > 0)
jobParams = new WsJobParameters(paramStore, preset, arguments);
else
jobParams = new WsJobParameters(paramStore, preset, null);
if (preset != null)
{
jobParams.setName(MessageManager.getString(
"label.adjusting_parameters_for_calculation"));
}
var stage = jobParams.showRunDialog();
return stage.thenApply((startJob) -> {
if (startJob)
{
if (jobParams.getPreset() == null)
{
return jobParams.getJobParams();
}
else
{
return jobParams.getPreset().getArguments();
}
}
else
{
return null;
}
});
}
private void startWorker(AlignFrame frame, AlignmentView msa,
List arguments, boolean submitGaps)
{
AlignViewport viewport = frame.getViewport();
PollingTaskExecutor executor = frame.getViewport().getWSExecutor();
if (msa != null)
{
String panelInfo = String.format("%s using service hosted at %s%n%s",
operation.getName(), operation.getHostName(),
Objects.requireNonNullElse(operation.getDescription(), ""));
var wsInfo = new WebserviceInfo(operation.getName(), panelInfo, false);
final String alnTitle = frame.getTitle();
AlignmentWorker worker = operation.new AlignmentWorker(msa,
arguments, frame.getTitle(), submitGaps, true,
viewport);
String outputHeader = String.format("%s of %s%nJob details%n",
submitGaps ? "Re-alignment" : "Alignment", alnTitle);
var awl = new AlignmentWorkerListener(worker, wsInfo, frame,
outputHeader);
worker.setResultConsumer(awl);
worker.addListener(awl);
executor.submit(worker);
}
}
private class AlignmentWorkerListener
implements WebServiceWorkerListener, Consumer
{
final WebServiceWorkerI worker;
final WebserviceInfo wsInfo;
final AlignFrame frame;
WebServiceInfoUpdater updater;
String outputHeader;
final long progbarId = MathUtils.getUID();
private AlignmentWorkerListener(WebServiceWorkerI worker, WebserviceInfo wsInfo,
AlignFrame frame, String header)
{
this.worker = worker;
this.wsInfo = wsInfo;
this.frame = frame;
this.outputHeader = header;
this.updater = new WebServiceInfoUpdater(worker, wsInfo);
updater.setOutputHeader(outputHeader);
}
@Override
public void workerStarted(WebServiceWorkerI source)
{
// wsInfo.setThisService() should happen here
wsInfo.setVisible(true);
}
@Override
public void workerNotStarted(WebServiceWorkerI source)
{
wsInfo.setVisible(false);
// TODO show notification dialog.
JvOptionPane.showMessageDialog(frame,
MessageManager.getString("info.invalid_msa_input_mininfo"),
MessageManager.getString("info.invalid_msa_notenough"),
JvOptionPane.INFORMATION_MESSAGE);
}
@Override
public void jobCreated(WebServiceWorkerI source, WSJob job)
{
int tabIndex = wsInfo.addJobPane();
wsInfo.setProgressName(String.format("region %d", job.getJobNum()),
tabIndex);
wsInfo.setProgressText(tabIndex, outputHeader);
job.addPropertyChangeListener(updater);
}
@Override
public void pollException(WebServiceWorkerI source, WSJob job, Exception e)
{
wsInfo.appendProgressText(job.getJobNum(),
MessageManager.formatMessage("info.server_exception",
operation.getName(), e.getMessage()));
}
@Override
public void workerCompleting(WebServiceWorkerI source)
{
// TODO Auto-generated method stub
wsInfo.setProgressBar(
MessageManager.getString("status.collecting_job_results"),
progbarId);
}
@Override
public void workerCompleted(WebServiceWorkerI source)
{
wsInfo.removeProgressBar(progbarId);
}
@Override
public void accept(AlignmentResult out)
{
if (out != null)
{
wsInfo.showResultsNewFrame.addActionListener(evt -> displayNewFrame(
new Alignment(out.getAln()), out.getAlorders(), out.getHidden()));
wsInfo.setResultsReady();
}
else
{
wsInfo.setFinishedNoResults();
}
}
private void displayNewFrame(AlignmentI aln,
List alorders, HiddenColumns hidden)
{
AlignFrame frame = new AlignFrame(aln, hidden,
AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
// TODO store feature renderer settings in worker object
// frame.getFeatureRenderer().transferSettings(featureSettings);
var regions = sortOrders(alorders);
if (alorders.size() == 1)
{
frame.addSortByOrderMenuItem(
format("%s Ordering", operation.getName()), alorders.get(0));
}
else
{
for (int i = 0; i < alorders.size(); i++)
{
final int j = i;
Iterable iter = () -> regions.get(j).stream()
.map(it -> Integer.toString(it)).iterator();
var orderName = format("%s Region %s Ordering", operation.getName(),
String.join(",", iter));
frame.addSortByOrderMenuItem(orderName, alorders.get(i));
}
}
/* TODO
* If alignment was requested from one half of a SplitFrame, show in a
* SplitFrame with the other pane similarly aligned.
*/
Desktop.addInternalFrame(frame, frame.getTitle(), AlignFrame.DEFAULT_WIDTH,
AlignFrame.DEFAULT_HEIGHT);
}
private List> sortOrders(List> alorders)
{
List> regions = new ArrayList<>();
for (int i = 0; i < alorders.size(); i++)
{
List regs = new ArrayList<>();
regs.add(i);
int j = i + 1;
while (j < alorders.size())
{
if (alorders.get(i).equals(alorders.get(j)))
{
alorders.remove(j);
regs.add(j);
}
else
{
j++;
}
}
regions.add(regs);
}
return regions;
}
}
}