<commit_before>'use strict';

const render = require('debug')('gpu:render');
const control = require('debug')('gpu:control');
const EventEmitter = require('events').EventEmitter;
const Canvas = require('./canvas');
const {
    LCDCONT, SCROLLY, SCROLLX, WNDPOSY, WNDPOSX, BGRDPAL, OBJ0PAL, OBJ1PAL
} = require('./registers');

require('./number');

// Non CGB gray shades

const GRAY_SHADES = [];

GRAY_SHADES[0] = [255, 255, 255];
GRAY_SHADES[1] = [192, 192, 192];
GRAY_SHADES[2] = [96, 96, 96];
GRAY_SHADES[3] = [0, 0, 0];

const FRAME_WIDTH = 160;
const FRAME_HEIGHT = 144;

class Gpu extends EventEmitter {
    constructor (video) {
        super();
        this._video = video;

        // Registers

        this._lcdc = 0;
        this._scy = 0;
        this._scx = 0;
        this._wy = 0;
        this._wx = 0;
        this._bgp = 0;
        this._obp0 = 0;
        this._obp1 = 0;

        // Display

        this._bgpal = null;
        this._objpal = [];

        // Canvas

        this._canvas = new Canvas(FRAME_WIDTH, FRAME_HEIGHT);
        this._ctx = this._canvas.getContext('2d');
        this._image = this._ctx.getImageData(0, 0, FRAME_WIDTH, FRAME_HEIGHT);
        this._data = this._image.data;
    }

    readByte (addr) {
        switch (addr) {
            case LCDCONT: return this._lcdc;
            case SCROLLY: return this._scy;
            case SCROLLX: return this._scx;
            case BGRDPAL: return this._bgp;
            case OBJ0PAL: return this._obp0;
            case OBJ1PAL: return this._obp1;
            case WNDPOSY: return this._wy;
            case WNDPOSX: return this._wx;
        }

        throw new Error(`unmapped address 0x${addr.toString(16)}`);
    }

    writeByte (addr, val) {
        switch (addr) {
            case LCDCONT: return this._lcdc = val;
            case SCROLLY: return this._scy = val;
            case SCROLLX: return this._scx = val;
            case BGRDPAL:
                this._bgpal = this._createPalette(val);
                return this._bgp = val;
            case OBJ0PAL:
                this._objpal[0] = this._createPalette(val);
                return this._obp0 = val;
            case OBJ1PAL:
                this._objpal[1] = this._createPalette(val);
                return this._obp1 = val;
            case WNDPOSY: return this._wy = val;
            case WNDPOSX: return this._wx = val;
        }

        throw new Error(`unmapped address 0x${addr.toString(16)}`);
    }

    drawLine (line) {
        const data = this._lcdc >> 4 & 1;

        // Background

        if (this._lcdc & 1) {
            const map = this._lcdc >> 3 & 1;
            this._drawBackground(line, this._scx, this._scy, map, data);
        }

        // Window

        if (this._lcdc & 0x20 && line >= this._wy) {
            const map = this._lcdc >> 6 & 1;
            this._drawWindow(line, this._wx - 7, this._wy, map, data);
        }

        // Sprites

        if (this._lcdc & 2) {
            this._drawSprites(line);
        }
    }

    render () {
        render('frame');

        // LCD Display Enable

        control('%s', this._lcdc.toString(2));

        if (this._lcdc & 0x80 == 0) {
            this._ctx.fillStyle = 'white';
            this._ctx.fillRect(0, 0, FRAME_WIDTH, FRAME_HEIGHT);
        }

        this._ctx.putImageData(this._image, 0, 0);
        this._ctx.drawImage(this._canvas, 0, 0);

        this.emit('frame', this._canvas);
    }

    _createPalette (palette) {
        return [
            GRAY_SHADES[palette & 3],
            GRAY_SHADES[palette >> 2 & 3],
            GRAY_SHADES[palette >> 4 & 3],
            GRAY_SHADES[palette >> 6 & 3]
        ];
    }

    _drawBackground (line, offsetX, offsetY, mapSelect, dataSelect) {
        const map = this._video.bgMap[mapSelect];
        const y = offsetY + line;

        for (let i = 0; i < FRAME_WIDTH; i++) {
            const x = offsetX + i;

            const col = (x & 0xff) >> 3;
            const row = (y & 0xff) >> 3;

            const n = map[row * 32 + col];
            const data = this._video.tiles[dataSelect ? n : 256 + n.signed()];
            const shade = this._bgpal[data[y & 7][x & 7]];

            let offset = (line * FRAME_WIDTH + i) * 4;

            this._data[offset] = shade[0];
            this._data[++offset] = shade[1];
            this._data[++offset] = shade[2];
            this._data[++offset] = 255;
        }
    }

    _drawWindow (line, posX, posY, mapSelect, dataSelect) {
        const map = this._video.bgMap[mapSelect];
        const y = line - posY;

        for (let i = posX; i < FRAME_WIDTH; i++) {
            const x = i - posX;

            const col = x >> 3;
            const row = y >> 3;

            const n = map[row * 32 + col];
            const data = this._video.tiles[dataSelect ? n : 256 + n.signed()];
            const shade = this._bgpal[data[y & 7][x & 7]];

            let offset = (line * FRAME_WIDTH + i) * 4;

            this._data[offset] = shade[0];
            this._data[++offset] = shade[1];
            this._data[++offset] = shade[2];
            this._data[++offset] = 255;
        }
    }

    _drawSprites (line) {
        const height = this._lcdc & 4 ? 16 : 8;
        let count = 0;

        for (let i = 0; i < 40; i++) {
            const sprite = this._video.sprites[i];

            // Position

            const sy = sprite[0] - 16;
            const sx = sprite[1] - 8;

            if (!(line >= sy && line < sy + height)) continue;
            if (sx >= FRAME_WIDTH || sy >= FRAME_HEIGHT) continue;
            if (++count > 10) continue;

            // Tile/Pattern Number

            const n = this._lcdc & 4 ? sprite[2] & 0xfe : sprite[2];

            // Attributes/Flags

            const attrs = sprite[3];

            const yflip = attrs >> 6 & 1;
            const xflip = attrs >> 5 & 1;
            const palette = this._objpal[attrs >> 4 & 1];

            // Draw

            const py = yflip ? (height - 1) - (line - sy) : line - sy;

            for (let x = sx; x < sx + 8; x++) {
                const data = this._video.tiles[n + (py >> 3 & 1)];
                const color = data[py & 7][xflip ? 7 - (x - sx) : x - sx];
                if (color == 0) continue;

                const shade = palette[color];

                let offset = (line * FRAME_WIDTH + x) * 4;

                this._data[offset] = shade[0];
                this._data[++offset] = shade[1];
                this._data[++offset] = shade[2];
                this._data[++offset] = 255;
            }
        };
    }
}

module.exports = Gpu;
<commit_msg>fix sprite screen wrap on negative pos x<commit_after>'use strict';

const render = require('debug')('gpu:render');
const control = require('debug')('gpu:control');
const EventEmitter = require('events').EventEmitter;
const Canvas = require('./canvas');
const {
    LCDCONT, SCROLLY, SCROLLX, WNDPOSY, WNDPOSX, BGRDPAL, OBJ0PAL, OBJ1PAL
} = require('./registers');

require('./number');

// Non CGB gray shades

const GRAY_SHADES = [];

GRAY_SHADES[0] = [255, 255, 255];
GRAY_SHADES[1] = [192, 192, 192];
GRAY_SHADES[2] = [96, 96, 96];
GRAY_SHADES[3] = [0, 0, 0];

const FRAME_WIDTH = 160;
const FRAME_HEIGHT = 144;

class Gpu extends EventEmitter {
    constructor (video) {
        super();
        this._video = video;

        // Registers

        this._lcdc = 0;
        this._scy = 0;
        this._scx = 0;
        this._wy = 0;
        this._wx = 0;
        this._bgp = 0;
        this._obp0 = 0;
        this._obp1 = 0;

        // Display

        this._bgpal = null;
        this._objpal = [];

        // Canvas

        this._canvas = new Canvas(FRAME_WIDTH, FRAME_HEIGHT);
        this._ctx = this._canvas.getContext('2d');
        this._image = this._ctx.getImageData(0, 0, FRAME_WIDTH, FRAME_HEIGHT);
        this._data = this._image.data;
    }

    readByte (addr) {
        switch (addr) {
            case LCDCONT: return this._lcdc;
            case SCROLLY: return this._scy;
            case SCROLLX: return this._scx;
            case BGRDPAL: return this._bgp;
            case OBJ0PAL: return this._obp0;
            case OBJ1PAL: return this._obp1;
            case WNDPOSY: return this._wy;
            case WNDPOSX: return this._wx;
        }

        throw new Error(`unmapped address 0x${addr.toString(16)}`);
    }

    writeByte (addr, val) {
        switch (addr) {
            case LCDCONT: return this._lcdc = val;
            case SCROLLY: return this._scy = val;
            case SCROLLX: return this._scx = val;
            case BGRDPAL:
                this._bgpal = this._createPalette(val);
                return this._bgp = val;
            case OBJ0PAL:
                this._objpal[0] = this._createPalette(val);
                return this._obp0 = val;
            case OBJ1PAL:
                this._objpal[1] = this._createPalette(val);
                return this._obp1 = val;
            case WNDPOSY: return this._wy = val;
            case WNDPOSX: return this._wx = val;
        }

        throw new Error(`unmapped address 0x${addr.toString(16)}`);
    }

    drawLine (line) {
        const data = this._lcdc >> 4 & 1;

        // Background

        if (this._lcdc & 1) {
            const map = this._lcdc >> 3 & 1;
            this._drawBackground(line, this._scx, this._scy, map, data);
        }

        // Window

        if (this._lcdc & 0x20 && line >= this._wy) {
            const map = this._lcdc >> 6 & 1;
            this._drawWindow(line, this._wx - 7, this._wy, map, data);
        }

        // Sprites

        if (this._lcdc & 2) {
            this._drawSprites(line);
        }
    }

    render () {
        render('frame');

        // LCD Display Enable

        control('%s', this._lcdc.toString(2));

        if (this._lcdc & 0x80 == 0) {
            this._ctx.fillStyle = 'white';
            this._ctx.fillRect(0, 0, FRAME_WIDTH, FRAME_HEIGHT);
        }

        this._ctx.putImageData(this._image, 0, 0);
        this._ctx.drawImage(this._canvas, 0, 0);

        this.emit('frame', this._canvas);
    }

    _createPalette (palette) {
        return [
            GRAY_SHADES[palette & 3],
            GRAY_SHADES[palette >> 2 & 3],
            GRAY_SHADES[palette >> 4 & 3],
            GRAY_SHADES[palette >> 6 & 3]
        ];
    }

    _drawBackground (line, offsetX, offsetY, mapSelect, dataSelect) {
        const map = this._video.bgMap[mapSelect];
        const y = offsetY + line;

        for (let i = 0; i < FRAME_WIDTH; i++) {
            const x = offsetX + i;

            const col = (x & 0xff) >> 3;
            const row = (y & 0xff) >> 3;

            const n = map[row * 32 + col];
            const data = this._video.tiles[dataSelect ? n : 256 + n.signed()];
            const shade = this._bgpal[data[y & 7][x & 7]];

            let offset = (line * FRAME_WIDTH + i) * 4;

            this._data[offset] = shade[0];
            this._data[++offset] = shade[1];
            this._data[++offset] = shade[2];
            this._data[++offset] = 255;
        }
    }

