package sk.stuba.fiit.perconik.activity.listeners.ui.text; import java.util.LinkedList; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IMarkSelection; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchPartReference; import sk.stuba.fiit.perconik.activity.listeners.ActivityListener; import sk.stuba.fiit.perconik.activity.serializers.ui.selection.MarkSelectionSerializer; import sk.stuba.fiit.perconik.core.annotations.Version; import sk.stuba.fiit.perconik.core.listeners.MarkSelectionListener; import sk.stuba.fiit.perconik.core.listeners.PartListener; import sk.stuba.fiit.perconik.data.events.Event; import sk.stuba.fiit.perconik.eclipse.jface.text.LineRegion; 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.text.TextMarkListener.Action.MARK; import static sk.stuba.fiit.perconik.activity.serializers.ConfigurableSerializer.StandardOption.TREE; 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 TextMarkListener extends AbstractTextListener implements MarkSelectionListener, PartListener { // TODO note that mark selection is generated only on incremental search (ctrl + j) // or for marked regions used in Emacs style editing static final TimeValue selectionEventPause = of(500L, MILLISECONDS); static final TimeValue selectionEventWindow = of(10L, SECONDS); private final TextMarkEvents events; public TextMarkListener() { this.events = new TextMarkEvents(this); } enum Action implements ActivityListener.Action { MARK; private final String name; private final String path; private Action() { this.name = actionName("eclipse", "text", 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 LinkedList<TextMarkEvent> sequence, final IWorkbenchPart part, final IMarkSelection selection, final LineRegion region) { assert sequence.getLast().selection.equals(selection); Event data = this.build(time, action, part, region); data.put(key("sequence", "first", "timestamp"), sequence.getFirst().time); data.put(key("sequence", "first", "raw"), new MarkSelectionSerializer(TREE).serialize(sequence.getFirst().selection)); data.put(key("sequence", "last", "timestamp"), sequence.getLast().time); data.put(key("sequence", "last", "raw"), new MarkSelectionSerializer(TREE).serialize(sequence.getLast().selection)); data.put(key("sequence", "count"), sequence.size()); return data; } void process(final long time, final Action action, final LinkedList<TextMarkEvent> sequence, final IWorkbenchPart part, final IMarkSelection selection) { IDocument document = selection.getDocument(); LineRegion region = LineRegion.compute(document, selection.getOffset(), selection.getLength()).normalize(); this.send(action.getPath(), this.intern(this.build(time, action, sequence, part, selection, region))); } static final class TextMarkEvents extends ContinuousEvent<TextMarkListener, TextMarkEvent> { TextMarkEvents(final TextMarkListener listener) { super(listener, "text-mark", selectionEventPause, selectionEventWindow); } @Override protected boolean accept(final LinkedList<TextMarkEvent> sequence, final TextMarkEvent event) { return true; } @Override protected boolean continuous(final LinkedList<TextMarkEvent> sequence, final TextMarkEvent event) { return sequence.getLast().isContinuousWith(event); } @Override protected void process(final LinkedList<TextMarkEvent> sequence) { this.listener.handleSelectionEvents(sequence); } } void handleSelectionEvents(final LinkedList<TextMarkEvent> sequence) { this.execute(new Runnable() { public void run() { TextMarkEvent event = sequence.getLast(); process(event.time, MARK, 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) { // ignore } 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 IMarkSelection selection) { this.events.push(new TextMarkEvent(time, part, selection)); } public void selectionChanged(final IWorkbenchPart part, final IMarkSelection selection) { final long time = this.currentTime(); this.push(time, part, selection); } }