summaryrefslogtreecommitdiff
path: root/propertysheet/src/org/eclipse/wb/internal/core/model/property/editor/AbstractTextPropertyEditor.java
blob: 1ee9237c2636b10091a52613b9e205102970b069 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
/*******************************************************************************
 * Copyright (c) 2011 Google, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Google, Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.wb.internal.core.model.property.editor;

import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.fieldassist.ContentProposalAdapter;
import org.eclipse.jface.fieldassist.IContentProposal;
import org.eclipse.jface.fieldassist.IContentProposalListener;
import org.eclipse.jface.fieldassist.IContentProposalListener2;
import org.eclipse.jface.fieldassist.IContentProposalProvider;
import org.eclipse.jface.fieldassist.IControlContentAdapter;
import org.eclipse.jface.fieldassist.TextContentAdapter;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;
import org.eclipse.wb.internal.core.model.property.Property;
import org.eclipse.wb.internal.core.model.property.table.PropertyTable;

/**
 * Abstract {@link PropertyEditor} for that uses {@link Text} as control.
 *
 * @author scheglov_ke
 * @coverage core.model.property.editor
 */
public abstract class AbstractTextPropertyEditor extends TextDisplayPropertyEditor {
  ////////////////////////////////////////////////////////////////////////////
  //
  // Editing
  //
  ////////////////////////////////////////////////////////////////////////////
  private Text m_textControl;
  private boolean m_ignoreFocusLost;

  // BEGIN ADT MODIFICATIONS
  // ContentProposalAdapter which exposes the openProposalPopup method such
  // that we can open the dialog up immediately on focus gain to show all available
  // alternatives (the default implementation requires at least one keytroke before
  // it shows up)
    private class ImmediateProposalAdapter extends ContentProposalAdapter
        implements FocusListener, IContentProposalListener, IContentProposalListener2 {
        private final PropertyTable m_propertyTable;
        public ImmediateProposalAdapter(
                Text control,
                IControlContentAdapter controlContentAdapter,
                IContentProposalProvider proposalProvider,
                KeyStroke keyStroke,
                char[] autoActivationCharacters,
                PropertyTable propertyTable) {
            super(control, controlContentAdapter, proposalProvider, keyStroke,
                    autoActivationCharacters);
            m_propertyTable = propertyTable;

            // On focus gain, start completing
            control.addFocusListener(this);

            // Listen on popup open and close events, in order to disable
            // focus handling on the textfield during those events.
            // This is necessary since otherwise as soon as the user clicks
            // on the popup with the mouse, the text field loses focus, and
            // then instantly closes the popup -- without the selection being
            // applied. See for example
            //   http://dev.eclipse.org/viewcvs/viewvc.cgi/org.eclipse.jface.snippets/
            //      Eclipse%20JFace%20Snippets/org/eclipse/jface/snippets/viewers/
            //      Snippet060TextCellEditorWithContentProposal.java?view=markup
            // for another example of this technique.
            addContentProposalListener((IContentProposalListener) this);
            addContentProposalListener((IContentProposalListener2) this);

            /* Triggering on empty is disabled for now: it has the unfortunate side-effect
               that it's impossible to enter a blank text field - blank matches everything,
               so the first item will automatically be selected when you press return.


            // If you edit the text and delete everything, the normal implementation
            // will close the popup; we'll reopen it
            control.addModifyListener(new ModifyListener() {
                @Override
                public void modifyText(ModifyEvent event) {
                    if (((Text) getControl()).getText().isEmpty()) {
                        openIfNecessary();
                    }
                }
            });
            */
        }

        private void openIfNecessary() {
            getControl().getDisplay().asyncExec(new Runnable() {
                @Override
                public void run() {
                    if (!isProposalPopupOpen()) {
                        openProposalPopup();
                    }
                }
            });
        }

        // ---- Implements FocusListener ----

        @Override
        public void focusGained(FocusEvent event) {
            openIfNecessary();
        }

        @Override
        public void focusLost(FocusEvent event) {
        }

        // ---- Implements IContentProposalListener ----

        @Override
        public void proposalAccepted(IContentProposal proposal) {
            closeProposalPopup();
            m_propertyTable.deactivateEditor(true);
        }

        // ---- Implements IContentProposalListener2 ----

        @Override
        public void proposalPopupClosed(ContentProposalAdapter adapter) {
            m_ignoreFocusLost = false;
        }

        @Override
        public void proposalPopupOpened(ContentProposalAdapter adapter) {
            m_ignoreFocusLost = true;
        }
    }
  // END ADT MODIFICATIONS

