Home | History | Annotate | Download | only in editor
      1 /*******************************************************************************
      2  * Copyright (c) 2011 Google, Inc.
      3  * All rights reserved. This program and the accompanying materials
      4  * are made available under the terms of the Eclipse Public License v1.0
      5  * which accompanies this distribution, and is available at
      6  * http://www.eclipse.org/legal/epl-v10.html
      7  *
      8  * Contributors:
      9  *    Google, Inc. - initial API and implementation
     10  *******************************************************************************/
     11 package org.eclipse.wb.internal.core.model.property.editor;
     12 
     13 import org.eclipse.jface.bindings.keys.KeyStroke;
     14 import org.eclipse.jface.fieldassist.ContentProposalAdapter;
     15 import org.eclipse.jface.fieldassist.IContentProposal;
     16 import org.eclipse.jface.fieldassist.IContentProposalListener;
     17 import org.eclipse.jface.fieldassist.IContentProposalListener2;
     18 import org.eclipse.jface.fieldassist.IContentProposalProvider;
     19 import org.eclipse.jface.fieldassist.IControlContentAdapter;
     20 import org.eclipse.jface.fieldassist.TextContentAdapter;
     21 import org.eclipse.jface.viewers.ILabelProvider;
     22 import org.eclipse.swt.SWT;
     23 import org.eclipse.swt.events.FocusEvent;
     24 import org.eclipse.swt.events.FocusListener;
     25 import org.eclipse.swt.events.KeyAdapter;
     26 import org.eclipse.swt.events.KeyEvent;
     27 import org.eclipse.swt.events.KeyListener;
     28 import org.eclipse.swt.graphics.Point;
     29 import org.eclipse.swt.graphics.Rectangle;
     30 import org.eclipse.swt.widgets.Display;
     31 import org.eclipse.swt.widgets.Event;
     32 import org.eclipse.swt.widgets.Listener;
     33 import org.eclipse.swt.widgets.Text;
     34 import org.eclipse.wb.internal.core.model.property.Property;
     35 import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
     36 
     37 /**
     38  * Abstract {@link PropertyEditor} for that uses {@link Text} as control.
     39  *
     40  * @author scheglov_ke
     41  * @coverage core.model.property.editor
     42  */
     43 public abstract class AbstractTextPropertyEditor extends TextDisplayPropertyEditor {
     44   ////////////////////////////////////////////////////////////////////////////
     45   //
     46   // Editing
     47   //
     48   ////////////////////////////////////////////////////////////////////////////
     49   private Text m_textControl;
     50   private boolean m_ignoreFocusLost;
     51 
     52   // BEGIN ADT MODIFICATIONS
     53   // ContentProposalAdapter which exposes the openProposalPopup method such
     54   // that we can open the dialog up immediately on focus gain to show all available
     55   // alternatives (the default implementation requires at least one keytroke before
     56   // it shows up)
     57     private class ImmediateProposalAdapter extends ContentProposalAdapter
     58         implements FocusListener, IContentProposalListener, IContentProposalListener2 {
     59         private final PropertyTable m_propertyTable;
     60         private final IContentProposalProvider m_proposalProvider;
     61         public ImmediateProposalAdapter(
     62                 Text control,
     63                 IControlContentAdapter controlContentAdapter,
     64                 IContentProposalProvider proposalProvider,
     65                 KeyStroke keyStroke,
     66                 char[] autoActivationCharacters,
     67                 PropertyTable propertyTable) {
     68             super(control, controlContentAdapter, proposalProvider, keyStroke,
     69                     autoActivationCharacters);
     70             m_propertyTable = propertyTable;
     71             m_proposalProvider = proposalProvider;
     72 
     73             // On focus gain, start completing
     74             control.addFocusListener(this);
     75 
     76             // Listen on popup open and close events, in order to disable
     77             // focus handling on the textfield during those events.
     78             // This is necessary since otherwise as soon as the user clicks
     79             // on the popup with the mouse, the text field loses focus, and
     80             // then instantly closes the popup -- without the selection being
     81             // applied. See for example
     82             //   http://dev.eclipse.org/viewcvs/viewvc.cgi/org.eclipse.jface.snippets/
     83             //      Eclipse%20JFace%20Snippets/org/eclipse/jface/snippets/viewers/
     84             //      Snippet060TextCellEditorWithContentProposal.java?view=markup
     85             // for another example of this technique.
     86             addContentProposalListener((IContentProposalListener) this);
     87             addContentProposalListener((IContentProposalListener2) this);
     88 
     89             /* Triggering on empty is disabled for now: it has the unfortunate side-effect
     90                that it's impossible to enter a blank text field - blank matches everything,
     91                so the first item will automatically be selected when you press return.
     92 
     93 
     94             // If you edit the text and delete everything, the normal implementation
     95             // will close the popup; we'll reopen it
     96             control.addModifyListener(new ModifyListener() {
     97                 @Override
     98                 public void modifyText(ModifyEvent event) {
     99                     if (((Text) getControl()).getText().isEmpty()) {
    100                         openIfNecessary();
    101                     }
    102                 }
    103             });
    104             */
    105         }
    106 
    107         private void openIfNecessary() {
    108             if (m_textControl == null || m_textControl.isDisposed() ||
    109                     m_proposalProvider.getProposals(m_textControl.getText(),
    110                             m_textControl.getCaretPosition()).length == 0) {
    111                 return;
    112             }
    113 
    114             getControl().getDisplay().asyncExec(new Runnable() {
    115                 @Override
    116                 public void run() {
    117                     if (!isProposalPopupOpen()) {
    118                         openProposalPopup();
    119                     }
    120                 }
    121             });
    122         }
    123 
    124         // ---- Implements FocusListener ----
    125 
    126         @Override
    127         public void focusGained(FocusEvent event) {
    128             openIfNecessary();
    129         }
    130 
    131         @Override
    132         public void focusLost(FocusEvent event) {
    133         }
    134 
    135         // ---- Implements IContentProposalListener ----
    136 
    137         @Override
    138         public void proposalAccepted(IContentProposal proposal) {
    139             closeProposalPopup();
    140             m_propertyTable.deactivateEditor(true);
    141         }
    142 
    143         // ---- Implements IContentProposalListener2 ----
    144 
    145         @Override
    146         public void proposalPopupClosed(ContentProposalAdapter adapter) {
    147             m_ignoreFocusLost = false;
    148         }
    149 
    150         @Override
    151         public void proposalPopupOpened(ContentProposalAdapter adapter) {
    152             m_ignoreFocusLost = true;
    153         }
    154     }
    155   // END ADT MODIFICATIONS
    156 
    157   @Override
    158   public boolean activate(final PropertyTable propertyTable, final Property property, Point location)
    159       throws Exception {
    160     // create Text
    161     {
    162       m_textControl = new Text(propertyTable, SWT.NONE);
    163 
    164       @SuppressWarnings("unused")
    165       TextControlActionsManager manager = new TextControlActionsManager(m_textControl);
    166 
    167       m_textControl.setEditable(isEditable());
    168 
    169         // BEGIN ADT MODIFICATIONS
    170         // Add support for field completion, if the property provides an IContentProposalProvider
    171         // via its the getAdapter method.
    172         IContentProposalProvider completion = property.getAdapter(IContentProposalProvider.class);
    173         if (completion != null) {
    174             ImmediateProposalAdapter adapter = new ImmediateProposalAdapter(
    175                     m_textControl, new TextContentAdapter(), completion, null, null,
    176                     propertyTable);
    177             adapter.setFilterStyle(ContentProposalAdapter.FILTER_NONE);
    178             adapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
    179             ILabelProvider labelProvider = property.getAdapter(ILabelProvider.class);
    180             if (labelProvider != null) {
    181                 adapter.setLabelProvider(labelProvider);
    182             }
    183         }
    184         // END ADT MODIFICATIONS
    185       m_textControl.setFocus();
    186     }
    187     // add listeners
    188     m_textControl.addKeyListener(new KeyAdapter() {
    189       @Override
    190       public void keyPressed(KeyEvent e) {
    191         try {
    192           handleKeyPressed(propertyTable, property, e);
    193         } catch (Throwable ex) {
    194           propertyTable.deactivateEditor(false);
    195           propertyTable.handleException(ex);
    196         }
    197       }
    198     });
    199     m_textControl.addListener(SWT.FocusOut, new Listener() {
    200       @Override
    201     public void handleEvent(Event event) {
    202         if (!m_ignoreFocusLost) {
    203           propertyTable.deactivateEditor(true);
    204         }
    205       }
    206     });
    207     // set data
    208     toWidget(property);
    209     // keep us active
    210     return true;
    211   }
    212 
    213   @Override
    214   public final void setBounds(Rectangle bounds) {
    215     m_textControl.setBounds(bounds);
    216   }
    217 
    218   @Override
    219   public final void deactivate(PropertyTable propertyTable, Property property, boolean save) {
    220     if (save) {
    221       try {
    222         toProperty(property);
    223       } catch (Throwable e) {
    224         propertyTable.deactivateEditor(false);
    225         propertyTable.handleException(e);
    226       }
    227     }
    228     // dispose Text widget
    229     if (m_textControl != null) {
    230       m_textControl.dispose();
    231       m_textControl = null;
    232     }
    233   }
    234 
    235   @Override
    236   public void keyDown(PropertyTable propertyTable, Property property, KeyEvent event)
    237       throws Exception {
    238     boolean withAlt = (event.stateMask & SWT.ALT) != 0;
    239     boolean withCtrl = (event.stateMask & SWT.CTRL) != 0;
    240     if (event.character != 0 && !(withAlt || withCtrl)) {
    241       propertyTable.activateEditor(property, null);
    242       postKeyEvent(SWT.KeyDown, event);
    243       postKeyEvent(SWT.KeyUp, event);
    244     }
    245   }
    246 
    247   /**
    248    * Posts low-level {@link SWT.KeyDown} or {@link SWT.KeyUp} event.
    249    */
    250   private static void postKeyEvent(int type, KeyEvent event) {
    251     Event lowEvent = new Event();
    252     lowEvent.type = type;
    253     lowEvent.keyCode = event.keyCode;
    254     lowEvent.character = event.character;
    255     // post event
    256     Display.getCurrent().post(lowEvent);
    257   }
    258 
    259   ////////////////////////////////////////////////////////////////////////////
    260   //
    261   // Events
    262   //
    263   ////////////////////////////////////////////////////////////////////////////
    264   /**
    265    * Handles {@link KeyListener#keyPressed(KeyEvent)}.
    266    */
    267   private void handleKeyPressed(PropertyTable propertyTable, Property property, KeyEvent e)
    268       throws Exception {
    269     if (e.keyCode == SWT.CR) {
    270       toProperty(property);
    271 
    272       // BEGIN ADT MODIFICATIONS
    273       // After pressing return, dismiss the text cursor to make the value "committed".
    274       // I'm not sure why this is necessary here and not in WindowBuilder proper.
    275       propertyTable.deactivateEditor(true);
    276       // END ADT MODIFICATIONS
    277     } else if (e.keyCode == SWT.ESC) {
    278       propertyTable.deactivateEditor(false);
    279     } else if (e.keyCode == SWT.ARROW_UP || e.keyCode == SWT.ARROW_DOWN) {
    280       e.doit = false;
    281       boolean success = toProperty(property);
    282       // don't allow navigation if current text can not be transferred to property
    283       if (!success) {
    284         return;
    285       }
    286       // OK, deactivate and navigate
    287       propertyTable.deactivateEditor(true);
    288       propertyTable.navigate(e);
    289     }
    290   }
    291 
    292   ////////////////////////////////////////////////////////////////////////////
    293   //
    294   // Implementation
    295   //
    296   ////////////////////////////////////////////////////////////////////////////
    297   private String m_currentText;
    298 
    299   /**
    300    * Transfers data from {@link Property} to widget.
    301    */
    302   private void toWidget(Property property) throws Exception {
    303     // prepare text
    304     String text = getEditorText(property);
    305     if (text == null) {
    306       text = "";
    307     }
    308     // set text
    309     m_currentText = text;
    310     m_textControl.setText(text);
    311     m_textControl.selectAll();
    312   }
    313 
    314   /**
    315    * Transfers data from widget to {@link Property}.
    316    *
    317    * @return <code>true</code> if transfer was successful.
    318    */
    319   private boolean toProperty(Property property) throws Exception {
    320     // BEGIN ADT MODIFICATIONS
    321     if (m_textControl == null) {
    322       return false;
    323     }
    324     // END ADT MODIFICATIONS
    325     String text = m_textControl.getText();
    326     // change property only if text was changed
    327     if (!m_currentText.equals(text)) {
    328       m_ignoreFocusLost = true;
    329       try {
    330         boolean success = setEditorText(property, text);
    331         if (!success) {
    332           return false;
    333         }
    334       } finally {
    335         m_ignoreFocusLost = false;
    336       }
    337       // if value was successfully changed, update current text
    338       m_currentText = text;
    339     }
    340     // OK, success
    341     return true;
    342   }
    343 
    344   ////////////////////////////////////////////////////////////////////////////
    345   //
    346   // Operations
    347   //
    348   ////////////////////////////////////////////////////////////////////////////
    349   /**
    350    * @return <code>true</code> if this editor can modify text.
    351    */
    352   protected boolean isEditable() {
    353     return true;
    354   }
    355 
    356   /**
    357    * @return the text to display in {@link Text} control.
    358    */
    359   protected abstract String getEditorText(Property property) throws Exception;
    360 
    361   /**
    362    * Modifies {@link Property} using given text.
    363    *
    364    * @return <code>true</code> if {@link Property} was successfully modified.
    365    */
    366   protected abstract boolean setEditorText(Property property, String text) throws Exception;
    367 }
    368