/****************************************************************************** * Copyright (C) 2013 Fabio Zadrozny * * 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: * Fabio Zadrozny <fabiofz@gmail.com> - initial API and implementation ******************************************************************************/ package org.python.pydev.shared_ui.outline; import java.lang.ref.WeakReference; import java.util.ArrayList; import org.eclipse.swt.widgets.Display; import org.python.pydev.shared_core.callbacks.CallbackWithListeners; import org.python.pydev.shared_core.callbacks.ICallbackWithListeners; import org.python.pydev.shared_core.editor.IBaseEditor; import org.python.pydev.shared_core.log.Log; import org.python.pydev.shared_core.model.ErrorDescription; import org.python.pydev.shared_core.model.IModelListener; import org.python.pydev.shared_core.model.ISimpleNode; public abstract class BaseModel implements IOutlineModel { protected final IBaseEditor editor; private WeakReference<BaseOutlinePage> outlinePageRef; protected final IModelListener modelListener; protected IParsedItem root = null; // A list of top nodes in this document. Used as a tree root protected abstract IParsedItem createParsedItemFromSimpleNode(ISimpleNode ast); private boolean disposed = false; public final ICallbackWithListeners<IOutlineModel> onModelChanged = new CallbackWithListeners<IOutlineModel>(); @Override public ICallbackWithListeners<IOutlineModel> getOnModelChangedCallback() { return onModelChanged; } @Override public void setOutlinePage(BaseOutlinePage baseOutlinePage) { outlinePageRef = new WeakReference<BaseOutlinePage>(baseOutlinePage); } public BaseModel(IBaseEditor editor) { this.editor = editor; // The notifications are only propagated to the outline page // // Tell parser that we want to know about all the changes // make sure that the changes are propagated on the main thread modelListener = new IModelListener() { @Override public void modelChanged(final ISimpleNode ast) { Display.getDefault().asyncExec(new Runnable() { @Override public void run() { synchronized (this) { IParsedItem newRoot = createParsedItemFromSimpleNode(ast); setRoot(newRoot); } } }); } @Override public void errorChanged(final ErrorDescription errorDesc) { Display.getDefault().asyncExec(new Runnable() { @Override public void run() { synchronized (this) { IParsedItem newRoot = duplicateRootAddingError(errorDesc); setRoot(newRoot); } } }); } }; root = this.createInitialRootFromEditor(); if (root == null) { Log.log("null root created in: " + this + " (should not happen)."); } editor.addModelListener(modelListener); } protected abstract IParsedItem createInitialRootFromEditor(); protected abstract IParsedItem duplicateRootAddingError(ErrorDescription errorDesc); @Override public void dispose() { if (!disposed) { disposed = true; editor.removeModelListener(modelListener); onModelChanged.unregisterAllListeners(); root = null; } } @Override public IParsedItem getRoot() { return root; } // patchRootHelper makes oldItem just like the newItem // the differnce between the two is private void patchRootHelper(IParsedItem oldItem, IParsedItem newItem, ArrayList<IParsedItem> itemsToRefresh, ArrayList<IParsedItem> itemsToUpdate) { IParsedItem[] newChildren = newItem.getChildren(); IParsedItem[] oldChildren = oldItem.getChildren(); // stuctural change, different number of children, can stop recursion if (newChildren.length != oldChildren.length) { //at this point, it'll recalculate the children... oldItem.updateTo(newItem); itemsToRefresh.add(oldItem); } else { // Number of children is the same, fix up all the children for (int i = 0; i < oldChildren.length; i++) { patchRootHelper(oldChildren[i], newChildren[i], itemsToRefresh, itemsToUpdate); } // see if the node needs redisplay String oldTitle = oldItem.toString(); String newTitle = newItem.toString(); if (!oldTitle.equals(newTitle) || !oldItem.sameNodeType(newItem)) { itemsToUpdate.add(oldItem); } oldItem.updateShallow(newItem); } } /** * Replaces current root */ public void setRoot(IParsedItem newRoot) { // We'll try to do the 'least flicker replace' // compare the two root structures, and tell outline what to refresh onModelChanged.call(this); try { if (root != null) { ArrayList<IParsedItem> itemsToRefresh = new ArrayList<IParsedItem>(); ArrayList<IParsedItem> itemsToUpdate = new ArrayList<IParsedItem>(); patchRootHelper(root, newRoot, itemsToRefresh, itemsToUpdate); if (outlinePageRef != null) { BaseOutlinePage outlinePage = outlinePageRef.get(); if (outlinePage == null) { return; } if (outlinePage.isDisconnectedFromTree()) { return; } //to update int itemsToUpdateSize = itemsToUpdate.size(); if (itemsToUpdateSize > 0) { outlinePage.updateItems(itemsToUpdate.toArray(new IParsedItem[itemsToUpdateSize])); } //to refresh int itemsToRefreshSize = itemsToRefresh.size(); if (itemsToRefreshSize > 0) { outlinePage.refreshItems(itemsToRefresh.toArray(new IParsedItem[itemsToRefreshSize])); } } } else { if (disposed) { Log.logInfo("It seems it's already disposed..."); } else { Log.logInfo("No old model root?"); } } } catch (Throwable e) { Log.log(e); } } }