    _drawWindow (line, posX, posY, mapSelect, dataSelect) {
        const map = this._video.bgMap[mapSelect];
        const y = line - posY;

        for (let i = posX; i < FRAME_WIDTH; i++) {
            const x = i - posX;

            const col = x >> 3;
            const row = y >> 3;

            const n = map[row * 32 + col];
            const data = this._video.tiles[dataSelect ? n : 256 + n.signed()];
            const shade = this._bgpal[data[y & 7][x & 7]];

            let offset = (line * FRAME_WIDTH + i) * 4;

            this._data[offset] = shade[0];
            this._data[++offset] = shade[1];
            this._data[++offset] = shade[2];
            this._data[++offset] = 255;
        }
    }

    _drawSprites (line) {
        const height = this._lcdc & 4 ? 16 : 8;
        let count = 0;

        for (let i = 0; i < 40; i++) {
            const sprite = this._video.sprites[i];

            // Position

            const sy = sprite[0] - 16;
            const sx = sprite[1] - 8;

            if (!(line >= sy && line < sy + height)) continue;
            if (sx >= FRAME_WIDTH || sy >= FRAME_HEIGHT) continue;
            if (++count > 10) continue;

            // Tile/Pattern Number

            const n = this._lcdc & 4 ? sprite[2] & 0xfe : sprite[2];

            // Attributes/Flags

            const attrs = sprite[3];

            const yflip = attrs >> 6 & 1;
            const xflip = attrs >> 5 & 1;
            const palette = this._objpal[attrs >> 4 & 1];

            // Draw

            const py = yflip ? (height - 1) - (line - sy) : line - sy;

            for (let x = sx; x < sx + 8; x++) {
                if (x < 0) continue;

                const data = this._video.tiles[n + (py >> 3 & 1)];
                const color = data[py & 7][xflip ? 7 - (x - sx) : x - sx];
                if (color == 0) continue;

                const shade = palette[color];

                let offset = (line * FRAME_WIDTH + x) * 4;

                this._data[offset] = shade[0];
                this._data[++offset] = shade[1];
                this._data[++offset] = shade[2];
                this._data[++offset] = 255;
            }
        };
    }
}

module.exports = Gpu;
<|endoftext|>


<commit_before>
;(function ( _app, undefined ) {
    'use strict';

    "use strict";

    var _capturing = false,
        _imageElement = new Image(),
        _mainElement = document.getElementById("main");

    var _imageCanvas = document.createElement("canvas"),
        _imageCanvasContext = _imageCanvas.getContext("2d");

    var _pixelCanvas = document.createElement("canvas"),
        _pixelCanvasContext = _pixelCanvas.getContext("2d");

    var _inputRGB = document.getElementsByName("rgb")[0],
        _inputPixel = document.getElementsByName("pixel")[0];

    _app._checkedRGB = false;
    _app._checkedPixel = false;

    function _getBit(_v) {
        var _ret = 0;
        if ( _v < 16 ) {
            _ret = 0;
        } else if ( _v < 48 ) {
            _ret = 32;
        } else if ( _v < 80 ) {
            _ret = 64;
        } else if ( _v < 112 ) {
            _ret = 96;
        } else if ( _v < 144 ) {
            _ret = 128;
        } else if ( _v < 176 ) {
            _ret = 160;
        } else if ( _v < 208 ) {
            _ret = 192;
        } else if ( _v < 240 ) {
            _ret = 224;
        } else {
            _ret = 256;
        }
        return _ret;
    }

    function checkRGB() {
        if (_inputRGB.checked) {
            _app._checkedRGB = true;
        } else {
            _app._checkedRGB = false;
        }
    }

    function checkPixel() {
        if (_inputPixel.checked) {
            _app._checkedPixel = true;
        } else {
            _app._checkedPixel = false;
        }
    }

    /* private method */
    function init() {

        document.body.insertBefore(_imageElement, _mainElement);

        _imageCanvas.width = 640;
        _imageCanvas.height = 480;

        _pixelCanvas.width = 640;
        _pixelCanvas.height = 480;

        _pixelCanvasContext.mozImageSmoothingEnabled = false;
        _pixelCanvasContext.webkitImageSmoothingEnabled = false;
        _pixelCanvasContext.imageSmoothingEnabled = false;

        _inputRGB.onchange = checkRGB;
        _inputPixel.onchange = checkPixel;

        camera.init({

            mirror: true,
            width: 640,
            height: 480,

            onFrame: function(canvas) {

                if (_app._checkedRGB) {

                    var imgData = canvas.getContext("2d").getImageData(0, 0, canvas.width, canvas.height);

                    // rendering 8-bit image

                    for (var i=0;i<imgData.data.length;i+=4) {
                        imgData.data[i+0] = _getBit(imgData.data[i+0]); //R
                        imgData.data[i+1] = _getBit(imgData.data[i+1]); //G
                        imgData.data[i+2] = _getBit(imgData.data[i+2]); //B
                    }

                    _imageCanvasContext.putImageData(imgData,0,0);

                    _imageElement.src = _imageCanvas.toDataURL("image/png");
                } else {
                    _imageElement.src = canvas.toDataURL("image/png");
                }

                // pixelating

                if (_app._checkedPixel) {
                    _pixelCanvasContext.drawImage(_imageElement, 0, 0, 160, 120);

                    _pixelCanvasContext.drawImage(_pixelCanvas, 0, 0, 160, 120, 0, 0, _pixelCanvas.width, _pixelCanvas.height);

                    _imageElement.src = _pixelCanvas.toDataURL("image/png");
                }

            },

            onSuccess: function() {
                document.getElementById("info").style.display = "none";

                _capturing = true;
                document.getElementById("pause").style.display = "block";
                document.getElementById("pause").onclick = function() {
                    if (_capturing) {
                        camera.pause();
                    } else {
                        camera.start();
                    }
                    _capturing = !_capturing;
                };
            },

            onError: function(error) {
                // TODO: log error
            },

            onNotSupported: function() {
                document.getElementById("info").style.display = "none";
                document.getElementById("notSupported").style.display = "block";
            }

        });
    }

    /* public method here used as "constructor" */
    _app.initialize = function () {
        init();
    };

}(window.app = window.app || {}));

app.initialize();
<commit_msg>Update index.js<commit_after>;(function ( _app, undefined ) {

    "use strict";

    var _capturing = false,
        _imageElement = new Image(),
        _mainElement = document.getElementById("main");

    var _imageCanvas = document.createElement("canvas"),
        _imageCanvasContext = _imageCanvas.getContext("2d");

    var _pixelCanvas = document.createElement("canvas"),
        _pixelCanvasContext = _pixelCanvas.getContext("2d");

    var _inputRGB = document.getElementsByName("rgb")[0],
        _inputPixel = document.getElementsByName("pixel")[0];

    _app._checkedRGB = false;
    _app._checkedPixel = false;

    function _getBit(_v) {
        var _ret = 0;
        if ( _v < 16 ) {
            _ret = 0;
        } else if ( _v < 48 ) {
            _ret = 32;
        } else if ( _v < 80 ) {
            _ret = 64;
        } else if ( _v < 112 ) {
            _ret = 96;
        } else if ( _v < 144 ) {
            _ret = 128;
        } else if ( _v < 176 ) {
            _ret = 160;
        } else if ( _v < 208 ) {
            _ret = 192;
        } else if ( _v < 240 ) {
            _ret = 224;
        } else {
            _ret = 256;
        }
        return _ret;
    }

    function checkRGB() {
        if (_inputRGB.checked) {
            _app._checkedRGB = true;
        } else {
            _app._checkedRGB = false;
        }
    }

    function checkPixel() {
        if (_inputPixel.checked) {
            _app._checkedPixel = true;
        } else {
            _app._checkedPixel = false;
        }
    }

    /* private method */
    function init() {

        document.body.insertBefore(_imageElement, _mainElement);

        _imageCanvas.width = 640;
        _imageCanvas.height = 480;

        _pixelCanvas.width = 640;
        _pixelCanvas.height = 480;

        _pixelCanvasContext.mozImageSmoothingEnabled = false;
        _pixelCanvasContext.webkitImageSmoothingEnabled = false;
        _pixelCanvasContext.imageSmoothingEnabled = false;

        _inputRGB.onchange = checkRGB;
        _inputPixel.onchange = checkPixel;

        camera.init({

            mirror: true,
            width: 640,
            height: 480,

            onFrame: function(canvas) {

                if (_app._checkedRGB) {

                    var imgData = canvas.getContext("2d").getImageData(0, 0, canvas.width, canvas.height);

                    // rendering 8-bit image

                    for (var i=0;i<imgData.data.length;i+=4) {
                        imgData.data[i+0] = _getBit(imgData.data[i+0]); //R
                        imgData.data[i+1] = _getBit(imgData.data[i+1]); //G
                        imgData.data[i+2] = _getBit(imgData.data[i+2]); //B
                    }

                    _imageCanvasContext.putImageData(imgData,0,0);

                    _imageElement.src = _imageCanvas.toDataURL("image/png");
                } else {
                    _imageElement.src = canvas.toDataURL("image/png");
                }

                // pixelating

                if (_app._checkedPixel) {
                    _pixelCanvasContext.drawImage(_imageElement, 0, 0, 160, 120);

                    _pixelCanvasContext.drawImage(_pixelCanvas, 0, 0, 160, 120, 0, 0, _pixelCanvas.width, _pixelCanvas.height);

                    _imageElement.src = _pixelCanvas.toDataURL("image/png");
                }

            },

            onSuccess: function() {
                document.getElementById("info").style.display = "none";

                _capturing = true;
                document.getElementById("pause").style.display = "block";
                document.getElementById("pause").onclick = function() {
                    if (_capturing) {
                        camera.pause();
                    } else {
                        camera.start();
                    }
                    _capturing = !_capturing;
                };
            },

            onError: function(error) {
                // TODO: log error
            },

            onNotSupported: function() {
                document.getElementById("info").style.display = "none";
                document.getElementById("notSupported").style.display = "block";
            }

        });
    }

    /* public method here used as "constructor" */
    _app.initialize = function () {
        init();
    };

}(window.app = window.app || {}));

app.initialize();
<|endoftext|>


<commit_before>if( $( '#selfmarks-window' ).length == 0 ) {
    $( 'head' ).append(
        '<link rel="stylesheet" href="#{Selfmarks::HOST}/uri/add_window.css" type="text/css" media="screen"/>'
    );
    var div = $( '#{@window_html}' );
    div.hide();
    $( 'body' ).prepend( div );

    var tags = $( "a[rel=tag]", document ).map(
        function( idx, tag ) { return(tag.innerHTML); }
    );
    var tags_text = $.unique( tags ).join( ' ' );
    $( '#selfmarks-tags' ).val( tags_text );

}
$( '#selfmarks-window' ).toggle( 'fast' );

$( '#selfmarks-submit' ).live( 'click', function() {
    var uri = $( '#selfmarks-uri' ).val();
    var title = $( '#selfmarks-title' ).val();
    var tags = $( '#selfmarks-tags' ).val();
    var notes = $( '#selfmarks-notes' ).val();
    $.getJSON(
        '#{Selfmarks::HOST}/uri/add_window_add?jsoncallback=?',
        {   uri   : uri,
            title : title,
            tags  : tags,
            notes : notes },
        function( json ) {
            if( json[ 'error' ] ) {
                $( '#selfmarks-error' ).append( 'Bookmarking error: ' + json[ 'error' ] );
            } else if( json[ 'success' ] == 'success' ) {
                $( '#selfmarks-message' ).append( 'Successfully bookmarked URI.' );
                setTimeout( function() {
                    $( '#selfmarks-window' ).toggle( 'slow' ); },
                    2000 );
            }
        } );
    return false;
} );

