View Javadoc
1   package org.vaadin.aceeditor.client;
2   
3   import com.google.gwt.core.client.JavaScriptObject;
4   import com.google.gwt.user.client.Window;
5   import com.vaadin.client.ServerConnector;
6   import com.vaadin.client.communication.RpcProxy;
7   import com.vaadin.client.communication.StateChangeEvent;
8   import com.vaadin.client.extensions.AbstractExtensionConnector;
9   import com.vaadin.shared.ui.Connect;
10  import org.vaadin.aceeditor.SuggestionExtension;
11  import org.vaadin.aceeditor.client.AceEditorWidget.SelectionChangeListener;
12  import org.vaadin.aceeditor.client.SuggestPopup.SuggestionSelectedListener;
13  import org.vaadin.aceeditor.client.gwt.GwtAceKeyboardEvent;
14  import org.vaadin.aceeditor.client.gwt.GwtAceKeyboardHandler;
15  
16  import java.util.List;
17  import java.util.logging.Logger;
18  
19  @SuppressWarnings("serial")
20  @Connect(SuggestionExtension.class)
21  public class SuggesterConnector extends AbstractExtensionConnector implements
22  		GwtAceKeyboardHandler, SuggestionSelectedListener, SelectionChangeListener {
23  
24  	protected static final int Y_OFFSET = 20;
25  
26  //	private final Logger logger = Logger.getLogger(SuggesterConnector.class.getName());
27  
28      protected AceEditorConnector connector;
29      protected AceEditorWidget widget;
30  
31      protected SuggesterServerRpc serverRpc = RpcProxy.create(
32  			SuggesterServerRpc.class, this);
33  
34  	protected String suggStartText;
35      protected AceRange suggStartCursor;
36  	
37  	private SuggesterClientRpcRpc.html#SuggesterClientRpc">SuggesterClientRpc clientRpc = new SuggesterClientRpc() {
38  		@Override
39  		public void showSuggestions(List<TransportSuggestion> suggs) {
40  			setSuggs(suggs);
41  		}
42  
43  		@Override
44  		public void applySuggestionDiff(TransportDiff td) {
45  			stopSuggesting();
46  			ClientSideDocDiff diff = ClientSideDocDiff.fromTransportDiff(td);
47  			widget.setTextAndAdjust(diff.applyTo(widget.getDoc()).getText());
48  			widget.fireTextChanged(); // XXX we need to do this here to alert AceEditorConnector...
49  		}
50  	};
51  
52  	protected boolean suggesting = false;
53  
54  	protected SuggestPopup popup;
55  
56  	protected Integer suggestionStartId;
57  
58  	protected boolean startSuggestingOnNextSelectionChange;
59  
60  	protected boolean suggestOnDot = true;
61  
62      protected boolean showDescriptions = true;
63  
64  	public SuggesterConnector() {
65  		registerRpc(SuggesterClientRpc.class, clientRpc);
66  	}
67  	
68  	@Override
69  	public void onStateChanged(StateChangeEvent stateChangeEvent) {
70  		super.onStateChanged(stateChangeEvent);
71  
72  		this.suggestOnDot = getState().suggestOnDot;
73          this.showDescriptions = getState().showDescriptions;
74  	}
75  	
76  	@Override
77  	public SuggesterState getState() {
78  		return (SuggesterState) super.getState();
79  	}
80  
81  	protected void setSuggs(List<TransportSuggestion> suggs) {
82  		if (suggesting) {
83  			popup.setSuggestions(suggs);
84  		}
85  	}
86  
87  	protected SuggestPopup createSuggestionPopup() {
88  		SuggestPopup sp = new SuggestPopup();
89  		sp.setOwner(widget);
90  		updatePopupPosition(sp);
91  		sp.setSuggestionSelectedListener(this);
92  		sp.show();
93  		return sp;
94  	}
95  
96  	@Override
97  	protected void extend(ServerConnector target) {
98  		connector = (AceEditorConnector) target;
99  		widget = connector.getWidget();
100 		widget.setKeyboardHandler(this);
101 	}
102 
103 	@Override
104 	public Command handleKeyboard(JavaScriptObject data, int hashId,
105 			String keyString, int keyCode, GwtAceKeyboardEvent e) {
106 		if (suggesting) {
107 			return keyPressWhileSuggesting(keyCode);
108 		}
109 		if (e == null) {
110 			return Command.DEFAULT;
111 		}
112 //		logger.info("handleKeyboard(" + data + ", " + hashId + ", " + keyString
113 //				+ ", " + keyCode + ", " + e.getKeyCode() + "---"
114 //				+ e.isCtrlKey() + ")");
115 
116 		if (keyCode == 32 && e.isCtrlKey()) {
117 			startSuggesting();
118 			return Command.NULL;
119 		} else if (suggestOnDot && ".".equals(keyString)) {
120 			startSuggestingOnNextSelectionChange = true;
121 			widget.addSelectionChangeListener(this);
122 			return Command.DEFAULT;
123 		}
124 
125 		return Command.DEFAULT;
126 	}
127 
128 	protected void startSuggesting() {
129         // ensure valid value of component on server before suggesting
130         connector.sendToServerImmediately();
131 
132 		suggStartText = widget.getText();
133 		suggStartCursor = widget.getSelection();
134 		serverRpc.suggest(suggStartText, suggStartCursor.asTransport());
135 
136 		suggestionStartId = widget.addInvisibleMarker(suggStartCursor);
137 		widget.addSelectionChangeListener(this);
138 		popup = createSuggestionPopup();
139         popup.showDescriptions = this.showDescriptions;
140 		suggesting = true;
141 	}
142 
143 	@Override
144 	public void suggestionSelected(TransportSuggestion s) {
145 		// ???
146 		//connector.setOnRoundtrip(true);
147 //		AceRange suggMarker = widget.getInvisibleMarker(suggestionStartId);
148 		serverRpc.suggestionSelected(s.index);
149 		stopAskingForSuggestions();
150 	}
151 
152 	@Override
153 	public void noSuggestionSelected() {
154 		stopAskingForSuggestions();
155 	}
156 
157 	protected void stopAskingForSuggestions() {
158 		widget.removeSelectionChangeListener(this);
159 		suggesting = false;
160 		widget.setFocus(true);
161 	}
162 	
163 	protected void stopSuggesting() {
164 		if (popup!=null) {
165 			popup.hide();
166 			popup = null;
167 		}
168 		if (suggestionStartId != null) {
169 			widget.removeContentsOfInvisibleMarker(suggestionStartId);
170 			widget.removeInvisibleMarker(suggestionStartId);
171 		}
172 	}
173 
174 	protected Command keyPressWhileSuggesting(int keyCode) {
175 		if (keyCode == 38 /* UP */) {
176 			popup.up();
177 		} else if (keyCode == 40 /* DOWN */) {
178 			popup.down();
179 		} else if (keyCode == 13 /* ENTER */) {
180 			popup.select();
181 		} else if (keyCode == 27 /* ESC */) {
182 			popup.close();
183 		} else {
184 			return Command.DEFAULT;
185 		}
186 		return Command.NULL;
187 	}
188 
189 	protected String getWord(String text, int row, int col1, int col2) {
190 		if (col1 == col2) {
191 			return "";
192 		}
193 		String[] lines = text.split("\n", -1);
194 		int start = Util.cursorPosFromLineCol(lines, row, col1, 0);
195 		int end = Util.cursorPosFromLineCol(lines, row, col2, 0);
196 		return text.substring(start, end);
197 	}
198 
199 	@Override
200 	public void selectionChanged() {
201 		if (startSuggestingOnNextSelectionChange) {
202 			widget.removeSelectionChangeListener(this);
203 			startSuggesting();
204 			startSuggestingOnNextSelectionChange = false;
205 			return;
206 		}
207 		
208 		AceRange sel = widget.getSelection();
209 		
210 		AceRange sug = widget.getInvisibleMarker(suggestionStartId);
211 		if (sug.getStartRow()!=sug.getEndRow()) {
212 			popup.close();
213 		}
214 		else if (sel.getEndRow() != sug.getStartRow() || sel.getEndRow() != sug.getEndRow()) {
215 			popup.close();
216 		} else if (sel.getEndCol()<sug.getStartCol() || sel.getEndCol()>sug.getEndCol()) {
217 			popup.close();
218 		} else {
219 			updatePopupPosition(popup);
220 			String s = getWord(widget.getText(), sug.getEndRow(),
221 					sug.getStartCol(), sug.getEndCol());
222 			popup.setStartOfValue(s);
223 		}
224 	}
225 
226 	protected void updatePopupPosition(SuggestPopup popup) {
227        
228 		int[] coords = widget.getCursorCoords();
229 		int wx = Window.getClientWidth();
230 		int wy = Window.getClientHeight();
231 		int sx = Window.getScrollLeft();
232 		int sy = Window.getScrollTop();
233 		int x = coords[0] - sx;
234 		int y = coords[1] - sy + Y_OFFSET;
235 		int maxx = wx - SuggestPopup.WIDTH - (showDescriptions ? SuggestPopup.DESCRIPTION_WIDTH : 0);
236 		if (x > maxx) {
237 			x -= SuggestPopup.WIDTH + (showDescriptions ? SuggestPopup.DESCRIPTION_WIDTH : 0) + 50;
238 		}
239 		int maxy = wy - SuggestPopup.HEIGHT;
240 		if (y > maxy) {
241 			y -= SuggestPopup.HEIGHT + 50;
242 		}
243 		popup.setPopupPosition(x, y);
244 	}
245 }