package sk.stuba.fiit.perconik.activity.listeners.ui.element;
import java.util.LinkedList;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.IWorkbenchWindow;
import sk.stuba.fiit.perconik.activity.events.LocalEvent;
import sk.stuba.fiit.perconik.activity.listeners.ActivityListener;
import sk.stuba.fiit.perconik.activity.serializers.ui.PartSerializer;
import sk.stuba.fiit.perconik.activity.serializers.ui.selection.StructuredSelectionSerializer;
import sk.stuba.fiit.perconik.core.annotations.Version;
import sk.stuba.fiit.perconik.core.listeners.PartListener;
import sk.stuba.fiit.perconik.core.listeners.StructuredSelectionListener;
import sk.stuba.fiit.perconik.data.content.Content;
import sk.stuba.fiit.perconik.data.events.Event;
import sk.stuba.fiit.perconik.utilities.concurrent.TimeValue;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static sk.stuba.fiit.perconik.activity.listeners.ui.element.ElementSelectionListener.Action.SELECT;
import static sk.stuba.fiit.perconik.activity.serializers.ConfigurableSerializer.StandardOption.TREE;
import static sk.stuba.fiit.perconik.activity.serializers.Serializations.identifyObject;
import static sk.stuba.fiit.perconik.activity.serializers.Serializers.asDisplayTask;
import static sk.stuba.fiit.perconik.activity.serializers.ui.Ui.dereferencePart;
import static sk.stuba.fiit.perconik.data.content.StructuredContents.key;
import static sk.stuba.fiit.perconik.utilities.concurrent.TimeValue.of;
/**
* TODO
*
* @author Pavol Zbell
* @since 1.0
*/
@Version("0.1.0.alpha")
public final class ElementSelectionListener extends ActivityListener implements PartListener, StructuredSelectionListener {
// TODO note that an element selection is generated on each part activation meaning that same
// selection events are generated when one switches-by-clicking for example between
// an editor and an outline; should it be fixed or left as is?
static final TimeValue selectionEventPause = of(500L, MILLISECONDS);
static final TimeValue selectionEventWindow = of(10L, SECONDS);
private final ElementSelectionEvents events;
public ElementSelectionListener() {
this.events = new ElementSelectionEvents(this);
}
enum Action implements ActivityListener.Action {
SELECT;
private final String name;
private final String path;
private Action() {
this.name = actionName("eclipse", "element", this);
this.path = actionPath(this.name);
}
public String getName() {
return this.name;
}
public String getPath() {
return this.path;
}
}
Event build(final long time, final Action action, final IWorkbenchPart part) {
Event data = LocalEvent.of(time, action.getName());
data.put(key("part"), this.execute(asDisplayTask(new PartSerializer(), part)));
IWorkbenchPartSite site = part.getSite();
if (site != null) {
IWorkbenchPage page = site.getPage();
IWorkbenchWindow window = page.getWorkbenchWindow();
IWorkbench workbench = window.getWorkbench();
data.put(key("part", "page"), identifyObject(page));
data.put(key("part", "page", "window"), identifyObject(window));
data.put(key("part", "page", "window", "workbench"), identifyObject(workbench));
}
return data;
}
Event build(final long time, final Action action, final LinkedList<ElementSelectionEvent> sequence, final IWorkbenchPart part, final IStructuredSelection selection) {
assert sequence.getLast().selection.equals(selection);
Event data = this.build(time, action, part);
data.put(key("sequence", "first", "timestamp"), sequence.getFirst().time);
data.put(key("sequence", "first", "raw"), new StructuredSelectionSerializer(TREE).serialize(sequence.getFirst().selection));
data.put(key("sequence", "last", "timestamp"), sequence.getLast().time);
data.put(key("sequence", "last", "raw"), new StructuredSelectionSerializer(TREE).serialize(sequence.getLast().selection));
data.put(key("sequence", "count"), sequence.size());
data.put(key("elements"), ((Content) data.get("sequence", "last", "raw")).toMap().get("elements"));
return data;
}
void process(final long time, final Action action, final LinkedList<ElementSelectionEvent> sequence, final IWorkbenchPart part, final IStructuredSelection selection) {
this.send(action.getPath(), this.intern(this.build(time, action, sequence, part, selection)));
}
static final class ElementSelectionEvents extends ContinuousEvent<ElementSelectionListener, ElementSelectionEvent> {
ElementSelectionEvents(final ElementSelectionListener listener) {
super(listener, "element-selection", selectionEventPause, selectionEventWindow);
}
@Override
protected boolean accept(final LinkedList<ElementSelectionEvent> sequence, final ElementSelectionEvent event) {
boolean empty = event.selection.isEmpty();
if (sequence.isEmpty()) {
return !empty;
}
ElementSelectionEvent last = sequence.getLast();
if (empty && last.part != event.part) {
return false;
}
if (last.contentEquals(event)) {
return false;
}
return true;
}
@Override
protected boolean continuous(final LinkedList<ElementSelectionEvent> sequence, final ElementSelectionEvent event) {
return sequence.getLast().isContinuousWith(event);
}
@Override
protected void process(final LinkedList<ElementSelectionEvent> sequence) {
this.listener.handleSelectionEvents(sequence);
}
}
void handleSelectionEvents(final LinkedList<ElementSelectionEvent> sequence) {
this.execute(new Runnable() {
public void run() {
ElementSelectionEvent event = sequence.getLast();
process(event.time, SELECT, sequence, event.part, event.selection);
}
});
}
public void partOpened(final IWorkbenchPartReference reference) {
// ignore
}
public void partClosed(final IWorkbenchPartReference reference) {
// ignore
}
public void partActivated(final IWorkbenchPartReference reference) {
// ensures that a selection change is always generated on part activation,
// this primarily helps handling selection context switch in some cases,
// as a side effect it breaks event continuation
this.selectionChanged(reference);
}
public void partDeactivated(final IWorkbenchPartReference reference) {
// ensures that pending events are flushed on part deactivation,
// this primarily handles proper selection termination on shutdown,
// as a side effect it breaks event continuation
this.events.flush();
}
public void partVisible(final IWorkbenchPartReference reference) {
// ignore
}
public void partHidden(final IWorkbenchPartReference reference) {
// ignore
}
public void partBroughtToTop(final IWorkbenchPartReference reference) {
// ignore
}
public void partInputChanged(final IWorkbenchPartReference reference) {
// ignore
}
private void push(final long time, final IWorkbenchPart part, final IStructuredSelection selection) {
this.events.push(new ElementSelectionEvent(time, part, selection));
}
public void selectionChanged(final IWorkbenchPartReference reference) {
final long time = this.currentTime();
IWorkbenchPart part = dereferencePart(reference);
ISelectionProvider provider;
if (part instanceof ISelectionProvider) {
provider = (ISelectionProvider) part;
} else {
provider = part.getSite().getSelectionProvider();
}
if (provider == null) {
if (this.log.isEnabled()) {
this.log.print("%s: provider not found for %s -> ignore", "element-selection", part);
}
return;
}
ISelection selection = provider.getSelection();
if (!(selection instanceof IStructuredSelection)) {
if (this.log.isEnabled()) {
this.log.print("%s: selection not structured in %s of %s -> ignore", "text-selection", provider, part);
}
return;
}
this.push(time, part, (IStructuredSelection) selection);
}
public void selectionChanged(final IWorkbenchPart part, final IStructuredSelection selection) {
final long time = this.currentTime();
this.push(time, part, selection);
}
}