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