blob: 7dbf143f4dc21b3490bebc5e44fe2cf039161354 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2009, 2021 Stephan Wahlbrink and others.
#
# 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, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.rj.eclient.graphics;
import static org.eclipse.statet.ecommons.ui.actions.UIActions.ADDITIONS_GROUP_ID;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.IHandler2;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.menus.CommandContributionItemParameter;
import org.eclipse.ui.services.IServiceLocator;
import org.eclipse.statet.jcommons.collections.CopyOnWriteIdentityListSet;
import org.eclipse.statet.jcommons.collections.ImIdentityList;
import org.eclipse.statet.jcommons.status.ProgressMonitor;
import org.eclipse.statet.jcommons.status.Status;
import org.eclipse.statet.jcommons.status.StatusException;
import org.eclipse.statet.jcommons.ts.core.SystemRunnable;
import org.eclipse.statet.jcommons.ts.core.Tool;
import org.eclipse.statet.ecommons.ui.SharedUIResources;
import org.eclipse.statet.ecommons.ui.actions.HandlerCollection;
import org.eclipse.statet.ecommons.ui.actions.HandlerContributionItem;
import org.eclipse.statet.internal.rj.eclient.graphics.RGraphicsPlugin;
import org.eclipse.statet.rj.eclient.graphics.util.AbstractLocalLocator;
import org.eclipse.statet.rj.ts.core.AbstractRToolRunnable;
import org.eclipse.statet.rj.ts.core.RToolService;
/**
* Actions for R graphics in an {@link RGraphicComposite}.
*/
public class RGraphicCompositeActionSet implements ERGraphic.ListenerLocatorExtension {
public static final String POSITION_STATUSLINE_ITEM_ID= "position"; //$NON-NLS-1$
public static final String CONTEXT_MENU_GROUP_ID= "context"; //$NON-NLS-1$
public static final String SIZE_MENU_GROUP_ID= "size"; //$NON-NLS-1$
private static final String LOCATOR_DONE_COMMAND_ID= ".locator.done"; //$NON-NLS-1$
private static final String LOCATOR_CANCEL_COMMAND_ID= ".locator.cancel"; //$NON-NLS-1$
private static final String RESIZE_FIT_COMMAND_ID= ".resize.fit"; //$NON-NLS-1$
public static interface LocationListener {
void loading();
void located(double x, double y);
}
private class ResizeFitRHandler extends AbstractHandler {
public ResizeFitRHandler() {
}
@Override
public void setEnabled(final Object evaluationContext) {
setBaseEnabled(RGraphicCompositeActionSet.this.graphic != null);
}
@Override
public Object execute(final ExecutionEvent event) throws ExecutionException {
if (RGraphicCompositeActionSet.this.graphic == null) {
return null;
}
final double[] size= RGraphicCompositeActionSet.this.graphicComposite.getGraphicFitSize();
final Status status= RGraphicCompositeActionSet.this.graphic.resize(size[0], size[1]);
if (status == null || status.getSeverity() != Status.OK) {
// TODO: Status message
Display.getCurrent().beep();
}
return null;
}
}
protected class StopLocatorHandler extends AbstractHandler {
private final String type;
public StopLocatorHandler(final String type) {
this.type= type;
}
@Override
public void setEnabled(final Object evaluationContext) {
setBaseEnabled(RGraphicCompositeActionSet.this.graphic != null && RGraphicCompositeActionSet.this.graphic.isLocatorStarted()
&& RGraphicCompositeActionSet.this.graphic.getLocatorStopTypes().contains(this.type) );
}
@Override
public Object execute(final ExecutionEvent event) throws ExecutionException {
if (RGraphicCompositeActionSet.this.graphic == null) {
return null;
}
RGraphicCompositeActionSet.this.graphic.stopLocator(this.type);
return null;
}
}
private static abstract class ConversionRunnable extends AbstractRToolRunnable implements SystemRunnable {
private final ERGraphic graphic;
private boolean scheduled;
private double[] todoSource;
private double[] convertedSource;
private double[] convertedTarget;
public ConversionRunnable(final ERGraphic graphic) {
super("r/rjgd/position", "Converting graphic coordinates"); //$NON-NLS-1$
this.graphic= graphic;
}
public boolean schedule(final double[] source) {
synchronized (this) {
this.todoSource= source;
if (this.scheduled) {
return true;
}
final Status status= this.graphic.getRHandle().getQueue().addHot(this);
if (status.getSeverity() == Status.OK) {
this.scheduled= true;
return true;
}
return false;
}
}
public void cancel() {
synchronized (this) {
if (this.scheduled) {
this.scheduled= false;
this.todoSource= null;
this.graphic.getRHandle().getQueue().removeHot(this);
}
}
}
@Override
public boolean canRunIn(final Tool tool) {
return (tool == this.graphic.getRHandle() && super.canRunIn(tool));
}
@Override
public boolean changed(final int event, final Tool tool) {
switch (event) {
case MOVING_FROM:
return false;
case REMOVING_FROM:
case BEING_ABANDONED:
case FINISHING_ERROR:
case FINISHING_CANCEL:
synchronized (this) {
this.scheduled= false;
break;
}
case FINISHING_OK:
converted(this.graphic, this.convertedSource, this.convertedTarget);
break;
}
return true;
}
@Override
protected void run(final RToolService service,
final ProgressMonitor m) throws StatusException {
double[] source= null;
double[] target= null;
while (true) {
synchronized (this) {
this.convertedSource= source;
this.convertedTarget= target;
source= this.todoSource;
if (source == null) {
this.scheduled= false;
return;
}
this.todoSource= null;
}
target= this.graphic.convertGraphic2User(source, m);
}
}
protected abstract void converted(ERGraphic graphic, double[] source, double[] target);
}
private class MouseLocationListener implements Listener {
private double[] currentGraphic;
private double[] currentTarget;
@Override
public void handleEvent(final Event event) {
switch (event.type) {
case SWT.MouseDown:
if (event.button == 1) {
final double[] request= this.currentGraphic= new double[] {
RGraphicCompositeActionSet.this.graphicComposite.convertWidget2GraphicX(event.x),
RGraphicCompositeActionSet.this.graphicComposite.convertWidget2GraphicY(event.y) };
this.currentTarget= null;
event.display.timerExec(1000, new Runnable() {
@Override
public void run() {
if (MouseLocationListener.this.currentTarget == null && MouseLocationListener.this.currentGraphic == request
&& !RGraphicCompositeActionSet.this.graphicComposite.isDisposed()) {
notifyMouseLocationListeners(null);
}
}
});
if (RGraphicCompositeActionSet.this.mouseLocationRunnable == null) {
RGraphicCompositeActionSet.this.mouseLocationRunnable= new ConversionRunnable(
RGraphicCompositeActionSet.this.graphic) {
@Override
protected void converted(final ERGraphic graphic,
final double[] source, final double[] target) {
if (RGraphicCompositeActionSet.this.graphic == graphic) {
RGraphicCompositeActionSet.this.display.asyncExec(new Runnable() {
@Override
public void run() {
if (RGraphicCompositeActionSet.this.graphic == graphic
&& source != null && target != null && MouseLocationListener.this.currentGraphic != null
&& source[0] == MouseLocationListener.this.currentGraphic[0] && source[1] == MouseLocationListener.this.currentGraphic[1]) {
MouseLocationListener.this.currentTarget= target;
notifyMouseLocationListeners(target);
}
}
});
}
}
};
}
RGraphicCompositeActionSet.this.mouseLocationRunnable.schedule(this.currentGraphic);
}
break;
}
}
}
private final List<IActionBars> actionBars= new ArrayList<>(4);
private ERGraphic graphic;
private final RGraphicComposite graphicComposite;
private final Display display;
private HandlerCollection handlerCollection;
private final CopyOnWriteIdentityListSet<LocationListener> mouseLocationListeners= new CopyOnWriteIdentityListSet<>();
private MouseLocationListener mouseListenerListener;
private ConversionRunnable mouseLocationRunnable;
public RGraphicCompositeActionSet(final RGraphicComposite composite) {
this.graphicComposite= composite;
this.display= this.graphicComposite.getDisplay();
}
public void setGraphic(final ERGraphic graphic) {
if (this.graphic != null) {
this.graphic.removeListener(this);
if (this.mouseLocationRunnable != null) {
this.mouseLocationRunnable.cancel();
this.mouseLocationRunnable= null;
}
}
this.graphic= graphic;
if (this.graphic != null) {
this.graphic.addListener(this);
}
update();
}
public void initActions(final IServiceLocator serviceLocator) {
this.handlerCollection= new HandlerCollection();
this.handlerCollection.add(LOCATOR_DONE_COMMAND_ID,
new StopLocatorHandler(ERGraphic.LOCATOR_DONE) );
this.handlerCollection.add(LOCATOR_CANCEL_COMMAND_ID,
new StopLocatorHandler(ERGraphic.LOCATOR_CANCEL) );
this.handlerCollection.add(RESIZE_FIT_COMMAND_ID,
new ResizeFitRHandler() );
}
public void contributeToActionsBars(final IServiceLocator serviceLocator,
final IActionBars actionBars) {
this.actionBars.add(actionBars);
final IToolBarManager toolBar= actionBars.getToolBarManager();
if (toolBar.find(CONTEXT_MENU_GROUP_ID) == null) {
toolBar.insertBefore(ADDITIONS_GROUP_ID, new Separator(CONTEXT_MENU_GROUP_ID));
}
if (toolBar.find(SIZE_MENU_GROUP_ID) == null) {
toolBar.insertBefore(ADDITIONS_GROUP_ID, new Separator(SIZE_MENU_GROUP_ID));
}
final ImageRegistry rGraphicsImageRegistry= RGraphicsPlugin.getInstance().getImageRegistry();
toolBar.appendToGroup(CONTEXT_MENU_GROUP_ID, new HandlerContributionItem(new CommandContributionItemParameter(
serviceLocator, null, HandlerContributionItem.NO_COMMAND_ID, null,
rGraphicsImageRegistry.getDescriptor(RGraphicsPlugin.IMG_LOCTOOL_LOCATOR_DONE), null, null,
"Stop Locator", null, null, HandlerContributionItem.STYLE_PUSH, null, true),
this.handlerCollection.get(LOCATOR_DONE_COMMAND_ID) ));
toolBar.appendToGroup(CONTEXT_MENU_GROUP_ID, new HandlerContributionItem(new CommandContributionItemParameter(
serviceLocator, null, HandlerContributionItem.NO_COMMAND_ID, null,
rGraphicsImageRegistry.getDescriptor(RGraphicsPlugin.IMG_LOCTOOL_LOCATOR_CANCEL), null, null,
"Cancel Locator", null, null, HandlerContributionItem.STYLE_PUSH, null, true),
this.handlerCollection.get(LOCATOR_CANCEL_COMMAND_ID) ));
}
protected void addTestLocator(final IServiceLocator serviceLocator, final IActionBars actionBars) {
final IToolBarManager toolBar= actionBars.getToolBarManager();
final IHandler2 handler= new AbstractHandler() {
@Override
public void setEnabled(final Object evaluationContext) {
setBaseEnabled(RGraphicCompositeActionSet.this.graphic != null && !RGraphicCompositeActionSet.this.graphic.isLocatorStarted());
}
@Override
public Object execute(final ExecutionEvent event) throws ExecutionException {
if (RGraphicCompositeActionSet.this.graphic == null || RGraphicCompositeActionSet.this.graphic.isLocatorStarted()) {
return null;
}
final AbstractLocalLocator locator= new AbstractLocalLocator(RGraphicCompositeActionSet.this.graphic) {
@Override
protected void finished(final List<double[]> graphic, final List<double[]> user) {
final StringBuilder sb= new StringBuilder();
for (int i= 0; i < user.size(); i++) {
sb.append(Arrays.toString(user.get(i))).append("\n");
}
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
MessageDialog.openInformation(null, "Locator Result",
sb.toString());
}
});
}
@Override
protected void canceled() {
}
};
locator.start();
return null;
}
};
this.handlerCollection.add(".locator.startTest", handler);
toolBar.appendToGroup(CONTEXT_MENU_GROUP_ID, new HandlerContributionItem(new CommandContributionItemParameter(
serviceLocator, null, HandlerContributionItem.NO_COMMAND_ID, null,
SharedUIResources.getImages().getDescriptor(SharedUIResources.LOCTOOL_SORT_SCORE_IMAGE_ID), null, null,
"Test Locator", null, null, HandlerContributionItem.STYLE_PUSH, null, false),
handler ));
}
protected void addSizeActions(final IServiceLocator serviceLocator, final IActionBars actionBars) {
final IToolBarManager toolBar= actionBars.getToolBarManager();
final ImageRegistry rGraphicsImageRegistry= RGraphicsPlugin.getInstance().getImageRegistry();
toolBar.appendToGroup(SIZE_MENU_GROUP_ID, new HandlerContributionItem(new CommandContributionItemParameter(
serviceLocator, null, HandlerContributionItem.NO_COMMAND_ID, null,
rGraphicsImageRegistry.getDescriptor(RGraphicsPlugin.IMG_LOCTOOL_RESIZE_FIT_R), null, null,
"Resize Fit in R", null, null, HandlerContributionItem.STYLE_PUSH, null, false ),
this.handlerCollection.get(RESIZE_FIT_COMMAND_ID) ));
update();
}
protected void update() {
if (this.actionBars.isEmpty()) {
return;
}
this.handlerCollection.update(null);
for (final IActionBars actionBars : this.actionBars) {
actionBars.getToolBarManager().update(true);
}
}
@Override
public void activated() {
}
@Override
public void deactivated() {
}
@Override
public void drawingStarted() {
}
@Override
public void drawingStopped() {
}
@Override
public void locatorStarted() {
update();
}
@Override
public void locatorStopped() {
update();
}
public void addMouseClickLocationListener(final LocationListener listener) {
this.mouseLocationListeners.add(listener);
if (this.mouseListenerListener == null) {
this.mouseListenerListener= new MouseLocationListener();
final Control widget= this.graphicComposite.getGraphicWidget();
widget.addListener(SWT.MouseDown, this.mouseListenerListener);
}
}
public void removeMouseLocationListener(final LocationListener listener) {
this.mouseLocationListeners.remove(listener);
}
private void notifyMouseLocationListeners(final double[] xy) {
final ImIdentityList<LocationListener> listeners= this.mouseLocationListeners.toList();
if (xy != null) {
for (final LocationListener listener : listeners) {
listener.located(xy[0], xy[1]);
}
}
else {
for (final LocationListener listener : listeners) {
listener.loading();
}
}
}
public void dispose(final IActionBars actionBars) {
this.actionBars.remove(actionBars);
}
public void dispose() {
setGraphic(null);
}
}