/*****************************************************************************
* Limpet - the Lightweight InforMation ProcEssing Toolkit
* http://limpet.info
*
* (C) 2015-2016, Deep Blue C Technologies Ltd
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the Eclipse Public License v1.0
* (http://www.eclipse.org/legal/epl-v10.html)
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*****************************************************************************/
package info.limpet.ui.editors;
import info.limpet.IChangeListener;
import info.limpet.ICommand;
import info.limpet.IContext;
import info.limpet.IOperation;
import info.limpet.IStore;
import info.limpet.IStoreGroup;
import info.limpet.IStoreItem;
import info.limpet.data.operations.AddLayerOperation;
import info.limpet.data.operations.GenerateDummyDataOperation;
import info.limpet.data.operations.admin.OperationsLibrary;
import info.limpet.data.persistence.xml.XStreamHandler;
import info.limpet.data.store.StoreGroup;
import info.limpet.data.store.StoreGroup.StoreChangeListener;
import info.limpet.ui.RCPContext;
import info.limpet.ui.data_provider.data.DataModel;
import info.limpet.ui.editors.dnd.DataManagerDropAdapter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.util.LocalSelectionTransfer;
import org.eclipse.jface.viewers.DecoratingLabelProvider;
import org.eclipse.jface.viewers.ILabelDecorator;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.FileTransfer;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.SaveAsDialog;
import org.eclipse.ui.part.EditorPart;
import org.eclipse.ui.part.FileEditorInput;
import org.osgi.framework.Bundle;
public class DataManagerEditor extends EditorPart
{
private IStore _store;
private TreeViewer viewer;
private IMenuListener _menuListener;
private Action refreshView;
private Action generateData;
private Action addFolder;
private boolean _dirty = false;
private DataModel _model;
private StoreChangeListener _changeListener = new StoreChangeListener()
{
@Override
public void changed()
{
// indicate the file is dirty
_dirty = true;
firePropertyChange(PROP_DIRTY);
// and refresh the UI
viewer.refresh();
}
};
private IContext _context = new RCPContext();
private IResourceChangeListener resourceChangeListener =
new IResourceChangeListener()
{
@Override
public void resourceChanged(IResourceChangeEvent event)
{
IResourceDelta delta = event.getDelta();
final int eventType = event.getType();
if (delta != null)
{
try
{
delta.accept(new IResourceDeltaVisitor()
{
@Override
public boolean visit(final IResourceDelta delta)
throws CoreException
{
IResource resource = delta.getResource();
if (resource instanceof IWorkspaceRoot)
{
return true;
}
if (resource instanceof IProject)
{
IEditorInput input = getEditorInput();
if (input instanceof IFileEditorInput)
{
IProject project =
((IFileEditorInput) input).getFile().getProject();
final boolean isPreDelete =
eventType == IResourceChangeEvent.PRE_DELETE;
final boolean isPreClose =
eventType == IResourceChangeEvent.PRE_CLOSE;
if (resource.equals(project)
&& (isPreDelete || isPreClose))
{
closeEditor();
return false;
}
}
return true;
}
if (resource instanceof IFolder)
{
return true;
}
if (resource instanceof IFile)
{
IEditorInput input = getEditorInput();
if (input instanceof IFileEditorInput)
{
IFile file = ((IFileEditorInput) input).getFile();
if (resource.equals(file)
&& delta.getKind() == IResourceDelta.REMOVED)
{
IPath movedToPath = delta.getMovedToPath();
if (movedToPath != null)
{
IResource path =
ResourcesPlugin.getWorkspace().getRoot()
.findMember(movedToPath);
if (path instanceof IFile)
{
final FileEditorInput newInput =
new FileEditorInput((IFile) path);
Display.getDefault().asyncExec(new Runnable()
{
@Override
public void run()
{
setInputWithNotify(newInput);
setPartName(newInput.getName());
}
});
}
}
else
{
closeEditor();
}
}
boolean resChanged =
delta.getKind() == IResourceDelta.CHANGED;
boolean contentChanged =
(delta.getFlags() & IResourceDelta.CONTENT) != 0;
if (resource.equals(file)
&& (resChanged && contentChanged))
{
reload();
}
}
}
return false;
}
});
}
catch (CoreException e)
{
log(e);
}
}
}
};
private void reload()
{
if (_store != null)
{
_store.removeChangeListener(_changeListener);
}
load(getEditorInput());
Display.getDefault().asyncExec(new Runnable()
{
@Override
public void run()
{
viewer.setInput(_store);
viewer.refresh();
}
});
}
private void closeEditor()
{
Display.getDefault().asyncExec(new Runnable()
{
@Override
public void run()
{
getSite().getPage().closeEditor(DataManagerEditor.this, false);
}
});
}
@Override
public void init(IEditorSite site, IEditorInput input)
throws PartInitException
{
// FIXME we will support FileEditorInput, FileStoreEditorInput and
// FileRevisionEditorInput
load(input);
setSite(site);
setInput(input);
setPartName(input.getName());
}
private void load(IEditorInput input)
{
if (input instanceof IFileEditorInput)
{
// just check if the document is empty
IFileEditorInput iF = (IFileEditorInput) input;
try
{
// ok, the file may be empty, do a quick check
if (iF.getFile().exists() && iF.getFile().getContents().available() > 1)
{
_store =
new XStreamHandler().load(((IFileEditorInput) input).getFile());
// we need to loop down through the data, setting all of the listeners
StoreGroup ms = (StoreGroup) _store;
// and get hooked up
Iterator<IStoreItem> iter = ms.iterator();
while (iter.hasNext())
{
connectUp(iter.next(), null, ms);
}
}
else
{
// ok, it was empty. generate an empty store
_store = new StoreGroup();
}
}
catch (IOException | CoreException e)
{
log(e);
}
}
_store.addChangeListener(_changeListener);
}
/**
* walk down through the object tree, connecting listeners as appropriate
*
* @param next
* @param parent
* @param listener
*/
private void connectUp(IStoreItem next, IStoreGroup parent,
IChangeListener listener)
{
if (next instanceof IStoreGroup)
{
IStoreGroup group = (IStoreGroup) next;
Iterator<IStoreItem> iter = group.iterator();
while (iter.hasNext())
{
connectUp(iter.next(), group, group);
}
}
next.addChangeListener(listener);
}
@Override
public boolean isDirty()
{
return _dirty;
}
@Override
public boolean isSaveAsAllowed()
{
return true;
}
@Override
public void createPartControl(Composite parent)
{
viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
_model = new DataModel();
viewer.setContentProvider(_model);
LabelProvider labelProvider = new LimpetLabelProvider();
ILabelDecorator decorator =
PlatformUI.getWorkbench().getDecoratorManager().getLabelDecorator();
viewer.setLabelProvider(new DecoratingLabelProvider(labelProvider,
decorator));
viewer.setInput(_store);
getSite().setSelectionProvider(viewer);
makeActions();
hookContextMenu();
IActionBars bars = getEditorSite().getActionBars();
fillLocalToolBar(bars.getToolBarManager());
configureDropSupport();
configureDragSupport();
ResourcesPlugin.getWorkspace().addResourceChangeListener(
resourceChangeListener,
IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE
| IResourceChangeEvent.POST_CHANGE);
}
private void configureDragSupport()
{
int ops = DND.DROP_COPY | DND.DROP_MOVE;
Transfer[] transfers = new Transfer[]
{TextTransfer.getInstance(),LocalSelectionTransfer.getTransfer()};
viewer.addDragSupport(ops, transfers, new LimpetDragListener(viewer));
}
/**
* sort out the drop target
*/
private void configureDropSupport()
{
final int dropOperation = DND.DROP_COPY | DND.DROP_MOVE;
final Transfer[] dropTypes =
{FileTransfer.getInstance(), TextTransfer.getInstance()};
viewer.addDropSupport(dropOperation, dropTypes, new DataManagerDropAdapter(
viewer, _store));
}
protected void fillLocalToolBar(IToolBarManager manager)
{
manager.add(refreshView);
manager.add(generateData);
manager.add(addFolder);
}
private void makeActions()
{
// our operation wrapper needs to be able to get the selection, help it out
final ISelectionProvider provider = new ISelectionProvider()
{
public List<IStoreItem> getSelection()
{
return getSuitableObjects();
}
};
addFolder =
new OperationWrapper(new AddLayerOperation(), "Add folder", PlatformUI
.getWorkbench().getSharedImages().getImageDescriptor(
ISharedImages.IMG_TOOL_PASTE), _context, _store, provider);
generateData =
new OperationWrapper(new GenerateDummyDataOperation("Small", 20),
"Create dummy data", PlatformUI.getWorkbench().getSharedImages()
.getImageDescriptor(ISharedImages.IMG_TOOL_NEW_WIZARD),
_context, _store, provider);
// refresh view is purely UI. So, we can implement it here
refreshView =
new Action("Refresh View", PlatformUI.getWorkbench().getSharedImages()
.getImageDescriptor(ISharedImages.IMG_TOOL_REDO))
{
@Override
public void run()
{
refresh();
}
};
}
public void showMessage(String message)
{
MessageDialog.openInformation(viewer.getControl().getShell(),
"Data Manager Editor", message);
}
protected IMenuListener createContextMenuListener()
{
return new IMenuListener()
{
public void menuAboutToShow(IMenuManager menu)
{
setFocus();
editorContextMenuAboutToShow(menu);
}
};
}
protected void editorContextMenuAboutToShow(IMenuManager menu)
{
// get any suitable objects from selection
List<IStoreItem> selection = getSuitableObjects();
// include some top level items
showThisList(selection, menu, OperationsLibrary.getTopLevel(), _store, _context, null);
// now the tree of operations
menu.add(new Separator());
// get the list of operations
HashMap<String, List<IOperation<?>>> ops =
OperationsLibrary.getOperations();
// and the RCP-specific operations
HashMap<String, List<IOperation<?>>> rcpOps =
RCPOperationsLibrary.getOperations();
ops.putAll(rcpOps);
// did we find anything?
Iterator<String> hIter = ops.keySet().iterator();
while (hIter.hasNext())
{
// ok, we're in a menu grouping
String name = (String) hIter.next();
// create a new menu tier
MenuManager newM = new MenuManager(name);
menu.add(newM);
// now loop through this set of operations
List<IOperation<?>> values = ops.get(name);
showThisList(selection, newM, values, _store, _context, null);
}
menu.add(new Separator());
menu.add(refreshView);
}
public static void showThisList(List<IStoreItem> selection, IMenuManager newM,
List<IOperation<?>> values, final IStore theStore, final IContext context, final Runnable listener)
{
Iterator<IOperation<?>> oIter = values.iterator();
while (oIter.hasNext())
{
@SuppressWarnings("unchecked")
final IOperation<IStoreItem> op = (IOperation<IStoreItem>) oIter.next();
Collection<ICommand<IStoreItem>> matches =
op.actionsFor(selection, theStore, context);
Iterator<ICommand<IStoreItem>> mIter = matches.iterator();
while (mIter.hasNext())
{
final ICommand<IStoreItem> thisC = (ICommand<IStoreItem>) mIter.next();
newM.add(new Action(thisC.getName())
{
@Override
public void run()
{
thisC.execute();
// do we have a listener?
if (listener != null)
{
listener.run();
}
}
});
}
}
}
private List<IStoreItem> getSuitableObjects()
{
ArrayList<IStoreItem> matches = new ArrayList<IStoreItem>();
// ok, find the applicable operations
ISelection sel = viewer.getSelection();
IStructuredSelection str = (IStructuredSelection) sel;
Iterator<?> iter = str.iterator();
while (iter.hasNext())
{
Object object = (Object) iter.next();
if (object instanceof IStoreItem)
{
matches.add((IStoreItem) object);
}
else if (object instanceof IAdaptable)
{
IAdaptable ada = (IAdaptable) object;
Object match = ada.getAdapter(IStoreItem.class);
if (match != null)
{
matches.add((IStoreItem) match);
}
}
}
return matches;
}
protected final IMenuListener getContextMenuListener()
{
if (_menuListener == null)
{
_menuListener = createContextMenuListener();
}
return _menuListener;
}
private void hookContextMenu()
{
String id = "#DataManagerEditor";
MenuManager menuMgr = new MenuManager(id, id);
menuMgr.setRemoveAllWhenShown(true);
menuMgr.addMenuListener(getContextMenuListener());
Menu menu = menuMgr.createContextMenu(viewer.getControl());
viewer.getControl().setMenu(menu);
// We shouldn't register menu because we will contribute menus
// using separate extension point
// getSite().registerContextMenu(menuMgr, viewer);
}
@Override
public void setFocus()
{
viewer.getControl().setFocus();
}
@Override
public void doSave(IProgressMonitor monitor)
{
final IEditorInput input = getEditorInput();
// FIXME we will support FileEditorInput, FileStoreEditorInput and
// FileRevisionEditorInput
if (input instanceof IFileEditorInput)
{
IFile file = ((IFileEditorInput) input).getFile();
doSaveAs(file, monitor);
}
}
private void doSaveAs(IFile file, IProgressMonitor monitor)
{
try
{
ResourcesPlugin.getWorkspace().removeResourceChangeListener(
resourceChangeListener);
new XStreamHandler().save(_store, file);
_dirty = false;
file.refreshLocal(IResource.DEPTH_INFINITE, monitor);
firePropertyChange(PROP_DIRTY);
}
catch (CoreException | IOException e)
{
log(e);
}
finally
{
ResourcesPlugin.getWorkspace().addResourceChangeListener(
resourceChangeListener,
IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE
| IResourceChangeEvent.POST_CHANGE);
}
}
private void log(Throwable t)
{
Bundle bundle = Platform.getBundle("info.limpet");
if (bundle != null)
{
ILog log = Platform.getLog(bundle);
if (log != null)
{
log.log(new Status(IStatus.WARNING, bundle.getSymbolicName(), t
.getMessage(), t));
return;
}
}
t.printStackTrace();
}
@Override
public void doSaveAs()
{
final SaveAsDialog dialog = new SaveAsDialog(getEditorSite().getShell());
dialog.setTitle("Save As");
if (getEditorInput() instanceof IFileEditorInput)
{
IFileEditorInput input = (IFileEditorInput) getEditorInput();
IFile file = input.getFile();
dialog.setOriginalFile(file);
}
dialog.create();
dialog.setMessage("Save file to another location.");
if (dialog.open() == Window.OK)
{
final IPath path = dialog.getResult();
final IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(path);
doSaveAs(file, new NullProgressMonitor());
IFileEditorInput input = new FileEditorInput(file);
setInput(input);
setPartName(input.getName());
viewer.refresh();
}
}
@Override
public void dispose()
{
super.dispose();
ResourcesPlugin.getWorkspace().removeResourceChangeListener(
resourceChangeListener);
if (_store != null)
{
_store.removeChangeListener(_changeListener);
}
}
public IStore getStore()
{
return _store;
}
public IContext getContext()
{
return _context;
}
public void refresh()
{
if (!viewer.getControl().isDisposed())
{
viewer.refresh();
}
}
}