$( '#selfmarks-cancel' ).live( 'click', function() {
    $( '#selfmarks-window' ).hide( 'fast' );
} );<commit_msg>Copied unique_strings from manveru's ubiquity command.<commit_after>function unique_strings( a ) {
    var r = new Array();
    o:for(var i = 0, n = a.length; i < n; i++) {
        for(var x = 0, y = r.length; x < y; x++) {
            if(r[x]==a[i]) continue o;
        }
        r[r.length] = a[i];
    }
    return r;
}

if( $( '#selfmarks-window' ).length == 0 ) {
    $( 'head' ).append(
        '<link rel="stylesheet" href="#{Selfmarks::HOST}/uri/add_window.css" type="text/css" media="screen"/>'
    );
    var div = $( '#{@window_html}' );
    div.hide();
    $( 'body' ).prepend( div );

    var tags = $( "a[rel=tag]", document ).map(
        function( idx, tag ) { return(tag.innerHTML); }
    );
    var tags_text = unique_strings( tags ).join( ' ' );
    $( '#selfmarks-tags' ).val( tags_text );

}
$( '#selfmarks-window' ).toggle( 'fast' );

$( '#selfmarks-submit' ).live( 'click', function() {
    var uri = $( '#selfmarks-uri' ).val();
    var title = $( '#selfmarks-title' ).val();
    var tags = $( '#selfmarks-tags' ).val();
    var notes = $( '#selfmarks-notes' ).val();
    $.getJSON(
        '#{Selfmarks::HOST}/uri/add_window_add?jsoncallback=?',
        {   uri   : uri,
            title : title,
            tags  : tags,
            notes : notes },
        function( json ) {
            if( json[ 'error' ] ) {
                $( '#selfmarks-error' ).append( 'Bookmarking error: ' + json[ 'error' ] );
            } else if( json[ 'success' ] == 'success' ) {
                $( '#selfmarks-message' ).append( 'Successfully bookmarked URI.' );
                setTimeout( function() {
                    $( '#selfmarks-window' ).toggle( 'slow' ); },
                    2000 );
            }
        } );
    return false;
} );

$( '#selfmarks-cancel' ).live( 'click', function() {
    $( '#selfmarks-window' ).hide( 'fast' );
} );<|endoftext|>


<commit_before>// Substance.Document 0.4.0
// (c) 2010-2013 Michael Aufreiter
// Substance.Document may be freely distributed under the MIT license.
// For all details and documentation:
// http://interior.substance.io/modules/document.html

(function(root) {

// Import
// ========

var _,
    ot,
    util,
    errors,
    Chronicle,
    ArrayOperation,
    TextOperation,
    Data;

if (typeof exports !== 'undefined') {
  _    = require('underscore');
  ot   = require('./lib/operation');
  util   = require('./lib/util/util');
  errors   = require('./lib/util/errors');
  Chronicle = require('./lib/chronicle/chronicle');
  Data = require('./lib/data/data');
} else {
  _ = root._;
  ot = root.ot;
  util = root.Substance.util;
  errors   = root.Substance.errors;
  Chronicle = root.Substance.Chronicle;
  Data = root.Substance.Data;
}

ArrayOperation = Chronicle.OT.ArrayOperation;
TextOperation = Chronicle.OT.TextOperation;

function convertStringOp(val, op) {
  var cops = []; // transformed ops
  var i = 0, j=0;
  _.each(op, function(el) {
    if (_.isString(el)) { // insert chars
      cops.push(TextOperation.Insert(j, el));
      j += el.length;
    } else if (el<0) { // delete n chars
      var offset = Math.abs(el);
      cops.push(TextOperation.Delete(j, val.slice(i, i+offset)));
      i += offset;
    } else { // skip n chars
      i += el;
      j += el;
    }
  });
  return cops;
}

// Implementation
// ========

// Default Document Schema
// --------

var SCHEMA = {
  // Static indexes
  "indexes": {
    // all comments are now indexed by node association
    "comments": {
      "type": "comment",
      "properties": ["node"]
    },
    // All comments are now indexed by node
    "annotations": {
      "type": "annotation",
      "properties": ["node"]
    }
  },

  "types": {
    // Specific type for substance documents, holding all content elements
    "content": {
      "properties": {

      }
    },

    "text": {
      "parent": "content",
      "properties": {
        "content": "string"
      }
    },

    "document": {
      "properties": {
        "views": ["array", "views"]
      }
    },

    "view": {
      "properties": {
        "nodes": ["array", "content"]
      }
    },

    "codeblock": {
      "parent": "content",
      "properties": {
        "content": "string"
      }
    },

    "image": {
      "parent": "content",
      "properties": {
        "large": "string",
        "medium": "string",
        "caption": "string"
      }
    },

    "heading": {
      "parent": "content",
      "properties": {
        "content": "string",
        "level": "number"
      }
    },

    // Annotations
    "annotation": {
      "properties": {
        "node": "content",
        "pos": ["array", "number"]
      }
    },

    "strong": {
      "parent": "annotation",
      "properties": {
      }
    },

    "emphasis": {
      "properties": {
      },
      "parent": "annotation"
    },

    "code": {
      "parent": "annotation",
      "properties": {
      }
    },

    "link": {
      "parent": "annotation",
      "properties": {
        "url": "string"
      }
    },

    "idea": {
      "parent": "annotation",
      "properties": {
      }
    },

    "error": {
      "parent": "annotation",
      "properties": {
      }
    },

    "question": {
      "parent": "annotation",
      "properties": {
      }
    },

    // Comments
    "comment": {
      "properties": {
        "content": "string",
        "node": "node"
      }
    }
  }
};

// Seed Data
// ========
//
// Some pre-defined nodes that are required for each document
// `document` stores all meta-information about the document
// `content` is the main view

var SEED = [
  ["create", {
      "id": "document",
      "type": "document",
      "views": ["content", "figures", "publications"]
    }
  ],
  ["create", {
      "id": "content",
      "type": "view",
      "nodes": []
    }
  ],
  ["create", {
      "id": "figures",
      "type": "view",
      "nodes": []
    }
  ],
  ["create", {
      "id": "publications",
      "type": "view",
      "nodes": []
    }
  ]
];

// Command Converter
// ========
//
// Turns document command into Data.Graph commands

var Converter = function() {

  // Position nodes in document
  // --------
  //
  // ["position", {"nodes": ["t1", "t2"], "target": -1}]

  this.position = function(graph, command) {
    var path = command.path.concat(["nodes"]);
    var view = graph.resolve(path).slice(0);
    var nodes = command.args.nodes;
    var ops = [];
    var idx;

    var seq = _.intersection(view, nodes);
    var l = view.length;

    while(seq.length > 0) {
      var id = seq.pop();
      idx = view.indexOf(id);
      if (idx >= 0) {
        ops.push(ArrayOperation.Delete(idx, id));
        l--;
      }
    }

    var target = (l == 0) ? 0 : (l + command.args.target) % l;

    for (idx = 0; idx < nodes.length; idx++) {
      ops.push(ArrayOperation.Insert(target + idx, nodes[idx]));
    }

    return {
        op: "update",
        path: path,
        args: ArrayOperation.Compound(ops)
    };
  };

  // Delete nodes from document
  // --------
  //
  // ["delete", {nodes: ["h1", "t1"]}]

  this.delete = function(graph, command) {
    return _.map(command.args.nodes, function(n) {
      return {
        "op": "delete",
        "path": [],
        "args": {id: n}
      };
    });
  };

  // Update incrementally
  // --------
  //
  // ["update", "h1", {
  //   "content": ["abc", 4, -1],
  //   "children": [4, "a", -2]
  // }]
  // ["+", 0, "abc"]
  // ["-", 7, "x"]

  // ["+", 0, "abc"]
  // ["-", 4, "adfx"]  -> ["+", 4, "adfx"]

  this.update = function(graph, command) {
    var res = [];

    var val = graph.resolve(command.path);
    var ops = convertStringOp(val, command.args);

    return {
      "op": "update",
      "path": command.path,
      "args": TextOperation.Compound(ops)
    };

  };

  // Set property values
  // --------
  //
  // Unlike update you can set values directly
  // ["update", "h1", "content", "Hello Welt"] // string update
  // ["update", "a1", "pos", ["a", "b", "c"]] // array update (not yet implemented)

  this.set = function(graph, command) {

    if (!command.args) { // for string updates
      command.args = command.path.pop();
    }

    var propertyBaseType = graph.propertyBaseType(graph.get(command.path[0]), command.path[1]);
    var ops = [];

    if (propertyBaseType === 'array') {
      throw new Error("Not yet implemented for arrays");
    } else {
      // Consider everything else as a string
      var val = graph.resolve(command.path).toString();
      var newVal = command.args.toString();

      var ops = [];
      // Delete old value
      ops.push({
        "op": "update",
        "path": command.path,
        "args": TextOperation.Delete(0, val)
      });

      // Insert new value
      ops.push({
        "op": "update",
        "path": command.path,
        "args": TextOperation.Insert(0, newVal)
      });
    }

    return ops;
  };

  // Annotate document
  // --------
  //
  // `command.path` defines the referenced content node and property
  // ["annotate", "t1", "content", {"id": "a1",  type": "idea"}]

  this.annotate = function(graph, command) {
    // TODO: check if source exists, otherwise reject annotation
    if (command.path.length !== 2) throw new Error("Invalid target: " + command.path);

    command.args.node = command.path[0];
    command.args.property = command.path[1];

    return {
      "op": "create",
      "path": [],
      "args": command.args
    };
  };

  // Comment
  // --------
  //
  // `command.path` holds the node id, where the comment should stick on

  this.comment = function(graph, command) {
    // Delegates to graph.create method
    // TODO: check if source exists, otherwise reject annotation

    // keep track of annotation
    if (command.path.length !== 1) throw new Error("Invalid target: " + command.path);
    command.args.node = command.path[0];
    if (!command.args.type) command.args.type = 'comment';

    return {
      "op": "create",
      "path": [],
      "args": command.args
    };
  };
};

// Document
// --------
//
// A generic model for representing and transforming digital documents

var Document = function(doc, schema) {
  Data.Graph.call(this, schema || SCHEMA);

  // Set public properties
  this.id = doc.id;

  this.reset();

  // Text Op for each annotation
  this.annotationOps = {};
};

Document.__prototype__ = function() {

  var __super__ = util.prototype(this);
  var converter = new Converter();

  // Resets the versioned object to a clean state.
  // --------
  //

  this.reset = function() {
    // Seed graph
    _.each(SEED, function(cmd) {
      this.exec(cmd);
    }, this);
  };

  // Executes a document manipulation command.
  // --------
  // The command is converted into a sequence of graph commands

  this.exec = function(command) {
    console.log("Executing command: ", command);
    var graphCommand;
    // convert the command into a Data.Graph compatible command
    command = new Data.Graph.Command(command);
    if (converter[command.op]) {
      graphCommand = converter[command.op](this, command);
    } else {
      graphCommand = command.toJSON(); // needed?
    }

    // console.log('converted cmd', command);
    var commands = _.isArray(graphCommand) ? graphCommand : [graphCommand];
    _.each(commands, function(c) {
      __super__.exec.call(this, c);

      console.log('=============', command.op, command);

      // Smart updating of annotations, when text ops are applied
      if (command.op === "annotate") {
        // track se annotation
        // c.args.id
        // extract string from pos
        console.log('new annotation arrived', command.args.id);

        // The text the annotation refers to
        var text = this.resolve(command.path);

        // console.log('pos', command.args.pos);
        var pos = command.args.pos;
        var annotatedText = text.substr(pos[0], pos[1]);

        // Store the op for reference (later)
        this.annotationOps[c.args.id] = TextOperation.Insert(pos[0], annotatedText);
        // console.log('THE OP', this.annotationOps[c.args.id]);

      } else if (command.op === "update") {
        // var tops = TextOperation.transform(this.annoationops[c.args.id];
        // this.annotationops[c.args.id] tops[0]
        var node = this.get(command.path[0]);
        var property = command.path[1];
        var change = command.args;

        // _.each(command.args, function(change, property) {
        // console.log('annotations for ', node.id, property);

        // Get all annotations for a given property
        var annotations = _.filter(this.find("annotations", node.id), function(a) {
          return a.property === property;
        });

        _.each(annotations, function(a) {
          var aop = this.annotationOps[a.id];

          console.log('before', aop);
          console.log('change', change);

          // console.log('OLDSTYLE CHANGE', graphCommand.);

          var tops = [null, aop];
          _.each(commands, function(c) {
            // console.log('MEH', );
            tops = TextOperation.transform(TextOperation.fromJSON(c.args), tops[1]);
            console.log('tops', tops);

            // console.log('tops', tops);
            // this.annotationops[a.id] = tops[1];
          });

          this.annotationOps[a.id] = tops[1];

          console.log("XXX", this.annotationOps[a.id]);
          // Update annotation object in memory
          aop = this.annotationOps[a.id];
          a.pos = [aop.pos, aop.str.length];

          // var tops = TextOperation.transform(this.annoationops[a.id], change);
          // Tranformed textop
          // var taop

          console.log('after', this.annotationOps[a.id]);

        }, this);

        console.log('annots', annotations);

        // }, this);

        // Get all annotations for the updated text bla
        // this.annotationOps =
        // var target
        console.log('now update annotations that stick on the text node', node);
      }
    }, this);
  };
};

// Document.__prototype__.prototype = Chronicle.Versioned.prototype;
Document.__prototype__.prototype = Data.Graph.prototype;
Document.prototype = new Document.__prototype__();

// add event support
_.extend(Document.prototype, util.Events);

// Export
// ========

if (typeof exports !== 'undefined') {
  module.exports = Document;
} else {
  root.Substance.Document = Document;
}

})(this);
<commit_msg>Fix node configuration.<commit_after>// Substance.Document 0.4.0
// (c) 2010-2013 Michael Aufreiter
// Substance.Document may be freely distributed under the MIT license.
// For all details and documentation:
// http://interior.substance.io/modules/document.html

