2 * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
3 * (C) Copyright 2008 John J. Foerch
5 * Use, modification, and distribution are subject to the terms specified in the
9 require("content-buffer.js");
11 define_current_buffer_hook("current_buffer_input_mode_change_hook", "input_mode_change_hook");
13 var content_buffer_input_mode_keymaps = {};
15 function define_input_mode(base_name, display_name, keymap_name, doc) {
16 var name = base_name + "_input_mode";
17 content_buffer_input_mode_keymaps[name] = keymap_name;
18 define_buffer_mode(name,
20 $class = "input_mode",
21 $enable = function (buffer) {
22 check_buffer(buffer, content_buffer);
23 content_buffer_update_keymap_for_input_mode(buffer); },
27 ignore_function_for_get_caller_source_code_reference("define_input_mode");
29 function content_buffer_update_keymap_for_input_mode(buffer) {
30 if (buffer.input_mode)
31 buffer.keymap = buffer.get(content_buffer_input_mode_keymaps[buffer.input_mode]);
34 add_hook("page_mode_change_hook", content_buffer_update_keymap_for_input_mode);
36 add_hook("content_buffer_location_change_hook", function (buf) { normal_input_mode(buf, true); });
39 // Input mode for "normal" view mode
40 define_input_mode("normal", null, "content_buffer_normal_keymap");
42 // Input modes for form elements
43 define_input_mode("select", "input:SELECT", "content_buffer_select_keymap");
44 define_input_mode("text", "input:TEXT", "content_buffer_text_keymap");
45 define_input_mode("textarea", "input:TEXTAREA", "content_buffer_textarea_keymap");
46 define_input_mode("richedit", "input:RICHEDIT", "content_buffer_richedit_keymap");
47 define_input_mode("checkbox", "input:CHECKBOX/RADIOBUTTON", "content_buffer_checkbox_keymap");
49 // Input modes for sending key events to gecko
51 "quote_next", "input:PASS-THROUGH(next)", "content_buffer_quote_next_keymap",
52 "This input mode sends the next key combo to the buffer, "+
53 "bypassing Conkeror's normal key handling. The mode disengages "+
54 "after one key combo.");
56 "quote", "input:PASS-THROUGH", "content_buffer_quote_keymap",
57 "This input mode sends all key combos to the buffer, "+
58 "bypassing Conkeror's normal key handling, until the "+
59 "Escape key is pressed.");
61 // Input mode for the visible caret
62 define_input_mode("caret", null, "content_buffer_caret_keymap");
65 function content_buffer_update_input_mode_for_focus(buffer, force) {
66 var mode = buffer.input_mode;
67 var form_input_mode_enabled = (mode == "text_input_mode" ||
68 mode == "textarea_input_mode" ||
69 mode == "select_input_mode" ||
70 mode == "checkbox_input_mode" ||
71 mode == "richedit_input_mode");
73 if (force || form_input_mode_enabled || mode == "normal_input_mode") {
74 let elem = buffer.focused_element;
77 var input_mode_function = null;
78 if (elem instanceof Ci.nsIDOMHTMLInputElement) {
79 var type = elem.getAttribute("type");
80 if (type != null) type = type.toLowerCase();
81 if (type == "checkbox" || type == "radio")
82 input_mode_function = checkbox_input_mode;
83 else if (type != "submit" &&
85 input_mode_function = text_input_mode;
87 else if (elem instanceof Ci.nsIDOMHTMLTextAreaElement)
88 input_mode_function = textarea_input_mode;
90 else if (elem instanceof Ci.nsIDOMHTMLSelectElement)
91 input_mode_function = select_input_mode;
94 if (!input_mode_function) {
96 let frame = buffer.focused_frame;
97 let in_design_mode = false;
98 if (frame && frame.document.designMode == "on")
99 in_design_mode = true;
103 switch (elem.contentEditable) {
105 in_design_mode = true;
109 default: // == "inherit"
110 elem = elem.parentNode;
115 input_mode_function = richedit_input_mode;
118 if (input_mode_function) {
120 browser_prevent_automatic_form_focus_mode_enabled &&
121 !form_input_mode_enabled &&
122 (buffer.last_user_input_received == null ||
123 (Date.now() - buffer.last_user_input_received)
124 > browser_automatic_form_focus_window_duration)) {
125 // Automatic focus attempt blocked
128 input_mode_function(buffer, true);
132 normal_input_mode(buffer, true);
136 add_hook("content_buffer_focus_change_hook",
138 content_buffer_update_input_mode_for_focus(buf, false);
141 define_buffer_mode('caret_mode', 'CARET',
142 $enable = function(buffer) {
143 buffer.browser.setAttribute('showcaret', 'true');
144 let sc = getFocusedSelCtrl(buffer);
145 sc.setCaretEnabled(true);
146 buffer.top_frame.focus();
147 caret_input_mode(buffer, true);
149 $disable = function(buffer) {
150 buffer.browser.setAttribute('showcaret', '');
151 let sc = getFocusedSelCtrl(buffer);
152 sc.setCaretEnabled(false);
153 buffer.browser.focus();
154 content_buffer_update_input_mode_for_focus(buffer, true);
157 //XXX: CARET_PREF is defined in find.js---why?
158 watch_pref(CARET_PREF, function() {
159 if (get_pref(CARET_PREF)) {
160 session_pref(CARET_PREF, false);
161 let window = window_watcher.activeWindow;
162 let buffer = window.buffers.current;
167 interactive("content-buffer-update-input-mode-for-focus", null, function (I) {
168 content_buffer_update_input_mode_for_focus(I.buffer, true);
171 function minibuffer_input_mode_indicator(window) {
172 this.window = window;
173 this.hook_func = method_caller(this, this.update);
174 add_hook.call(window, "select_buffer_hook", this.hook_func);
175 add_hook.call(window, "current_buffer_input_mode_change_hook", this.hook_func);
179 minibuffer_input_mode_indicator.prototype = {
180 update : function () {
181 var buf = this.window.buffers.current;
182 var mode = buf.input_mode;
184 this.window.minibuffer.element.className = "minibuffer-" + buf.input_mode.replace("_","-","g");
186 uninstall : function () {
187 remove_hook.call(window, "select_buffer_hook", this.hook_func);
188 remove_hook.call(window, "current_buffer_input_mode_change_hook", this.hook_func);
192 define_global_window_mode("minibuffer_input_mode_indicator", "window_initialize_hook");
193 minibuffer_input_mode_indicator_mode(true);
196 define_variable("browser_automatic_form_focus_window_duration", 20,
197 "Time window (in milliseconds) during which a form element "+
198 "is allowed to gain focus following a mouse click or key "+
199 "press, if `browser_prevent_automatic_form_focus_mode' is "+
202 define_global_mode("browser_prevent_automatic_form_focus_mode",
203 function () {}, // enable
204 function () {} // disable
208 "browser_form_field_xpath_expression",
210 // "translate(@type,'RADIO','radio')!='radio' and " +
211 // "translate(@type,'CHECKBOX','checkbox')!='checkbox' and " +
212 "translate(@type,'HIDEN','hiden')!='hidden'"
213 // "translate(@type,'SUBMIT','submit')!='submit' and " +
214 // "translate(@type,'REST','rest')!='reset'"
217 // "translate(@type,'RADIO','radio')!='radio' and " +
218 // "translate(@type,'CHECKBOX','checkbox')!='checkbox' and " +
219 "translate(@type,'HIDEN','hiden')!='hidden'"
220 // "translate(@type,'SUBMIT','submit')!='submit' and " +
221 // "translate(@type,'REST','rest')!='reset'"
223 "//select | //xhtml:select | " +
224 "//textarea | //xhtml:textarea | " +
225 "//textbox | //xul:textbox",
226 "XPath expression matching elements to be selected by `browser-focus-next-form-field' " +
227 "and `browser-focus-previous-form-field.'");
229 function browser_focus_next_form_field(buffer, count, xpath_expr) {
230 var focused_elem = buffer.focused_element;
232 return; // invalid count
234 function helper(win, skip_win) {
237 var doc = win.document;
238 var res = doc.evaluate(xpath_expr, doc, xpath_lookup_namespace,
239 Ci.nsIDOMXPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
240 null /* existing results */);
241 var length = res.snapshotLength;
243 let valid_nodes = [];
244 for (let i = 0; i < length; ++i) {
245 let elem = res.snapshotItem(i);
246 if (elem.clientWidth == 0 &&
247 elem.clientHeight == 0)
249 let style = win.getComputedStyle(elem, "");
250 if (style.display == "none" || style.visibility == "hidden")
252 valid_nodes.push(elem);
255 if (valid_nodes.length > 0) {
257 if (focused_elem != null)
258 index = valid_nodes.indexOf(focused_elem);
266 index = index + count;
267 index = index % valid_nodes.length;
269 index += valid_nodes.length;
271 return valid_nodes[index];
274 // Recurse on sub-frames
275 for (var i = 0; i < win.frames.length; ++i) {
276 var elem = helper(win.frames[i], skip_win);
283 var focused_win = buffer.focused_frame;
284 var elem = helper(focused_win, null);
286 elem = helper(buffer.top_frame, focused_win);
288 browser_element_focus(buffer, elem);
290 throw interactive_error("No form field found");
293 interactive("browser-focus-next-form-field",
294 "Focus the next element matching "+
295 "`browser_form_field_xpath_expression'.",
297 browser_focus_next_form_field(
298 I.buffer, I.p, browser_form_field_xpath_expression);
301 interactive("browser-focus-previous-form-field",
302 "Focus the previous element matching "+
303 "`browser_form_field_xpath_expression'.",
305 browser_focus_next_form_field(
306 I.buffer, -I.p, browser_form_field_xpath_expression);
309 function edit_field_in_external_editor(buffer, elem) {
310 if (elem instanceof Ci.nsIDOMHTMLInputElement) {
311 var type = elem.getAttribute("type");
313 type = type.toLowerCase();
314 if (type == "hidden" || type == "checkbox" || type == "radio")
315 throw interactive_error("Element is not a text field.");
316 } else if (!(elem instanceof Ci.nsIDOMHTMLTextAreaElement))
317 throw interactive_error("Element is not a text field.");
319 var name = elem.getAttribute("name");
320 if (!name || name.length == 0)
321 name = elem.getAttribute("id");
324 name = name.replace(/[^a-zA-Z0-9\-_]/g, "");
325 if (name.length == 0)
328 var file = get_temporary_file(name);
332 write_text_file(file, elem.value);
338 // FIXME: decide if we should do this
339 var old_class = elem.className;
340 elem.className = "__conkeror_textbox_edited_externally " + old_class;
343 yield open_file_with_external_editor(file);
345 elem.value = read_text_file(file);
347 elem.className = old_class;
352 interactive("edit-current-field-in-external-editor",
353 "Edit the contents of the currently-focused text field in an external editor.",
356 yield edit_field_in_external_editor(buf, buf.focused_element);
357 unfocus(I.window, buf);
360 define_variable("kill_whole_line", false,
361 "If true, `kill-line' with no arg at beg of line kills the whole line.");
363 function cut_to_end_of_line (buffer) {
364 var elem = buffer.focused_element;
366 var st = elem.selectionStart;
367 var en = elem.selectionEnd;
369 // there is no selection. set one up.
370 var eol = elem.value.indexOf ("\n", en);
373 elem.selectionEnd = elem.textLength;
374 } else if (eol == st) {
375 elem.selectionEnd = eol + 1;
376 } else if (kill_whole_line &&
377 (st == 0 || elem.value[st - 1] == "\n"))
379 elem.selectionEnd = eol + 1;
381 elem.selectionEnd = eol;
384 buffer.do_command ('cmd_cut');
386 /* FIXME: Make this work for richedit mode as well */
391 "cut-to-end-of-line",
394 cut_to_end_of_line (I.buffer);