/*
* JBoss, Home of Professional Open Source.
*
* See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing.
*
* See the AUTHORS.txt file distributed with this work for a full listing of individual contributors.
*/
package org.teiid.designer.ui.actions;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TreeEditor;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.actions.TextActionHandler;
import org.teiid.core.designer.ModelerCoreException;
import org.teiid.designer.core.ModelerCore;
import org.teiid.designer.core.workspace.ModelResource;
import org.teiid.designer.ui.UiConstants;
import org.teiid.designer.ui.common.eventsupport.SelectionUtilities;
import org.teiid.designer.ui.editors.ModelEditorManager;
import org.teiid.designer.ui.viewsupport.DatatypeUtilities;
import org.teiid.designer.ui.viewsupport.ModelUtilities;
/**
* TreeViewerRenameAction is an inline tree editor for any TreeViewer containing EObjects. It cannot be obtained directly from the
* ActionService by class, since each instance of this action must be provided with a TreeViewer.
*
* @since 8.0
*/
public class TreeViewerRenameAction extends RenameAction {
private TreeEditor treeEditor;
Tree navigatorTree;
private TreeViewer treeViewer;
Text textEditor;
Composite textEditorParent;
private TextActionHandler textActionHandler;
private TreeItem[] cachedSelection;
/** the object whose name is being edited inline */
EObject currentObject;
/** if the transaction for this rename should produce a significant undo action */
boolean isSignificant = false;
/**
* Construct an instance of TreeViewerRenameAction.
*/
public TreeViewerRenameAction() {
super();
}
/**
* Set the TreeViewer that this Rename action will run on.
*
* @param treeViewer
* @param labelProvider
*/
public void setTreeViewer( TreeViewer treeViewer,
ILabelProvider labelProvider ) {
if (treeViewer != null) {
this.treeViewer = treeViewer;
navigatorTree = treeViewer.getTree();
treeEditor = new TreeEditor(navigatorTree);
// this.labelProvider = labelProvider;
// this listener is needed since a right-click in the tree when the editor is active does
// NOT fire a focus event and the editor remains active.
navigatorTree.addMouseListener(new MouseAdapter() {
@Override
public void mouseDown( MouseEvent theEvent ) {
if (theEvent.button == 3) {
if (isTextEditorActive()) {
saveChangesAndDispose((EObject)getSelectedObject());
}
}
}
});
}
}
/**
* <p>
* </p>
*
* @see org.eclipse.ui.ISelectionListener#selectionChanged(IWorkbenchPart, ISelection)
* @since 4.0
*/
@Override
public void selectionChanged( final IWorkbenchPart part,
final ISelection selection ) {
super.selectionChanged(part, selection);
}
/* (non-Javadoc)
* @see org.eclipse.jface.action.IAction#run()
*/
@Override
public void doRun() {
// if (preRun()) {
final EObject currentObject = (EObject)getSelectedObject();
cachedSelection = navigatorTree.getSelection();
// Defect 23413 - we only need one or the other or both
if (currentObject != null || cachedSelection != null) {
Display.getCurrent().asyncExec(new Runnable() {
@Override
public void run() {
if (navigatorTree != null) {
isSignificant = true;
renameInline(currentObject);
} else {
// should never happen, so just log it.
RuntimeException e = new RuntimeException();
UiConstants.Util.log(IStatus.ERROR, e, "TreeViewerRenameAction doRun() called on a null Tree"); //$NON-NLS-1$
}
}
});
}
// }
}
/**
* Overloaded to allow newly created objects to be named initially without generating an Undo. This method must be run
* programatically, since it does not get called via Action.
*/
public void doRun( final boolean generateUndo ) {
preRun();
final EObject currentObject = (EObject)getSelectedObject();
cachedSelection = navigatorTree.getSelection();
Display.getCurrent().asyncExec(new Runnable() {
@Override
public void run() {
if (navigatorTree != null) {
isSignificant = generateUndo;
if (ModelerCore.getModelEditor().hasName(currentObject)) {
renameInline(currentObject);
}
} else {
// should never happen, log it jsut in case.
RuntimeException e = new RuntimeException();
UiConstants.Util.log(IStatus.ERROR, e, "TreeViewerRenameAction doRun() called on a null Tree"); //$NON-NLS-1$
}
}
});
}
void renameInline( final EObject eObject ) {
EObject editingEObject = eObject;
// Defect 22944 - Make sure the renaming object is the current/cached selection
// Defect 23282 - Added cachedSelection == null or zero length check
// Defect 23413 - had to tweak one more time because the select was being changed to the Diagram under an xml document
// And in some cases, the eObject is coming in NULL too.
// Basically use whichever one is
if (eObject != null && cachedSelection != null && cachedSelection.length == 1
&& cachedSelection[0].getData() instanceof EObject && eObject == (EObject)cachedSelection[0].getData()) {
// DO NOTHING, THIS IS OK
} else if (eObject != null && (cachedSelection == null || cachedSelection.length == 0)) {
IStructuredSelection selection = new StructuredSelection(eObject);
treeViewer.setSelection(selection);
cachedSelection = navigatorTree.getSelection();
} else if (eObject != null && cachedSelection.length == 1 && (cachedSelection[0].getData() instanceof IFile)) {
IStructuredSelection selection = new StructuredSelection(eObject);
treeViewer.setSelection(selection);
cachedSelection = navigatorTree.getSelection();
} else if (cachedSelection != null && cachedSelection[0] != null
&& (eObject == null || cachedSelection[0].getData() != eObject)) {
IStructuredSelection selection = new StructuredSelection(cachedSelection[0]);
editingEObject = (EObject)cachedSelection[0].getData();
treeViewer.setSelection(selection);
} else {
return;
}
// Make sure text editor is created only once. Simply reset text
// editor when action is executed more than once. Fixes bug 22269.
if (textEditorParent == null) {
createTextEditor(editingEObject);
}
String name = ModelerCore.getModelEditor().getName(editingEObject);
if (name != null) {
textEditor.setText(name);
}
// Open text editor with initial size.
textEditorParent.setVisible(true);
Point textSize = textEditor.computeSize(SWT.DEFAULT, SWT.DEFAULT);
textSize.x += textSize.y; // Add extra space for new characters.
Point parentSize = textEditorParent.getSize();
textEditor.setBounds(2, 1, Math.min(textSize.x, parentSize.x - 4), parentSize.y - 2);
textEditorParent.redraw();
textEditor.selectAll();
textEditor.setFocus();
}
private void createTextEditor( final EObject eObj ) {
// Create text editor parent. This draws a nice bounding rect.
textEditorParent = createEditorParent();
textEditorParent.setVisible(false);
textEditorParent.addListener(SWT.Paint, new Listener() {
@Override
public void handleEvent( Event e ) {
Point textSize = textEditor.getSize();
Point parentSize = textEditorParent.getSize();
e.gc.drawRectangle(0, 0, Math.min(textSize.x + 4, parentSize.x - 1), parentSize.y - 1);
}
});
// Create inner text editor.
textEditor = new Text(textEditorParent, SWT.NONE);
textEditorParent.setBackground(textEditor.getBackground());
textEditor.addListener(SWT.Modify, new Listener() {
@Override
public void handleEvent( Event e ) {
Point textSize = textEditor.computeSize(SWT.DEFAULT, SWT.DEFAULT);
textSize.x += textSize.y; // Add extra space for new characters.
Point parentSize = textEditorParent.getSize();
textEditor.setBounds(2, 1, Math.min(textSize.x, parentSize.x - 4), parentSize.y - 2);
textEditorParent.redraw();
}
});
textEditor.addListener(SWT.Traverse, new Listener() {
@Override
public void handleEvent( Event event ) {
// Workaround for Bug 20214 due to extra
// traverse events
switch (event.detail) {
case SWT.TRAVERSE_ESCAPE:
// Do nothing in this case
disposeTextWidget();
event.doit = true;
event.detail = SWT.TRAVERSE_NONE;
break;
case SWT.TRAVERSE_RETURN:
saveChangesAndDispose(eObj);
event.doit = true;
event.detail = SWT.TRAVERSE_NONE;
break;
}
}
});
textEditor.addFocusListener(new FocusAdapter() {
@Override
public void focusLost( FocusEvent fe ) {
saveChangesAndDispose(eObj);
}
});
if (textActionHandler != null) textActionHandler.addText(textEditor);
}
Composite createEditorParent() {
Tree tree = navigatorTree;
Composite result = new Composite(tree, SWT.NONE);
// Now let's make sure the target eObject is selected
TreeItem[] selectedItems = cachedSelection; // tree.getSelection();
if (selectedItems.length > 0) {
treeEditor.horizontalAlignment = SWT.LEFT;
treeEditor.grabHorizontal = true;
treeEditor.setEditor(result, selectedItems[0]);
}
return result;
}
/**
* Save the changes and dispose of the text widget.
*
* @param resource - the resource to move.
*/
void saveChangesAndDispose( EObject resource ) {
// Cache the resource to avoid selection loss since a selection of
// another item can trigger this method
currentObject = resource;
final String newName = textEditor.getText();
// Run this in an async to make sure that the operation that triggered
// this action is completed. Otherwise this leads to problems when the
// icon of the item being renamed is clicked (i.e., which causes the rename
// text widget to lose focus and trigger this method).
Runnable query = new Runnable() {
@Override
public void run() {
// Dispose the text widget regardless
disposeTextWidget();
// String oldName = labelProvider.getText(currentObject);
// Need to check only the "Name" here. Not the visible text
// (i.e. Columns will include datatype info ..... someName: string(25))
if (currentObject != null) {
String oldName = ModelerCore.getModelEditor().getName(currentObject);
if (!newName.equals(oldName) && currentObject != null) {
String undoLabel = UiConstants.Util.getString("RenameAction.undoLabel", oldName); //$NON-NLS-1$
boolean started = ModelerCore.startTxn(isSignificant, undoLabel, this);
boolean succeeded = false;
try {
if (!DatatypeUtilities.renameSqlColumn(currentObject, newName)) {
if (newName.length() > 0) {
ModelerCore.getModelEditor().rename(currentObject, newName);
}
}
succeeded = true;
} catch (ModelerCoreException e) {
UiConstants.Util.log(IStatus.ERROR, e, e.getMessage());
} finally {
if (started) {
if (succeeded) {
ModelerCore.commitTxn();
} else {
ModelerCore.rollbackTxn();
}
}
}
}
}
currentObject = null;
}
};
navigatorTree.getShell().getDisplay().asyncExec(query);
}
/**
* Indicates if the text editor is currently active and not disposed.
*
* @return <code>true</code>if active; <code>false</code> otherwise.
* @since 4.2
*/
boolean isTextEditorActive() {
return (this.textEditor != null);
}
/**
* Close the text widget and reset the editorText field.
*/
void disposeTextWidget() {
if (textActionHandler != null) textActionHandler.removeText(textEditor);
if (textEditorParent != null) {
textEditorParent.dispose();
textEditorParent = null;
textEditor = null;
treeEditor.setEditor(null, null);
}
insureOpenEditor();
}
/**
* This method is called in the run() method of AbstractAction to give the actions a hook into canceling the run at the last
* minute. This overrides the AbstractAction preRun() method.
*/
@Override
protected boolean preRun() {
if (requiresEditorForRun()) {
List allSelectedEObjects = SelectionUtilities.getSelectedEObjects(getSelection());
if (allSelectedEObjects != null && !allSelectedEObjects.isEmpty()) {
EObject eObject = (EObject)allSelectedEObjects.get(0);
ModelResource mr = ModelUtilities.getModelResourceForModelObject(eObject);
if (mr != null) {
if( !ModelEditorManager.isOpen(eObject) ) {
ModelEditorManager.activate(mr, true);
}
}
}
}
return true;
}
@Override
protected boolean requiresEditorForRun() {
return true;
}
private void insureOpenEditor() {
if (requiresEditorForRun()) {
List allSelectedEObjects = SelectionUtilities.getSelectedEObjects(getSelection());
if (allSelectedEObjects != null && !allSelectedEObjects.isEmpty()) {
EObject eObject = (EObject)allSelectedEObjects.get(0);
ModelResource mr = ModelUtilities.getModelResourceForModelObject(eObject);
if (mr != null) {
ModelEditorManager.open(eObject, true, UiConstants.ObjectEditor.REFRESH_EDITOR_IF_OPEN);
}
}
}
}
}