(function(root) {

// Import
// ========

var _,
    ot,
    util,
    errors,
    Chronicle,
    ArrayOperation,
    TextOperation,
    Data;

if (typeof exports !== 'undefined') {
  _    = require('underscore');
  ot   = require('./lib/operation');
  util   = require('./lib/util/util');
  errors   = require('./lib/util/errors');
  Chronicle = require('./lib/chronicle/chronicle');
  ArrayOperation = require('./lib/chronicle/lib/ot/array_operation');
  TextOperation = require('./lib/chronicle/lib/ot/text_operation');
  Data = require('./lib/data/data');
} else {
  _ = root._;
  ot = root.ot;
  util = root.Substance.util;
  errors   = root.Substance.errors;
  Chronicle = root.Substance.Chronicle;
  ArrayOperation = Chronicle.OT.ArrayOperation;
  TextOperation = Chronicle.OT.TextOperation;
  Data = root.Substance.Data;
}

function convertStringOp(val, op) {
  var cops = []; // transformed ops
  var i = 0, j=0;
  _.each(op, function(el) {
    if (_.isString(el)) { // insert chars
      cops.push(TextOperation.Insert(j, el));
      j += el.length;
    } else if (el<0) { // delete n chars
      var offset = Math.abs(el);
      cops.push(TextOperation.Delete(j, val.slice(i, i+offset)));
      i += offset;
    } else { // skip n chars
      i += el;
      j += el;
    }
  });
  return cops;
}

// Implementation
// ========

// Default Document Schema
// --------

var SCHEMA = {
  // Static indexes
  "indexes": {
    // all comments are now indexed by node association
    "comments": {
      "type": "comment",
      "properties": ["node"]
    },
    // All comments are now indexed by node
    "annotations": {
      "type": "annotation",
      "properties": ["node"]
    }
  },

  "types": {
    // Specific type for substance documents, holding all content elements
    "content": {
      "properties": {

      }
    },

    "text": {
      "parent": "content",
      "properties": {
        "content": "string"
      }
    },

    "document": {
      "properties": {
        "views": ["array", "views"]
      }
    },

    "view": {
      "properties": {
        "nodes": ["array", "content"]
      }
    },

    "codeblock": {
      "parent": "content",
      "properties": {
        "content": "string"
      }
    },

    "image": {
      "parent": "content",
      "properties": {
        "large": "string",
        "medium": "string",
        "caption": "string"
      }
    },

    "heading": {
      "parent": "content",
      "properties": {
        "content": "string",
        "level": "number"
      }
    },

    // Annotations
    "annotation": {
      "properties": {
        "node": "content",
        "pos": ["array", "number"]
      }
    },

    "strong": {
      "parent": "annotation",
      "properties": {
      }
    },

    "emphasis": {
      "properties": {
      },
      "parent": "annotation"
    },

    "code": {
      "parent": "annotation",
      "properties": {
      }
    },

    "link": {
      "parent": "annotation",
      "properties": {
        "url": "string"
      }
    },

    "idea": {
      "parent": "annotation",
      "properties": {
      }
    },

    "error": {
      "parent": "annotation",
      "properties": {
      }
    },

    "question": {
      "parent": "annotation",
      "properties": {
      }
    },

    // Comments
    "comment": {
      "properties": {
        "content": "string",
        "node": "node"
      }
    }
  }
};

// Seed Data
// ========
//
// Some pre-defined nodes that are required for each document
// `document` stores all meta-information about the document
// `content` is the main view

var SEED = [
  ["create", {
      "id": "document",
      "type": "document",
      "views": ["content", "figures", "publications"]
    }
  ],
  ["create", {
      "id": "content",
      "type": "view",
      "nodes": []
    }
  ],
  ["create", {
      "id": "figures",
      "type": "view",
      "nodes": []
    }
  ],
  ["create", {
      "id": "publications",
      "type": "view",
      "nodes": []
    }
  ]
];

// Command Converter
// ========
//
// Turns document command into Data.Graph commands

var Converter = function() {

  // Position nodes in document
  // --------
  //
  // ["position", {"nodes": ["t1", "t2"], "target": -1}]

  this.position = function(graph, command) {
    var path = command.path.concat(["nodes"]);
    var view = graph.resolve(path).slice(0);
    var nodes = command.args.nodes;
    var ops = [];
    var idx;

    var seq = _.intersection(view, nodes);
    var l = view.length;

    while(seq.length > 0) {
      var id = seq.pop();
      idx = view.indexOf(id);
      if (idx >= 0) {
        ops.push(ArrayOperation.Delete(idx, id));
        l--;
      }
    }

    var target = (l == 0) ? 0 : (l + command.args.target) % l;

    for (idx = 0; idx < nodes.length; idx++) {
      ops.push(ArrayOperation.Insert(target + idx, nodes[idx]));
    }

    return {
        op: "update",
        path: path,
        args: ArrayOperation.Compound(ops)
    };
  };

  // Delete nodes from document
  // --------
  //
  // ["delete", {nodes: ["h1", "t1"]}]

  this.delete = function(graph, command) {
    return _.map(command.args.nodes, function(n) {
      return {
        "op": "delete",
        "path": [],
        "args": {id: n}
      };
    });
  };

  // Update incrementally
  // --------
  //

  this.update = function(graph, command) {
    var res = [];

    var val = graph.resolve(command.path);
    var ops = convertStringOp(val, command.args);

    return {
      "op": "update",
      "path": command.path,
      "args": TextOperation.Compound(ops)
    };

  };

  // Set property values
  // --------
  //
  // Unlike update you can set values directly
  // ["update", "h1", "content", "Hello Welt"] // string update
  // ["update", "a1", "pos", ["a", "b", "c"]] // array update (not yet implemented)

  this.set = function(graph, command) {

    if (!command.args) { // for string updates
      command.args = command.path.pop();
    }

    var propertyBaseType = graph.propertyBaseType(graph.get(command.path[0]), command.path[1]);
    var ops = [];

    if (propertyBaseType === 'array') {
      throw new Error("Not yet implemented for arrays");
    } else {
      // Consider everything else as a string
      var val = graph.resolve(command.path).toString();
      var newVal = command.args.toString();

      var ops = [];
      // Delete old value
      ops.push({
        "op": "update",
        "path": command.path,
        "args": TextOperation.Delete(0, val)
      });

      // Insert new value
      ops.push({
        "op": "update",
        "path": command.path,
        "args": TextOperation.Insert(0, newVal)
      });
    }

    return ops;
  };

  // Annotate document
  // --------
  //
  // `command.path` defines the referenced content node and property
  // ["annotate", "t1", "content", {"id": "a1",  type": "idea"}]

  this.annotate = function(graph, command) {
    // TODO: check if source exists, otherwise reject annotation
    if (command.path.length !== 2) throw new Error("Invalid target: " + command.path);

    command.args.node = command.path[0];
    command.args.property = command.path[1];

    return {
      "op": "create",
      "path": [],
      "args": command.args
    };
  };

  // Comment
  // --------
  //
  // `command.path` holds the node id, where the comment should stick on

  this.comment = function(graph, command) {
    // Delegates to graph.create method
    // TODO: check if source exists, otherwise reject annotation

    // keep track of annotation
    if (command.path.length !== 1) throw new Error("Invalid target: " + command.path);
    command.args.node = command.path[0];
    if (!command.args.type) command.args.type = 'comment';

    return {
      "op": "create",
      "path": [],
      "args": command.args
    };
  };
};

// Document
// --------
//
// A generic model for representing and transforming digital documents

var Document = function(doc, schema) {
  Data.Graph.call(this, schema || SCHEMA);

  // Set public properties
  this.id = doc.id;

  this.reset();

  // Text Op for each annotation
  this.annotationOps = {};
};

Document.__prototype__ = function() {

  var __super__ = util.prototype(this);
  var converter = new Converter();

  // Resets the versioned object to a clean state.
  // --------
  //

  this.reset = function() {
    // Seed graph
    _.each(SEED, function(cmd) {
      this.exec(cmd);
    }, this);
  };

  // Executes a document manipulation command.
  // --------
  // The command is converted into a sequence of graph commands

  this.exec = function(command) {
    console.log("Executing command: ", command);
    var graphCommand;
    // convert the command into a Data.Graph compatible command
    command = new Data.Graph.Command(command);
    if (converter[command.op]) {
      graphCommand = converter[command.op](this, command);
    } else {
      graphCommand = command.toJSON(); // needed?
    }

    // console.log('converted cmd', command);
    var commands = _.isArray(graphCommand) ? graphCommand : [graphCommand];
    _.each(commands, function(c) {
      __super__.exec.call(this, c);

      console.log('=============', command.op, command);

      // Smart updating of annotations, when text ops are applied
      if (command.op === "annotate") {
        // track se annotation
        // c.args.id
        // extract string from pos
        console.log('new annotation arrived', command.args.id);

        // The text the annotation refers to
        var text = this.resolve(command.path);

        // console.log('pos', command.args.pos);
        var pos = command.args.pos;
        var annotatedText = text.substr(pos[0], pos[1]);

        // Store the op for reference (later)
        this.annotationOps[c.args.id] = TextOperation.Insert(pos[0], annotatedText);
        // console.log('THE OP', this.annotationOps[c.args.id]);

      } else if (command.op === "update") {
        // var tops = TextOperation.transform(this.annoationops[c.args.id];
        // this.annotationops[c.args.id] tops[0]
        var node = this.get(command.path[0]);
        var property = command.path[1];
        var change = command.args;

        // _.each(command.args, function(change, property) {
        // console.log('annotations for ', node.id, property);

        // Get all annotations for a given property
        var annotations = _.filter(this.find("annotations", node.id), function(a) {
          return a.property === property;
        });

        _.each(annotations, function(a) {
          var aop = this.annotationOps[a.id];

          console.log('before', aop);
          console.log('change', change);

          // console.log('OLDSTYLE CHANGE', graphCommand.);

          var tops = [null, aop];
          _.each(commands, function(c) {
            // console.log('MEH', );
            tops = TextOperation.transform(TextOperation.fromJSON(c.args), tops[1]);
            console.log('tops', tops);

            // console.log('tops', tops);
            // this.annotationops[a.id] = tops[1];
          });

          this.annotationOps[a.id] = tops[1];

          console.log("XXX", this.annotationOps[a.id]);
          // Update annotation object in memory
          aop = this.annotationOps[a.id];
          a.pos = [aop.pos, aop.str.length];

          // var tops = TextOperation.transform(this.annoationops[a.id], change);
          // Tranformed textop
          // var taop

          console.log('after', this.annotationOps[a.id]);

        }, this);

        console.log('annots', annotations);

        // }, this);

        // Get all annotations for the updated text bla
        // this.annotationOps =
        // var target
        console.log('now update annotations that stick on the text node', node);
      }
    }, this);
  };
};

// Document.__prototype__.prototype = Chronicle.Versioned.prototype;
Document.__prototype__.prototype = Data.Graph.prototype;
Document.prototype = new Document.__prototype__();

// add event support
_.extend(Document.prototype, util.Events);

// Export
// ========

if (typeof exports !== 'undefined') {
  module.exports = Document;
} else {
  root.Substance.Document = Document;
}

})(this);
<|endoftext|>


