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