/*******************************************************************************
* Copyright (c) 2009, 2011 David Green and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* David Green - initial API and implementation
*******************************************************************************/
package org.eclipse.mylyn.internal.wikitext.ui.editor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.jface.text.ITextOperationTarget;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
import org.eclipse.jface.text.source.projection.ProjectionViewer;
import org.eclipse.mylyn.wikitext.parser.outline.OutlineItem;
import org.eclipse.swt.graphics.Point;
/**
* @author David Green
*/
class FoldingStructure implements IFoldingStructure {
private interface AnnotationOperation {
public boolean operate(HeadingProjectionAnnotation annotation);
}
private static abstract class AbstractItemsAnnotationOperation implements AnnotationOperation {
final Set<String> ids;
protected AbstractItemsAnnotationOperation(Collection<OutlineItem> items) {
ids = idsOf(items);
}
Set<String> idsOf(Collection<OutlineItem> items) {
if (items == null || items.isEmpty()) {
return Collections.emptySet();
}
Set<String> ids = new HashSet<>();
for (OutlineItem item : items) {
ids.add(item.getId());
}
return ids;
}
public final boolean operate(HeadingProjectionAnnotation annotation) {
if (ids.contains(annotation.getHeadingId())) {
return operateOnSelected(annotation);
} else {
return operateOnUnselected(annotation);
}
}
public abstract boolean operateOnSelected(HeadingProjectionAnnotation annotation);
public boolean operateOnUnselected(HeadingProjectionAnnotation annotation) {
return false;
}
}
final ProjectionViewer viewer;
final ITextOperationTarget textOperationTarget;
public FoldingStructure(MarkupEditor editor) {
viewer = (ProjectionViewer) editor.getViewer();
textOperationTarget = (ITextOperationTarget) editor.getAdapter(ITextOperationTarget.class);
}
public void collapseAll(boolean collapseRegionContainingCaret) {
if (!isFoldingEnabled()) {
return;
}
if (collapseRegionContainingCaret) {
textOperationTarget.doOperation(ProjectionViewer.COLLAPSE_ALL);
} else {
operateOnAnnotations(new AbstractItemsAnnotationOperation(new ArrayList<OutlineItem>()) {
@Override
public boolean operateOnSelected(HeadingProjectionAnnotation annotation) {
return operateOnUnselected(annotation);
}
@Override
public boolean operateOnUnselected(HeadingProjectionAnnotation annotation) {
if (!annotation.isCollapsed()) {
annotation.markCollapsed();
return true;
}
return false;
}
}, collapseRegionContainingCaret);
}
}
public void collapseElements(Collection<OutlineItem> items, final boolean collapseRegionContainingCaret) {
if (!isFoldingEnabled()) {
return;
}
if (items == null || items.isEmpty()) {
return;
}
operateOnAnnotations(new AbstractItemsAnnotationOperation(items) {
@Override
public boolean operateOnSelected(HeadingProjectionAnnotation annotation) {
if (!annotation.isCollapsed()) {
annotation.markCollapsed();
return true;
}
return false;
}
}, collapseRegionContainingCaret);
}
public void expandAll() {
if (!isFoldingEnabled()) {
return;
}
textOperationTarget.doOperation(ProjectionViewer.EXPAND_ALL);
}
public void expandElements(Collection<OutlineItem> items) {
if (!isFoldingEnabled()) {
return;
}
if (items == null || items.isEmpty()) {
return;
}
operateOnAnnotations(new AbstractItemsAnnotationOperation(items) {
@Override
public boolean operateOnSelected(HeadingProjectionAnnotation annotation) {
if (annotation.isCollapsed()) {
annotation.markExpanded();
return true;
}
return false;
}
}, true);
}
public void expandElementsExclusive(Collection<OutlineItem> items, boolean collapseRegionContainingCaret) {
if (!isFoldingEnabled()) {
return;
}
if (items == null || items.isEmpty()) {
collapseAll(collapseRegionContainingCaret);
return;
}
operateOnAnnotations(new AbstractItemsAnnotationOperation(items) {
@Override
public boolean operateOnSelected(HeadingProjectionAnnotation annotation) {
if (annotation.isCollapsed()) {
annotation.markExpanded();
return true;
}
return false;
}
@Override
public boolean operateOnUnselected(HeadingProjectionAnnotation annotation) {
if (!annotation.isCollapsed()) {
annotation.markCollapsed();
return true;
}
return false;
}
}, collapseRegionContainingCaret);
}
@SuppressWarnings("unchecked")
public void operateOnAnnotations(AnnotationOperation operation, boolean collapseRegionIncludingCaret) {
if (!isFoldingEnabled()) {
return;
}
Point selectedRange = viewer.getSelectedRange();
Position selectedPosition = selectedRange == null ? null : new Position(selectedRange.x, selectedRange.y);
boolean updateSelectedRange = false;
ProjectionAnnotationModel annotationModel = viewer.getProjectionAnnotationModel();
List<Annotation> modifications = null;
Iterator<Annotation> iterator = annotationModel.getAnnotationIterator();
while (iterator.hasNext()) {
Annotation annotation = iterator.next();
if (annotation instanceof HeadingProjectionAnnotation) {
HeadingProjectionAnnotation projectionAnnotation = (HeadingProjectionAnnotation) annotation;
if (operation.operate(projectionAnnotation)) {
if (modifications == null) {
modifications = new ArrayList<>();
}
modifications.add(projectionAnnotation);
Position position = annotationModel.getPosition(projectionAnnotation);
if (!collapseRegionIncludingCaret && projectionAnnotation.isCollapsed() && selectedPosition != null
&& selectedPosition.overlapsWith(position.getOffset(), position.getLength())) {
projectionAnnotation.markExpanded();
}
if (selectedPosition != null && position != null && projectionAnnotation.isCollapsed()
&& selectedPosition.overlapsWith(position.offset, position.length)) {
updateSelectedRange = true;
}
}
}
}
if (modifications != null) {
if (updateSelectedRange) {
// a collapsed region overlaps with the selection. Attempt to relocate the selection to a region that is not collapsed,
// or if we can't find one move it to 0.
int offset = 0;
iterator = annotationModel.getAnnotationIterator();
while (iterator.hasNext()) {
Annotation annotation = iterator.next();
if (annotation instanceof HeadingProjectionAnnotation) {
HeadingProjectionAnnotation projectionAnnotation = (HeadingProjectionAnnotation) annotation;
if (!projectionAnnotation.isCollapsed()) {
Position position = annotationModel.getPosition(projectionAnnotation);
if (position != null) {
offset = position.offset;
break;
}
}
}
}
viewer.setSelectedRange(offset, 0);
}
annotationModel.modifyAnnotations(null, null, modifications.toArray(new Annotation[modifications.size()]));
}
}
public final boolean isFoldingEnabled() {
return viewer.getProjectionAnnotationModel() != null;
}
}