<commit_before>define([
	'jquery',
	'lodash',
	'app/config',
	'app/components/events',
	'app/components/component',
	'app/components/ui/modal',
	'app/utils/instruments',
	'app/widgets/key_signature',
	'app/widgets/analyze',
	'app/widgets/highlight'
], function(
	$,
	_,
	Config,
	EVENTS,
	Component,
	ModalComponent,
	Instruments,
	KeySignatureWidget,
	AnalyzeWidget,
	HighlightWidget
) {

	/**
	 * Defines the title of the app info modal.
	 * @type {string}
	 * @const
	 */
	var APP_INFO_TITLE = Config.get('helpText.appInfo.title');
	/**
	 * Defines the content of the app info modal.
	 * @type {string}
	 * @const
	 */
	var APP_INFO_CONTENT = Config.get('helpText.appInfo.content');
	/**
	 * Defines whether the shortcuts are enabled by default or not.
	 * @type {boolean}
	 * @const
	 */
	var KEYBOARD_SHORTCUTS_ENABLED = Config.get('general.keyboardShortcutsEnabled');
	/**
	 * Defines the default keyboard size.
	 * @type {number}
	 * @const
	 */
	var DEFAULT_KEYBOARD_SIZE = Config.get('general.defaultKeyboardSize');

	/**
	 * Defines a namespace for settings.
	 * canvas.
	 *
	 * @namespace
	 */
	var TabControlsComponent = function(settings) {
		this.settings = settings || {};
		if(!("keySignature" in settings)) {
			throw new Error("missing keySignature setting");
		}
		if(!("midiDevice" in settings)) {
			throw new Error("missing midiDevice setting");
		}
		this.keySignature = settings.keySignature;
		this.midiDevice = settings.midiDevice;

		if(settings.exerciseContext) {
			this.exerciseContext = settings.exerciseContext;
		} else {
			this.exerciseContext = false;
		}

		this.addComponent(new ModalComponent());

		_.bindAll(this, ['onClickInfo']);
	};

	TabControlsComponent.prototype = new Component();

	_.extend(TabControlsComponent.prototype, {
		/**
		 * Initializes the component.
		 *
		 * @return undefined
		 */
		initComponent: function() {
			var headerEl = $('#header');
			var settingsEl = $("#settings");

			$('.js-btn-screenshot').on('mousedown', this.onClickScreenshot);
			$('.js-btn-info').on('click', this.onClickInfo);
			$('.js-settings').on('click', function(e) {
				e.preventDefault();
				settingsEl.animate({width:'toggle'}, 350);
				return false;
			});
			settingsEl.children(".accordion").accordion({
				collapsible: true,
				heightStyle: "content"
			});
			this.initKeySignatureTab(headerEl);
			this.initNotationTab(settingsEl);
			this.renderInstrumentSelect(settingsEl);
			this.renderKeyboardSizeSelect(settingsEl);
			this.renderKeyboardShortcuts(settingsEl);
			this.initMidiTab(settingsEl);
		},
		/**
		 * Initializes the content of the midi.
		 *
		 * @param {object} containerEl
		 * @return undefined
		 */
		initMidiTab: function(containerEl) {
			var renderDevices = function(midiDevice) {
				var inputs = midiDevice.getInputs();
				var outputs = midiDevice.getOutputs();
				var tpl = _.template('<option value="<%= id %>"><%= name %></option>');
				var makeOptions = function(device, idx) {
					return tpl({ id: idx, name: device.deviceName });
				};
				var devices = {
					'input': {
						'selector': $('.js-select-midi-input', containerEl),
						'options': _.map(inputs, makeOptions)
					},
					'output': {
						'selector': $('.js-select-midi-output', containerEl),
						'readonly': true,
						'options': _.map(outputs, makeOptions) }
				};

				_.each(devices, function(device, type) {
					if(device.options.length > 0) {
						$(device.selector).html(device.options.join(''));
					} else {
						$(device.selector).html('<option>--</option>');
					}

					if(device.readonly) {
						$(device.selector).attr('disabled', 'disabled');
					} else {
						$(device.selector).on('change', function() {
							var index = parseInt($(this).val(), 10);
							midiDevice[type=='input'?'selectInput':'selectOutput'](index);
						});
					}
				});

			};

			$('.js-refresh-midi-devices', containerEl).on('click', this.midiDevice.update);

			this.midiDevice.bind("updated", renderDevices);

			renderDevices(this.midiDevice);
		},
		/**
		 * Initializes the content of the key signature.
		 *
		 * @param {object} containerEl
		 * @return undefined
		 */
		initKeySignatureTab: function(containerEl) {
			var el = $('.js-keysignature-widget', containerEl);
			var widget = new KeySignatureWidget(this.keySignature);
			widget.render();
			el.append(widget.el);
		},
		/**
		 * Initializes the content of the notation containerEl.
		 *
		 * @param {object} containerEl
		 * @return undefined
		 */
		initNotationTab: function(containerEl) {
			var that = this;
			var el = $('.js-analyze-widget', containerEl);
			var analysisSettings = {};
			var highlightSettings = {};
			if(this.exerciseContext) {
				analysisSettings = this.exerciseContext.getDefinition().getAnalysisSettings();
				highlightSettings = this.exerciseContext.getDefinition().getHighlightSettings();
			}
			var analyze_widget = new AnalyzeWidget(analysisSettings);
			var highlight_widget = new HighlightWidget(highlightSettings);
			var event_for = {
				'highlight': EVENTS.BROADCAST.HIGHLIGHT_NOTES,
				'analyze': EVENTS.BROADCAST.ANALYZE_NOTES
			};
			var onChangeCategory = function(category, enabled) {
				if(event_for[category]) {
					that.broadcast(event_for[category], {key: "enabled", value: enabled});
				}
			};
			var onChangeOption = function(category, mode, enabled) {
				var value = {};
				if(event_for[category]) {
					value[mode] = enabled;
					that.broadcast(event_for[category], {key: "mode", value: value});
				}
			};

			highlight_widget.bind('changeCategory', onChangeCategory);
			highlight_widget.bind('changeOption', onChangeOption);

			analyze_widget.bind('changeCategory', onChangeCategory);
			analyze_widget.bind('changeOption', onChangeOption);

			analyze_widget.render();
			highlight_widget.render();

			el.append(highlight_widget.el, analyze_widget.el);
		},
		/**
		 * Renders the instrument selector.
		 *
		 * @param {object} containerEl
		 * @return undefined
		 */
		renderInstrumentSelect: function(containerEl) {
			var that = this;
			var el = $('.js-instrument', containerEl);
			var selectEl = $("<select/>");
			var tpl = _.template('<% _.forEach(instruments, function(inst) { %><option value="<%= inst.num %>"><%- inst.name %></option><% }); %>');
			var options = tpl({ instruments: Instruments.getEnabled() });

			selectEl.append(options);
			selectEl.on('change', function() {
				var instrument_num = $(this).val();
				that.broadcast(EVENTS.BROADCAST.INSTRUMENT, instrument_num);
			});

			el.append(selectEl);
		},
		/**
		 * Renders the keyboard size selector.
		 *
		 * @param {object} containerEl
		 * @return undefined
		 */
		renderKeyboardSizeSelect: function(containerEl) {
			var that = this;
			var el = $('.js-keyboardsize', containerEl);
			var selectEl = $("<select/>");
			var tpl = _.template('<% _.forEach(sizes, function(size) { %><option value="<%= size %>"><%- size %></option><% }); %>');
			var options = tpl({sizes: [25,37,49,88]})
			var selected = DEFAULT_KEYBOARD_SIZE;

			selectEl.append(options);
			selectEl.find("[value="+selected+"]").attr("selected", "selected");
			selectEl.on('change', function() {
				var size = parseInt($(this).val(), 10);
				that.broadcast(EVENTS.BROADCAST.KEYBOARD_SIZE, size);
			});

			el.append(selectEl).wrapInner("Keyboard size:");
		},
		/**
		 * Renders the keyboard shorcuts.
		 *
		 * @param {object} containerEl
		 * @return undefined
		 */
		renderKeyboardShortcuts: function(containerEl) {
			var that = this;
			var el = $('.js-keyboardshortcuts', containerEl);
			var inputEl = $('<input type="checkbox" name="keyboard_shortcuts" value="on" />');
			el.append("Keyboard Shortcuts").append(inputEl).wrap("");

			// toggle shortcuts on/off via gui control
			inputEl.attr('checked', KEYBOARD_SHORTCUTS_ENABLED);
			inputEl.on('change', function() {
				var toggle = $(this).is(':checked') ? true : false;
				that.broadcast(EVENTS.BROADCAST.TOGGLE_SHORTCUTS, toggle);
				$(this).blur(); // trigger blur so it loses focus
			});

			// update gui control when toggled via ESC key
			this.subscribe(EVENTS.BROADCAST.TOGGLE_SHORTCUTS, function(enabled) {
				inputEl[0].checked = enabled;
			});
		},
		/**
		 * Handler to generate a screenshot/image of the staff area.
		 *
		 * @param {object} evt
		 * @return {boolean} true
		 */
		onClickScreenshot: function(evt) {
			var $canvas = $('#staff-area canvas');
			var $target = $(evt.target);
			var data_url = $canvas[0].toDataURL();
			$target[0].href = data_url;
			$target[0].target = '_blank';
			return true;
		},
		/**
		 * Handler to shows the info modal.
		 *
		 * @param {object} evt
		 * @return {boolean} false
		 */
		onClickInfo: function(evt) {
			this.trigger("modal", {title: APP_INFO_TITLE, content: APP_INFO_CONTENT});
			return false;
		}
	});

	return TabControlsComponent;
});
<commit_msg>Click outside settings to close the menu.<commit_after>define([
	'jquery',
	'lodash',
	'app/config',
	'app/components/events',
	'app/components/component',
	'app/components/ui/modal',
	'app/utils/instruments',
	'app/widgets/key_signature',
	'app/widgets/analyze',
	'app/widgets/highlight'
], function(
	$,
	_,
	Config,
	EVENTS,
	Component,
	ModalComponent,
	Instruments,
	KeySignatureWidget,
	AnalyzeWidget,
	HighlightWidget
) {

	/**
	 * Defines the title of the app info modal.
	 * @type {string}
	 * @const
	 */
	var APP_INFO_TITLE = Config.get('helpText.appInfo.title');
	/**
	 * Defines the content of the app info modal.
	 * @type {string}
	 * @const
	 */
	var APP_INFO_CONTENT = Config.get('helpText.appInfo.content');
	/**
	 * Defines whether the shortcuts are enabled by default or not.
	 * @type {boolean}
	 * @const
	 */
	var KEYBOARD_SHORTCUTS_ENABLED = Config.get('general.keyboardShortcutsEnabled');
	/**
	 * Defines the default keyboard size.
	 * @type {number}
	 * @const
	 */
	var DEFAULT_KEYBOARD_SIZE = Config.get('general.defaultKeyboardSize');

	/**
	 * Defines a namespace for settings.
	 * canvas.
	 *
	 * @namespace
	 */
	var TabControlsComponent = function(settings) {
		this.settings = settings || {};
		if(!("keySignature" in settings)) {
			throw new Error("missing keySignature setting");
		}
		if(!("midiDevice" in settings)) {
			throw new Error("missing midiDevice setting");
		}
		this.keySignature = settings.keySignature;
		this.midiDevice = settings.midiDevice;

		if(settings.exerciseContext) {
			this.exerciseContext = settings.exerciseContext;
		} else {
			this.exerciseContext = false;
		}

		this.addComponent(new ModalComponent());

		this.headerEl = $('#header');
		this.settingsEl = $("#settings");

		_.bindAll(this, ['onClickInfo', 'onClickSettings', 'onClickOutsideSettingsMenu']);
	};

	TabControlsComponent.prototype = new Component();

	_.extend(TabControlsComponent.prototype, {
		/**
		 * Initializes the component.
		 *
		 * @return undefined
		 */
		initComponent: function() {

			$('.js-settings', this.headerEl).on('click', this.onClickSettings);
			$('.js-btn-info', this.headerEl).on('click', this.onClickInfo);
			$('.js-btn-screenshot').on('mousedown', this.onClickScreenshot);

			this.initSettingsMenu();
			this.initKeySignatureTab();
			this.initNotationTab();
			this.renderInstrumentSelect();
			this.renderKeyboardSizeSelect();
			this.renderKeyboardShortcuts();
			this.initMidiTab();
		},
		/**
		 * Initializes the slide-out menu.
		 *
		 * @return undefined
		 */
		initSettingsMenu: function() {
			this.settingsEl.children(".accordion").accordion({
				collapsible: true,
				heightStyle: "content"
			});
		},
		/**
		 * Initializes the content of the midi.
		 *
		 * @return undefined
		 */
		initMidiTab: function() {
			var containerEl = this.settingsEl;
			var renderDevices = function(midiDevice) {
				var inputs = midiDevice.getInputs();
				var outputs = midiDevice.getOutputs();
				var tpl = _.template('<option value="<%= id %>"><%= name %></option>');
				var makeOptions = function(device, idx) {
					return tpl({ id: idx, name: device.deviceName });
				};
				var devices = {
					'input': {
						'selector': $('.js-select-midi-input', containerEl),
						'options': _.map(inputs, makeOptions)
					},
					'output': {
						'selector': $('.js-select-midi-output', containerEl),
						'readonly': true,
						'options': _.map(outputs, makeOptions) }
				};

				_.each(devices, function(device, type) {
					if(device.options.length > 0) {
						$(device.selector).html(device.options.join(''));
					} else {
						$(device.selector).html('<option>--</option>');
					}

					if(device.readonly) {
						$(device.selector).attr('disabled', 'disabled');
					} else {
						$(device.selector).on('change', function() {
							var index = parseInt($(this).val(), 10);
							midiDevice[type=='input'?'selectInput':'selectOutput'](index);
						});
					}
				});

			};

			$('.js-refresh-midi-devices', containerEl).on('click', this.midiDevice.update);

			this.midiDevice.bind("updated", renderDevices);

			renderDevices(this.midiDevice);
		},
		/**
		 * Initializes the content of the key signature.
		 *
		 * @return undefined
		 */
		initKeySignatureTab: function() {
			var containerEl = this.headerEl;
			var el = $('.js-keysignature-widget', containerEl);
			var widget = new KeySignatureWidget(this.keySignature);
			widget.render();
			el.append(widget.el);
		},
		/**
		 * Initializes the content of the notation containerEl.
		 *
		 * @return undefined
		 */
		initNotationTab: function() {
			var that = this;
			var containerEl = this.settingsEl;
			var el = $('.js-analyze-widget', containerEl);
			var analysisSettings = {};
			var highlightSettings = {};
			if(this.exerciseContext) {
				analysisSettings = this.exerciseContext.getDefinition().getAnalysisSettings();
				highlightSettings = this.exerciseContext.getDefinition().getHighlightSettings();
			}
			var analyze_widget = new AnalyzeWidget(analysisSettings);
			var highlight_widget = new HighlightWidget(highlightSettings);
			var event_for = {
				'highlight': EVENTS.BROADCAST.HIGHLIGHT_NOTES,
				'analyze': EVENTS.BROADCAST.ANALYZE_NOTES
			};
			var onChangeCategory = function(category, enabled) {
				if(event_for[category]) {
					that.broadcast(event_for[category], {key: "enabled", value: enabled});
				}
			};
			var onChangeOption = function(category, mode, enabled) {
				var value = {};
				if(event_for[category]) {
					value[mode] = enabled;
					that.broadcast(event_for[category], {key: "mode", value: value});
				}
			};

			highlight_widget.bind('changeCategory', onChangeCategory);
			highlight_widget.bind('changeOption', onChangeOption);

			analyze_widget.bind('changeCategory', onChangeCategory);
			analyze_widget.bind('changeOption', onChangeOption);

			analyze_widget.render();
			highlight_widget.render();

			el.append(highlight_widget.el, analyze_widget.el);
		},
		/**
		 * Renders the instrument selector.
		 *
		 * @return undefined
		 */
		renderInstrumentSelect: function() {
			var that = this;
			var containerEl = this.settingsEl;
			var el = $('.js-instrument', containerEl);
			var selectEl = $("<select/>");
			var tpl = _.template('<% _.forEach(instruments, function(inst) { %><option value="<%= inst.num %>"><%- inst.name %></option><% }); %>');
			var options = tpl({ instruments: Instruments.getEnabled() });

			selectEl.append(options);
			selectEl.on('change', function() {
				var instrument_num = $(this).val();
				that.broadcast(EVENTS.BROADCAST.INSTRUMENT, instrument_num);
			});

			el.append(selectEl);
		},
		/**
		 * Renders the keyboard size selector.
		 *
		 * @return undefined
		 */
		renderKeyboardSizeSelect: function() {
			var that = this;
			var containerEl = this.settingsEl;
			var el = $('.js-keyboardsize', containerEl);
			var selectEl = $("<select/>");
			var tpl = _.template('<% _.forEach(sizes, function(size) { %><option value="<%= size %>"><%- size %></option><% }); %>');
			var options = tpl({sizes: [25,37,49,88]})
			var selected = DEFAULT_KEYBOARD_SIZE;

			selectEl.append(options);
			selectEl.find("[value="+selected+"]").attr("selected", "selected");
			selectEl.on('change', function() {
				var size = parseInt($(this).val(), 10);
				that.broadcast(EVENTS.BROADCAST.KEYBOARD_SIZE, size);
			});

			el.append(selectEl).wrapInner("Keyboard size:");
		},
		/**
		 * Renders the keyboard shorcuts.
		 *
		 * @return undefined
		 */
		renderKeyboardShortcuts: function() {
			var that = this;
			var containerEl = this.settingsEl;
			var el = $('.js-keyboardshortcuts', containerEl);
			var inputEl = $('<input type="checkbox" name="keyboard_shortcuts" value="on" />');
			el.append("Keyboard Shortcuts").append(inputEl).wrap("");

			// toggle shortcuts on/off via gui control
			inputEl.attr('checked', KEYBOARD_SHORTCUTS_ENABLED);
			inputEl.on('change', function() {
				var toggle = $(this).is(':checked') ? true : false;
				that.broadcast(EVENTS.BROADCAST.TOGGLE_SHORTCUTS, toggle);
				$(this).blur(); // trigger blur so it loses focus
			});

			// update gui control when toggled via ESC key
			this.subscribe(EVENTS.BROADCAST.TOGGLE_SHORTCUTS, function(enabled) {
				inputEl[0].checked = enabled;
			});
		},
		/**
		 * Shows or hides the settings menu (slide out).
		 *
		 * @return undefined
		 */
		toggleSettingsMenu: function(state) {
			var that = this;
			this.settingsEl.animate({
				width: (state?"show":"hide")
			}, {
				duration: 350,
				complete: function() {
					$(window)[state?'on':'off']('click', that.onClickOutsideSettingsMenu);
				}
			});
		},
		/**
		 * Handler to generate a screenshot/image of the staff area.
		 *
		 * @param {object} evt
		 * @return {boolean} true
		 */
		onClickScreenshot: function(evt) {
			var $canvas = $('#staff-area canvas');
			var $target = $(evt.target);
			var data_url = $canvas[0].toDataURL();
			$target[0].href = data_url;
			$target[0].target = '_blank';
			return true;
		},
		/**
		 * Handler to shows the info modal.
		 *
		 * @param {object} evt
		 * @return {boolean} false
		 */
		onClickInfo: function(evt) {
			this.trigger("modal", {title: APP_INFO_TITLE, content: APP_INFO_CONTENT});
			return false;
		},
		/**
		 * Handles clicking ont he settings button.
		 *
		 * @return undefined
		 */
		onClickSettings: function(evt) {
			this.settingsToggleState = !this.settingsToggleState;
			this.toggleSettingsMenu(this.settingsToggleState);
			evt.preventDefault();
		},
		/**
		 * Handles closing the settings menu when clicking outside the menu.
		 *
		 * @return undefined
		 */
		onClickOutsideSettingsMenu: function(evt) {
			var isOutside = $(evt.target).closest(this.settingsEl).length == 0;
			if (isOutside) {
				this.settingsToggleState = false;
				this.toggleSettingsMenu(this.settingsToggleState);
			}
		}
	});

	return TabControlsComponent;
});
<|endoftext|>


