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
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 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();
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
113
114
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
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
147
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 ) {
176 popup.up();
177 } else if (keyCode == 40 ) {
178 popup.down();
179 } else if (keyCode == 13 ) {
180 popup.select();
181 } else if (keyCode == 27 ) {
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 }