/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.tools.designer.editor;
import com.google.common.collect.Lists;
import com.google.dart.tools.designer.model.EditorContextCommitListener;
import com.google.dart.tools.designer.model.XmlObjectInfo;
import org.apache.commons.collections.map.LRUMap;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.widgets.Display;
import org.eclipse.wb.core.model.broadcast.ObjectEventListener;
import org.eclipse.wb.internal.core.editor.DesignPageSite;
import org.eclipse.wb.internal.core.editor.ObjectPathHelper;
import org.eclipse.wb.internal.core.editor.structure.components.IComponentsTree;
import java.util.List;
import java.util.Map;
/**
* Manager for handling undo/redo modifications in XML {@link IDocument}.
*
* @author scheglov_ke
* @coverage XML.editor
*/
public final class UndoManager {
private final XmlDesignPage m_designPage;
private final IDocument m_document;
private String m_currentSource;
private String m_currentDump;
private IComponentsTree m_componentsTree;
private ISelectionProvider m_selectionProvider;
private ITreeContentProvider m_componentsProvider;
private ObjectPathHelper m_objectPathHelper;
@SuppressWarnings("unchecked")
private final Map<String, int[][]> m_dumpToSelection = new LRUMap(32);
@SuppressWarnings("unchecked")
private final Map<String, int[][]> m_dumpToExpanded = new LRUMap(32);
////////////////////////////////////////////////////////////////////////////
//
// Constructor
//
////////////////////////////////////////////////////////////////////////////
public UndoManager(XmlDesignPage designPage, IDocument document) {
m_designPage = designPage;
m_document = document;
}
////////////////////////////////////////////////////////////////////////////
//
// Access
//
////////////////////////////////////////////////////////////////////////////
private boolean m_active;
private XmlObjectInfo m_rootObject;
/**
* Activates {@link UndoManager}, so it starts listening for document changes.
*/
public void activate() {
if (!m_active) {
m_active = true;
if (!StringUtils.equals(m_currentSource, m_document.get())) {
refreshDesignerEditor();
}
addDocumentListener();
}
}
/**
* Deactivates {@link UndoManager}, so it stops listening for document changes.
*/
public void deactivate() {
m_active = false;
removeDocumentListener();
}
/**
* Sets the new root {@link XmlObjectInfo} in editor.
*/
public void setRoot(XmlObjectInfo rootObject) {
m_rootObject = rootObject;
rootObject.addBroadcastListener(m_commitListener);
rootObject.addBroadcastListener(m_refreshListener);
// get components tree
{
DesignPageSite site = DesignPageSite.Helper.getSite(rootObject);
m_componentsTree = site.getComponentTree();
m_componentsProvider = m_componentsTree.getContentProvider();
m_selectionProvider = m_componentsTree.getSelectionProvider();
m_objectPathHelper = new ObjectPathHelper(m_componentsProvider);
// listen for tree expansion state
m_componentsTree.setExpandListener(new Runnable() {
@Override
public void run() {
Display.getCurrent().asyncExec(new Runnable() {
@Override
public void run() {
rememberState();
}
});
}
});
}
}
////////////////////////////////////////////////////////////////////////////
//
// IDocument listener
//
////////////////////////////////////////////////////////////////////////////
private int m_bufferChangeCount = 0;
private IRefreshStrategy m_refreshStrategy = IRefreshStrategy.IMMEDIATELY;
private final IDocumentListener m_documentListener = new IDocumentListener() {
@Override
public void documentChanged(DocumentEvent event) {
if (event.getText() != null || event.getLength() != 0) {
scheduleRefresh_onBufferChange();
}
}
@Override
public void documentAboutToBeChanged(DocumentEvent event) {
}
private void scheduleRefresh_onBufferChange() {
final int bufferChangeCount = ++m_bufferChangeCount;
Runnable runnable = new Runnable() {
@Override
public void run() {
if (bufferChangeCount == m_bufferChangeCount) {
refreshDesignerEditor();
}
}
};
if (m_refreshStrategy.shouldImmediately()) {
runnable.run();
} else if (m_refreshStrategy.shouldWithDelay()) {
int delay = m_refreshStrategy.getDelay();
Display.getDefault().timerExec(delay, runnable);
}
}
};
void setRefreshStrategy(IRefreshStrategy refreshStrategy) {
m_refreshStrategy = refreshStrategy;
}
/**
* Adds {@link IDocumentListener}.
*/
private void addDocumentListener() {
m_document.addDocumentListener(m_documentListener);
}
/**
* Removes {@link IDocumentListener}.
*/
private void removeDocumentListener() {
m_document.removeDocumentListener(m_documentListener);
}
////////////////////////////////////////////////////////////////////////////
//
// Refresh listener
//
////////////////////////////////////////////////////////////////////////////
/**
* Remove IDocument listeners during commit.
*/
private final EditorContextCommitListener m_commitListener = new EditorContextCommitListener() {
@Override
public void aboutToCommit() {
removeDocumentListener();
}
@Override
public void doneCommit() {
addDocumentListener();
}
};
/**
* Remember new state after refresh.
*/
private final ObjectEventListener m_refreshListener = new ObjectEventListener() {
@Override
public void refreshBeforeCreate() throws Exception {
removeSelectionListener();
}
@Override
public void refreshed2() throws Exception {
addSelectionListener();
rememberSource();
rememberDump();
rememberState();
}
};
////////////////////////////////////////////////////////////////////////////
//
// Selection listener
//
////////////////////////////////////////////////////////////////////////////
private final ISelectionChangedListener m_selectionListener = new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
rememberState();
}
};
/**
* Adds {@link ISelectionChangedListener}.
*/
private void addSelectionListener() {
m_selectionProvider.addSelectionChangedListener(m_selectionListener);
}
/**
* Removes {@link ISelectionChangedListener}
*/
private void removeSelectionListener() {
if (m_selectionProvider != null) {
m_selectionProvider.removeSelectionChangedListener(m_selectionListener);
}
}
////////////////////////////////////////////////////////////////////////////
//
// Refresh
//
////////////////////////////////////////////////////////////////////////////
/**
* Informs {@link DesignPage} that buffer was changed and reparse required.
*/
void refreshDesignerEditor() {
rememberSource();
// refresh viewer
removeSelectionListener();
if (m_designPage.internal_refreshGEF()) {
addSelectionListener();
// restore state
rememberDump();
restoreState();
}
}
////////////////////////////////////////////////////////////////////////////
//
// State
//
////////////////////////////////////////////////////////////////////////////
/**
* Remembers current source.
*/
private void rememberSource() {
m_currentSource = m_document.get();
}
/**
* Remembers current dump.
*/
private void rememberDump() {
m_currentDump = ObjectPathHelper.getObjectsDump(m_rootObject, 0);
}
/**
* Remembers selected/expanded elements for current source.
*/
private void rememberState() {
// selection
{
// prepare selected objects
Object[] selectedObjects;
{
IStructuredSelection structuredSelection = (IStructuredSelection) m_selectionProvider.getSelection();
selectedObjects = structuredSelection.toArray();
}
// remember
int[][] paths = m_objectPathHelper.getObjectsPaths(selectedObjects);
//m_sourceToSelection.put(m_currentSource, paths);
m_dumpToSelection.put(m_currentDump, paths);
}
// expanded
{
Object[] expandedObjects = m_componentsTree.getExpandedElements();
int[][] paths = m_objectPathHelper.getObjectsPaths(expandedObjects);
m_dumpToExpanded.put(m_currentDump, paths);
}
}
/**
* Tries to restore selected/expanded elements for current source.
*/
private void restoreState() {
restoreSelection();
restoreExpanded();
}
/**
* Tries to restore selected elements for current source.
*/
private void restoreSelection() {
// prepare selection
int[][] paths = m_dumpToSelection.get(m_currentDump);
// do restore
if (paths != null) {
removeSelectionListener();
try {
Object[] objects = m_objectPathHelper.getObjectsForPaths(paths);
m_selectionProvider.setSelection(new StructuredSelection(objects));
} finally {
addSelectionListener();
}
}
}
/**
* Tries to restore expanded elements for current dump.
*/
private void restoreExpanded() {
int[][] paths = m_dumpToExpanded.get(m_currentDump);
// do restore
if (paths != null) {
Object[] objects = m_objectPathHelper.getObjectsForPaths(paths);
m_componentsTree.setExpandedElements(objects);
}
// if no expanded element, perform default expanding
if (m_componentsTree.getExpandedElements().length == 0) {
List<Object> expandedElements = Lists.newArrayList();
Object element = m_rootObject;
while (true) {
expandedElements.add(element);
Object[] children = m_componentsProvider.getChildren(element);
if (children.length != 1) {
break;
}
element = children[0];
}
m_componentsTree.setExpandedElements(expandedElements.toArray());
}
}
}