1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.eclipse.org/org/documents/epl-v10.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ide.eclipse.adt.internal.editors.ui; 18 19 import com.android.sdklib.SdkConstants; 20 21 import org.eclipse.core.runtime.Assert; 22 import org.eclipse.jface.viewers.DialogCellEditor; 23 import org.eclipse.swt.SWT; 24 import org.eclipse.swt.events.FocusAdapter; 25 import org.eclipse.swt.events.FocusEvent; 26 import org.eclipse.swt.events.KeyAdapter; 27 import org.eclipse.swt.events.KeyEvent; 28 import org.eclipse.swt.events.ModifyEvent; 29 import org.eclipse.swt.events.ModifyListener; 30 import org.eclipse.swt.events.MouseAdapter; 31 import org.eclipse.swt.events.MouseEvent; 32 import org.eclipse.swt.events.SelectionAdapter; 33 import org.eclipse.swt.events.SelectionEvent; 34 import org.eclipse.swt.events.TraverseEvent; 35 import org.eclipse.swt.events.TraverseListener; 36 import org.eclipse.swt.widgets.Button; 37 import org.eclipse.swt.widgets.Composite; 38 import org.eclipse.swt.widgets.Control; 39 import org.eclipse.swt.widgets.Text; 40 41 import java.text.MessageFormat; 42 43 /** 44 * Custom DialogCellEditor, replacing the Label with an editable {@link Text} widget. 45 * <p/>Also set the button to {@link SWT#FLAT} to make sure it looks good on MacOS X. 46 * <p/>Most of the code comes from TextCellEditor. 47 */ 48 public abstract class EditableDialogCellEditor extends DialogCellEditor { 49 50 private Text text; 51 52 private ModifyListener modifyListener; 53 54 /** 55 * State information for updating action enablement 56 */ 57 private boolean isSelection = false; 58 59 private boolean isDeleteable = false; 60 61 private boolean isSelectable = false; 62 63 EditableDialogCellEditor(Composite parent) { 64 super(parent); 65 } 66 67 /* 68 * Re-implement this method only to properly set the style in the button, or it won't look 69 * good in MacOS X 70 */ 71 @Override 72 protected Button createButton(Composite parent) { 73 Button result = new Button(parent, SWT.DOWN | SWT.FLAT); 74 result.setText("..."); //$NON-NLS-1$ 75 return result; 76 } 77 78 79 @Override 80 protected Control createContents(Composite cell) { 81 text = new Text(cell, SWT.SINGLE); 82 text.addSelectionListener(new SelectionAdapter() { 83 @Override 84 public void widgetDefaultSelected(SelectionEvent e) { 85 handleDefaultSelection(e); 86 } 87 }); 88 text.addKeyListener(new KeyAdapter() { 89 // hook key pressed - see PR 14201 90 @Override 91 public void keyPressed(KeyEvent e) { 92 keyReleaseOccured(e); 93 94 // as a result of processing the above call, clients may have 95 // disposed this cell editor 96 if ((getControl() == null) || getControl().isDisposed()) { 97 return; 98 } 99 checkSelection(); // see explanation below 100 checkDeleteable(); 101 checkSelectable(); 102 } 103 }); 104 text.addTraverseListener(new TraverseListener() { 105 public void keyTraversed(TraverseEvent e) { 106 if (e.detail == SWT.TRAVERSE_ESCAPE 107 || e.detail == SWT.TRAVERSE_RETURN) { 108 e.doit = false; 109 } 110 } 111 }); 112 // We really want a selection listener but it is not supported so we 113 // use a key listener and a mouse listener to know when selection changes 114 // may have occurred 115 text.addMouseListener(new MouseAdapter() { 116 @Override 117 public void mouseUp(MouseEvent e) { 118 checkSelection(); 119 checkDeleteable(); 120 checkSelectable(); 121 } 122 }); 123 text.addFocusListener(new FocusAdapter() { 124 @Override 125 public void focusLost(FocusEvent e) { 126 EditableDialogCellEditor.this.focusLost(); 127 } 128 }); 129 text.setFont(cell.getFont()); 130 text.setBackground(cell.getBackground()); 131 text.setText("");//$NON-NLS-1$ 132 text.addModifyListener(getModifyListener()); 133 return text; 134 } 135 136 /** 137 * Checks to see if the "deletable" state (can delete/ 138 * nothing to delete) has changed and if so fire an 139 * enablement changed notification. 140 */ 141 private void checkDeleteable() { 142 boolean oldIsDeleteable = isDeleteable; 143 isDeleteable = isDeleteEnabled(); 144 if (oldIsDeleteable != isDeleteable) { 145 fireEnablementChanged(DELETE); 146 } 147 } 148 149 /** 150 * Checks to see if the "selectable" state (can select) 151 * has changed and if so fire an enablement changed notification. 152 */ 153 private void checkSelectable() { 154 boolean oldIsSelectable = isSelectable; 155 isSelectable = isSelectAllEnabled(); 156 if (oldIsSelectable != isSelectable) { 157 fireEnablementChanged(SELECT_ALL); 158 } 159 } 160 161 /** 162 * Checks to see if the selection state (selection / 163 * no selection) has changed and if so fire an 164 * enablement changed notification. 165 */ 166 private void checkSelection() { 167 boolean oldIsSelection = isSelection; 168 isSelection = text.getSelectionCount() > 0; 169 if (oldIsSelection != isSelection) { 170 fireEnablementChanged(COPY); 171 fireEnablementChanged(CUT); 172 } 173 } 174 175 /* (non-Javadoc) 176 * Method declared on CellEditor. 177 */ 178 @Override 179 protected void doSetFocus() { 180 if (text != null) { 181 text.selectAll(); 182 text.setFocus(); 183 checkSelection(); 184 checkDeleteable(); 185 checkSelectable(); 186 } 187 } 188 189 /* 190 * (non-Javadoc) 191 * @see org.eclipse.jface.viewers.DialogCellEditor#updateContents(java.lang.Object) 192 */ 193 @Override 194 protected void updateContents(Object value) { 195 Assert.isTrue(text != null && (value == null || (value instanceof String))); 196 if (value != null) { 197 text.removeModifyListener(getModifyListener()); 198 text.setText((String) value); 199 text.addModifyListener(getModifyListener()); 200 } 201 } 202 203 /** 204 * The <code>TextCellEditor</code> implementation of 205 * this <code>CellEditor</code> framework method returns 206 * the text string. 207 * 208 * @return the text string 209 */ 210 @Override 211 protected Object doGetValue() { 212 return text.getText(); 213 } 214 215 216 /** 217 * Processes a modify event that occurred in this text cell editor. 218 * This framework method performs validation and sets the error message 219 * accordingly, and then reports a change via <code>fireEditorValueChanged</code>. 220 * Subclasses should call this method at appropriate times. Subclasses 221 * may extend or reimplement. 222 * 223 * @param e the SWT modify event 224 */ 225 protected void editOccured(ModifyEvent e) { 226 String value = text.getText(); 227 if (value == null) { 228 value = "";//$NON-NLS-1$ 229 } 230 Object typedValue = value; 231 boolean oldValidState = isValueValid(); 232 boolean newValidState = isCorrect(typedValue); 233 234 if (!newValidState) { 235 // try to insert the current value into the error message. 236 setErrorMessage(MessageFormat.format(getErrorMessage(), 237 new Object[] { value })); 238 } 239 valueChanged(oldValidState, newValidState); 240 } 241 242 /** 243 * Return the modify listener. 244 */ 245 private ModifyListener getModifyListener() { 246 if (modifyListener == null) { 247 modifyListener = new ModifyListener() { 248 public void modifyText(ModifyEvent e) { 249 editOccured(e); 250 } 251 }; 252 } 253 return modifyListener; 254 } 255 256 /** 257 * Handles a default selection event from the text control by applying the editor 258 * value and deactivating this cell editor. 259 * 260 * @param event the selection event 261 * 262 * @since 3.0 263 */ 264 protected void handleDefaultSelection(SelectionEvent event) { 265 // same with enter-key handling code in keyReleaseOccured(e); 266 fireApplyEditorValue(); 267 deactivate(); 268 } 269 270 /** 271 * The <code>TextCellEditor</code> implementation of this 272 * <code>CellEditor</code> method returns <code>true</code> if 273 * the current selection is not empty. 274 */ 275 @Override 276 public boolean isCopyEnabled() { 277 if (text == null || text.isDisposed()) { 278 return false; 279 } 280 return text.getSelectionCount() > 0; 281 } 282 283 /** 284 * The <code>TextCellEditor</code> implementation of this 285 * <code>CellEditor</code> method returns <code>true</code> if 286 * the current selection is not empty. 287 */ 288 @Override 289 public boolean isCutEnabled() { 290 if (text == null || text.isDisposed()) { 291 return false; 292 } 293 return text.getSelectionCount() > 0; 294 } 295 296 /** 297 * The <code>TextCellEditor</code> implementation of this 298 * <code>CellEditor</code> method returns <code>true</code> 299 * if there is a selection or if the caret is not positioned 300 * at the end of the text. 301 */ 302 @Override 303 public boolean isDeleteEnabled() { 304 if (text == null || text.isDisposed()) { 305 return false; 306 } 307 return text.getSelectionCount() > 0 308 || text.getCaretPosition() < text.getCharCount(); 309 } 310 311 /** 312 * The <code>TextCellEditor</code> implementation of this 313 * <code>CellEditor</code> method always returns <code>true</code>. 314 */ 315 @Override 316 public boolean isPasteEnabled() { 317 if (text == null || text.isDisposed()) { 318 return false; 319 } 320 return true; 321 } 322 323 /** 324 * Check if save all is enabled 325 * @return true if it is 326 */ 327 public boolean isSaveAllEnabled() { 328 if (text == null || text.isDisposed()) { 329 return false; 330 } 331 return true; 332 } 333 334 /** 335 * Returns <code>true</code> if this cell editor is 336 * able to perform the select all action. 337 * <p> 338 * This default implementation always returns 339 * <code>false</code>. 340 * </p> 341 * <p> 342 * Subclasses may override 343 * </p> 344 * @return <code>true</code> if select all is possible, 345 * <code>false</code> otherwise 346 */ 347 @Override 348 public boolean isSelectAllEnabled() { 349 if (text == null || text.isDisposed()) { 350 return false; 351 } 352 return text.getCharCount() > 0; 353 } 354 355 /** 356 * Processes a key release event that occurred in this cell editor. 357 * <p> 358 * The <code>TextCellEditor</code> implementation of this framework method 359 * ignores when the RETURN key is pressed since this is handled in 360 * <code>handleDefaultSelection</code>. 361 * An exception is made for Ctrl+Enter for multi-line texts, since 362 * a default selection event is not sent in this case. 363 * </p> 364 * 365 * @param keyEvent the key event 366 */ 367 @Override 368 protected void keyReleaseOccured(KeyEvent keyEvent) { 369 if (keyEvent.character == '\r') { // Return key 370 // Enter is handled in handleDefaultSelection. 371 // Do not apply the editor value in response to an Enter key event 372 // since this can be received from the IME when the intent is -not- 373 // to apply the value. 374 // See bug 39074 [CellEditors] [DBCS] canna input mode fires bogus event from Text Control 375 // 376 // An exception is made for Ctrl+Enter for multi-line texts, since 377 // a default selection event is not sent in this case. 378 if (text != null && !text.isDisposed() 379 && (text.getStyle() & SWT.MULTI) != 0) { 380 if ((keyEvent.stateMask & SWT.CTRL) != 0) { 381 super.keyReleaseOccured(keyEvent); 382 } 383 } 384 return; 385 } 386 super.keyReleaseOccured(keyEvent); 387 } 388 389 /** 390 * The <code>TextCellEditor</code> implementation of this 391 * <code>CellEditor</code> method copies the 392 * current selection to the clipboard. 393 */ 394 @Override 395 public void performCopy() { 396 text.copy(); 397 } 398 399 /** 400 * The <code>TextCellEditor</code> implementation of this 401 * <code>CellEditor</code> method cuts the 402 * current selection to the clipboard. 403 */ 404 @Override 405 public void performCut() { 406 text.cut(); 407 checkSelection(); 408 checkDeleteable(); 409 checkSelectable(); 410 } 411 412 /** 413 * The <code>TextCellEditor</code> implementation of this 414 * <code>CellEditor</code> method deletes the 415 * current selection or, if there is no selection, 416 * the character next character from the current position. 417 */ 418 @Override 419 public void performDelete() { 420 if (text.getSelectionCount() > 0) { 421 // remove the contents of the current selection 422 text.insert(""); //$NON-NLS-1$ 423 } else { 424 // remove the next character 425 int pos = text.getCaretPosition(); 426 if (pos < text.getCharCount()) { 427 text.setSelection(pos, pos + 1); 428 text.insert(""); //$NON-NLS-1$ 429 } 430 } 431 checkSelection(); 432 checkDeleteable(); 433 checkSelectable(); 434 } 435 436 /** 437 * The <code>TextCellEditor</code> implementation of this 438 * <code>CellEditor</code> method pastes the 439 * the clipboard contents over the current selection. 440 */ 441 @Override 442 public void performPaste() { 443 text.paste(); 444 checkSelection(); 445 checkDeleteable(); 446 checkSelectable(); 447 } 448 449 /** 450 * The <code>TextCellEditor</code> implementation of this 451 * <code>CellEditor</code> method selects all of the 452 * current text. 453 */ 454 @Override 455 public void performSelectAll() { 456 text.selectAll(); 457 checkSelection(); 458 checkDeleteable(); 459 } 460 461 @Override 462 protected void focusLost() { 463 if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_LINUX) { 464 // On Linux, something about the order of focus event delivery prevents the 465 // callback on the "..." button to be invoked, which means the 466 // customizer dialog never shows up (see issue #18348). 467 // (Note that simply trying to Display.asyncRun() the super.focusLost() 468 // method does not work.) 469 // 470 // We can work around this by not deactivating on a focus loss. 471 // This means that in some cases the cell editor will still be 472 // shown in the property sheet, but I've tested that the values 473 // are all committed as before. This is better than having a non-operational 474 // customizer, but since this issue only happens on Linux the workaround 475 // is only done on Linux such that on other platforms we deactivate 476 // immediately on focus loss. 477 // 478 if (isActivated()) { 479 fireApplyEditorValue(); 480 // super.focusLost calls the following which we're deliberately 481 // suppressing here: 482 // deactivate(); 483 } 484 } else { 485 super.focusLost(); 486 } 487 } 488 } 489