Change to coroutine infrastructure
[conkeror.git] / modules / content-buffer-input.js
blob03d73c64d916dc7573d1a538f5cc4480e7ffeb20
1 require("content-buffer.js");
3 define_buffer_local_hook("content_buffer_input_mode_change_hook");
4 define_current_buffer_hook("current_content_buffer_input_mode_change_hook", "content_buffer_input_mode_change_hook");
6 function define_content_buffer_input_mode(base_name, keymap_name) {
7     var name = "content_buffer_" + base_name + "_input_mode";
8     buffer[name + "_enabled"] = false;
9     define_buffer_local_hook(name + "_enable_hook");
10     define_buffer_local_hook(name + "_disable_hook");
11     conkeror[name] = function (buffer) {
12         if (buffer[name + "_enabled"])
13             return;
14         if (buffer.current_input_mode) {
15             conkeror[buffer.current_input_mode + "_disable_hook"].run(buffer);
16             buffer[buffer.current_input_mode + "_enabled"] = false;
17         }
18         buffer.current_input_mode = name;
19         buffer[name + "_enabled"] = true;
20         buffer.keymap = conkeror[keymap_name];
21         conkeror[name + "_enable_hook"].run(buffer);
22         content_buffer_input_mode_change_hook.run(buffer);
23     }
24     var hyphen_name = name.replace("_","-","g");
25     interactive(hyphen_name, function(I) {conkeror[name](check_buffer(I.buffer,content_buffer));});
28 define_content_buffer_input_mode("normal", "content_buffer_normal_keymap");
30 // For SELECT elements
31 define_content_buffer_input_mode("select", "content_buffer_select_keymap");
33 // For text INPUT and TEXTAREA elements
34 define_content_buffer_input_mode("text", "content_buffer_text_keymap");
35 define_content_buffer_input_mode("textarea", "content_buffer_textarea_keymap");
37 define_content_buffer_input_mode("quote_next", "content_buffer_quote_next_keymap");
38 define_content_buffer_input_mode("quote", "content_buffer_quote_keymap");
40 add_hook("content_buffer_focus_change_hook", function (buffer) {
41         var form_input_mode_enabled =
42             buffer.content_buffer_text_input_mode_enabled ||
43             buffer.content_buffer_textarea_input_mode_enabled ||
44             buffer.content_buffer_select_input_mode_enabled;
46         if (form_input_mode_enabled || buffer.content_buffer_normal_input_mode_enabled) {
47             var elem = buffer.focused_element;
49             if (elem) {
50                 var input_mode_function = null;
51                 if (elem instanceof Ci.nsIDOMHTMLInputElement) {
52                     var type = elem.getAttribute("type");
53                     if (type != null) type = type.toLowerCase();
54                     if (type != "radio" &&
55                         type != "checkbox" &&
56                         type != "submit" &&
57                         type != "reset")
58                         input_mode_function = content_buffer_text_input_mode;
59                 }
60                 else if (elem instanceof Ci.nsIDOMHTMLTextAreaElement)
61                     input_mode_function = content_buffer_textarea_input_mode;
63                 else if (elem instanceof Ci.nsIDOMHTMLSelectElement)
64                     input_mode_function = content_buffer_select_input_mode;
66                 if (input_mode_function) {
67                     if (browser_prevent_automatic_form_focus_mode_enabled &&
68                         !form_input_mode_enabled &&
69                         (buffer.last_user_input_received == null ||
70                          (Date.now() - buffer.last_user_input_received)
71                          > browser_automatic_form_focus_window_duration)) {
72                         // Automatic focus attempt blocked
73                         elem.blur();
74                     } else
75                         input_mode_function(buffer);
76                     return;
77                 }
78             }
79             content_buffer_normal_input_mode(buffer);
80         }
81     });
83 function browser_input_minibuffer_status(window) {
84     this.window = window;
85     var element = create_XUL(window, "label");
86     element.setAttribute("id", "minibuffer-input-status");
87     element.collapsed = true;
88     element.setAttribute("class", "minibuffer");
89     var insert_before = window.document.getElementById("minibuffer-prompt");
90     insert_before.parentNode.insertBefore(element, insert_before);
91     this.element = element;
92     this.select_buffer_hook_func = add_hook.call(window, "select_buffer_hook", method_caller(this, this.update));
93     this.mode_change_hook_func = add_hook.call(window, "current_content_buffer_input_mode_change_hook",
94                                                method_caller(this, this.update));
95     this.update();
97 browser_input_minibuffer_status.prototype = {
98     update : function () {
99         var buf = this.window.buffers.current;
100         if (buf.content_buffer_normal_input_mode_enabled) {
101             this.element.collapsed = true;
102             this.window.minibuffer.element.className = "";
103         } else {
104             var name = "";
105             var className = null;
106             if (buf.content_buffer_text_input_mode_enabled) {
107                 name = "[TEXT INPUT]";
108                 className = "text";
109             } else if (buf.content_buffer_textarea_input_mode_enabled) {
110                 name = "[TEXTAREA INPUT]";
111                 className = "textarea";
112             } else if (buf.content_buffer_select_input_mode_enabled) {
113                 name = "[SELECT INPUT]";
114                 className = "select";
115             } else if (buf.content_buffer_quote_next_input_mode_enabled) {
116                 name = "[PASS THROUGH (next)]";
117                 className = "quote-next";
118             } else if (buf.content_buffer_quote_input_mode_enabled) {
119                 name = "[PASS THROUGH]";
120                 className = "quote";
121             } else /* error */;
123             this.element.value = name;
124             this.element.collapsed = false;
125             this.window.minibuffer.element.className = "minibuffer-" + className + "-input-mode";
126         }
127     },
128     uninstall : function () {
129         remove_hook.call(window, "select_buffer_hook", this.select_buffer_hook_func);
130         remove_hook.call(window, "current_content_buffer_input_mode_change_hook",
131                          method_caller(this, this.update), this.mode_change_hook_func);
132         this.element.parentNode.removeChild(this.element);
133     }
136 function browser_input_minibuffer_status_install(window) {
137     if (window.browser_input_minibuffer_status)
138         throw new Error("browser input minibuffer status already initialized for window");
139     window.browser_input_minibuffer_status = new browser_input_minibuffer_status(window);
142 function browser_input_minibuffer_status_uninstall(window) {
143     if (window.browser_input_minibuffer_status)
144         return;
145     window.browser_input_minibuffer_status.uninstall();
146     delete window.browser_input_minibuffer_status;
149 define_global_mode("browser_input_minibuffer_status_mode",
150                    function () { // enable
151                        add_hook("window_initialize_hook", browser_input_minibuffer_status_install);
152                        for_each_window(browser_input_minibuffer_status_install);
153                    },
154                    function () { // disable
155                        remove_hook("window_initialize_hook", browser_input_minibuffer_status_install);
156                        for_each_window(browser_input_minibuffer_status_uninstall);
157                    });
158 browser_input_minibuffer_status_mode(true);
160 /* USER PREFERENCE */
161 // Milliseconds
162 var browser_automatic_form_focus_window_duration = 20;
164 define_global_mode("browser_prevent_automatic_form_focus_mode",
165                    function () {}, // enable
166                    function () {} // disable
167                    );
168 browser_prevent_automatic_form_focus_mode(true);
170 var browser_form_field_xpath_expression =
171     "//input[" + (
172 //        "translate(@type,'RADIO','radio')!='radio' and " +
173 //        "translate(@type,'CHECKBOX','checkbox')!='checkbox' and " +
174         "translate(@type,'HIDEN','hiden')!='hidden'"
175 //        "translate(@type,'SUBMIT','submit')!='submit' and " +
176 //        "translate(@type,'REST','rest')!='reset'"
177         ) +  "] | " +
178     "//xhtml:input[" + (
179 //        "translate(@type,'RADIO','radio')!='radio' and " +
180 //        "translate(@type,'CHECKBOX','checkbox')!='checkbox' and " +
181         "translate(@type,'HIDEN','hiden')!='hidden'"
182 //        "translate(@type,'SUBMIT','submit')!='submit' and " +
183 //        "translate(@type,'REST','rest')!='reset'"
184         ) +  "] |" +
185     "//select | //xhtml:select | " +
186     "//textarea | //xhtml:textarea | " +
187     "//textbox | //xul:textbox";
189 function browser_focus_next_form_field(buffer, count, xpath_expr) {
190     var focused_elem = buffer.focused_element;
191     if (count == 0)
192         return; // invalid count
194     function helper(win, skip_win) {
195         if (win == skip_win)
196             return null;
197         var doc = win.document;
198         var res = doc.evaluate(xpath_expr, doc, xpath_lookup_namespace,
199                                Ci.nsIDOMXPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
200                                null /* existing results */);
201         var length = res.snapshotLength;
202         if (length > 0) {
203             var index = null;
204             if (focused_elem != null) {
205                 for (var i = 0; i < length; ++i) {
206                     if (res.snapshotItem(i) == focused_elem) {
207                         index = i;
208                         break;
209                     }
210                 }
211             }
212             if (index == null) {
213                 if (count > 0)
214                     index = count - 1;
215                 else
216                     index = -count;
217             }
218             else
219                 index = index + count;
220             index = index % length;
221             if (index < 0)
222                 index += length;
224             return res.snapshotItem(index);
225         }
227         // Recurse on sub-frames
228         for (var i = 0; i < win.frames.length; ++i) {
229             var elem = helper(win.frames[i], skip_win);
230             if (elem)
231                 return elem;
232         }
233         return null;
234     }
236     var focused_win = buffer.focused_frame;
237     var elem = helper(focused_win, null);
238     if (!elem)
239         elem = helper(buffer.top_frame, focused_win);
240     if (elem) {
241         browser_element_focus(buffer, elem);
242     } else
243         throw interactive_error("No form field found");
246 interactive("browser-focus-next-form-field", function (I) {
247     browser_focus_next_form_field(I.buffer, I.p, browser_form_field_xpath_expression);
250 interactive("browser-focus-previous-form-field", function (I) {
251     browser_focus_next_form_field(I.buffer, -I.p, browser_form_field_xpath_expression);
254 function edit_field_in_external_editor(buffer, elem) {
255     if (elem instanceof Ci.nsIDOMHTMLInputElement) {
256         var type = elem.getAttribute("type");
257         if (type != null)
258             type = type.toLowerCase();
259         if (type == "hidden" || type == "checkbox" || type == "radio")
260             throw interactive_error("Element is not a text field.");
261     } else if (!(elem instanceof Ci.nsIDOMHTMLTextAreaElement))
262         throw interactive_error("Element is not a text field.");
264     var file = file_locator.get("TmpD", Ci.nsIFile);
265     var name = elem.getAttribute("name") || "";
266     name = name.replace(/[^a-zA-Z0-9\-_]/g, "");
267     if (name.length == 0)
268         name = "text";
269     name += ".txt";
270     file.append(name);
271     file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
273     // Write to file
274     try {
275         write_text_file(file, elem.value);
276     } catch (e) {
277         file.remove(false);
278         throw e;
279     }
281     var old_readonly = elem.getAttribute("readonly");
282     elem.setAttribute("readonly", "true");
283     // FIXME: decide if we should do this
284     //var oldBg = elem.style.backgroundColor;
285     //elem.style.backgroundColor = "#bbbbbb";
286     function cleanup() {
287         if (old_readonly)
288             elem.setAttribute("readonly", old_readonly);
289         else
290             elem.removeAttribute("readonly");
291         file.remove(false);
292     }
293     open_file_with_external_editor(
294         file,
295         $callback = function () {
296             try {
297                 elem.value = read_text_file(file);
298             } catch (e) {
299             }
300             // FIXME: flash the textbox?
301             cleanup();
302         },
303         $failure_callback = cleanup);
305 interactive("edit-current-field-in-external-editor", function (I) {
306     var buf = I.buffer;
307     edit_field_in_external_editor(buf, buf.focused_element);