001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 *
019 */
020 /*******************************************************************************
021 * Copyright (c) 2007 IBM Corporation and others.
022 * All rights reserved. This program and the accompanying materials
023 * are made available under the terms of the Eclipse Public License v1.0
024 * which accompanies this distribution, and is available at
025 * http://www.eclipse.org/legal/epl-v10.html
026 *
027 * Contributors:
028 * IBM Corporation - Initial API and implementation
029 *******************************************************************************/
030 package org.apache.directory.studio.apacheds.actions;
031
032
033 import org.apache.directory.studio.apacheds.ApacheDsPluginConstants;
034 import org.apache.directory.studio.apacheds.model.Server;
035 import org.apache.directory.studio.apacheds.model.ServersHandler;
036 import org.apache.directory.studio.apacheds.views.ServersView;
037 import org.eclipse.jface.action.Action;
038 import org.eclipse.jface.action.IAction;
039 import org.eclipse.jface.dialogs.MessageDialog;
040 import org.eclipse.jface.viewers.ISelection;
041 import org.eclipse.jface.viewers.StructuredSelection;
042 import org.eclipse.swt.SWT;
043 import org.eclipse.swt.custom.TreeEditor;
044 import org.eclipse.swt.events.FocusAdapter;
045 import org.eclipse.swt.events.FocusEvent;
046 import org.eclipse.swt.graphics.Point;
047 import org.eclipse.swt.widgets.Composite;
048 import org.eclipse.swt.widgets.Control;
049 import org.eclipse.swt.widgets.Event;
050 import org.eclipse.swt.widgets.Listener;
051 import org.eclipse.swt.widgets.Text;
052 import org.eclipse.swt.widgets.Tree;
053 import org.eclipse.swt.widgets.TreeItem;
054 import org.eclipse.ui.IWorkbenchWindow;
055 import org.eclipse.ui.IWorkbenchWindowActionDelegate;
056 import org.eclipse.ui.actions.TextActionHandler;
057
058
059 /**
060 * This class implements the open action for a server.
061 *
062 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
063 * @version $Rev$, $Date$
064 */
065 public class RenameAction extends Action implements IWorkbenchWindowActionDelegate
066 {
067 /** The associated view */
068 private ServersView view;
069 private Tree tree;
070 private TreeEditor treeEditor;
071 protected Composite textEditorParent;
072 protected Text textEditor;
073 private TextActionHandler textActionHandler;
074 // The server being edited if this is being done inline
075 protected Server editedServer;
076
077 protected boolean saving = false;
078
079
080 /**
081 * Creates a new instance of RenameAction.
082 */
083 public RenameAction()
084 {
085 super( Messages.getString( "RenameAction.Rename" ) ); //$NON-NLS-1$
086 init();
087 }
088
089
090 /**
091 * Creates a new instance of RenameAction.
092 *
093 * @param view
094 * the associated view
095 */
096 public RenameAction( ServersView view )
097 {
098 super( Messages.getString( "RenameAction.Rename" ) ); //$NON-NLS-1$
099 this.view = view;
100 this.tree = view.getViewer().getTree();
101 this.treeEditor = new TreeEditor( tree );
102 init();
103 }
104
105
106 /**
107 * Initializes the action.
108 */
109 private void init()
110 {
111 setId( ApacheDsPluginConstants.CMD_RENAME );
112 setActionDefinitionId( ApacheDsPluginConstants.CMD_RENAME );
113 setToolTipText( Messages.getString( "RenameAction.RenameToolTip" ) ); //$NON-NLS-1$
114 }
115
116
117 /* (non-Javadoc)
118 * @see org.eclipse.jface.action.Action#run()
119 */
120 public void run()
121 {
122 if ( view != null )
123 {
124 // What we get from the TableViewer is a StructuredSelection
125 StructuredSelection selection = ( StructuredSelection ) view.getViewer().getSelection();
126
127 // Here's the real object
128 Server server = ( Server ) selection.getFirstElement();
129 if ( server != null )
130 {
131 queryNewServerNameInline( server );
132 }
133 }
134 }
135
136
137 /**
138 * Return the new name to be given to the target resource or
139 * <code>null<code>
140 * if the query was canceled. Rename the currently selected server using the table editor.
141 * Continue the action when the user is done.
142 *
143 * @param server the server to rename
144 */
145 private void queryNewServerNameInline( final Server server )
146 {
147 // Make sure text editor is created only once. Simply reset text
148 // editor when action is executed more than once. Fixes bug 22269
149 if ( textEditorParent == null )
150 {
151 createTextEditor( server );
152 }
153 textEditor.setText( server.getName() );
154
155 // Open text editor with initial size
156 textEditorParent.setVisible( true );
157 Point textSize = textEditor.computeSize( SWT.DEFAULT, SWT.DEFAULT );
158 textSize.x += textSize.y; // Add extra space for new characters
159 Point parentSize = textEditorParent.getSize();
160 int inset = getCellEditorInset( textEditorParent );
161 textEditor.setBounds( 2, inset, Math.min( textSize.x, parentSize.x - 4 ), parentSize.y - 2 * inset );
162 textEditorParent.redraw();
163 textEditor.selectAll();
164 textEditor.setFocus();
165 }
166
167
168 /**
169 * Create the text editor widget.
170 *
171 * @param server the server to rename
172 */
173 private void createTextEditor( final Server server )
174 {
175 // Create text editor parent. This draws a nice bounding rect
176 textEditorParent = createParent();
177 textEditorParent.setVisible( false );
178 final int inset = getCellEditorInset( textEditorParent );
179 if ( inset > 0 )
180 {
181 textEditorParent.addListener( SWT.Paint, new Listener()
182 {
183 public void handleEvent( Event e )
184 {
185 Point textSize = textEditor.getSize();
186 Point parentSize = textEditorParent.getSize();
187 e.gc.drawRectangle( 0, 0, Math.min( textSize.x + 4, parentSize.x - 1 ), parentSize.y - 1 );
188 }
189 } );
190 }
191 // Create inner text editor
192 textEditor = new Text( textEditorParent, SWT.NONE );
193 textEditor.setFont( tree.getFont() );
194 textEditorParent.setBackground( textEditor.getBackground() );
195 textEditor.addListener( SWT.Modify, new Listener()
196 {
197 public void handleEvent( Event e )
198 {
199 Point textSize = textEditor.computeSize( SWT.DEFAULT, SWT.DEFAULT );
200 textSize.x += textSize.y; // Add extra space for new
201 // characters.
202 Point parentSize = textEditorParent.getSize();
203 textEditor.setBounds( 2, inset, Math.min( textSize.x, parentSize.x - 4 ), parentSize.y - 2 * inset );
204 textEditorParent.redraw();
205 }
206 } );
207 textEditor.addListener( SWT.Traverse, new Listener()
208 {
209 public void handleEvent( Event event )
210 {
211
212 // Workaround for Bug 20214 due to extra
213 // traverse events
214 switch ( event.detail )
215 {
216 case SWT.TRAVERSE_ESCAPE:
217 // Do nothing in this case
218 disposeTextWidget();
219 event.doit = true;
220 event.detail = SWT.TRAVERSE_NONE;
221 break;
222 case SWT.TRAVERSE_RETURN:
223 saveChangesAndDispose( server );
224 event.doit = true;
225 event.detail = SWT.TRAVERSE_NONE;
226 break;
227 }
228 }
229 } );
230 textEditor.addFocusListener( new FocusAdapter()
231 {
232 public void focusLost( FocusEvent fe )
233 {
234 saveChangesAndDispose( server );
235 }
236 } );
237
238 if ( textActionHandler != null )
239 {
240 textActionHandler.addText( textEditor );
241 }
242 }
243
244
245 private Composite createParent()
246 {
247 Tree tree2 = tree;
248 Composite result = new Composite( tree2, SWT.NONE );
249 TreeItem[] selectedItems = tree2.getSelection();
250 treeEditor.horizontalAlignment = SWT.LEFT;
251 treeEditor.grabHorizontal = true;
252 treeEditor.setEditor( result, selectedItems[0] );
253 return result;
254 }
255
256
257 /**
258 * Close the text widget and reset the editorText field.
259 */
260 protected void disposeTextWidget()
261 {
262 if ( textActionHandler != null )
263 textActionHandler.removeText( textEditor );
264
265 if ( textEditorParent != null )
266 {
267 textEditorParent.dispose();
268 textEditorParent = null;
269 textEditor = null;
270 treeEditor.setEditor( null, null );
271 }
272 }
273
274
275 /**
276 * Save the changes and dispose of the text widget.
277 *
278 * @param server the server to rename
279 */
280 protected void saveChangesAndDispose( Server server )
281 {
282 if ( saving == true )
283 return;
284
285 saving = true;
286 // Cache the resource to avoid selection loss since a selection of
287 // another item can trigger this method
288 editedServer = server;
289 final String newName = textEditor.getText();
290 // Run this in an async to make sure that the operation that triggered
291 // this action is completed. Otherwise this leads to problems when the
292 // icon of the item being renamed is clicked (i.e., which causes the
293 // rename text widget to lose focus and trigger this method)
294 tree.getShell().getDisplay().asyncExec( new Runnable()
295 {
296 public void run()
297 {
298 try
299 {
300 if ( !newName.equals( editedServer.getName() ) )
301 {
302 if ( !ServersHandler.getDefault().isNameAvailable( newName ) )
303 {
304 MessageDialog.openError( tree.getShell(), Messages.getString( "RenameAction.Server" ), //$NON-NLS-1$
305 Messages.getString( "RenameAction.ErrorNameInUse" ) ); //$NON-NLS-1$
306 }
307 else
308 {
309 editedServer.setName( newName );
310 }
311 }
312 editedServer = null;
313 // Dispose the text widget regardless
314 disposeTextWidget();
315 // Ensure the Navigator tree has focus, which it may not if
316 // the text widget previously had focus
317 if ( tree != null && !tree.isDisposed() )
318 {
319 tree.setFocus();
320 }
321 }
322 finally
323 {
324 saving = false;
325 }
326 }
327 } );
328 }
329
330
331 /**
332 * On Mac the text widget already provides a border when it has focus, so
333 * there is no need to draw another one. The value of returned by this
334 * method is usd to control the inset we apply to the text field bound's in
335 * order to get space for drawing a border. A value of 1 means a one-pixel
336 * wide border around the text field. A negative value supresses the border.
337 * However, in M9 the system property
338 * "org.eclipse.swt.internal.carbon.noFocusRing" has been introduced as a
339 * temporary workaround for bug #28842. The existence of the property turns
340 * the native focus ring off if the widget is contained in a main window
341 * (not dialog). The check for the property should be removed after a final
342 * fix for #28842 has been provided.
343 */
344 private static int getCellEditorInset( Control c )
345 {
346 // special case for MacOS X
347 if ( "carbon".equals( SWT.getPlatform() ) ) { //$NON-NLS-1$
348 if ( System.getProperty( "org.eclipse.swt.internal.carbon.noFocusRing" ) == null || c.getShell().getParent() != null ) { //$NON-NLS-1$
349 return -2; // native border
350 }
351 }
352 return 1; // one pixel wide black border
353 }
354
355
356 /* (non-Javadoc)
357 * @see org.eclipse.ui.IActionDelegate#run(org.eclipse.jface.action.IAction)
358 */
359 public void run( IAction action )
360 {
361 run();
362 }
363
364
365 /* (non-Javadoc)
366 * @see org.eclipse.ui.IWorkbenchWindowActionDelegate#dispose()
367 */
368 public void dispose()
369 {
370 // Nothing to do
371 }
372
373
374 /* (non-Javadoc)
375 * @see org.eclipse.ui.IWorkbenchWindowActionDelegate#init(org.eclipse.ui.IWorkbenchWindow)
376 */
377 public void init( IWorkbenchWindow window )
378 {
379 // Nothing to do
380 }
381
382
383 /* (non-Javadoc)
384 * @see org.eclipse.ui.IActionDelegate#selectionChanged(org.eclipse.jface.action.IAction, org.eclipse.jface.viewers.ISelection)
385 */
386 public void selectionChanged( IAction action, ISelection selection )
387 {
388 // Nothing to do
389 }
390 }