/******************************************************************************* * 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; } }