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.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