Home | History | Annotate | Download | only in ui
      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