<commit_before>"use strict";

var env = require('../../helpers/env')
  , setup = require("./setup-base")
  , _ = require('underscore')
  , getAppPath = require('../../helpers/app').getAppPath;

var desired = {
  app: getAppPath('ApiDemos'),
  newCommandTimeout: 90
};
if (env.SELENDROID) {
  desired.automationName = 'selendroid';
}

var runTextEditTest = function (driver, testText, keys, done) {
  var el;
  driver
    .waitForElementsByClassName('android.widget.EditText')
    // use a text field with no hint text, so clear is faster
    .then(function (els) {
      el = _.last(els);
      return el;
    })
    .clear()
    .then(function () {
      if (keys) {
        return driver.keys(testText);
      } else {
        return el.sendKeys(testText);
      }
    })
    .then(function () {
      if (env.SELENDROID) {
        // in Selendroid mode we sometimes get the text before
        // it is fully sent to the element
        return driver.sleep(300);
      }
    })
    .then(function () { return el.text(); })
    .then(function (text) {
      // For samsung samsung S5 text is appended with ". Editing."
      text = text.replace(". Editing.", "");
      return text.should.be.equal(testText);
    })
    .nodeify(done);
};

var runEditAndClearTest = function (driver, testText, keys, done) {
  var el;
  driver
    .waitForElementsByClassName('android.widget.EditText')
    .then(function (els) {
      el = _.last(els);
      return el;
    })
    .clear()
    .then(function () {
      if (keys) {
        return driver.keys(testText);
      } else {
        return el.sendKeys(testText);
      }
    })
    .then(function () {
      el.text().should.become(testText);
    })
    .then(function () {
      return el.clear().should.not.be.rejected;
    })
    .then(function () {
      return el.text();
    })
    .then(function (text) {
      // For samsung samsung S5 text is appended with ". Editing."
      text = text.replace("Editing.", "");
      text.should.be.equal("");
    })
    .nodeify(done);
};

