/*******************************************************************************
* Copyright (c) 2011, 2014 Wind River Systems, Inc. and others. 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:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.te.tcf.ui.views.scriptpad;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.action.GroupMarker;
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.resource.JFaceResources;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CaretEvent;
import org.eclipse.swt.custom.CaretListener;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.FileTransfer;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.tcf.te.runtime.statushandler.StatusHandlerUtil;
import org.eclipse.tcf.te.runtime.utils.StatusHelper;
import org.eclipse.tcf.te.tcf.locator.interfaces.nodes.IPeerNode;
import org.eclipse.tcf.te.tcf.ui.views.help.IContextHelpIds;
import org.eclipse.tcf.te.tcf.ui.views.nls.Messages;
import org.eclipse.tcf.te.tcf.ui.views.scriptpad.actions.CopyAction;
import org.eclipse.tcf.te.tcf.ui.views.scriptpad.actions.CutAction;
import org.eclipse.tcf.te.tcf.ui.views.scriptpad.actions.DeleteAction;
import org.eclipse.tcf.te.tcf.ui.views.scriptpad.actions.PasteAction;
import org.eclipse.tcf.te.tcf.ui.views.scriptpad.actions.PeerAction;
import org.eclipse.tcf.te.tcf.ui.views.scriptpad.actions.SelectAllAction;
import org.eclipse.tcf.te.ui.swt.DisplayUtil;
import org.eclipse.tcf.te.ui.swt.SWTControlUtil;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.ISaveablePart;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPartConstants;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.part.ViewPart;
/**
* Script Pad view implementation.
*/
public class ScriptPad extends ViewPart implements ISelectionProvider, SelectionListener, ISaveablePart {
// Reference to the header line
private Label head;
// Reference to the Text widget
/* default */ StyledText text;
// Reference to the line style listener
private ScriptPadLineStyleListener lineStyleListener;
// The list of registered selection changed listeners
private final List<ISelectionChangedListener> listeners = new ArrayList<ISelectionChangedListener>();
// Reference to the selected peer model
/* default */ IPeerNode peerNode;
// References to the global action handlers
private CutAction cutHandler;
private CopyAction copyHandler;
private PasteAction pasteHandler;
/* default */ DeleteAction deleteHandler;
private SelectAllAction selectAllHandler;
// If the user loaded a script either via the "Open" action or DnD, remember the file name
// so the user can save it back.
private String fileLoaded = null;
// The dirty state
private boolean dirty = false;
/**
* Constructor.
*/
public ScriptPad() {
super();
listeners.clear();
}
/* (non-Javadoc)
* @see org.eclipse.ui.part.WorkbenchPart#dispose()
*/
@Override
public void dispose() {
if (text != null && !text.isDisposed()) {
text.removeSelectionListener(this);
if (lineStyleListener != null) {
text.removeLineStyleListener(lineStyleListener);
lineStyleListener.dispose();
lineStyleListener = null;
}
}
listeners.clear();
fileLoaded = null;
super.dispose();
}
/* (non-Javadoc)
* @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
*/
@Override
public void createPartControl(Composite parent) {
Composite panel = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout();
layout.marginHeight = 0; layout.marginWidth = 0;
panel.setLayout(layout);
panel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
// Create the head label
head = new Label(panel, SWT.HORIZONTAL);
head.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
// Create the StyledText widget
text = new StyledText(panel, SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI);
text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
text.setFont(JFaceResources.getTextFont());
text.addSelectionListener(this);
text.addCaretListener(new CaretListener() {
@Override
public void caretMoved(CaretEvent event) {
if (deleteHandler != null) deleteHandler.updateEnabledState();
}
});
text.addModifyListener(new ModifyListener() {
@Override
public void modifyText(ModifyEvent e) {
if (!isDirty()) markDirty(true);
}
});
lineStyleListener = new ScriptPadLineStyleListener();
text.addLineStyleListener(lineStyleListener);
// Register ourselves as selection provider
getViewSite().setSelectionProvider(this);
// Create the context menu
createContextMenu();
// Create the toolbar
createToolbar();
// Hook the global actions
hookGlobalActions();
// Setup Drag n Drop
setupDnD();
// Update the action bars
getViewSite().getActionBars().updateActionBars();
}
/**
* Creates the views context menu and register it to enable contributions.
*/
private void createContextMenu() {
// Create menu manager.
MenuManager menuMgr = new MenuManager();
menuMgr.setRemoveAllWhenShown(true);
menuMgr.addMenuListener(new IMenuListener() {
@Override
public void menuAboutToShow(IMenuManager mgr) {
fillContextMenu(mgr);
}
});
// Create menu.
Menu menu = menuMgr.createContextMenu(text);
text.setMenu(menu);
// Register menu for extension.
getSite().registerContextMenu(menuMgr, this);
}
/**
* Fill the context menu.
*
* @param manager The menu manager. Must not be <code>null</code>.
*/
protected void fillContextMenu(IMenuManager manager) {
Assert.isNotNull(manager);
}
/**
* Hook the global actions.
*/
protected void hookGlobalActions() {
IActionBars actionBars = getViewSite().getActionBars();
cutHandler = new CutAction(this);
actionBars.setGlobalActionHandler(ActionFactory.CUT.getId(), cutHandler);
copyHandler = new CopyAction(this);
actionBars.setGlobalActionHandler(ActionFactory.COPY.getId(), copyHandler);
pasteHandler = new PasteAction(this);
actionBars.setGlobalActionHandler(ActionFactory.PASTE.getId(), pasteHandler);
deleteHandler = new DeleteAction(this);
actionBars.setGlobalActionHandler(ActionFactory.DELETE.getId(), deleteHandler);
selectAllHandler = new SelectAllAction(this);
actionBars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), selectAllHandler);
updateActionEnablements();
}
/**
* Creates the views toolbar.
*/
private void createToolbar() {
IToolBarManager manager = getViewSite().getActionBars().getToolBarManager();
manager.add(new Separator());
manager.add(new GroupMarker("open")); //$NON-NLS-1$
manager.add(new GroupMarker("save")); //$NON-NLS-1$
manager.add(new Separator());
manager.add(new GroupMarker("clear")); //$NON-NLS-1$
manager.add(new Separator());
manager.add(new GroupMarker("play")); //$NON-NLS-1$
manager.add(new Separator());
manager.add(new GroupMarker("peers")); //$NON-NLS-1$
manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
}
/**
* Setup Drag n Drop support.
*/
private void setupDnD() {
DropTarget target = new DropTarget(text, DND.DROP_DEFAULT | DND.DROP_COPY);
target.setTransfer(new Transfer[] { TextTransfer.getInstance(), FileTransfer.getInstance() });
target.addDropListener(new DropTargetAdapter() {
@Override
public void drop(DropTargetEvent event) {
if (FileTransfer.getInstance().isSupportedType(event.currentDataType)) {
String[] names = (String[]) FileTransfer.getInstance().nativeToJava(event.currentDataType);
if (names != null && names.length > 0) {
openFile(names[0]);
}
}
else if (TextTransfer.getInstance().isSupportedType(event.currentDataType)) {
String content = (String) TextTransfer.getInstance().nativeToJava(event.currentDataType);
if (content != null && !"".equals(content)) { //$NON-NLS-1$
if (text != null && !text.isDisposed()) text.setText(content);
}
}
}
@Override
public void dragOperationChanged(DropTargetEvent event) {
if (event.detail == DND.DROP_DEFAULT) event.detail = DND.DROP_COPY;
}
@Override
public void dragEnter(DropTargetEvent event) {
if (event.detail == DND.DROP_DEFAULT) event.detail = DND.DROP_COPY;
}
});
}
/* (non-Javadoc)
* @see org.eclipse.ui.part.WorkbenchPart#setFocus()
*/
@Override
public void setFocus() {
if (text != null) text.setFocus();
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.ISelectionProvider#addSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener)
*/
@Override
public void addSelectionChangedListener(ISelectionChangedListener listener) {
Assert.isNotNull(listener);
if (!listeners.contains(listener)) listeners.add(listener);
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.ISelectionProvider#removeSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener)
*/
@Override
public void removeSelectionChangedListener(ISelectionChangedListener listener) {
Assert.isNotNull(listener);
listeners.remove(listener);
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.ISelectionProvider#getSelection()
*/
@Override
public ISelection getSelection() {
String selected = text.getSelectionText();
return selected != null ? new StructuredSelection(selected) : null;
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.ISelectionProvider#setSelection(org.eclipse.jface.viewers.ISelection)
*/
@Override
public void setSelection(ISelection selection) {
if (selection instanceof IStructuredSelection && !selection.isEmpty()) {
Object element = ((IStructuredSelection)selection).getFirstElement();
if (element instanceof String) {
int start = text.getText().indexOf((String)element);
if (start != -1) {
text.setSelection(start, start + ((String)element).length());
}
}
} else {
text.setSelection(text.getCaretOffset(), text.getCaretOffset());
}
// Fire the selection changed event
fireSelectionChanged();
updateActionEnablements();
}
/**
* Notify the registered selection changed listener about a changed selection.
*/
private void fireSelectionChanged() {
if (getSelection() == null) return;
SelectionChangedEvent event = new SelectionChangedEvent(this, getSelection());
Iterator<ISelectionChangedListener> iterator = listeners.iterator();
while (iterator.hasNext()) {
ISelectionChangedListener listener = iterator.next();
listener.selectionChanged(event);
}
}
/**
* Update the action enablements
*/
public void updateActionEnablements() {
if (cutHandler != null) cutHandler.updateEnabledState();
if (copyHandler != null) copyHandler.updateEnabledState();
if (pasteHandler != null) pasteHandler.updateEnabledState();
if (deleteHandler != null) deleteHandler.updateEnabledState();
if (selectAllHandler != null) selectAllHandler.updateEnabledState();
}
/**
* Update the head label
*/
protected void updateHeadLabel() {
StringBuilder label = new StringBuilder();
if (fileLoaded == null) {
label.append("<no file>"); //$NON-NLS-1$
} else {
IPath path = new Path(fileLoaded);
label.append(path.lastSegment());
}
label.append(" - "); //$NON-NLS-1$
if (peerNode == null) {
label.append("<no peer>"); //$NON-NLS-1$
} else {
PeerAction action = new PeerAction(this, peerNode);
label.append(action.getText());
}
SWTControlUtil.setText(head, label.toString());
}
/* (non-Javadoc)
* @see org.eclipse.swt.events.SelectionListener#widgetDefaultSelected(org.eclipse.swt.events.SelectionEvent)
*/
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
/* (non-Javadoc)
* @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent)
*/
@Override
public void widgetSelected(SelectionEvent e) {
fireSelectionChanged();
updateActionEnablements();
}
/**
* Sets the peer model.
*
* @param peerNode The peer model or <code>null</code>.
*/
public void setPeerModel(IPeerNode peerNode) {
this.peerNode = peerNode;
// Update the head label
updateHeadLabel();
// Update the action bars
getViewSite().getActionBars().updateActionBars();
// Fire a property change (in the UI Thread)
DisplayUtil.safeAsyncExec(new Runnable() {
@SuppressWarnings("synthetic-access")
@Override
public void run() {
firePropertyChange(IWorkbenchPartConstants.PROP_INPUT);
}
});
}
/**
* Returns the associated peer model.
*
* @return The associated peer model.
*/
public IPeerNode getPeerModel() {
return peerNode;
}
/**
* Returns the styled text widget.
*
* @return The styled text widget or <code>null</code>.
*/
public StyledText getStyledText() {
return text;
}
/**
* Sets the dirty state.
*
* @param dirty <code>True</code> to mark the view dirty, <code>false</code> otherwise.
*/
public void markDirty(boolean dirty) {
if (this.dirty != dirty) {
this.dirty = dirty;
firePropertyChange(IWorkbenchPartConstants.PROP_DIRTY);
}
}
/* (non-Javadoc)
* @see org.eclipse.ui.ISaveablePart#isDirty()
*/
@Override
public boolean isDirty() {
return dirty;
}
/* (non-Javadoc)
* @see org.eclipse.ui.ISaveablePart#doSave(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public void doSave(IProgressMonitor monitor) {
if (fileLoaded != null) saveFile(fileLoaded);
else doSaveAs();
}
/* (non-Javadoc)
* @see org.eclipse.ui.ISaveablePart#doSaveAs()
*/
@Override
public void doSaveAs() {
FileDialog dialog = new FileDialog(getViewSite().getShell(), SWT.SAVE);
dialog.setFilterExtensions(new String[] { "*.tcf", "*" }); //$NON-NLS-1$ //$NON-NLS-2$
dialog.setFilterPath(fileLoaded != null ? fileLoaded : System.getProperty("user.home")); //$NON-NLS-1$
String file = dialog.open();
if (file != null) {
saveFile(file);
// Save succeeded ?
if (!isDirty()) {
fileLoaded = file;
updateHeadLabel();
}
}
}
/* (non-Javadoc)
* @see org.eclipse.ui.ISaveablePart#isSaveAsAllowed()
*/
@Override
public boolean isSaveAsAllowed() {
return true;
}
/* (non-Javadoc)
* @see org.eclipse.ui.ISaveablePart#isSaveOnCloseNeeded()
*/
@Override
public boolean isSaveOnCloseNeeded() {
return isDirty();
}
/**
* Clears all content and reset the loaded file path.
*/
public void clear() {
this.fileLoaded = null;
updateHeadLabel();
if (text != null && !text.isDisposed()) text.setText(""); //$NON-NLS-1$
}
/**
* Opens the file.
*
* @param file The absolute file name of the script to load. Must not be <code>null</code>.
*/
public void openFile(String file) {
Assert.isNotNull(file);
// The file name must be absolute and denote a readable file
File f = new File(file);
if (f.isAbsolute() && f.isFile() && f.canRead()) {
// Remember the file name
this.fileLoaded = file;
updateHeadLabel();
// Clear out the old text
text.setText(""); //$NON-NLS-1$
FileReader reader = null;
try {
reader = new FileReader(f);
StringBuilder buffer = new StringBuilder();
int c;
while ((c = reader.read()) != -1) {
buffer.append((char)c);
}
text.setText(buffer.toString());
markDirty(false);
} catch (IOException e) {
String template = NLS.bind(Messages.ScriptPad_error_openFile, file, Messages.ScriptPad_error_possibleCause);
StatusHandlerUtil.handleStatus(StatusHelper.getStatus(e), this, template,
Messages.ScriptPad_error_title, IContextHelpIds.SCRIPT_PAD_ERROR_OPEN_FILE, this, null);
} finally {
if (reader != null) try { reader.close(); } catch (IOException e) { /* ignored on purpose */ }
}
}
updateActionEnablements();
}
/**
* Saves the file.
*
* @param file The absolute file name to save the script to. Must not be <code>null</code>.
*/
public void saveFile(String file) {
Assert.isNotNull(file);
// The file name must be absolute and denote a writable file
File f = new File(file);
if (f.isAbsolute() && ((f.exists() && f.isFile() && f.canWrite() || !f.exists()))) {
String content = text.getText();
FileWriter writer = null;
try {
writer = new FileWriter(f);
writer.write(content);
markDirty(false);
} catch (Exception e) {
String template = NLS.bind(Messages.ScriptPad_error_saveFile, file, Messages.ScriptPad_error_possibleCause);
StatusHandlerUtil.handleStatus(StatusHelper.getStatus(e), this, template,
Messages.ScriptPad_error_title, IContextHelpIds.SCRIPT_PAD_ERROR_SAVE_FILE, this, null);
} finally {
if (writer != null) try { writer.close(); } catch (IOException e) { /* ignored on purpose */ }
}
}
updateActionEnablements();
}
}