package org.archstudio.archipelago2.core;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import org.archstudio.archipelago2.IArchipelago2Editor;
import org.archstudio.eclipse.core.startup.InstantiateArchStudio;
import org.archstudio.filemanager.CantOpenFileException;
import org.archstudio.filemanager.IFileManager;
import org.archstudio.filemanager.IFileManagerListener;
import org.archstudio.myx.fw.MyxRegistry;
import org.archstudio.sysutils.SystemUtils;
import org.archstudio.sysutils.UIDGenerator;
import org.archstudio.xarchadt.IXArchADT;
import org.archstudio.xarchadt.IXArchADTModelListener;
import org.archstudio.xarchadt.ObjRef;
import org.archstudio.xarchadt.XArchADTModelEvent;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPathEditorInput;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.part.EditorPart;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
public class Archipelago2Editor extends EditorPart
implements IFileManagerListener, IXArchADTModelListener {
protected final MyxRegistry myxRegistry;
protected final Archipelago2MyxComponent brick;
protected final String uniqueEditorId;
protected final IXArchADT xarch;
protected final IFileManager fileman;
protected ObjRef docRef = null;
protected Archipelago2OutlinePage outlinePage = null;
protected Composite parent = null;
protected Composite editorParent = null;
protected IArchipelago2Editor activeEditor = null;
protected Set<ObjRef> activeEditorRefs = Sets.newHashSet();
protected List<Object> activeEditorElementPath = null;
protected Multimap<String, IArchipelago2Editor> editorProviders = ArrayListMultimap.create();
public Archipelago2Editor() {
InstantiateArchStudio.instantiate();
myxRegistry = MyxRegistry.getSharedInstance();
brick = myxRegistry.waitForBrick(Archipelago2MyxComponent.class);
myxRegistry.registerObject(brick, this);
uniqueEditorId = UIDGenerator.generateUID(Archipelago2Editor.class.getName());
xarch = brick.getXarch();
fileman = brick.getFileManager();
IExtensionRegistry reg = Platform.getExtensionRegistry();
if (reg != null) {
for (IConfigurationElement configurationElement : reg
.getConfigurationElementsFor("org.archstudio.archipelago2.editors")) {
String bundleName = configurationElement.getDeclaringExtension().getContributor().getName();
String editorName = configurationElement.getAttribute("name");
String editorClassName = configurationElement.getAttribute("class");
try {
@SuppressWarnings("unchecked")
Class<IArchipelago2Editor> editorClass = (Class<IArchipelago2Editor>) Platform
.getBundle(bundleName).loadClass(editorClassName);
IArchipelago2Editor editor = editorClass.newInstance();
editor.init(xarch, docRef);
editorProviders.put(editorName, editor);
} catch (Exception e) {
Activator.getDefault().getLog().log(
new Status(IStatus.ERROR, bundleName, "Cannot instantiate " + editorClassName, e));
}
}
}
}
@Override
public void init(IEditorSite site, IEditorInput input) throws PartInitException {
if (input instanceof IFileEditorInput) {
IFile f = ((IFileEditorInput) input).getFile();
try {
f.refreshLocal(IResource.DEPTH_ONE, null);
} catch (CoreException e) {
throw new PartInitException("Unable to refresh file: " + f);
}
try {
docRef = fileman.open(uniqueEditorId, f);
} catch (CantOpenFileException e) {
throw new PartInitException("Cannot open: " + f, e);
}
setPartName(f.getName());
} else if (input instanceof IPathEditorInput) {
IPath p = ((IPathEditorInput) input).getPath();
java.io.File f = p.toFile();
try {
docRef = fileman.open(uniqueEditorId, f);
} catch (CantOpenFileException e) {
throw new PartInitException("Cannot open: " + f, e);
}
setPartName(f.getName());
} else {
throw new PartInitException("Unrecognized input: " + input.getClass().getName());
}
setSite(site);
setInput(input);
outlinePage = new Archipelago2OutlinePage(xarch, docRef);
// If there is an editor selection event in the outline, update the editor.
outlinePage.addHardSelectionListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
IStructuredSelection selection =
SystemUtils.castOrNull(event.getSelection(), IStructuredSelection.class);
if (selection != null) {
@SuppressWarnings("unchecked")
List<Object> elementPath =
SystemUtils.castOrNull(selection.getFirstElement(), List.class);
if (elementPath != null) {
handleHardSelection(elementPath);
}
}
}
});
myxRegistry.registerObject(brick, outlinePage);
}
@Override
public void dispose() {
disposeActiveEditor();
if (docRef != null) {
fileman.close(uniqueEditorId, docRef);
}
outlinePage.dispose();
super.dispose();
}
protected void disposeActiveEditor() {
// Dispose of current editor.
if (activeEditor != null) {
activeEditor.dispose();
activeEditor = null;
}
activeEditorElementPath = null;
// Remove editor controls.
if (editorParent != null) {
editorParent.dispose();
editorParent = null;
}
activeEditorRefs.clear();
}
protected void createActiveEditor(Class<? extends IArchipelago2Editor> editorProviderClass,
List<Object> elementPath) {
disposeActiveEditor();
try {
// Record the ObjRefs relavent to the editor.
activeEditorRefs.clear();
for (Object element : elementPath) {
if (element instanceof ObjRef) {
activeEditorRefs.add((ObjRef) element);
}
}
// Instantiate the editor.
activeEditorElementPath = elementPath;
activeEditor = editorProviderClass.newInstance();
activeEditor.init(xarch, docRef);
// Create a composite in which to hold the editor.
editorParent = new Composite(parent, SWT.NONE);
activeEditor.createPartControl(this, editorParent, elementPath);
parent.layout(true);
} catch (Exception e) {
disposeActiveEditor();
Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID,
"Cannot create editor for " + editorProviderClass, e));
return;
}
}
@Override
public void handleXArchADTModelEvent(XArchADTModelEvent evt) {
if (activeEditorRefs.contains(evt.getOldValue())) {
getSite().getShell().getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
handleHardSelection(Collections.singletonList((Object) docRef));
}
});
}
}
@Override
public void doSave(IProgressMonitor monitor) {
fileman.save(docRef, monitor);
firePropertyChange(IEditorPart.PROP_DIRTY);
}
@Override
public boolean isSaveAsAllowed() {
return false;
}
@Override
public void doSaveAs() {}
@Override
public void fileSaving(ObjRef documentRootRef, IProgressMonitor monitor) {}
@Override
public void fileDirtyStateChanged(ObjRef xArchRef, boolean dirty) {
if (xArchRef.equals(docRef)) {
getSite().getShell().getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
firePropertyChange(IEditorPart.PROP_DIRTY);
}
});
}
}
@Override
public boolean isDirty() {
if (docRef != null) {
return fileman.isDirty(docRef);
}
return false;
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public Object getAdapter(Class adapter) {
if (IContentOutlinePage.class.equals(adapter)) {
return outlinePage;
}
return super.getAdapter(adapter);
}
@Override
public void createPartControl(Composite parent) {
parent.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_WHITE));
this.parent = parent;
handleHardSelection(Collections.singletonList((Object) docRef));
}
@Override
public void setFocus() {}
/**
* Opens an editor for the selected element and tries to focus on the particular element.
* <p/>
* First attempts to open an editor for the given element. If none exists, then searches for an
* editor for its parent, traversing up the tree to the root node. Then tries to focus on that
* node in the given editor.
*
* @param elementPath The element selected from the outline view.
*/
protected void handleHardSelection(List<Object> elementPath) {
// Search for a potential editor provider.
Entry<String, IArchipelago2Editor> editorProvider = null;
List<Object> editorElementPath = elementPath;
SEARCH: for (int lastElement = elementPath.size(); lastElement >= 0; --lastElement) {
editorElementPath = elementPath.subList(0, lastElement);
for (Entry<String, IArchipelago2Editor> potentialProvider : editorProviders.entries()) {
if (potentialProvider.getValue().canEdit(editorElementPath)) {
editorProvider = potentialProvider;
break SEARCH;
}
}
}
// Check that a provider was found, although this shouldn't happen since
// Archipelago2DefaultEditor accepts the empty list and should always match.
if (editorProvider == null) {
Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID,
"No editor is available for " + elementPath));
return;
}
// Create a new editor if (1) the active editor is currently null, (2) the active editor type
// does not match the provider type selected, or (3) the active editor has a different input
// element path.
if (activeEditor == null
|| !activeEditor.getClass().equals(editorProvider.getValue().getClass())
|| !activeEditorElementPath.equals(editorElementPath)) {
createActiveEditor(editorProvider.getValue().getClass(), editorElementPath);
}
// Select the element.
if (activeEditor != null) {
activeEditor.setFocus(elementPath);
}
}
}