/*******************************************************************************
* Copyright (c) 2008
* The code, documentation and other materials contained herein have been
* licensed under the Eclipse Public License - v 1.0 by the individual
* copyright holders listed below, as Initial Contributors under such license.
* The text of such license is available at
* http://www.eclipse.org/legal/epl-v10.html.
*
* Contributors:
* Henrik Lindberg
*******************************************************************************/
package org.eclipse.equinox.p2.authoring.forms;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URI;
import java.net.URL;
import org.eclipse.core.commands.operations.IOperationHistory;
import org.eclipse.core.commands.operations.IUndoContext;
import org.eclipse.core.commands.operations.ObjectUndoContext;
import org.eclipse.core.commands.operations.OperationHistoryFactory;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.equinox.p2.authoring.P2AuthoringUIPlugin;
import org.eclipse.equinox.p2.authoring.internal.DerivedExternalFileEditorInput;
import org.eclipse.equinox.p2.authoring.internal.EditorEventBus;
import org.eclipse.equinox.p2.authoring.internal.ExternalFileEditorInput;
import org.eclipse.equinox.p2.authoring.internal.FileReader;
import org.eclipse.equinox.p2.authoring.internal.IDerivedEditorInput;
import org.eclipse.equinox.p2.authoring.internal.IEditEventBusProvider;
import org.eclipse.equinox.p2.authoring.internal.IEditorEventBus;
import org.eclipse.equinox.p2.authoring.internal.IFileInfo;
import org.eclipse.equinox.p2.authoring.internal.IUndoOperationSupport;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorMatchingStrategy;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPathEditorInput;
import org.eclipse.ui.IURIEditorInput;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.dialogs.SaveAsDialog;
import org.eclipse.ui.editors.text.ILocationProvider;
import org.eclipse.ui.forms.editor.FormEditor;
import org.eclipse.ui.forms.editor.IFormPage;
import org.eclipse.ui.forms.widgets.FormText;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.operations.RedoActionHandler;
import org.eclipse.ui.operations.UndoActionHandler;
/**
* Base class for Form Editors with support added for handling of editor input, undo/redo, clipboard global actions,
* and an editor event bus.
*
* TODO: global support for copy/paste of objects - commented out.
* @author Henrik Lindberg
*
*/
public abstract class RichFormEditor extends FormEditor implements IEditorMatchingStrategy, IGlobalActionPerformer
{
private String m_tmpPrefix = "richEdit-";
private String m_tmpSuffix = "tmp";
protected EditorEventBus m_eventBus = new EditorEventBus();
private RedoActionHandler m_redoAction;
private UndoActionHandler m_undoAction;
private IUndoContext m_undoContext;
private Clipboard m_clipboard;
public IEditorEventBus getEventBus()
{
return m_eventBus;
}
@Override
protected FormToolkit createToolkit(Display display)
{
// Create a toolkit that shares colors between editors.
return new RichFormToolkit(display);
}
@Override
public void dispose()
{
m_eventBus.close();
if(m_clipboard != null)
m_clipboard.dispose();
super.dispose();
}
private static IFileInfo readInto(URL url, OutputStream output, IProgressMonitor monitor) throws CoreException, FileNotFoundException
{
FileReader reader = new FileReader();
reader.readInto(url, output, monitor);
return reader.getLastFileInfo();
}
public ExternalFileEditorInput getExternalFileEditorInput(IURIEditorInput input) throws CoreException, IOException
{
URI uri = input.getURI();
URL url = uri.toURL();
String protocol = url.getProtocol();
File iuFile = null;
if(protocol == null || "file".equals(protocol))
{
iuFile = new File(uri);
}
if(iuFile == null || !iuFile.canWrite())
{
iuFile = File.createTempFile(m_tmpPrefix, m_tmpSuffix);
iuFile.deleteOnExit();
OutputStream os = null;
try
{
os = new FileOutputStream(iuFile);
readInto(url, os, null);
}
finally
{
try
{
os.close();
}
catch(IOException e)
{
}
}
}
return new DerivedExternalFileEditorInput(input, iuFile, new Path(uri.getPath()).lastSegment(), url.toString());
}
/**
* An Editor Toolkit that provides support for RichFormEditor features:
* - An event bus that is convenient to use for broadcasted events. The editor toolkit is generally available throughout the editor and
* is a convenient place to pass around a handle to the bus. The use of an event bus decouples event sources and listeners.
* - hooks created form elements with event listeners that propagate events to the event bus.
*
* TODO: Only text focus events are currently set up to be propagated
*
* @author Henrik Lindberg
*
*/
private class RichFormToolkit extends FormToolkit implements IEditEventBusProvider, IUndoOperationSupport
{
private FocusListener m_focusListener;
public RichFormToolkit(Display display)
{
super(P2AuthoringUIPlugin.getDefault().getFormColors(display));
m_focusListener = new FocusListener(){
public void focusGained(FocusEvent e)
{
// if(e.getSource() instanceof Text)
// System.err.print("Focus gained to "+ ((Text)e.getSource()).getText() + "\n");
// if(e.getSource() instanceof FormText)
// System.err.print("Focus gained to FormText\n");
getEventBus().publishEvent(e);
}
public void focusLost(FocusEvent e)
{
// if(e.getSource() instanceof Text)
// System.err.print("Focus lost from "+ ((Text)e.getSource()).getText() + "\n");
// if(e.getSource() instanceof FormText)
// System.err.print("Focus lost from FormText\n");
//
getEventBus().publishEvent(e);
}
};
}
public IEditorEventBus getEventBus()
{
return RichFormEditor.this.m_eventBus;
}
public IUndoContext getUndoContext()
{
// return this editors undo context
return m_undoContext;
}
public IOperationHistory getOperationHistory()
{
// return the global one
return OperationHistoryFactory.getOperationHistory();
}
@Override
public Text createText(Composite parent, String value)
{
return createText(parent,value, SWT.NONE);
}
@Override
public Text createText(Composite parent, String value, int style)
{
Text text = super.createText(parent, value, style);
text.addFocusListener(m_focusListener);
return text;
}
@Override
public FormText createFormText(Composite parent, boolean trackFocus)
{
FormText text = super.createFormText(parent, trackFocus);
text.addFocusListener(m_focusListener);
return text;
}
}
protected final String getTmpPrefix()
{
return m_tmpPrefix;
}
protected final void setTmpPrefix(String tmpPrefix)
{
m_tmpPrefix = tmpPrefix;
}
protected final String getTmpSuffix()
{
return m_tmpSuffix;
}
protected final void setTmpSuffix(String tmpSuffix)
{
m_tmpSuffix = tmpSuffix;
}
public boolean matches(IEditorReference editorRef, IEditorInput input)
{
IEditorPart part = (IEditorPart)editorRef.getPart(false);
if(part != null)
{
IEditorInput editorInput = part.getEditorInput();
if(editorInput != null)
{
if(editorInput.equals(input))
return true;
if(editorInput instanceof IDerivedEditorInput)
{
IEditorInput originalEditorInput = ((IDerivedEditorInput)editorInput).getOriginalInput();
if(originalEditorInput.equals(input))
return true;
}
}
}
return false;
}
@Override
public void setInput(IEditorInput input)
{
super.setInput(input);
}
protected abstract void saveToPath(IPath path);
protected boolean commitChanges()
{
super.commitPages(true);
// TODO: now always return true... should check if something is still dirty
return true;
}
protected Clipboard getClipboard()
{
return m_clipboard;
}
@Override
public void doSave(IProgressMonitor monitor)
{
if(!commitChanges())
return;
IEditorInput input = getEditorInput();
if(input == null)
return;
IPath path = (input instanceof ILocationProvider)
? ((ILocationProvider)input).getPath(input)
: ((IPathEditorInput)input).getPath();
saveToPath(path);
}
@Override
public void doSaveAs()
{
if(!commitChanges())
return;
IEditorInput input = getEditorInput();
if(input == null)
return;
SaveAsDialog dialog = new SaveAsDialog(getSite().getShell());
IFile original = (input instanceof IFileEditorInput)
? ((IFileEditorInput)input).getFile()
: null;
if(original != null)
dialog.setOriginalFile(original);
if(dialog.open() == Window.CANCEL)
return;
IPath filePath = dialog.getResult();
if(filePath == null)
return;
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IFile file = workspace.getRoot().getFile(filePath);
saveToPath(file.getLocation());
}
@Override
public boolean isSaveAsAllowed()
{
return true;
}
/**
* Creates things needed before pages are added.
*/
@Override
protected void createPages()
{
m_clipboard = new Clipboard(getContainer().getDisplay());
super.createPages();
}
protected void createActions()
{
m_undoContext = new ObjectUndoContext(this);
m_undoAction = new UndoActionHandler(getSite(), m_undoContext);
m_redoAction = new RedoActionHandler(getSite(), m_undoContext);
}
public IAction getAction(String workbenchActionId)
{
// Return the actions for UNDO and REDO that were created in createActions.
// These actions work against one undo context for the editor.
if(ActionFactory.UNDO.getId().equals(workbenchActionId))
return m_undoAction;
if(ActionFactory.REDO.getId().equals(workbenchActionId))
return m_redoAction;
return null;
}
public boolean canPasteFromClipboard()
{
return canPaste(getClipboard());
}
public boolean canPaste(Clipboard clipboard)
{
IFormPage page = getActivePageInstance();
if(page instanceof IGlobalActionPerformer)
{
return ((IGlobalActionPerformer)page).canPaste(clipboard);
}
return false;
}
public boolean canCopy(ISelection selection)
{
if(selection == null || selection.isEmpty())
{
IFormPage page = getActivePageInstance();
if(page instanceof IGlobalActionPerformer)
{
return ((IGlobalActionPerformer)page).canCopy(selection);
}
return false;
}
if(selection instanceof IStructuredSelection)
return !selection.isEmpty();
if(selection instanceof ITextSelection)
{
ITextSelection textSelection = (ITextSelection)selection;
return textSelection.getLength() > 0;
}
return false;
}
public boolean canCut(ISelection selection)
{
if(selection == null || selection.isEmpty())
{
IFormPage page = getActivePageInstance();
if(page instanceof IGlobalActionPerformer)
{
return ((IGlobalActionPerformer)page).canCut(selection);
}
return false;
}
if(selection instanceof IStructuredSelection)
return !selection.isEmpty();
if(selection instanceof ITextSelection)
{
ITextSelection textSelection = (ITextSelection)selection;
return textSelection.getLength() > 0;
}
return false;
}
/**
* Returns selection, or null if initializing and the editors site is null see {@link #getSite()}.
* @return null if initializing, or selection from selection provider from the editor's site.
*/
public ISelection getSelection()
{
IWorkbenchPartSite site = getSite();
if(site == null)
return null;
ISelectionProvider provider = site.getSelectionProvider();
if(provider == null)
return null;
return provider.getSelection();
}
public boolean doGlobalAction(String id)
{
// preserve selection
ISelection selection = getSelection();
boolean handled = getActivePageInstance() instanceof IGlobalActionPerformer ?
((IGlobalActionPerformer)getActivePageInstance()).doGlobalAction(id)
: false;
if(!handled)
{
// FormPage page = getActivePageInstance();
// if (page instanceof RichFormPage) {
// if (id.equals(ActionFactory.UNDO.getId())) {
// fInputContextManager.undo();
// return;
// }
// if (id.equals(ActionFactory.REDO.getId())) {
// fInputContextManager.redo();
// return;
// }
// if not handled directly, perform copy/cut to clipboard here.
//
if(id.equals(ActionFactory.CUT.getId()) || id.equals(ActionFactory.COPY.getId()))
{
copyToClipboard(selection);
handled = true;
}
}
return handled;
}
/**
* Copies selection to clipboard - used as last default for copy and cut actions.
* @param selection
*/
private void copyToClipboard(ISelection selection)
{
Object[] objects = null;
String textVersion = null;
if(selection instanceof IStructuredSelection)
{
IStructuredSelection ssel = (IStructuredSelection)selection;
if(ssel == null || ssel.size() == 0)
return;
objects = ssel.toArray();
StringWriter writer = new StringWriter();
PrintWriter pwriter = new PrintWriter(writer);
Class<?> objClass = null;
for(int i = 0; i < objects.length; i++)
{
Object obj = objects[i];
if(objClass == null)
objClass = obj.getClass();
else if(objClass.equals(obj.getClass()) == false)
return;
// TODO: Handle writeable objects
// if (obj instanceof IWritable)
// {
// // Add a customized delimiter in between all serialized
// // objects to format the text representation
// if ((i != 0) && (obj instanceof IWritableDelimiter))
// {
// ((IWritableDelimiter) obj).writeDelimeter(pwriter);
// }
// ((IWritable) obj).write("", pwriter); //$NON-NLS-1$
// }
else if(obj instanceof String)
{
// Delimiter is always a newline
pwriter.println((String)obj);
}
}
pwriter.flush();
textVersion = writer.toString();
try
{
pwriter.close();
writer.close();
}
catch(IOException e)
{
}
}
else if(selection instanceof ITextSelection)
{
textVersion = ((ITextSelection)selection).getText();
}
if((textVersion == null || textVersion.length() == 0) && objects == null)
return;
// set the clipboard contents
Object[] o = null;
Transfer[] t = null;
if(objects == null)
{
o = new Object[] { textVersion };
t = new Transfer[] { TextTransfer.getInstance() };
}
// TODO: Transfer of model objects
// else if (textVersion == null || textVersion.length() == 0) {
// o = new Object[] {objects};
// t = new Transfer[] {ModelDataTransfer.getInstance()};
// }
// else {
// o = new Object[] {objects, textVersion};
// t = new Transfer[] {ModelDataTransfer.getInstance(), TextTransfer.getInstance()};
// }
m_clipboard.setContents(o, t);
}
public void setSelection(ISelection selection) {
getSite().getSelectionProvider().setSelection(selection);
// getContributor().updateSelectableActions(selection);
}
@Override
public void init(IEditorSite site, IEditorInput input) throws PartInitException
{
super.init(site, input);
// this.getSite().getSelectionProvider().addSelectionChangedListener(new ISelectionChangedListener(){
//
// public void selectionChanged(SelectionChangedEvent event)
// {
// System.err.print("Selection changed 1\n");
// }
//
// });
// getSite().getPage().addPostSelectionListener(new ISelectionListener(){
//
// public void selectionChanged(IWorkbenchPart part, ISelection selection)
// {
// System.err.print("Selection changed 2\n");
// }
//
// });
}
}