Re: [dev] surf patch: bind keystrokes to snippets of Javascript

From: Troels Henriksen <athas_AT_sigkill.dk>
Date: Mon, 17 May 2010 19:10:21 +0200

Troels Henriksen <athas_AT_sigkill.dk> writes:

> Sean Whitton <sean_AT_silentflame.com> writes:
>
>> Hi,
>>
>> On Tue, May 11, 2010 at 02:13:01PM +0100, Sean Whitton wrote:
>>> On Tue, May 11, 2010 at 11:10:04AM +0200, Troels Henriksen wrote:
>>> > The attached patch lets you bind keys to small lines of Javascript to
>>> > run.
>>> >
>>> > The intent is that the usual ~/.surf/script.js defines various methods,
>>> > which are then run by the bindings. This is much cleaner than having
>>> > script.js create its own event listeners for keybindings going outside
>>> > surf's own system.
>>>
>>> This works brilliantly - thank you. If anyone was wondering how to add
>>> keybindings to your config file, this is what I have in config.h for
>>> hinting (that works):
>>>
>>> { 0, GDK_f, eval, { .v = (char *[]){ "hintMode()", NULL } } },
>>> { 0, GDK_F, eval, { .v = (char *[]){ "hintMode(true)", NULL } } },
>>> { 0, GDK_Escape, eval, { .v = (char *[]){ "removeHints()", NULL } } },
>>>
>>> ... and just comment out the line document.addEventListener all the way
>>> down to the end of the hinting script in script.js.
>>
>> Don't comment any JavaScript out; this breaks the binding of the numbers
>> to actually use the hints. Sorry.
>
> You can remove some of the Javascript. This is the modified
> script.js I use for hint mode (it also has a minor optimisation in the
> tagging loop).

