View Javadoc
1   package org.vaadin.aceeditor.client;
2   
3   import java.util.Map;
4   import java.util.Map.Entry;
5   
6   import org.vaadin.aceeditor.AceEditor;
7   import org.vaadin.aceeditor.client.AceEditorWidget.FocusChangeListener;
8   import org.vaadin.aceeditor.client.AceEditorWidget.SelectionChangeListener;
9   import org.vaadin.aceeditor.client.AceEditorWidget.TextChangeListener;
10  import org.vaadin.aceeditor.client.gwt.GwtAceEditor;
11  
12  import com.google.gwt.core.client.GWT;
13  import com.google.gwt.user.client.Timer;
14  import com.google.gwt.user.client.ui.Widget;
15  import com.vaadin.client.ComponentConnector;
16  import com.vaadin.client.ConnectorHierarchyChangeEvent;
17  import com.vaadin.client.communication.RpcProxy;
18  import com.vaadin.client.communication.StateChangeEvent;
19  import com.vaadin.client.ui.AbstractHasComponentsConnector;
20  import com.vaadin.client.ui.layout.ElementResizeEvent;
21  import com.vaadin.client.ui.layout.ElementResizeListener;
22  import com.vaadin.shared.ui.Connect;
23  
24  @SuppressWarnings("serial")
25  @Connect(AceEditor.class)
26  public class AceEditorConnector extends AbstractHasComponentsConnector
27  		implements TextChangeListener, SelectionChangeListener, FocusChangeListener {
28  
29  //	private static Logger logger = Logger.getLogger(AceEditorConnector.class.getName());
30  
31      protected AceEditorServerRpc serverRpc =
32              RpcProxy.create(AceEditorServerRpc.class, this);
33  
34  
35      protected enum TextChangeEventMode {
36  		EAGER, TIMEOUT, LAZY
37  	}
38  
39      protected TextChangeEventMode changeMode = null;
40      protected int changeTimeout = 400;
41  
42      protected class SendTimer extends Timer {
43  		private boolean scheduled;
44  		private SendCond send = SendCond.NO;
45  		
46  		public void schedule(int ms, SendCond send) {
47  			super.schedule(ms);
48  			this.send = this.send.or(send);
49  			scheduled = true;
50  		}
51  
52  		public void scheduleIfNotAlready(int ms, SendCond send) {
53  			if (!scheduled) {
54  				schedule(ms,send);
55  			}
56  		}
57  
58  		@Override
59  		public void run() {
60  			scheduled = false;
61  			sendToServerImmediately(send);
62  			send = SendCond.NO;
63  		}
64  		
65  		@Override
66  		public void cancel() {
67  			super.cancel();
68  			send = SendCond.NO;
69  		}
70  	}
71  
72  	protected SendTimer sendTimer = null;
73  
74      protected AceDoc shadow;
75  
76      protected boolean onRoundtrip = false;
77      
78      protected enum SendCond {
79      	NO, IF_CHANGED, ALWAYS;
80  		public SendCond or(SendCond sw2) {
81  			return this.ordinal() > sw2.ordinal() ? this : sw2;
82  		}
83      }
84      
85      protected SendCond sendAfterRoundtrip = SendCond.NO;
86  
87      protected AceEditorClientRpcRpc.html#AceEditorClientRpc">AceEditorClientRpc clientRpc = new AceEditorClientRpc() {
88  		@Override
89  		public void diff(TransportDiff ad) {
90  			ClientSideDocDiff diff = ClientSideDocDiff.fromTransportDiff(ad);
91  			shadow = diff.applyTo(shadow);
92  
93  			AceDoc doc1 = getWidget().getDoc();
94  			AceDoc doc2 = diff.applyTo(doc1);
95  
96  			getWidget().setDoc(doc2);
97  
98  			if (selectionAfterApplyingDiff!=null) {
99  				getWidget().setSelection(selectionAfterApplyingDiff);
100 				selectionAfterApplyingDiff = null;
101 			}
102 
103 			if (scrollToRowAfterApplyingDiff != -1) {
104 				getWidget().scrollToRow(scrollToRowAfterApplyingDiff);
105 				scrollToRowAfterApplyingDiff = -1;
106 			}
107 
108 			setOnRoundtrip(false);
109 		}
110 
111 		@Override
112 		public void changedOnServer() {
113 			if (!isOnRoundtrip()) {
114 				sendToServer(SendCond.ALWAYS, true);
115 			}
116 			// else ? should we send after roundtrip or not?
117 		}
118 
119 	};
120 
121     protected boolean listenToSelectionChanges;
122     protected boolean listenToFocusChanges;
123 
124 	// When setting selection or scrollToRow, we must make
125 	// sure that the text value is set before that.
126 	// That is, we must make the diff sync roundtrip and set
127 	// these things after that.
128 	// That's why this complication.
129 	// TODO: this may not be the cleanest way to do it...
130 	protected int scrollToRowAfterApplyingDiff = -1;
131 	protected AceRange selectionAfterApplyingDiff;
132 
133 	public AceEditorConnector() {
134 		super();
135 		registerRpc(AceEditorClientRpc.class, clientRpc);
136 	}
137 	
138 	
139 
140 	@Override
141 	public void init() {
142 		super.init();
143 		
144 		// Needed if inside a resizable subwindow.
145 		// Should we check that and only listen if yes?
146 		getLayoutManager().addElementResizeListener(getWidget().getElement(), new ElementResizeListener() {
147 			@Override
148 			public void onElementResize(ElementResizeEvent e) {
149                 getWidget().resize();
150 			}
151 		});
152 	}
153 	
154 	@Override
155 	public void onStateChanged(StateChangeEvent stateChangeEvent) {
156 		super.onStateChanged(stateChangeEvent);
157 
158 		setTextChangeEventMode(getState().changeMode);
159 		setTextChangeTimeout(getState().changeTimeout);
160 		
161 		ClientSideDocDiff.dmp.setDiff_EditCost(getState().diff_editCost);
162 
163 		// TODO: are these needed?
164 //		widget.setHideErrors(getState().hideErrors);
165 //		widget.setRequired(getState().required);
166 //		widget.setModified(getState().modified);
167 		
168 		boolean firstTime = !getWidget().isInitialized();
169 		if (firstTime) {
170 			// To make sure Ace config is applied before the editor is created,
171 			// we delay the initialization till then first call to onStateChanged,
172 			// not initializing in createWidget() right away.
173 			applyConfig(getState().config);
174             getWidget().initialize();
175 		}
176 
177         getWidget().setMode(getState().mode);
178         getWidget().setTheme(getState().theme);
179 		listenToSelectionChanges = getState().listenToSelectionChanges;
180 		listenToFocusChanges = getState().listenToFocusChanges;
181         getWidget().setUseWorker(getState().useWorker);
182         getWidget().setWordwrap(getState().wordwrap);
183 
184         getWidget().setShowGutter(getState().showGutter);
185         getWidget().setShowPrintMargin(getState().showPrintMargin);
186         getWidget().setHighlightActiveLineEnabled(getState().highlightActiveLine);
187 
188         getWidget().setPropertyReadOnly(getState().propertyReadOnly);
189         getWidget().setTabIndex(getState().tabIndex);
190         getWidget().setReadOnly(getState().readOnly);
191 		
192 		// TODO: How should we deal with immediateness. Since there's already textChangeEventMode...
193 		//immediate = getState().immediate;
194 		
195 		if (firstTime) {
196 			shadow = AceDoc.fromTransport(getState().initialValue);
197             getWidget().setDoc(shadow);
198 		}
199 		
200 		if (getState().selection != null) {
201 			AceRange sel = AceRange.fromTransport(getState().selection);
202 			if (firstTime) {
203 				getWidget().setSelection(sel);
204 			}
205 			else {
206 				selectionAfterApplyingDiff = sel;
207 			}
208 		}
209 		
210 		if (getState().scrollToRow != -1) {
211 			if (firstTime) {
212 				getWidget().scrollToRow(getState().scrollToRow);
213 			}
214 			else {
215 				scrollToRowAfterApplyingDiff = getState().scrollToRow;
216 			}
217 		}
218 	}
219 	
220 	protected static void applyConfig(Map<String, String> config) {
221 		for (Entry<String, String> e : config.entrySet()) {
222 			GwtAceEditor.setAceConfig(e.getKey(), e.getValue());
223 		}
224 	}
225 
226 	@Override
227 	protected Widget createWidget() {
228         AceEditorWidget widget = GWT.create(AceEditorWidget.class);
229 		widget.addTextChangeListener(this);
230 		widget.addSelectionChangeListener(this);
231 		widget.setFocusChangeListener(this);
232 		return widget;
233 	}
234 
235 	@Override
236 	public AceEditorWidget getWidget() {
237 		return (AceEditorWidget) super.getWidget();
238 	}
239 
240 	@Override
241 	public AceEditorState getState() {
242 		return (AceEditorState) super.getState();
243 	}
244 	
245 	@Override
246 	public void focusChanged(boolean focused) {
247 		// TODO: it'd be better if we didn't register as listener
248 		// if !listenToFocusChanges in the first place...
249 		if (!listenToFocusChanges) {
250 			return;
251 		}
252 		
253 		if (isOnRoundtrip()) {
254 			sendAfterRoundtrip = sendAfterRoundtrip.or(SendCond.ALWAYS);
255 		}
256 		else {
257 			sendToServerImmediately(SendCond.ALWAYS);
258 		}
259 	}
260 	
261 	public void setTextChangeEventMode(String mode) {
262 		TextChangeEventMode newMode = TextChangeEventMode.valueOf(mode);
263 		if (newMode!=changeMode) {
264 			changeTextChangeEventMode(newMode);
265 		}
266 	}
267 
268     protected void setTextChangeTimeout(int timeout) {
269 		changeTimeout = timeout;
270 	}
271 
272     protected void changeTextChangeEventMode(TextChangeEventMode newMode) {
273 		if (sendTimer != null) {
274 			sendTimer.cancel();
275 			sendTimer = null;
276 		}
277 		this.changeMode = newMode;
278 	}
279     
280     protected void sendChangeAccordingToMode(SendCond send) {
281     	sendChangeAccordingToMode(send, changeMode);
282     }
283 	
284 	protected void sendChangeAccordingToMode(SendCond send, TextChangeEventMode mode) {
285 		if (mode == TextChangeEventMode.EAGER) {
286 			if (sendTimer != null) {
287 				sendTimer.cancel();
288 			}
289 			sendToServerImmediately(send);
290 		} else if (mode == TextChangeEventMode.LAZY) {
291 			if (sendTimer == null) {
292 				sendTimer = new SendTimer();
293 			}
294 			sendTimer.schedule(changeTimeout, send);
295 		} else if (mode == TextChangeEventMode.TIMEOUT) {
296 			if (sendTimer == null) {
297 				sendTimer = new SendTimer();
298 			}
299 			sendTimer.scheduleIfNotAlready(changeTimeout, send);
300 		}
301 	}
302 
303     protected void sendToServer(SendCond send, boolean immediately) {
304     	if (send==SendCond.NO) {
305     		return;
306     	}
307     	
308 		AceDoc doc = getWidget().getDoc();
309 		ClientSideDocDiff diff = ClientSideDocDiff.diff(shadow, doc);
310 		if (send==SendCond.ALWAYS) {
311 			// Go on...
312 		}
313 		else if (send==SendCond.IF_CHANGED && !diff.isIdentity()) {
314 			// Go on...
315 		}
316 		else {
317 			return;
318 		}
319 		
320 		TransportDiff td = diff.asTransport();
321 		
322 		if (immediately) {
323 			serverRpc.changed(td, getWidget().getSelection().asTransport(), getWidget().isFocused());
324 		} else {
325 			serverRpc.changedDelayed(td, getWidget().getSelection().asTransport(), getWidget().isFocused());
326 		}
327 		
328 		shadow = doc;
329 		setOnRoundtrip(true); // What if delayed???
330 		sendAfterRoundtrip = SendCond.NO;
331 	}
332 
333     protected void sendToServerDelayed(SendCond send) {
334 		sendToServer(send, false);
335 	}
336     
337     public void sendToServerImmediately() {
338     	sendToServerImmediately(SendCond.ALWAYS);
339     }
340 
341     protected void sendToServerImmediately(SendCond send) {
342 		sendToServer(send, true);
343 	}
344 	
345 	@Override
346 	public void flush() {
347 		super.flush();
348 		sendWhenPossible(SendCond.ALWAYS, TextChangeEventMode.EAGER); // ???
349 	}
350 
351 	@Override
352 	public void changed() {
353 		if (isOnRoundtrip()) {
354 			sendAfterRoundtrip = sendAfterRoundtrip.or(SendCond.IF_CHANGED);
355 		}
356 		else {
357 			sendChangeAccordingToMode(SendCond.IF_CHANGED);
358 		}
359 	}
360 
361 	@Override
362 	public void updateCaption(ComponentConnector connector) {
363 		// TODO Auto-generated method stub
364 		
365 	}
366 
367 	@Override
368 	public void onConnectorHierarchyChange(
369 			ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent) {
370 		// TODO Auto-generated method stub
371 		
372 	}
373 
374 	@Override
375 	public void selectionChanged() {
376 		// TODO: it'd be better if we didn't register as listener
377 		// if !listenToSelectionChanges in the first place...
378 		if (listenToSelectionChanges) {
379 			sendWhenPossible(SendCond.ALWAYS);
380 		}
381 	}
382 	
383 	protected void sendWhenPossible(SendCond send) {
384 		if (isOnRoundtrip()) {
385 			sendAfterRoundtrip = sendAfterRoundtrip.or(send);
386 		}
387 		else {
388 			sendChangeAccordingToMode(send);
389 		}
390 	}
391 	
392 	protected void sendWhenPossible(SendCond send, TextChangeEventMode mode) {
393 		if (isOnRoundtrip()) {
394 			sendAfterRoundtrip = sendAfterRoundtrip.or(send);
395 		}
396 		else {
397 			sendChangeAccordingToMode(send, mode);
398 		}
399 	}
400 
401 	// TODO XXX not sure if this roundtrip thing is correct, seems to work ok...
402 	private void setOnRoundtrip(boolean on) {
403 		if (on==onRoundtrip) {
404 			return;
405 		}
406 		onRoundtrip = on;
407 		if (!onRoundtrip) {
408 			sendToServerImmediately(sendAfterRoundtrip);
409 		}
410 	}
411 	
412 	public boolean isOnRoundtrip() {
413 		return onRoundtrip;
414 	}
415 }