  @Override
  public boolean activate(final PropertyTable propertyTable, final Property property, Point location)
      throws Exception {
    // create Text
    {
      m_textControl = new Text(propertyTable, SWT.NONE);

      @SuppressWarnings("unused")
      TextControlActionsManager manager = new TextControlActionsManager(m_textControl);

      m_textControl.setEditable(isEditable());

        // BEGIN ADT MODIFICATIONS
        // Add support for field completion, if the property provides an IContentProposalProvider
        // via its the getAdapter method.
        IContentProposalProvider completion = property.getAdapter(IContentProposalProvider.class);
        if (completion != null) {
            ImmediateProposalAdapter adapter = new ImmediateProposalAdapter(
                    m_textControl, new TextContentAdapter(), completion, null, null,
                    propertyTable);
            adapter.setFilterStyle(ContentProposalAdapter.FILTER_NONE);
            adapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
            ILabelProvider labelProvider = property.getAdapter(ILabelProvider.class);
            if (labelProvider != null) {
                adapter.setLabelProvider(labelProvider);
            }
        }
        // END ADT MODIFICATIONS
      m_textControl.setFocus();
    }
    // add listeners
    m_textControl.addKeyListener(new KeyAdapter() {
      @Override
      public void keyPressed(KeyEvent e) {
        try {
          handleKeyPressed(propertyTable, property, e);
        } catch (Throwable ex) {
          propertyTable.deactivateEditor(false);
          propertyTable.handleException(ex);
        }
      }
    });
    m_textControl.addListener(SWT.FocusOut, new Listener() {
      @Override
    public void handleEvent(Event event) {
        if (!m_ignoreFocusLost) {
          propertyTable.deactivateEditor(true);
        }
      }
    });
    // set data
    toWidget(property);
    // keep us active
    return true;
  }

  @Override
  public final void setBounds(Rectangle bounds) {
    m_textControl.setBounds(bounds);
  }

  @Override
  public final void deactivate(PropertyTable propertyTable, Property property, boolean save) {
    if (save) {
      try {
        toProperty(property);
      } catch (Throwable e) {
        propertyTable.deactivateEditor(false);
        propertyTable.handleException(e);
      }
    }
    // dispose Text widget
    if (m_textControl != null) {
      m_textControl.dispose();
      m_textControl = null;
    }
  }

  @Override
  public void keyDown(PropertyTable propertyTable, Property property, KeyEvent event)
      throws Exception {
    boolean withAlt = (event.stateMask & SWT.ALT) != 0;
    boolean withCtrl = (event.stateMask & SWT.CTRL) != 0;
    if (event.character != 0 && !(withAlt || withCtrl)) {
      propertyTable.activateEditor(property, null);
      postKeyEvent(SWT.KeyDown, event);
      postKeyEvent(SWT.KeyUp, event);
    }
  }

  /**
   * Posts low-level {@link SWT.KeyDown} or {@link SWT.KeyUp} event.
   */
  private static void postKeyEvent(int type, KeyEvent event) {
    Event lowEvent = new Event();
    lowEvent.type = type;
    lowEvent.keyCode = event.keyCode;
    lowEvent.character = event.character;
    // post event
    Display.getCurrent().post(lowEvent);
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Events
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * Handles {@link KeyListener#keyPressed(KeyEvent)}.
   */
  private void handleKeyPressed(PropertyTable propertyTable, Property property, KeyEvent e)
      throws Exception {
    if (e.keyCode == SWT.CR) {
      toProperty(property);

      // BEGIN ADT MODIFICATIONS
      // After pressing return, dismiss the text cursor to make the value "committed".
      // I'm not sure why this is necessary here and not in WindowBuilder proper.
      propertyTable.deactivateEditor(true);
      // END ADT MODIFICATIONS
    } else if (e.keyCode == SWT.ESC) {
      propertyTable.deactivateEditor(false);
    } else if (e.keyCode == SWT.ARROW_UP || e.keyCode == SWT.ARROW_DOWN) {
      e.doit = false;
      boolean success = toProperty(property);
      // don't allow navigation if current text can not be transferred to property
      if (!success) {
        return;
      }
      // OK, deactivate and navigate
      propertyTable.deactivateEditor(true);
      propertyTable.navigate(e);
    }
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Implementation
  //
  ////////////////////////////////////////////////////////////////////////////
  private String m_currentText;

  /**
   * Transfers data from {@link Property} to widget.
   */
  private void toWidget(Property property) throws Exception {
    // prepare text
    String text = getEditorText(property);
    if (text == null) {
      text = "";
    }
    // set text
    m_currentText = text;
    m_textControl.setText(text);
    m_textControl.selectAll();
  }

  /**
   * Transfers data from widget to {@link Property}.
   *
   * @return <code>true</code> if transfer was successful.
   */
  private boolean toProperty(Property property) throws Exception {
    String text = m_textControl.getText();
    // change property only if text was changed
    if (!m_currentText.equals(text)) {
      m_ignoreFocusLost = true;
      try {
        boolean success = setEditorText(property, text);
        if (!success) {
          return false;
        }
      } finally {
        m_ignoreFocusLost = false;
      }
      // if value was successfully changed, update current text
      m_currentText = text;
    }
    // OK, success
    return true;
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Operations
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * @return <code>true</code> if this editor can modify text.
   */
  protected boolean isEditable() {
    return true;
  }

  /**
   * @return the text to display in {@link Text} control.
   */
  protected abstract String getEditorText(Property property) throws Exception;

  /**
   * Modifies {@link Property} using given text.
   *
   * @return <code>true</code> if {@link Property} was successfully modified.
   */
  protected abstract boolean setEditorText(Property property, String text) throws Exception;
}