/******************************************************************************* * Copyright (c) 2003, 2015 IBM Corporation 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.ui.internal.navigator; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.IStructuredSelection; 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.graphics.Point; import org.eclipse.swt.widgets.Composite; 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.internal.navigator.extensions.INavigatorSiteEditor; import org.eclipse.ui.navigator.CommonViewer; /** * A NavigatorSiteEditor is used to edit (i.e., rename) elements in a Navigator view. It displays a * text editor box overlay on the Navigator tree widget. * * @since 3.2 */ public class NavigatorSiteEditor implements INavigatorSiteEditor { private Tree navigatorTree; private TreeEditor treeEditor; private Text textEditor; private Composite textEditorParent; private TextActionHandler textActionHandler; private String text; // the text being edited private CommonViewer commonViewer; /** * Creates an instance of a NavigatorSiteEditor. * * @param aCommonViewer * the viewer this editor applies to * @param navigatorTree * the tree that is being edited */ public NavigatorSiteEditor(CommonViewer aCommonViewer, Tree navigatorTree) { commonViewer = aCommonViewer; this.navigatorTree = navigatorTree; treeEditor = new TreeEditor(navigatorTree); } /** * Creates the parent composite for the editor overlay. * * @return the parent composite for the editor overlay */ Composite createParent() { Composite result = new Composite(navigatorTree, SWT.NONE); TreeItem[] selectedItems = navigatorTree.getSelection(); treeEditor.horizontalAlignment = SWT.LEFT; treeEditor.grabHorizontal = true; treeEditor.setEditor(result, selectedItems[0]); return result; } /** * Creates the text editor widget. * * @param runnable * the Runnable to execute when editing ends by the user pressing enter or clicking * outside the text editor box. */ void createTextEditor(final Runnable runnable) { // Create text editor parent. This draws a nice bounding rect. textEditorParent = createParent(); 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(runnable); event.doit = true; event.detail = SWT.TRAVERSE_NONE; break; } } }); textEditor.addFocusListener(new FocusAdapter() { @Override public void focusLost(FocusEvent fe) { saveChangesAndDispose(runnable); } }); if (textActionHandler != null) { textActionHandler.addText(textEditor); } } /** * Closes the text editor widget. */ void disposeTextWidget() { if (textActionHandler != null) { textActionHandler.removeText(textEditor); } if (textEditorParent != null) { textEditorParent.dispose(); textEditorParent = null; textEditor = null; treeEditor.setEditor(null, null); } } /** * Displays a text editor overlay on the tree widget. * * @param runnable * Runnable to execute when editing ends either by the user pressing enter or * clicking outside the editor box. */ @Override public void edit(Runnable runnable) { IStructuredSelection selection = commonViewer.getStructuredSelection(); if (selection.size() != 1) { return; } text = getLabel(selection.getFirstElement()); if (text == null) { 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(runnable); } textEditor.setText(text); // 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(); } /** * Returns the displayed label of the given element. * * @param element * the element that is displayed in the navigator * @return the displayed label of the given element. */ String getLabel(Object element) { return ((ILabelProvider) commonViewer.getLabelProvider()).getText(element); } @Override public String getText() { return text; } /** * Saves the changes and disposes of the text widget. * * @param runnable * Runnable to execute */ void saveChangesAndDispose(final Runnable runnable) { final String newText = 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 editRunnable = new Runnable() { @Override public void run() { disposeTextWidget(); if (newText.length() > 0 && newText.equals(text) == false) { text = newText; runnable.run(); } text = null; } }; navigatorTree.getShell().getDisplay().asyncExec(editRunnable); } @Override public void setTextActionHandler(TextActionHandler actionHandler) { textActionHandler = actionHandler; } }