var runKeyboardTests = function (driver, testText) {
  return function () {
    it('should work with sendKeys', function (done) {
      runTextEditTest(driver, testText, false, done);
    });
    it('should work with keys', function (done) {
      runTextEditTest(driver, testText, true, done);
    });
  };
};

var runKeyEventTests = function (driver) {
  var editTextField = 'android.widget.TextView';
  if (env.SELENDROID) {
    // with Selendroid we can't find classes by their parent class
    // and with uiautomator we can't find the subclass.
    editTextField = 'io.appium.android.apis.text.LogTextBox';
  }

  // skip selendroid because selendroid implements keyevent with an adb
  // call, and we are unable to send metastate that way
  it('should be able to send combination keyevents @skip-selendroid-all', function (done) {
    driver
      .elementById('clear').click()
      .pressDeviceKey(29, 193)
      .elementsByClassName(editTextField)
      .then(function (els) {
        return _.last(els).text();
      })
      .then(function (txt) {
        txt.should.include('keyCode=KEYCODE_A');
        txt.should.include('metaState=META_SHIFT_ON');
      })
      .nodeify(done);
  });

  it('should be able to send keyevents', function (done) {
    driver
      .elementById('clear').click()
      .pressDeviceKey(82)
      .elementsByClassName(editTextField)
      .then(function (els) {
        return _.last(els).text();
      })
      .then(function (txt) {
        txt.should.include('[keycode=82]');
        txt.should.include('keyCode=KEYCODE_MENU');
      })
      .nodeify(done);
  });
};

var runManualClearTests = function (driver) {
  var testText = "The answer is 42.";
  it('should work with sendKeys', function (done) {
    runEditAndClearTest(driver, testText, false, done);
  });
  it('should work with keys', function (done) {
    runEditAndClearTest(driver, testText, true, done);
  });
};

var appActivity = '.view.TextFields';
var tests = [
  { label: 'editing a text field', text: 'Life, the Universe and Everything.' },
  { label: 'sending &-', text: '&-' },
  { label: 'sending & and - in other text', text: 'In the mid-1990s he ate fish & chips as mayor-elect.' },
  { label: 'sending - in text', text: 'Super-test.' },
];

var languageTests = [
  { label: 'should be able to send - in unicode text', text: 'परीक्षा-परीक्षण' },
  { label: 'should be able to send & in text', text: 'Fish & chips' },
  { label: 'should be able to send & in unicode text', text: 'Mīna & chips' },
  { label: 'should be able to send roman characters with diacritics', text: 'Áé Œ ù ḍ' },
  { label: 'should be able to send a u with an umlaut', text: 'ü' },
  { label: 'should be able to send Tamil', text: 'சோதனை' },
  { label: 'should be able to send Gujarati', text: 'પરીક્ષણ' },
  { label: 'should be able to send Chinese', text: '测试' },
  { label: 'should be able to send Russian', text: 'тестирование' },
  // skip rtl languages, which don't clear correctly atm
  // { label: 'should be able to send Arabic', 'تجريب'],
  // { label: 'should be able to send Hebrew', 'בדיקות'],
];

exports.textFieldTests = function () {
  var driver;

  describe('editing a text field', function () {
    describe('ascii', function () {
      setup(this, _.defaults({
        appActivity: appActivity
      }, desired)).then(function (d) { driver = d; });

      _.each(tests, function (test) {
        describe(test.label, runKeyboardTests(driver, test.text));
      });

      describe('editing and manually clearing a text field', function () {
        runManualClearTests(driver);
      });
    });
  });
};

exports.unicodeTextFieldTests = function () {
  var driver;

  describe('unicode', function () {
    setup(this,  _.defaults({
      appActivity: appActivity,
      unicodeKeyboard: true,
      resetKeyboard: true
    }, desired)).then(function (d) { driver = d; });

    _.each(tests, function (test) {
      describe(test.label, runKeyboardTests(driver, test.text));
    });

    describe('editing and manually clearing a text field', function () {
      runManualClearTests(driver);
    });
  });
};

exports.unicodeLanguagesTests = function () {
  var driver;

  describe('unicode', function () {
    setup(this,  _.defaults({
      appActivity: appActivity,
      unicodeKeyboard: true,
      resetKeyboard: true
    }, desired)).then(function (d) { driver = d; });

    _.each(languageTests, function (test) {
      describe(test.label, runKeyboardTests(driver, test.text));
    });

    describe('editing and manually clearing a text field', function () {
      runManualClearTests(driver);
    });
  });
};

exports.keyEventsTests = function () {
  var driver;

  describe('key events', function () {
    var appActivity = '.text.KeyEventText';
    describe('ascii', function () {
      setup(this, _.defaults({
        appActivity: appActivity
      }, desired)).then(function (d) { driver = d; });
      describe('pressing device key', function () {
        runKeyEventTests(driver);
      });
    });
    describe('unicode', function () {
      setup(this, _.defaults({
        appActivity: appActivity,
        unicodeKeyboard: true,
        resetKeyboard: true
      }, desired)).then(function (d) { driver = d; });
      describe('pressing device key', function () {
        runKeyEventTests(driver);
      });
    });
  });
};
<commit_msg>spreading keyboard tests<commit_after>"use strict";

var env = require('../../helpers/env')
  , setup = require("./setup-base")
  , _ = require('underscore')
  , getAppPath = require('../../helpers/app').getAppPath;

var desired = {
  app: getAppPath('ApiDemos'),
  newCommandTimeout: 90
};
if (env.SELENDROID) {
  desired.automationName = 'selendroid';
}

var runTextEditTest = function (driver, testText, keys, done) {
  var el;
  driver
    .waitForElementsByClassName('android.widget.EditText')
    // use a text field with no hint text, so clear is faster
    .then(function (els) {
      el = _.last(els);
      return el;
    })
    .clear()
    .then(function () {
      if (keys) {
        return driver.keys(testText);
      } else {
        return el.sendKeys(testText);
      }
    })
    .then(function () {
      if (env.SELENDROID) {
        // in Selendroid mode we sometimes get the text before
        // it is fully sent to the element
        return driver.sleep(300);
      }
    })
    .then(function () { return el.text(); })
    .then(function (text) {
      // For samsung samsung S5 text is appended with ". Editing."
      text = text.replace(". Editing.", "");
      return text.should.be.equal(testText);
    })
    .nodeify(done);
};

var runEditAndClearTest = function (driver, testText, keys, done) {
  var el;
  driver
    .waitForElementsByClassName('android.widget.EditText')
    .then(function (els) {
      el = _.last(els);
      return el;
    })
    .clear()
    .then(function () {
      if (keys) {
        return driver.keys(testText);
      } else {
        return el.sendKeys(testText);
      }
    })
    .then(function () {
      el.text().should.become(testText);
    })
    .then(function () {
      return el.clear().should.not.be.rejected;
    })
    .then(function () {
      return el.text();
    })
    .then(function (text) {
      // For samsung samsung S5 text is appended with ". Editing."
      text = text.replace("Editing.", "");
      text.should.be.equal("");
    })
    .nodeify(done);
};

var runKeyboardTests = function (driver, testText) {
  return function () {
    it('should work with sendKeys', function (done) {
      runTextEditTest(driver, testText, false, done);
    });
    it('should work with keys', function (done) {
      runTextEditTest(driver, testText, true, done);
    });
  };
};

var runKeyEventTests = function (driver) {
  var editTextField = 'android.widget.TextView';
  if (env.SELENDROID) {
    // with Selendroid we can't find classes by their parent class
    // and with uiautomator we can't find the subclass.
    editTextField = 'io.appium.android.apis.text.LogTextBox';
  }

  // skip selendroid because selendroid implements keyevent with an adb
  // call, and we are unable to send metastate that way
  it('should be able to send combination keyevents @skip-selendroid-all', function (done) {
    driver
      .elementById('clear').click()
      .pressDeviceKey(29, 193)
      .elementsByClassName(editTextField)
      .then(function (els) {
        return _.last(els).text();
      })
      .then(function (txt) {
        txt.should.include('keyCode=KEYCODE_A');
        txt.should.include('metaState=META_SHIFT_ON');
      })
      .nodeify(done);
  });

  it('should be able to send keyevents', function (done) {
    driver
      .elementById('clear').click()
      .pressDeviceKey(82)
      .elementsByClassName(editTextField)
      .then(function (els) {
        return _.last(els).text();
      })
      .then(function (txt) {
        txt.should.include('[keycode=82]');
        txt.should.include('keyCode=KEYCODE_MENU');
      })
      .nodeify(done);
  });
};

var runManualClearTests = function (driver) {
  var testText = "The answer is 42.";
  it('should work with sendKeys', function (done) {
    runEditAndClearTest(driver, testText, false, done);
  });
  it('should work with keys', function (done) {
    runEditAndClearTest(driver, testText, true, done);
  });
};

var appActivity = '.view.TextFields';
var tests = [
  { label: 'editing a text field', text: 'Life, the Universe and Everything.' },
  { label: 'sending &-', text: '&-' },
  { label: 'sending & and - in other text', text: 'In the mid-1990s he ate fish & chips as mayor-elect.' },
  { label: 'sending - in text', text: 'Super-test.' },
];

var unicodeTests = _.union(tests, [
  { label: 'should be able to send - in unicode text', text: 'परीक्षा-परीक्षण' },
  { label: 'should be able to send & in text', text: 'Fish & chips' },
  { label: 'should be able to send & in unicode text', text: 'Mīna & chips' },
  { label: 'should be able to send roman characters with diacritics', text: 'Áé Œ ù ḍ' },
  { label: 'should be able to send a u with an umlaut', text: 'ü' },
]);