Here is a further improved link hinting script, which positions the
hints better when the element is broken across lines, and prevents
recursive invocations of hintMode(). (Sorry for not attaching it, but
my nearby sucky mailserver won't permit me to send .js-files).

/* Based on chromium plugin code, adapted by Nibble<.gs_AT_gmail.com> and
 * further refined by Troels Henriksen <athas_AT_sigkill.dk> */
var hint_num_str = '';
var hint_elems = [];
var hint_enabled = false;
var hint_new_win = false;
var hintsheet;

function hintMode(new_win){
    if (!hint_enabled) {
        hint_enabled = true;
        hint_new_win = new_win;
        setHints();
        document.addEventListener('keydown', hintHandler, false);
        hint_num_str = '';
    }
}

function hintHandler(e){
    e.preventDefault(); //Stop Default Event
    var pressedKey = get_key(e);
    if (pressedKey == 'Enter') {
        if (hint_num_str == '')
            hint_num_str = '1';
        judgeHintNum(Number(hint_num_str));
    } else if (/[0-9]/.test(pressedKey) == false) {
        removeHints();
    } else {
        hint_num_str += pressedKey;
        var hint_num = Number(hint_num_str);
        if (hint_num * 10 > hint_elems.length + 1) {
            judgeHintNum(hint_num);
        } else {
            var hint_elem = hint_elems[hint_num - 1];
            if (hint_elem != undefined && hint_elem.tagName.toLowerCase() == 'a') {
                setHighlight(hint_elem, true);
            }
        }
    }
}

function setHighlight(elem, is_active) {
    if (is_active) {
        var active_elem = document.body.querySelector('a[highlight=hint_active]');
        if (active_elem != undefined)
            active_elem.setAttribute('highlight', 'hint_elem');
        elem.setAttribute('highlight', 'hint_active');
    } else {
        elem.setAttribute('highlight', 'hint_elem');
    }

}

function setHintRules() {
    if (!hintsheet) {
        var ss = document.createElement('style');
        ss.type = 'text/css';
        ss.rel = 'stylesheet';
        ss.media = 'screen';
        ss.title = 'hintSheet';
        document.getElementsByTagName("head")[0].appendChild(ss);
        // This cannot be the best way.
        for (var i=0; i < document.styleSheets.length; i++) {
            if (document.styleSheets[i].title == 'hintSheet') {
                hintsheet = document.styleSheets[i];
                break;
            }
        }
    }
    hintsheet.insertRule('a[highlight=hint_elem] {background-color: yellow}', 0);
    hintsheet.insertRule('a[highlight=hint_active] {background-color: lime}', 0);
}

function deleteHintRules() {
    hintsheet.deleteRule(0);
    hintsheet.deleteRule(0);
}

function judgeHintNum(hint_num) {
    var hint_elem = hint_elems[hint_num - 1];
    if (hint_elem != undefined) {
        execSelect(hint_elem);
    } else {
        removeHints();
    }
}

function execSelect(elem) {
    var tag_name = elem.tagName.toLowerCase();
    var type = elem.type ? elem.type.toLowerCase() : "";
    if (tag_name == 'a' && elem.href != '') {
        setHighlight(elem, true);
        // TODO: ajax, <select>
        if (hint_new_win) {
            window.open(elem.href);
        } else {
            location.href=elem.href;
        }
    } else if (tag_name == 'input' && (type == "submit" || type == "button" || type == "reset")) {
        elem.click();
    } else if (tag_name == 'input' && (type == "radio" || type == "checkbox")) {
        // TODO: toggle checkbox
        elem.checked = !elem.checked;
    } else if (tag_name == 'input' || tag_name == 'textarea') {
        elem.focus();
        elem.setSelectionRange(elem.value.length, elem.value.length);
    }
    removeHints();
}

function setHints() {
    setHintRules();
    var win_top = window.scrollY;
    var win_bottom = win_top + window.innerHeight;
    var win_left = window.scrollX;
    var win_right = win_left + window.innerWidth;
    // TODO: <area>
    var elems = document.body.querySelectorAll('a, input:not([type=hidden]), textarea, select, button');
    var div = document.createElement('div');
    var base_node = document.createElement('span');
    base_node.style.cssText = ['position: absolute;',
                               'font-size: 13px;',
                               'background-color: ' + 'red' + ';',
                               'color: white;',
                               'font-weight: bold;',
                               'padding: 0px 1px;',
                               'z-index: 100000;'
                               ].join('');
    div.setAttribute('highlight', 'hints');
    document.body.appendChild(div);
    for (var i = 0; i < elems.length; i++) {
        var elem = elems[i];
        if (!isHintDisplay(elem))
            continue;
        var rect = elem.getClientRects()[0];
        var elem_top = win_top + rect.top;
        var elem_bottom = win_top + rect.bottom;
        var elem_left = win_left + rect.left;
        var elem_right = win_left + rect.left;
        if ((rect
             && elem_left <= win_right
             && elem_right >= win_left
             && elem_top <= win_bottom
             && elem_bottom >= win_top)) {
            hint_elems.push(elem);
            setHighlight(elem, false);
            var span = base_node.cloneNode();
            span.style.left = win_left + rect.left + "px";
            span.style.top = win_top + rect.top + "px";
            span.innerHTML = hint_elems.length;
            div.appendChild(span);
            if (elem.tagName.toLowerCase() == 'a') {
                if (hint_elems.length == 1) {
                    setHighlight(elem, true);
                } else {
                    setHighlight(elem, false);
                }
            }
        }
    }
}

function isHintDisplay(elem) {
    var rect = elem.getBoundingClientRect();
    return (rect.height != 0 && rect.width != 0);
}

function removeHints() {
    if (!hint_enabled)
        return;
    hint_enabled = false;
    deleteHintRules();
    for (var i = 0; i < hint_elems.length; i++) {
        hint_elems[i].removeAttribute('highlight');
    }
    hint_elems = [];
    hint_num_str = '';
    var div = document.body.querySelector('div[highlight=hints]');
    if (div != undefined) {
        document.body.removeChild(div);
    }
    document.removeEventListener('keydown', hintHandler, false);
}

function get_key(evt){
    var key = keyId[evt.keyIdentifier] || evt.keyIdentifier,
        ctrl = evt.ctrlKey ? 'C-' : '',
        meta = (evt.metaKey || evt.altKey) ? 'M-' : '',
        shift = evt.shiftKey ? 'S-' : '';
    if (evt.shiftKey){
        if (/^[a-z]$/.test(key))
            return ctrl+meta+key.toUpperCase();
        if (/^[0-9]$/.test(key)) {
            switch(key) {
                // TODO
            case "4":
                key = "$";
                break;
            };
            return key;
        }
        if (/^(Enter|Space|BackSpace|Tab|Esc|Home|End|Left|Right|Up|Down|PageUp|PageDown|F(\d\d?))$/.test(key))
            return ctrl+meta+shift+key;
    }
    return ctrl+meta+key;
}

var keyId = {
    "U+0008" : "BackSpace",
    "U+0009" : "Tab",
    "U+0018" : "Cancel",
    "U+001B" : "Esc",
    "U+0020" : "Space",
    "U+0021" : "!",
    "U+0022" : "\"",
    "U+0023" : "#",
    "U+0024" : "$",
    "U+0026" : "&",
    "U+0027" : "'",
    "U+0028" : "(",
    "U+0029" : ")",
    "U+002A" : "*",
    "U+002B" : "+",
    "U+002C" : ",",
    "U+002D" : "-",
    "U+002E" : ".",
    "U+002F" : "/",
    "U+0030" : "0",
    "U+0031" : "1",
    "U+0032" : "2",
    "U+0033" : "3",
    "U+0034" : "4",
    "U+0035" : "5",
    "U+0036" : "6",
    "U+0037" : "7",
    "U+0038" : "8",
    "U+0039" : "9",
    "U+003A" : ":",
    "U+003B" : ";",
    "U+003C" : "<",
    "U+003D" : "=",
    "U+003E" : ">",
    "U+003F" : "?",
    "U+0040" : "@",
    "U+0041" : "a",
    "U+0042" : "b",
    "U+0043" : "c",
    "U+0044" : "d",
    "U+0045" : "e",
    "U+0046" : "f",
    "U+0047" : "g",
    "U+0048" : "h",
    "U+0049" : "i",
    "U+004A" : "j",
    "U+004B" : "k",
    "U+004C" : "l",
    "U+004D" : "m",
    "U+004E" : "n",
    "U+004F" : "o",
    "U+0050" : "p",
    "U+0051" : "q",
    "U+0052" : "r",
    "U+0053" : "s",
    "U+0054" : "t",
    "U+0055" : "u",
    "U+0056" : "v",
    "U+0057" : "w",
    "U+0058" : "x",
    "U+0059" : "y",
    "U+005A" : "z",
    //"U+005B" : "[",
    //"U+005C" : "\\",
    //"U+005D" : "]",
    "U+00DB" : "[",
    "U+00DC" : "\\",
    "U+00DD" : "]",
    "U+005E" : "^",
    "U+005F" : "_",
    "U+0060" : "`",
    "U+007B" : "{",
    "U+007C" : "|",
    "U+007D" : "}",
    "U+007F" : "Delete",
    "U+00A1" : "¡",
    "U+0300" : "CombGrave",
    "U+0300" : "CombAcute",
    "U+0302" : "CombCircum",
    "U+0303" : "CombTilde",
    "U+0304" : "CombMacron",
    "U+0306" : "CombBreve",
    "U+0307" : "CombDot",
    "U+0308" : "CombDiaer",
    "U+030A" : "CombRing",
    "U+030B" : "CombDblAcute",
    "U+030C" : "CombCaron",
    "U+0327" : "CombCedilla",
    "U+0328" : "CombOgonek",
    "U+0345" : "CombYpogeg",
    "U+20AC" : "€",
    "U+3099" : "CombVoice",
    "U+309A" : "CombSVoice",
}

-- 
\  Troels
/\ Henriksen
Received on Mon May 17 2010 - 17:10:21 UTC

This archive was generated by hypermail 2.2.0 : Mon May 17 2010 - 17:12:02 UTC