browser/metro/base/content/helperui/ChromeSelectionHandler.js

changeset 2
7e26c7da4463
equal deleted inserted replaced
-1:000000000000 0:f0b3befe63b9
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 /*
6 * Selection handler for chrome text inputs
7 */
8
9 let Ci = Components.interfaces;
10
11 var ChromeSelectionHandler = {
12 _mode: this._SELECTION_MODE,
13
14 /*************************************************
15 * Messaging wrapper
16 */
17
18 sendAsync: function sendAsync(aMsg, aJson) {
19 SelectionHelperUI.receiveMessage({
20 name: aMsg,
21 json: aJson
22 });
23 },
24
25 /*************************************************
26 * Browser event handlers
27 */
28
29 /*
30 * General selection start method for both caret and selection mode.
31 */
32 _onSelectionAttach: function _onSelectionAttach(aJson) {
33 // Clear previous ChromeSelectionHandler state.
34 this._deactivate();
35
36 // Initialize ChromeSelectionHandler state.
37 this._domWinUtils = Util.getWindowUtils(window);
38 this._contentWindow = window;
39 this._targetElement = aJson.target;
40 this._targetIsEditable = Util.isTextInput(this._targetElement) ||
41 this._targetElement instanceof Ci.nsIDOMXULTextBoxElement;
42 if (!this._targetIsEditable) {
43 this._onFail("not an editable?", this._targetElement);
44 return;
45 }
46
47 let selection = this._getSelection();
48 if (!selection) {
49 this._onFail("no selection.");
50 return;
51 }
52
53 if (!this._getTargetElementValue()) {
54 this._onFail("Target element does not contain any content to select.");
55 return;
56 }
57
58 // Add a listener to respond to programmatic selection changes.
59 selection.QueryInterface(Ci.nsISelectionPrivate).addSelectionListener(this);
60
61 if (!selection.isCollapsed) {
62 this._mode = this._SELECTION_MODE;
63 this._updateSelectionUI("start", true, true);
64 } else {
65 this._mode = this._CARET_MODE;
66 this._updateSelectionUI("caret", false, false, true);
67 }
68
69 this._targetElement.addEventListener("blur", this, false);
70 },
71
72 /*
73 * Selection monocle start move event handler
74 */
75 _onSelectionMoveStart: function _onSelectionMoveStart(aMsg) {
76 if (!this.targetIsEditable) {
77 this._onFail("_onSelectionMoveStart with bad targetElement.");
78 return;
79 }
80
81 if (this._selectionMoveActive) {
82 this._onFail("mouse is already down on drag start?");
83 return;
84 }
85
86 // We bail if things get out of sync here implying we missed a message.
87 this._selectionMoveActive = true;
88
89 if (this._targetIsEditable) {
90 // If we're coming out of an out-of-bounds scroll, the node the user is
91 // trying to drag may be hidden (the monocle will be pegged to the edge
92 // of the edit). Make sure the node the user wants to move is visible
93 // and has focus.
94 this._updateInputFocus(aMsg.change);
95 }
96
97 // Update the position of our selection monocles
98 this._updateSelectionUI("update", true, true);
99 },
100
101 /*
102 * Selection monocle move event handler
103 */
104 _onSelectionMove: function _onSelectionMove(aMsg) {
105 if (!this.targetIsEditable) {
106 this._onFail("_onSelectionMove with bad targetElement.");
107 return;
108 }
109
110 if (!this._selectionMoveActive) {
111 this._onFail("mouse isn't down for drag move?");
112 return;
113 }
114
115 this._handleSelectionPoint(aMsg, false);
116 },
117
118 /*
119 * Selection monocle move finished event handler
120 */
121 _onSelectionMoveEnd: function _onSelectionMoveComplete(aMsg) {
122 if (!this.targetIsEditable) {
123 this._onFail("_onSelectionMoveEnd with bad targetElement.");
124 return;
125 }
126
127 if (!this._selectionMoveActive) {
128 this._onFail("mouse isn't down for drag move?");
129 return;
130 }
131
132 this._handleSelectionPoint(aMsg, true);
133 this._selectionMoveActive = false;
134
135 // Clear any existing scroll timers
136 this._clearTimers();
137
138 // Update the position of our selection monocles
139 this._updateSelectionUI("end", true, true);
140 },
141
142 _onSelectionUpdate: function _onSelectionUpdate() {
143 if (!this._targetHasFocus()) {
144 this._closeSelection();
145 return;
146 }
147 this._updateSelectionUI("update",
148 this._mode == this._SELECTION_MODE,
149 this._mode == this._SELECTION_MODE,
150 this._mode == this._CARET_MODE);
151 },
152
153 /*
154 * Switch selection modes. Currently we only support switching
155 * from "caret" to "selection".
156 */
157 _onSwitchMode: function _onSwitchMode(aMode, aMarker, aX, aY) {
158 if (aMode != "selection") {
159 this._onFail("unsupported mode switch");
160 return;
161 }
162
163 // Sanity check to be sure we are initialized
164 if (!this._targetElement) {
165 this._onFail("not initialized");
166 return;
167 }
168
169 // Similar to _onSelectionStart - we need to create initial selection
170 // but without the initialization bits.
171 let framePoint = this._clientPointToFramePoint({ xPos: aX, yPos: aY });
172 if (!this._domWinUtils.selectAtPoint(framePoint.xPos, framePoint.yPos,
173 Ci.nsIDOMWindowUtils.SELECT_CHARACTER)) {
174 this._onFail("failed to set selection at point");
175 return;
176 }
177
178 // We bail if things get out of sync here implying we missed a message.
179 this._selectionMoveActive = true;
180 this._mode = this._SELECTION_MODE;
181
182 // Update the position of the selection marker that is *not*
183 // being dragged.
184 this._updateSelectionUI("update", aMarker == "end", aMarker == "start");
185 },
186
187 /*
188 * Selection close event handler
189 *
190 * @param aClearSelection requests that selection be cleared.
191 */
192 _onSelectionClose: function _onSelectionClose(aClearSelection) {
193 if (aClearSelection) {
194 this._clearSelection();
195 }
196 this._closeSelection();
197 },
198
199 /*
200 * Called if for any reason we fail during the selection
201 * process. Cancels the selection.
202 */
203 _onFail: function _onFail(aDbgMessage) {
204 if (aDbgMessage && aDbgMessage.length > 0)
205 Util.dumpLn(aDbgMessage);
206 this.sendAsync("Content:SelectionFail");
207 this._clearSelection();
208 this._closeSelection();
209 },
210
211 /*
212 * Empty conversion routines to match those in
213 * browser. Called by SelectionHelperUI when
214 * sending and receiving coordinates in messages.
215 */
216
217 ptClientToBrowser: function ptClientToBrowser(aX, aY, aIgnoreScroll, aIgnoreScale) {
218 return { x: aX, y: aY }
219 },
220
221 rectBrowserToClient: function rectBrowserToClient(aRect, aIgnoreScroll, aIgnoreScale) {
222 return {
223 left: aRect.left,
224 right: aRect.right,
225 top: aRect.top,
226 bottom: aRect.bottom
227 }
228 },
229
230 ptBrowserToClient: function ptBrowserToClient(aX, aY, aIgnoreScroll, aIgnoreScale) {
231 return { x: aX, y: aY }
232 },
233
234 ctobx: function ctobx(aCoord) {
235 return aCoord;
236 },
237
238 ctoby: function ctoby(aCoord) {
239 return aCoord;
240 },
241
242 btocx: function btocx(aCoord) {
243 return aCoord;
244 },
245
246 btocy: function btocy(aCoord) {
247 return aCoord;
248 },
249
250
251 /*************************************************
252 * Selection helpers
253 */
254
255 /*
256 * _clearSelection
257 *
258 * Clear existing selection if it exists and reset our internal state.
259 */
260 _clearSelection: function _clearSelection() {
261 let selection = this._getSelection();
262 if (selection) {
263 selection.removeAllRanges();
264 }
265 },
266
267 /*
268 * _closeSelection
269 *
270 * Shuts ChromeSelectionHandler and SelectionHelperUI down.
271 */
272 _closeSelection: function _closeSelection() {
273 this._deactivate();
274 this.sendAsync("Content:HandlerShutdown", {});
275 },
276
277 /*
278 * _deactivate
279 *
280 * Resets ChromeSelectionHandler state, previously initialized in
281 * general selection start-method |_onSelectionAttach()|.
282 */
283 _deactivate: function _deactivate() {
284 // Remove our selection notification listener.
285 let selection = this._getSelection();
286 if (selection) {
287 try {
288 selection.QueryInterface(Ci.nsISelectionPrivate).removeSelectionListener(this);
289 } catch(e) {
290 // Fail safe during multiple _deactivate() calls.
291 }
292 }
293
294 this._clearTimers();
295 this._cache = null;
296 this._contentWindow = null;
297 if (this._targetElement) {
298 this._targetElement.removeEventListener("blur", this, true);
299 this._targetElement = null;
300 }
301 this._selectionMoveActive = false;
302 this._domWinUtils = null;
303 this._targetIsEditable = false;
304 this._mode = null;
305 },
306
307 get hasSelection() {
308 if (!this._targetElement) {
309 return false;
310 }
311 let selection = this._getSelection();
312 return (selection && !selection.isCollapsed);
313 },
314
315 _targetHasFocus: function() {
316 if (!this._targetElement || !document.commandDispatcher.focusedElement) {
317 return false;
318 }
319 let bindingParent = this._contentWindow.document.getBindingParent(document.commandDispatcher.focusedElement);
320 return (bindingParent && this._targetElement == bindingParent);
321 },
322
323 /*************************************************
324 * Events
325 */
326
327 /*
328 * Scroll + selection advancement timer when the monocle is
329 * outside the bounds of an input control.
330 */
331 scrollTimerCallback: function scrollTimerCallback() {
332 let result = ChromeSelectionHandler.updateTextEditSelection();
333 // Update monocle position and speed if we've dragged off to one side
334 if (result.trigger) {
335 ChromeSelectionHandler._updateSelectionUI("update", result.start, result.end);
336 }
337 },
338
339 handleEvent: function handleEvent(aEvent) {
340 if (aEvent.type == "blur" && !this._targetHasFocus()) {
341 this._closeSelection();
342 }
343 },
344
345 msgHandler: function msgHandler(aMsg, aJson) {
346 if (this._debugEvents && "Browser:SelectionMove" != aMsg) {
347 Util.dumpLn("ChromeSelectionHandler:", aMsg);
348 }
349 switch(aMsg) {
350 case "Browser:SelectionDebug":
351 this._onSelectionDebug(aJson);
352 break;
353
354 case "Browser:SelectionAttach":
355 this._onSelectionAttach(aJson);
356 break;
357
358 case "Browser:CaretAttach":
359 this._onSelectionAttach(aJson);
360 break;
361
362 case "Browser:SelectionClose":
363 this._onSelectionClose(aJson.clearSelection);
364 break;
365
366 case "Browser:SelectionUpdate":
367 this._onSelectionUpdate();
368 break;
369
370 case "Browser:SelectionMoveStart":
371 this._onSelectionMoveStart(aJson);
372 break;
373
374 case "Browser:SelectionMove":
375 this._onSelectionMove(aJson);
376 break;
377
378 case "Browser:SelectionMoveEnd":
379 this._onSelectionMoveEnd(aJson);
380 break;
381
382 case "Browser:CaretUpdate":
383 this._onCaretPositionUpdate(aJson.caret.xPos, aJson.caret.yPos);
384 break;
385
386 case "Browser:CaretMove":
387 this._onCaretMove(aJson.caret.xPos, aJson.caret.yPos);
388 break;
389
390 case "Browser:SelectionSwitchMode":
391 this._onSwitchMode(aJson.newMode, aJson.change, aJson.xPos, aJson.yPos);
392 break;
393 }
394 },
395
396 /*************************************************
397 * Utilities
398 */
399
400 _getSelection: function _getSelection() {
401 let targetElementEditor = this._getTargetElementEditor();
402
403 return targetElementEditor ? targetElementEditor.selection : null;
404 },
405
406 _getTargetElementValue: function _getTargetElementValue() {
407 if (this._targetElement instanceof Ci.nsIDOMXULTextBoxElement) {
408 return this._targetElement.inputField.value;
409 } else if (Util.isTextInput(this._targetElement)) {
410 return this._targetElement.value;
411 }
412 return null;
413 },
414
415 _getSelectController: function _getSelectController() {
416 let targetElementEditor = this._getTargetElementEditor();
417
418 return targetElementEditor ? targetElementEditor.selectionController : null;
419 },
420
421 _getTargetElementEditor: function() {
422 if (this._targetElement instanceof Ci.nsIDOMXULTextBoxElement) {
423 return this._targetElement.QueryInterface(Ci.nsIDOMXULTextBoxElement)
424 .editor;
425 } else if (Util.isTextInput(this._targetElement)) {
426 return this._targetElement.QueryInterface(Ci.nsIDOMNSEditableElement)
427 .editor;
428 }
429 return null;
430 }
431 };
432
433 ChromeSelectionHandler.__proto__ = new SelectionPrototype();
434 ChromeSelectionHandler.type = 1; // kChromeSelector
435

mercurial