var languageTests = [
  { label: 'should be able to send Tamil', text: 'சோதனை' },
  { label: 'should be able to send Gujarati', text: 'પરીક્ષણ' },
  { label: 'should be able to send Chinese', text: '测试' },
  { label: 'should be able to send Russian', text: 'тестирование' },
  // skip rtl languages, which don't clear correctly atm
  // { label: 'should be able to send Arabic', 'تجريب'],
  // { label: 'should be able to send Hebrew', 'בדיקות'],
];

exports.textFieldTests = function () {
  var driver;

  describe('editing a text field', function () {
    describe('ascii', function () {
      setup(this, _.defaults({
        appActivity: appActivity
      }, desired)).then(function (d) { driver = d; });

      _.each(tests, function (test) {
        describe(test.label, runKeyboardTests(driver, test.text));
      });

      describe('editing and manually clearing a text field', function () {
        runManualClearTests(driver);
      });
    });
  });
};

exports.unicodeTextFieldTests = function () {
  var driver;

  describe('unicode', function () {
    setup(this,  _.defaults({
      appActivity: appActivity,
      unicodeKeyboard: true,
      resetKeyboard: true
    }, desired)).then(function (d) { driver = d; });

    _.each(unicodeTests, function (test) {
      describe(test.label, runKeyboardTests(driver, test.text));
    });

    describe('editing and manually clearing a text field', function () {
      runManualClearTests(driver);
    });
  });
};

exports.unicodeLanguagesTests = function () {
  var driver;

  describe('unicode', function () {
    setup(this,  _.defaults({
      appActivity: appActivity,
      unicodeKeyboard: true,
      resetKeyboard: true
    }, desired)).then(function (d) { driver = d; });

    _.each(languageTests, function (test) {
      describe(test.label, runKeyboardTests(driver, test.text));
    });

    describe('editing and manually clearing a text field', function () {
      runManualClearTests(driver);
    });
  });
};

exports.keyEventsTests = function () {
  var driver;

  describe('key events', function () {
    var appActivity = '.text.KeyEventText';
    describe('ascii', function () {
      setup(this, _.defaults({
        appActivity: appActivity
      }, desired)).then(function (d) { driver = d; });
      describe('pressing device key', function () {
        runKeyEventTests(driver);
      });
    });
    describe('unicode', function () {
      setup(this, _.defaults({
        appActivity: appActivity,
        unicodeKeyboard: true,
        resetKeyboard: true
      }, desired)).then(function (d) { driver = d; });
      describe('pressing device key', function () {
        runKeyEventTests(driver);
      });
    });
  });
};
<|endoftext|>


<commit_before>define('app/views/backend_add', ['text!app/templates/backend_add.html', 'ember'],
    /**
     *  Add Backend Panel
     *
     *  @returns Class
     */
    function(backend_add_html) {
        return Ember.View.extend({

            /**
             *  Properties
             */

            firstFieldLabel: 'API Key',
            secondFieldLabel: 'API Secret',
            template: Ember.Handlebars.compile(backend_add_html),

            /**
             *
             *  Methods
             *
             */

            updateAddButton: function() {
                if (Mist.backendsController.addingBackend || !Mist.backendAddController.formReady) {
                    $('#new-backend-ok').addClass('ui-state-disabled');
                } else {
                    $('#new-backend-ok').removeClass('ui-state-disabled');
                }
            },

            /**
             *
             *  Actions
             *
             */

            actions: {

                selectProvider: function(provider) {

                    Mist.backendAddController.set('newBackendFirstField', '');
                    Mist.backendAddController.set('newBackendSecondField', '');

                    $('#new-backend-second-field').attr('type', 'password');
                    $('#new-backend-provider').collapsible('collapse');
                    $('#openstack-bundle').hide();
                    $('#non-hp-cloud').hide();
                    $('#baremetal-bundle').hide();

                    if (provider.provider.indexOf('rackspace') > -1 || provider.provider.indexOf('linode') > -1) {
                        this.set('firstFieldLabel', 'Username');
                        this.set('secondFieldLabel', 'API Key');
                    } else if (provider.provider.indexOf('nephoscale') > -1) {
                        this.set('firstFieldLabel', 'Username');
                        this.set('secondFieldLabel', 'Password');
                    } else if (provider.provider.indexOf('digitalocean') > -1) {
                        this.set('firstFieldLabel', 'Client ID');
                        this.set('secondFieldLabel', 'API Key');
                    } else if (provider.provider.indexOf('openstack') > -1) {
                        this.set('firstFieldLabel', 'Username');
                        this.set('secondFieldLabel', 'Password');
                        $('#openstack-bundle').show();

                        //This is for HP Cloud specific
                        if (provider.provider.indexOf('region-') > -1) {
                            Mist.backendAddController.set('newBackendOpenStackURL', 'https://region-a.geo-1.identity.hpcloudsvc.com:35357/v2.0/');
                        } else {
                            $('#non-hp-cloud').show();
                        }
                    } else if (provider.provider.indexOf('bare_metal') > -1) {
                        this.set('firstFieldLabel', 'Hostname');
                        this.set('secondFieldLabel', 'User');
                        Mist.backendAddController.set('newBackendSecondField', 'root')

                        $('#new-backend-key .ui-listview').listview('refresh');
                        $('#new-backend-second-field').attr('type', '');
                        $('#baremetal-bundle').show();

                    } else {
                        this.set('firstFieldLabel', 'API Key');
                        this.set('secondFieldLabel', 'API Secret');
                    }

                    Mist.backendAddController.set('newBackendProvider', provider);
                    $('#new-backend-provider').collapsible('option', 'collapsedIcon', 'check');

                    // Autocomplete credentials
                    Mist.backendsController.content.some(function(backend) {
                        if ((provider.provider.split('_')[0] == 'ec2' && backend.provider.split('_')[0] == 'ec2') ||
                            (provider.provider.substr(0,9) == 'rackspace' && backend.provider.substr(0,9) == 'rackspace')) {
                                Mist.backendAddController.set('newBackendFirstField', backend.apikey);
                                Mist.backendAddController.set('newBackendSecondField', 'getsecretfromdb');
                                return true;
                            }
                    });
                },

                selectKey: function(key) {
                    $('#new-backend-key').collapsible('collapse');
                    Mist.backendAddController.set('newBackendKey', key);
                },

                createKeyClicked: function() {
                    Mist.keyAddController.open( function (success, key) {
                        if (success) {
                            Mist.backendAddController.set('newBackendKey', key);
                            $('#new-backend-key').collapsible('collapse');
                            $('#new-backend-key .ui-listview').listview('refresh');
                        }
                    });
                },

                backClicked: function() {
                    Mist.backendAddController.close();
                },

                addClicked: function() {
                    Mist.backendAddController.add();
                }
            },

            /**
             *
             *  Observers
             *
             */

            updateDoneButtonObserver: function() {
                Ember.run.once(this, 'updateAddButton');
            }.observes('Mist.backendsController.addingBackend', 'Mist.backendAddController.formReady')
        });
    }
);
<commit_msg>Clear backend add controller on provider selection<commit_after>define('app/views/backend_add', ['text!app/templates/backend_add.html', 'ember'],
    /**
     *  Add Backend Panel
     *
     *  @returns Class
     */
    function(backend_add_html) {
        return Ember.View.extend({

            /**
             *  Properties
             */

            firstFieldLabel: 'API Key',
            secondFieldLabel: 'API Secret',
            template: Ember.Handlebars.compile(backend_add_html),

            /**
             *
             *  Methods
             *
             */

            updateAddButton: function() {
                if (Mist.backendsController.addingBackend || !Mist.backendAddController.formReady) {
                    $('#new-backend-ok').addClass('ui-state-disabled');
                } else {
                    $('#new-backend-ok').removeClass('ui-state-disabled');
                }
            },

            /**
             *
             *  Actions
             *
             */

            actions: {

                selectProvider: function(provider) {

                    Mist.backendAddController._clear();

                    $('#new-backend-second-field').attr('type', 'password');
                    $('#new-backend-provider').collapsible('collapse');
                    $('#openstack-bundle').hide();
                    $('#non-hp-cloud').hide();
                    $('#baremetal-bundle').hide();

                    if (provider.provider.indexOf('rackspace') > -1 || provider.provider.indexOf('linode') > -1) {
                        this.set('firstFieldLabel', 'Username');
                        this.set('secondFieldLabel', 'API Key');
                    } else if (provider.provider.indexOf('nephoscale') > -1) {
                        this.set('firstFieldLabel', 'Username');
                        this.set('secondFieldLabel', 'Password');
                    } else if (provider.provider.indexOf('digitalocean') > -1) {
                        this.set('firstFieldLabel', 'Client ID');
                        this.set('secondFieldLabel', 'API Key');
                    } else if (provider.provider.indexOf('openstack') > -1) {
                        this.set('firstFieldLabel', 'Username');
                        this.set('secondFieldLabel', 'Password');
                        $('#openstack-bundle').show();

                        //This is for HP Cloud specific
                        if (provider.provider.indexOf('region-') > -1) {
                            Mist.backendAddController.set('newBackendOpenStackURL', 'https://region-a.geo-1.identity.hpcloudsvc.com:35357/v2.0/');
                        } else {
                            $('#non-hp-cloud').show();
                        }
                    } else if (provider.provider.indexOf('bare_metal') > -1) {
                        this.set('firstFieldLabel', 'Hostname');
                        this.set('secondFieldLabel', 'User');
                        Mist.backendAddController.set('newBackendSecondField', 'root')

                        $('#new-backend-key .ui-listview').listview('refresh');
                        $('#new-backend-second-field').attr('type', '');
                        $('#baremetal-bundle').show();

                    } else {
                        this.set('firstFieldLabel', 'API Key');
                        this.set('secondFieldLabel', 'API Secret');
                    }

                    Mist.backendAddController.set('newBackendProvider', provider);
                    $('#new-backend-provider').collapsible('option', 'collapsedIcon', 'check');

                    // Autocomplete credentials
                    Mist.backendsController.content.some(function(backend) {
                        if ((provider.provider.split('_')[0] == 'ec2' && backend.provider.split('_')[0] == 'ec2') ||
                            (provider.provider.substr(0,9) == 'rackspace' && backend.provider.substr(0,9) == 'rackspace')) {
                                Mist.backendAddController.set('newBackendFirstField', backend.apikey);
                                Mist.backendAddController.set('newBackendSecondField', 'getsecretfromdb');
                                return true;
                            }
                    });
                },

                selectKey: function(key) {
                    $('#new-backend-key').collapsible('collapse');
                    Mist.backendAddController.set('newBackendKey', key);
                },

                createKeyClicked: function() {
                    Mist.keyAddController.open( function (success, key) {
                        if (success) {
                            Mist.backendAddController.set('newBackendKey', key);
                            $('#new-backend-key').collapsible('collapse');
                            $('#new-backend-key .ui-listview').listview('refresh');
                        }
                    });
                },

                backClicked: function() {
                    Mist.backendAddController.close();
                },

                addClicked: function() {
                    Mist.backendAddController.add();
                }
            },

            /**
             *
             *  Observers
             *
             */

            updateDoneButtonObserver: function() {
                Ember.run.once(this, 'updateAddButton');
            }.observes('Mist.backendsController.addingBackend', 'Mist.backendAddController.formReady')
        });
    }
);
<|endoftext|>