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