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.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 @Override 106 public void keyTraversed(TraverseEvent e) { 107 if (e.detail == SWT.TRAVERSE_ESCAPE 108 || e.detail == SWT.TRAVERSE_RETURN) { 109 e.doit = false; 110 } 111 } 112 }); 113 // We really want a selection listener but it is not supported so we 114 // use a key listener and a mouse listener to know when selection changes 115 // may have occurred 116 text.addMouseListener(new MouseAdapter() { 117 @Override 118 public void mouseUp(MouseEvent e) { 119 checkSelection(); 120 checkDeleteable(); 121 checkSelectable(); 122 } 123 }); 124 text.addFocusListener(new FocusAdapter() { 125 @Override 126 public void focusLost(FocusEvent e) { 127 EditableDialogCellEditor.this.focusLost(); 128 } 129 }); 130 text.setFont(cell.getFont()); 131 text.setBackground(cell.getBackground()); 132 text.setText("");//$NON-NLS-1$ 133 text.addModifyListener(getModifyListener()); 134 return text; 135 } 136 137 /** 138 * Checks to see if the "deletable" state (can delete/ 139 * nothing to delete) has changed and if so fire an 140 * enablement changed notification. 141 */ 142 private void checkDeleteable() { 143 boolean oldIsDeleteable = isDeleteable; 144 isDeleteable = isDeleteEnabled(); 145 if (oldIsDeleteable != isDeleteable) { 146 fireEnablementChanged(DELETE); 147 } 148 } 149 150 /** 151 * Checks to see if the "selectable" state (can select) 152 * has changed and if so fire an enablement changed notification. 153 */ 154 private void checkSelectable() { 155 boolean oldIsSelectable = isSelectable; 156 isSelectable = isSelectAllEnabled(); 157 if (oldIsSelectable != isSelectable) { 158 fireEnablementChanged(SELECT_ALL); 159 } 160 } 161 162 /** 163 * Checks to see if the selection state (selection / 164 * no selection) has changed and if so fire an 165 * enablement changed notification. 166 */ 167 private void checkSelection() { 168 boolean oldIsSelection = isSelection; 169 isSelection = text.getSelectionCount() > 0; 170 if (oldIsSelection != isSelection) { 171 fireEnablementChanged(COPY); 172 fireEnablementChanged(CUT); 173 } 174 } 175 176 /* (non-Javadoc) 177 * Method declared on CellEditor. 178 */ 179 @Override 180 protected void doSetFocus() { 181 if (text != null) { 182 text.selectAll(); 183 text.setFocus(); 184 checkSelection(); 185 checkDeleteable(); 186 checkSelectable(); 187 } 188 } 189 190 /* 191 * (non-Javadoc) 192 * @see org.eclipse.jface.viewers.DialogCellEditor#updateContents(java.lang.Object) 193 */ 194 @Override 195 protected void updateContents(Object value) { 196 Assert.isTrue(text != null && (value == null || (value instanceof String))); 197 if (value != null) { 198 text.removeModifyListener(getModifyListener()); 199 text.setText((String) value); 200 text.addModifyListener(getModifyListener()); 201 } 202 } 203 204 /** 205 * The <code>TextCellEditor</code> implementation of 206 * this <code>CellEditor</code> framework method returns 207 * the text string. 208 * 209 * @return the text string 210 */ 211 @Override 212 protected Object doGetValue() { 213 return text.getText(); 214 } 215 216 217 /** 218 * Processes a modify event that occurred in this text cell editor. 219 * This framework method performs validation and sets the error message 220 * accordingly, and then reports a change via <code>fireEditorValueChanged</code>. 221 * Subclasses should call this method at appropriate times. Subclasses 222 * may extend or reimplement. 223 * 224 * @param e the SWT modify event 225 */ 226 protected void editOccured(ModifyEvent e) { 227 String value = text.getText(); 228 if (value == null) { 229 value = "";//$NON-NLS-1$ 230 } 231 Object typedValue = value; 232 boolean oldValidState = isValueValid(); 233 boolean newValidState = isCorrect(typedValue); 234 235 if (!newValidState) { 236 // try to insert the current value into the error message. 237 setErrorMessage(MessageFormat.format(getErrorMessage(), 238 new Object[] { value })); 239 } 240 valueChanged(oldValidState, newValidState); 241 } 242 243 /** 244 * Return the modify listener. 245 */ 246 private ModifyListener getModifyListener() { 247 if (modifyListener == null) { 248 modifyListener = new ModifyListener() { 249 @Override 250 public void modifyText(ModifyEvent e) { 251 editOccured(e); 252 } 253 }; 254 } 255 return modifyListener; 256 } 257 258 /** 259 * Handles a default selection event from the text control by applying the editor 260 * value and deactivating this cell editor. 261 * 262 * @param event the selection event 263 * 264 * @since 3.0 265 */ 266 protected void handleDefaultSelection(SelectionEvent event) { 267 // same with enter-key handling code in keyReleaseOccured(e); 268 fireApplyEditorValue(); 269 deactivate(); 270 } 271 272 /** 273 * The <code>TextCellEditor</code> implementation of this 274 * <code>CellEditor</code> method returns <code>true</code> if 275 * the current selection is not empty. 276 */ 277 @Override 278 public boolean isCopyEnabled() { 279 if (text == null || text.isDisposed()) { 280 return false; 281 } 282 return text.getSelectionCount() > 0; 283 } 284 285 /** 286 * The <code>TextCellEditor</code> implementation of this 287 * <code>CellEditor</code> method returns <code>true</code> if 288 * the current selection is not empty. 289 */ 290 @Override 291 public boolean isCutEnabled() { 292 if (text == null || text.isDisposed()) { 293 return false; 294 } 295 return text.getSelectionCount() > 0; 296 } 297 298 /** 299 * The <code>TextCellEditor</code> implementation of this 300 * <code>CellEditor</code> method returns <code>true</code> 301 * if there is a selection or if the caret is not positioned 302 * at the end of the text. 303 */ 304 @Override 305 public boolean isDeleteEnabled() { 306 if (text == null || text.isDisposed()) { 307 return false; 308 } 309 return text.getSelectionCount() > 0 310 || text.getCaretPosition() < text.getCharCount(); 311 } 312 313 /** 314 * The <code>TextCellEditor</code> implementation of this 315 * <code>CellEditor</code> method always returns <code>true</code>. 316 */ 317 @Override 318 public boolean isPasteEnabled() { 319 if (text == null || text.isDisposed()) { 320 return false; 321 } 322 return true; 323 } 324 325 /** 326 * Check if save all is enabled 327 * @return true if it is 328 */ 329 public boolean isSaveAllEnabled() { 330 if (text == null || text.isDisposed()) { 331 return false; 332 } 333 return true; 334 } 335 336 /** 337 * Returns <code>true</code> if this cell editor is 338 * able to perform the select all action. 339 * <p> 340 * This default implementation always returns 341 * <code>false</code>. 342 * </p> 343 * <p> 344 * Subclasses may override 345 * </p> 346 * @return <code>true</code> if select all is possible, 347 * <code>false</code> otherwise 348 */ 349 @Override 350 public boolean isSelectAllEnabled() { 351 if (text == null || text.isDisposed()) { 352 return false; 353 } 354 return text.getCharCount() > 0; 355 } 356 357 /** 358 * Processes a key release event that occurred in this cell editor. 359 * <p> 360 * The <code>TextCellEditor</code> implementation of this framework method 361 * ignores when the RETURN key is pressed since this is handled in 362 * <code>handleDefaultSelection</code>. 363 * An exception is made for Ctrl+Enter for multi-line texts, since 364 * a default selection event is not sent in this case. 365 * </p> 366 * 367 * @param keyEvent the key event 368 */ 369 @Override 370 protected void keyReleaseOccured(KeyEvent keyEvent) { 371 if (keyEvent.character == '\r') { // Return key 372 // Enter is handled in handleDefaultSelection. 373 // Do not apply the editor value in response to an Enter key event 374 // since this can be received from the IME when the intent is -not- 375 // to apply the value. 376 // See bug 39074 [CellEditors] [DBCS] canna input mode fires bogus event from Text Control 377 // 378 // An exception is made for Ctrl+Enter for multi-line texts, since 379 // a default selection event is not sent in this case. 380 if (text != null && !text.isDisposed() 381 && (text.getStyle() & SWT.MULTI) != 0) { 382 if ((keyEvent.stateMask & SWT.CTRL) != 0) { 383 super.keyReleaseOccured(keyEvent); 384 } 385 } 386 return; 387 } 388 super.keyReleaseOccured(keyEvent); 389 } 390 391 /** 392 * The <code>TextCellEditor</code> implementation of this 393 * <code>CellEditor</code> method copies the 394 * current selection to the clipboard. 395 */ 396 @Override 397 public void performCopy() { 398 text.copy(); 399 } 400 401 /** 402 * The <code>TextCellEditor</code> implementation of this 403 * <code>CellEditor</code> method cuts the 404 * current selection to the clipboard. 405 */ 406 @Override 407 public void performCut() { 408 text.cut(); 409 checkSelection(); 410 checkDeleteable(); 411 checkSelectable(); 412 } 413 414 /** 415 * The <code>TextCellEditor</code> implementation of this 416 * <code>CellEditor</code> method deletes the 417 * current selection or, if there is no selection, 418 * the character next character from the current position. 419 */ 420 @Override 421 public void performDelete() { 422 if (text.getSelectionCount() > 0) { 423 // remove the contents of the current selection 424 text.insert(""); //$NON-NLS-1$ 425 } else { 426 // remove the next character 427 int pos = text.getCaretPosition(); 428 if (pos < text.getCharCount()) { 429 text.setSelection(pos, pos + 1); 430 text.insert(""); //$NON-NLS-1$ 431 } 432 } 433 checkSelection(); 434 checkDeleteable(); 435 checkSelectable(); 436 } 437 438 /** 439 * The <code>TextCellEditor</code> implementation of this 440 * <code>CellEditor</code> method pastes the 441 * the clipboard contents over the current selection. 442 */ 443 @Override 444 public void performPaste() { 445 text.paste(); 446 checkSelection(); 447 checkDeleteable(); 448 checkSelectable(); 449 } 450 451 /** 452 * The <code>TextCellEditor</code> implementation of this 453 * <code>CellEditor</code> method selects all of the 454 * current text. 455 */ 456 @Override 457 public void performSelectAll() { 458 text.selectAll(); 459 checkSelection(); 460 checkDeleteable(); 461 } 462 463 @Override 464 protected void focusLost() { 465 if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_LINUX) { 466 // On Linux, something about the order of focus event delivery prevents the 467 // callback on the "..." button to be invoked, which means the 468 // customizer dialog never shows up (see issue #18348). 469 // (Note that simply trying to Display.asyncRun() the super.focusLost() 470 // method does not work.) 471 // 472 // We can work around this by not deactivating on a focus loss. 473 // This means that in some cases the cell editor will still be 474 // shown in the property sheet, but I've tested that the values 475 // are all committed as before. This is better than having a non-operational 476 // customizer, but since this issue only happens on Linux the workaround 477 // is only done on Linux such that on other platforms we deactivate 478 // immediately on focus loss. 479 // 480 if (isActivated()) { 481 fireApplyEditorValue(); 482 // super.focusLost calls the following which we're deliberately 483 // suppressing here: 484 // deactivate(); 485 } 486 } else { 487 super.focusLost(); 488 } 489 } 490 } 491