<commit_before>/**
 * MultilineTextInputWidgets, like HTML textareas, are featuring customization options to
 * configure number of rows visible. In addition, these widgets can be autosized to fit user
 * inputs and can show {@link OO.ui.mixin.IconElement icons} and
 * {@link OO.ui.mixin.IndicatorElement indicators}.
 * Please see the [OOUI documentation on MediaWiki] [1] for more information and examples.
 *
 * This widget can be used inside an HTML form, such as a OO.ui.FormLayout.
 *
 *     @example
 *     // A MultilineTextInputWidget.
 *     var multilineTextInput = new OO.ui.MultilineTextInputWidget( {
 *         value: 'Text input on multiple lines'
 *     } );
 *     $( document.body ).append( multilineTextInput.$element );
 *
 * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs#MultilineTextInputWidget
 *
 * @class
 * @extends OO.ui.TextInputWidget
 *
 * @constructor
 * @param {Object} [config] Configuration options
 * @cfg {number} [rows] Number of visible lines in textarea. If used with `autosize`,
 *  specifies minimum number of rows to display.
 * @cfg {boolean} [autosize=false] Automatically resize the text input to fit its content.
 *  Use the #maxRows config to specify a maximum number of displayed rows.
 * @cfg {number} [maxRows] Maximum number of rows to display when #autosize is set to true.
 *  Defaults to the maximum of `10` and `2 * rows`, or `10` if `rows` isn't provided.
 */
OO.ui.MultilineTextInputWidget = function OoUiMultilineTextInputWidget( config ) {
	config = $.extend( {
		type: 'text'
	}, config );
	// Parent constructor
	OO.ui.MultilineTextInputWidget.super.call( this, config );

	// Properties
	this.autosize = !!config.autosize;
	this.styleHeight = null;
	this.minRows = config.rows !== undefined ? config.rows : '';
	this.maxRows = config.maxRows || Math.max( 2 * ( this.minRows || 0 ), 10 );

	// Clone for resizing
	if ( this.autosize ) {
		this.$clone = this.$input
			.clone()
			.removeAttr( 'id' )
			.removeAttr( 'name' )
			.insertAfter( this.$input )
			.attr( 'aria-hidden', 'true' )
			.addClass( 'oo-ui-element-hidden' );
	}

	// Events
	this.connect( this, {
		change: 'onChange'
	} );

	// Initialization
	if ( config.rows ) {
		this.$input.attr( 'rows', config.rows );
	}
	if ( this.autosize ) {
		this.$input.addClass( 'oo-ui-textInputWidget-autosized' );
		this.isWaitingToBeAttached = true;
		this.installParentChangeDetector();
	}
};

/* Setup */

OO.inheritClass( OO.ui.MultilineTextInputWidget, OO.ui.TextInputWidget );

/* Events */

/**
 * An `resize` event is emitted when the widget changes size via the autosize functionality.
 *
 * @event resize
 */

/* Static Methods */

/**
 * @inheritdoc
 */
OO.ui.MultilineTextInputWidget.static.gatherPreInfuseState = function ( node, config ) {
	var state = OO.ui.MultilineTextInputWidget.super.static.gatherPreInfuseState( node, config );
	state.scrollTop = config.$input.scrollTop();
	return state;
};

/* Methods */

/**
 * @inheritdoc
 */
OO.ui.MultilineTextInputWidget.prototype.onElementAttach = function () {
	OO.ui.MultilineTextInputWidget.super.prototype.onElementAttach.call( this );
	this.adjustSize();
};

/**
 * Handle change events.
 *
 * @private
 */
OO.ui.MultilineTextInputWidget.prototype.onChange = function () {
	this.adjustSize();
};

/**
 * @inheritdoc
 */
OO.ui.MultilineTextInputWidget.prototype.updatePosition = function () {
	OO.ui.MultilineTextInputWidget.super.prototype.updatePosition.call( this );
	this.adjustSize();
};

/**
 * @inheritdoc
 *
 * Modify to emit 'enter' on Ctrl/Meta+Enter, instead of plain Enter
 */
OO.ui.MultilineTextInputWidget.prototype.onKeyPress = function ( e ) {
	if (
		( e.which === OO.ui.Keys.ENTER && ( e.ctrlKey || e.metaKey ) ) ||
		// Some platforms emit keycode 10 for Control+Enter keypress in a textarea
		e.which === 10
	) {
		this.emit( 'enter', e );
	}
};

/**
 * Automatically adjust the size of the text input.
 *
 * This only affects multiline inputs that are {@link #autosize autosized}.
 *
 * @chainable
 * @return {OO.ui.Widget} The widget, for chaining
 * @fires resize
 */
OO.ui.MultilineTextInputWidget.prototype.adjustSize = function () {
	var scrollHeight, innerHeight, outerHeight, maxInnerHeight, measurementError,
		idealHeight, newHeight, scrollWidth, property;

	if ( this.$input.val() !== this.valCache ) {
		if ( this.autosize ) {
			this.$clone
				.val( this.$input.val() )
				.attr( 'rows', this.minRows )
				// Set inline height property to 0 to measure scroll height
				.css( 'height', 0 );

			this.$clone.removeClass( 'oo-ui-element-hidden' );

			this.valCache = this.$input.val();

			scrollHeight = this.$clone[ 0 ].scrollHeight;

			// Remove inline height property to measure natural heights
			this.$clone.css( 'height', '' );
			innerHeight = this.$clone.innerHeight();
			outerHeight = this.$clone.outerHeight();

			// Measure max rows height
			this.$clone
				.attr( 'rows', this.maxRows )
				.css( 'height', 'auto' )
				.val( '' );
			maxInnerHeight = this.$clone.innerHeight();

			// Difference between reported innerHeight and scrollHeight with no scrollbars present.
			// This is sometimes non-zero on Blink-based browsers, depending on zoom level.
			measurementError = maxInnerHeight - this.$clone[ 0 ].scrollHeight;
			idealHeight = Math.min( maxInnerHeight, scrollHeight + measurementError );

			this.$clone.addClass( 'oo-ui-element-hidden' );

			// Only apply inline height when expansion beyond natural height is needed
			// Use the difference between the inner and outer height as a buffer
			newHeight = idealHeight > innerHeight ? idealHeight + ( outerHeight - innerHeight ) : '';
			if ( newHeight !== this.styleHeight ) {
				this.$input.css( 'height', newHeight );
				this.styleHeight = newHeight;
				this.emit( 'resize' );
			}
		}
		scrollWidth = this.$input[ 0 ].offsetWidth - this.$input[ 0 ].clientWidth;
		if ( scrollWidth !== this.scrollWidth ) {
			property = this.$element.css( 'direction' ) === 'rtl' ? 'left' : 'right';
			// Reset
			this.$label.css( { right: '', left: '' } );
			this.$indicator.css( { right: '', left: '' } );

			if ( scrollWidth ) {
				this.$indicator.css( property, scrollWidth );
				if ( this.labelPosition === 'after' ) {
					this.$label.css( property, scrollWidth );
				}
			}

			this.scrollWidth = scrollWidth;
			this.positionLabel();
		}
	}
	return this;
};

/**
 * @inheritdoc
 * @protected
 */
OO.ui.MultilineTextInputWidget.prototype.getInputElement = function () {
	return $( '<textarea>' );
};

/**
 * Check if the input automatically adjusts its size.
 *
 * @return {boolean}
 */
OO.ui.MultilineTextInputWidget.prototype.isAutosizing = function () {
	return !!this.autosize;
};

/**
 * @inheritdoc
 */
OO.ui.MultilineTextInputWidget.prototype.restorePreInfuseState = function ( state ) {
	OO.ui.MultilineTextInputWidget.super.prototype.restorePreInfuseState.call( this, state );
	if ( state.scrollTop !== undefined ) {
		this.$input.scrollTop( state.scrollTop );
	}
};
<commit_msg>MultilineTextInput: Add 'force' param to adjustSize<commit_after>/**
 * MultilineTextInputWidgets, like HTML textareas, are featuring customization options to
 * configure number of rows visible. In addition, these widgets can be autosized to fit user
 * inputs and can show {@link OO.ui.mixin.IconElement icons} and
 * {@link OO.ui.mixin.IndicatorElement indicators}.
 * Please see the [OOUI documentation on MediaWiki] [1] for more information and examples.
 *
 * This widget can be used inside an HTML form, such as a OO.ui.FormLayout.
 *
 *     @example
 *     // A MultilineTextInputWidget.
 *     var multilineTextInput = new OO.ui.MultilineTextInputWidget( {
 *         value: 'Text input on multiple lines'
 *     } );
 *     $( document.body ).append( multilineTextInput.$element );
 *
 * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs#MultilineTextInputWidget
 *
 * @class
 * @extends OO.ui.TextInputWidget
 *
 * @constructor
 * @param {Object} [config] Configuration options
 * @cfg {number} [rows] Number of visible lines in textarea. If used with `autosize`,
 *  specifies minimum number of rows to display.
 * @cfg {boolean} [autosize=false] Automatically resize the text input to fit its content.
 *  Use the #maxRows config to specify a maximum number of displayed rows.
 * @cfg {number} [maxRows] Maximum number of rows to display when #autosize is set to true.
 *  Defaults to the maximum of `10` and `2 * rows`, or `10` if `rows` isn't provided.
 */
OO.ui.MultilineTextInputWidget = function OoUiMultilineTextInputWidget( config ) {
	config = $.extend( {
		type: 'text'
	}, config );
	// Parent constructor
	OO.ui.MultilineTextInputWidget.super.call( this, config );

	// Properties
	this.autosize = !!config.autosize;
	this.styleHeight = null;
	this.minRows = config.rows !== undefined ? config.rows : '';
	this.maxRows = config.maxRows || Math.max( 2 * ( this.minRows || 0 ), 10 );

	// Clone for resizing
	if ( this.autosize ) {
		this.$clone = this.$input
			.clone()
			.removeAttr( 'id' )
			.removeAttr( 'name' )
			.insertAfter( this.$input )
			.attr( 'aria-hidden', 'true' )
			.addClass( 'oo-ui-element-hidden' );
	}

	// Events
	this.connect( this, {
		change: 'onChange'
	} );

	// Initialization
	if ( config.rows ) {
		this.$input.attr( 'rows', config.rows );
	}
	if ( this.autosize ) {
		this.$input.addClass( 'oo-ui-textInputWidget-autosized' );
		this.isWaitingToBeAttached = true;
		this.installParentChangeDetector();
	}
};

/* Setup */

OO.inheritClass( OO.ui.MultilineTextInputWidget, OO.ui.TextInputWidget );

/* Events */

/**
 * An `resize` event is emitted when the widget changes size via the autosize functionality.
 *
 * @event resize
 */

/* Static Methods */

/**
 * @inheritdoc
 */
OO.ui.MultilineTextInputWidget.static.gatherPreInfuseState = function ( node, config ) {
	var state = OO.ui.MultilineTextInputWidget.super.static.gatherPreInfuseState( node, config );
	state.scrollTop = config.$input.scrollTop();
	return state;
};

/* Methods */

/**
 * @inheritdoc
 */
OO.ui.MultilineTextInputWidget.prototype.onElementAttach = function () {
	OO.ui.MultilineTextInputWidget.super.prototype.onElementAttach.call( this );
	this.adjustSize();
};

/**
 * Handle change events.
 *
 * @private
 */
OO.ui.MultilineTextInputWidget.prototype.onChange = function () {
	this.adjustSize();
};

/**
 * @inheritdoc
 */
OO.ui.MultilineTextInputWidget.prototype.updatePosition = function () {
	OO.ui.MultilineTextInputWidget.super.prototype.updatePosition.call( this );
	this.adjustSize();
};

/**
 * @inheritdoc
 *
 * Modify to emit 'enter' on Ctrl/Meta+Enter, instead of plain Enter
 */
OO.ui.MultilineTextInputWidget.prototype.onKeyPress = function ( e ) {
	if (
		( e.which === OO.ui.Keys.ENTER && ( e.ctrlKey || e.metaKey ) ) ||
		// Some platforms emit keycode 10 for Control+Enter keypress in a textarea
		e.which === 10
	) {
		this.emit( 'enter', e );
	}
};

/**
 * Automatically adjust the size of the text input.
 *
 * This only affects multiline inputs that are {@link #autosize autosized}.
 *
 * @chainable
 * @param {boolean} [force] Force an update, even if the value hasn't changed
 * @return {OO.ui.Widget} The widget, for chaining
 * @fires resize
 */
OO.ui.MultilineTextInputWidget.prototype.adjustSize = function ( force ) {
	var scrollHeight, innerHeight, outerHeight, maxInnerHeight, measurementError,
		idealHeight, newHeight, scrollWidth, property;

	if ( force || this.$input.val() !== this.valCache ) {
		if ( this.autosize ) {
			this.$clone
				.val( this.$input.val() )
				.attr( 'rows', this.minRows )
				// Set inline height property to 0 to measure scroll height
				.css( 'height', 0 );

			this.$clone.removeClass( 'oo-ui-element-hidden' );

			this.valCache = this.$input.val();

			scrollHeight = this.$clone[ 0 ].scrollHeight;

			// Remove inline height property to measure natural heights
			this.$clone.css( 'height', '' );
			innerHeight = this.$clone.innerHeight();
			outerHeight = this.$clone.outerHeight();

			// Measure max rows height
			this.$clone
				.attr( 'rows', this.maxRows )
				.css( 'height', 'auto' )
				.val( '' );
			maxInnerHeight = this.$clone.innerHeight();

			// Difference between reported innerHeight and scrollHeight with no scrollbars present.
			// This is sometimes non-zero on Blink-based browsers, depending on zoom level.
			measurementError = maxInnerHeight - this.$clone[ 0 ].scrollHeight;
			idealHeight = Math.min( maxInnerHeight, scrollHeight + measurementError );

			this.$clone.addClass( 'oo-ui-element-hidden' );

			// Only apply inline height when expansion beyond natural height is needed
			// Use the difference between the inner and outer height as a buffer
			newHeight = idealHeight > innerHeight ? idealHeight + ( outerHeight - innerHeight ) : '';
			if ( newHeight !== this.styleHeight ) {
				this.$input.css( 'height', newHeight );
				this.styleHeight = newHeight;
				this.emit( 'resize' );
			}
		}
		scrollWidth = this.$input[ 0 ].offsetWidth - this.$input[ 0 ].clientWidth;
		if ( scrollWidth !== this.scrollWidth ) {
			property = this.$element.css( 'direction' ) === 'rtl' ? 'left' : 'right';
			// Reset
			this.$label.css( { right: '', left: '' } );
			this.$indicator.css( { right: '', left: '' } );

			if ( scrollWidth ) {
				this.$indicator.css( property, scrollWidth );
				if ( this.labelPosition === 'after' ) {
					this.$label.css( property, scrollWidth );
				}
			}

			this.scrollWidth = scrollWidth;
			this.positionLabel();
		}
	}
	return this;
};

/**
 * @inheritdoc
 * @protected
 */
OO.ui.MultilineTextInputWidget.prototype.getInputElement = function () {
	return $( '<textarea>' );
};

/**
 * Check if the input automatically adjusts its size.
 *
 * @return {boolean}
 */
OO.ui.MultilineTextInputWidget.prototype.isAutosizing = function () {
	return !!this.autosize;
};

/**
 * @inheritdoc
 */
OO.ui.MultilineTextInputWidget.prototype.restorePreInfuseState = function ( state ) {
	OO.ui.MultilineTextInputWidget.super.prototype.restorePreInfuseState.call( this, state );
	if ( state.scrollTop !== undefined ) {
		this.$input.scrollTop( state.scrollTop );
	}
};
<|endoftext|>


<commit_before>// Ranks the upcoming movies against the user's likes (movie_likes)

var User = require('./../models/user');
var UserLikes = require('./../models/user_likes');
var UpcomingMovies = require('./../models/upcoming_movies');

var GENRE_AMOUNT = 5;
var DIRECTOR_AMOUNT = 1;
var ACTOR_AMOUNT = 2;
var FRIEND_AMOUNT = 5;

function computeSimilarity(array, otherArray, score) {
    var result = 0;

    for (var i = 0; i < array.length; i++) {
        var item = array[i];
        if (otherArray.indexOf(item) !== -1) {
            result += score;
        }
    }

    return result;
}

module.exports = function(user, callback) {
    console.log(user);
    UpcomingMovies.find({}, function(error, movies) {
        if (error) {
            callback(null);
        }

        var rankedData = []; // List of movie objects in the correct order (highest rank to lowest)
        for (var i = 0; i < movies.length; i++) {
            var movie = movies[i];
            // Check to see that this movie has not already been seen
            if (user.seen && user.seen.indexOf(movie) === -1) {
                // movie_score = max_{a \in movie_likes} similarity(a, movie)
                var bestScore = 0;
                for (var j = 0; j < user.movie_likes.length; j++) {
                    var likedMovie = user.movie_likes[j];
                    var genreScore = computeSimilarity(movie.genres, likedMovie.genres, GENRE_AMOUNT);
                    var directorScore = computeSimilarity(movie.directors, likedMovie.directors, DIRECTOR_AMOUNT);
                    var actorScore = computeSimilarity(movie.actors, likedMovie.actors, ACTOR_AMOUNT);
                    var score = genreScore + directorScore + actorScore;

                    if (score > bestScore) {
                        bestScore = score;
                    }
                }

                // Increase the score if recommended by a friend
                if (movie.recommended_by != "us") {
                    bestScore += FRIEND_AMOUNT;
                }

                rankedData.push({score : bestScore, data : movie});
            }
        }

        // Sort the data by the scores
        rankedData.sort(function(a, b) {
            return b.score - a.score;
        });

        // Grab the final objects and ignore the scores
        data = [];
        for (var i = 0; i < rankedData.length; i++) {
            movieScore = rankedData[i];
            data.push(movieScore.data);
        }

        user.queue = data;
        user.save(function(error, newUser) {
            if (error) {
                return callback(null);
            }

            return callback(newUser);
        });
    });
};
<commit_msg>Final Weights?<commit_after>// Ranks the upcoming movies against the user's likes (movie_likes)

var User = require('./../models/user');
var UserLikes = require('./../models/user_likes');
var UpcomingMovies = require('./../models/upcoming_movies');

var GENRE_AMOUNT = 4;
var DIRECTOR_AMOUNT = 1;
var ACTOR_AMOUNT = 3;
var FRIEND_AMOUNT = 3;

function computeSimilarity(array, otherArray, score) {
    var result = 0;

    for (var i = 0; i < array.length; i++) {
        var item = array[i];
        if (otherArray.indexOf(item) !== -1) {
            result += score;
        }
    }

    return result;
}

module.exports = function(user, callback) {
    console.log(user);
    UpcomingMovies.find({}, function(error, movies) {
        if (error) {
            callback(null);
        }

        var rankedData = []; // List of movie objects in the correct order (highest rank to lowest)
        for (var i = 0; i < movies.length; i++) {
            var movie = movies[i];
            // Check to see that this movie has not already been seen
            if (user.seen && user.seen.indexOf(movie) === -1) {
                // movie_score = max_{a \in movie_likes} similarity(a, movie)
                var bestScore = 0;
                for (var j = 0; j < user.movie_likes.length; j++) {
                    var likedMovie = user.movie_likes[j];
                    var genreScore = computeSimilarity(movie.genres, likedMovie.genres, GENRE_AMOUNT);
                    var directorScore = computeSimilarity(movie.directors, likedMovie.directors, DIRECTOR_AMOUNT);
                    var actorScore = computeSimilarity(movie.actors, likedMovie.actors, ACTOR_AMOUNT);
                    var score = genreScore + directorScore + actorScore;

                    if (score > bestScore) {
                        bestScore = score;
                    }
                }

                // Increase the score if recommended by a friend
                if (movie.recommended_by != "us") {
                    bestScore += FRIEND_AMOUNT;
                }

                rankedData.push({score : bestScore, data : movie});
            }
        }

        // Sort the data by the scores
        rankedData.sort(function(a, b) {
            return b.score - a.score;
        });

        // Grab the final objects and ignore the scores
        data = [];
        for (var i = 0; i < rankedData.length; i++) {
            movieScore = rankedData[i];
            data.push(movieScore.data);
        }

        user.queue = data;
        user.save(function(error, newUser) {
            if (error) {
                return callback(null);
            }

            return callback(newUser);
        });
    });
};
<|endoftext|>


<commit_before>var Class = require("../core/class");
var Object = require("../core/object");

var CCore = require("../helpers/ccore").CCore;
var CJournal = require("../helpers/cjournal").CJournal;

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////

var util = require("util");
var crc = require("../helpers/crc");

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////

var isValidBlock = function (block) {

	if (block.length != 4) {
		return false;
	}

	var sum = 27;

	for (var idx = 0; idx < block.length - 1; idx++) {
		var item = block[idx];

		for (var i = 0; i < item.length; i++) {
			sum += item.charCodeAt(i);
		}
	}

	sum = sum & 0xFF;

	return (block[block.length - 1] === sum.toString(16));
};

var covertResponse = function (response) {
	var object = {};

	for (var block in response) {
		var circuit = {};

		if (object[block[0]] != undefined) {
			circuit = object[block[0]];
		} else {
			object[block[0]] = circuit;
		}

		circuit[block[1]] = block[2];
	}

	return object;
};

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////

var TC05 = Class.Inherit("TC05", Object, function (name, context) {

	Class.Construct(this, name);

	this.core = new CCore("CCoreHelper", context);
	this.journal = new CJournal("CJournalHelper", context);

	return this;
});

TC05.prototype.rawRequestPacket = function (cmd, param1, param2) {

	var requestPacket = null;

	if (param1 || param2) {
		var params = param1;

		if (param2 != null) {
			params = (params != null) ? params + "," + param2 : param2;
		}

		requestPacket = util.format("%s(%s)\r", cmd, params);
	} else {
		requestPacket = util.format("%s\r", cmd);
	}

	return new Buffer(requestPacket, "ascii");
};

TC05.prototype.rawResponsePacket = function (buffer) {

	var responsePacket = buffer.toString("ascii").split("\r");
	var response = [];

	for (rawBlock in responsePacket) {
		var block = rawBlock.split("\t");

		if (isValidBlock(block)) {
			response.push(block)
		}
	}

	return  (response.length > 0) ? covertResponse(response) : null;
};

TC05.prototype.getDeviceInfo = function (rawReadFunc, rawWriteFunc, cb) {

	var that = this;

	try {
		rawWriteFunc(that.rawRequestPacket("RH"), function (err) {
			try {
				rawReadFunc(2000, function (err, rawData) {
					try {
						var data = that.rawResponsePacket(rawData);

						if (data != null) {
							console.log(data);

							//cb(null, data);
						} else {
							cb(new Error("An error occurred when reading from device"));
						}
					} catch (e) {
						that.journal.error(e.stack.toString());
						cb(e);
					}
				});
			} catch (e) {
				that.journal.error(e.stack.toString());
				cb(e);
			}
		});
	} catch (e) {
		that.journal.error(e.stack.toString());
		cb(e);
	}
};

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////

module.exports.TC05 = TC05;<commit_msg>Some changes<commit_after>var Class = require("../core/class");
var Object = require("../core/object");

var CCore = require("../helpers/ccore").CCore;
var CJournal = require("../helpers/cjournal").CJournal;

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////

var util = require("util");
var crc = require("../helpers/crc");

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////

var isValidBlock = function (block) {

	if (block.length != 4) {
		return false;
	}

	var sum = 27;

	for (var idx = 0; idx < block.length - 1; idx++) {
		var item = block[idx];

		for (var i = 0; i < item.length; i++) {
			sum += item.charCodeAt(i);
		}
	}

	sum = sum & 0xFF;

	return (block[block.length - 1].toUpperCase() === sum.toString(16).toUpperCase());
};

var covertResponse = function (response) {
	var object = {};

	for (var i = 0; i < response.length; i++) {
		var block = response[i];
		var circuit = {};

		if (object[block[0]] != undefined) {
			circuit = object[block[0]];
		} else {
			object[block[0]] = circuit;
		}

		circuit[block[1]] = block[2];
	}

	return object;
};

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////

var TC05 = Class.Inherit("TC05", Object, function (name, context) {

	Class.Construct(this, name);

	this.core = new CCore("CCoreHelper", context);
	this.journal = new CJournal("CJournalHelper", context);

	return this;
});

TC05.prototype.rawRequestPacket = function (cmd, param1, param2) {

	var requestPacket = null;

	if (param1 || param2) {
		var params = param1;

		if (param2 != null) {
			params = (params != null) ? params + "," + param2 : param2;
		}

		requestPacket = util.format("%s(%s)\r", cmd, params);
	} else {
		requestPacket = util.format("%s\r", cmd);
	}

	return new Buffer(requestPacket, "ascii");
};

TC05.prototype.rawResponsePacket = function (buffer) {

	var responsePacket = buffer.toString("ascii").split("\r");
	var response = [];

	for (var i = 0; i < responsePacket.length - 1; i++) {
		var block = responsePacket[i].split("\t");

		if (isValidBlock(block)) {
			response.push(block);
		}
	}

	return  (response.length > 0) ? covertResponse(response) : null;
};

TC05.prototype.getDeviceInfo = function (rawReadFunc, rawWriteFunc, cb) {

	var that = this;

	try {
		rawWriteFunc(that.rawRequestPacket("RH"), function (err) {
			try {
				rawReadFunc(2000, function (err, rawData) {
					try {
						var data = that.rawResponsePacket(rawData);

						if (data != null) {
							console.log(data);

							//cb(null, data);
						} else {
							cb(new Error("An error occurred when reading from device"));
						}
					} catch (e) {
						that.journal.error(e.stack.toString());
						cb(e);
					}
				});
			} catch (e) {
				that.journal.error(e.stack.toString());
				cb(e);
			}
		});
	} catch (e) {
		that.journal.error(e.stack.toString());
		cb(e);
	}
};

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////

module.exports.TC05 = TC05;<|endoftext|>


<commit_before>/*
# Copyright (c) 2015 Blizzard Entertainment
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
*/
"use strict";

const path = require('path');
const fs = require('fs');

const mpq = require('mpyqjs/mpyq');
const MPQArchive = exports.MPQArchive = mpq.MPQArchive;
const protocol29406 = require('./lib/protocol29406');

// sort object and read buffer strings, recursively
function prepare(obj) {
  if (!obj) {
     return obj;
  } else if (obj instanceof Buffer) {
    return obj.toString();
  } else if (Array.isArray(obj)) {
    for (var i = 0, ln = obj.length; i < ln; i += 1) {
      obj[i] = prepare(obj[i]);
    }
    return obj;
  } else if (typeof obj === 'object') {
    var ret = {};
    Object.keys(obj).sort().forEach(key => ret[key] = prepare(obj[key]));
    return ret;
  }
  return obj;
}

// EventLogger
function EventLogger() {
  this._eventStats = {};
}

EventLogger.prototype.log = function(event) {
  // update stats
  if ('_event' in event && '_bits' in event) {
    var stat = this._eventStats[event._event] || [0, 0];
    stats[0] += 1; // count of events
    stats[1] += event._bits; // count of bits
    this._eventStats[event._event] = stat;
  }

  console.log(event);
};

EventLogger.prototype.logStats = function() {
  throw 'not yet ported';
  Object.keys(this._eventStats).sort().forEach(key => {

  });
};

// ReplayDecoder
const ReplayDecoder = exports.ReplayDecoder = function(file) {
  if (typeof file === 'string') {
    try {
      if (!path.isAbsolute(file))
        file = path.join(process.cwd(), file);
      this.archive = new MPQArchive(file);
      this.filename = file;
    } catch (err) {
      console.log('Error opening replay: ', file);
      console.log(err);
    }
  } else if (file instanceof MPQArchive) {
    archive = file;
    if (archive.filename) this.filename = archive.filename;
  } else {
    console.log('Unsupported setup parameter: ', file);
  }

  if (!this.archive) return null;

  this.logger = new EventLogger();

  // Read the protocol header, this can be read with any protocol
  var contents = this.archive.header.userDataHeader.content;
  this.header = prepare(protocol29406.decodeReplayHeader(contents));

  // The header's baseBuild determines which protocol to use
  this.baseBuild = this.header.m_version.m_baseBuild;
  try {
    this.protocol = require('./lib/protocol' + this.baseBuild);
  } catch (err) {
    // TODO - should return error instead of console.log to not pollute output
    console.log('Unsupported base build: ' + this.baseBuild);
  }
};

ReplayDecoder.prototype.parse = function(name) {
  if (name === 'details') {
    return this.details = prepare(this.protocol.decodeReplayDetails(this.archive.readFile('replay.details')));
  } else if (name === 'initdata') {
    return this.initdata = prepare(this.protocol.decodeReplayInitdata(this.archive.readFile('replay.initData')));
  } else if (name === 'gameevents') {
    this.gameevents = [];
    for (event of this.protocol.decodeReplayGameEvents(this.archive.readFile('replay.game.events'))) {
      this.gameevents.push(prepare(event));
    }
    return this.gameevents;
  } else if (name === 'messageevents') {
    this.messageevents = [];
    for (event of this.protocol.decodeReplayMessageEvents(this.archive.readFile('replay.message.events'))) {
      this.messageevents.push(prepare(event));
    }
    return this.messageevents;
  } else if (name === 'trackerevents') {
    this.trackerevents = [];
    for (event of this.protocol.decodeReplayTrackerEvents(this.archive.readFile('replay.tracker.events'))) {
      this.trackerevents.push(prepare(event));
    }
    return this.trackerevents;
  } else if (name === 'attributesevents') {
    return this.attributesevents = prepare(this.protocol.decodeReplayAttributesEvents(this.archive.readFile('replay.attributes.events')));
  } else {
    console.log('Unsupported file:', name);
  }
};

ReplayDecoder.prototype.log = function(name) {
  if (this[name]) {
    if (name.indexOf('events') > -1) {
      var events = this[name], event;
      for (event of events) this.log(event);
    } else {
      if (name === 'initdata') {
        this.logger.log(this.initdata.m_syncLobbyState.m_gameDescription.m_cacheHandles);
      }
      this.logger.log(this[name]);
    }
  }
};

ReplayDecoder.prototype.logStats = function () {
  this.logger.logStats();
};

ReplayDecoder.prototype.extractSync = function(name) {
  var attr = name.split('.').join('');
  if (this[attr]) {
    var dirname = path.join(process.cwd(), path.basename(this.filename, path.extname(this.filename)));

    try {
      fs.statSync(dirname);
    } catch (err) {
      fs.mkdirSync(dirname);
    }

    try {
      fs.writeFileSync(
        path.join(dirname, ['replay', name, 'json'].join('.')),
        JSON.stringify(this[attr], null, '  ')
      );
    } catch(err) {
      console.trace(err);
    }
  }
};

if (require.main === module) {
  (function () {
    const yargs = require('yargs')
                    .usage('usage: heroprotocol.js replayFile [--help] [--gameevents] [--messageevents] [--trackerevents] [--attributeevents] [--header] [--details] [--initdata] [--stats] [--print] [--extract]')
                    .demand(1)
                    .option('h', { alias: 'help', type: 'boolean', desc: 'show this help' })
                    .option('p', { alias: 'print', type: 'boolean', desc: 'print parsed information' })
                    .option('x', { alias: 'extract', type: 'boolean', desc: 'extract parsed information to disk in JSON format' })
                    .option('H', { alias: 'header', type: 'boolean', desc: 'parse protocol header' })
                    .option('d', { alias: 'details', type: 'boolean', desc: 'parse protocol details' })
                    .option('i', { alias: 'initdata', type: 'boolean', desc: 'parse protocol initdata' })
                    .option('g', { alias: 'gameevents', type: 'boolean', desc: 'parse protocol gameevents' })
                    .option('m', { alias: 'messageevents', type: 'boolean', desc: 'parse protocol messageevents' })
                    .option('t', { alias: 'trackerevents', type: 'boolean', desc: 'parse protocol trackerevents' })
                    .option('a', { alias: 'attributeevents', type: 'boolean', desc: 'parse protocol attributeevents' })
                    //.option('s', { alias: 'stats', type: 'boolean', desc: 'print stat' })
                    .option('players', { type: 'boolean', desc: 'print players name' });
    const args = yargs.argv;

    if (args.help) {
      yargs.showHelp();
      process.exit();
    }

    if (!args.print && !args.extract && !args.players) {
      console.log('At leas one command must be specified: -p --print, -x --extract, --players');
      process.exit(1);
    }

    if (args.players) args.details = args.d = true;

    var replayDecoder = new ReplayDecoder(process.cwd() + path.sep + args._[0]);

    if (args.header) {
      if (args.print) replayDecoder.log('header');
      if (args.extract) replayDecoder.extractSync('header');
    }

    if(!replayDecoder.protocol) process.exit(1);

    // Handle protocol details
    if (args.details) {
      replayDecoder.parse('details');
      if (args.print) replayDecoder.log('details');
      if (args.extract) replayDecoder.extractSync('details');
    }

    // Handle protocol init data
    if (args.initdata) {
      replayDecoder.parse('initdata');
      if (args.print) replayDecoder.log('initdata');
      if (args.extract) replayDecoder.extractSync('initdata');
    }

    // Handle game events and/or game events stats
    if (args.gameevents) {
      replayDecoder.parse('gameevents');
      if (args.print) replayDecoder.log('gameevents');
      if (args.extract) replayDecoder.extractSync('game.events');
    }

    // Handle message events
    if (args.messageevents) {
      replayDecoder.parse('messageevents');
      if (args.print) replayDecoder.log('messageevents');
      if (args.extract) replayDecoder.extractSync('message.events');
    }

    // Handle tracker events
    if (args.trackerevents) {
      replayDecoder.parse('trackerevents');
      if (args.print) replayDecoder.log('trackerevents');
      if (args.extract) replayDecoder.extractSync('tracker.events');
    }

    // Handle attributes events
    if (args.attributeevents) {
      replayDecoder.parse('attributesevents');
      if (args.print) replayDecoder.log('attributesevents');
      if (args.extract) replayDecoder.extractSync('attributes.events');
    }

    // Print stats
    if (args.stats) {
      replayDecoder.logStats();
    }

  })();
}
<commit_msg>catch event parsing errors<commit_after>/*
# Copyright (c) 2015 Blizzard Entertainment
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
*/
"use strict";

const path = require('path');
const fs = require('fs');

const mpq = require('mpyqjs/mpyq');
const MPQArchive = exports.MPQArchive = mpq.MPQArchive;
const protocol29406 = require('./lib/protocol29406');

// sort object and read buffer strings, recursively
function prepare(obj) {
  if (!obj) {
     return obj;
  } else if (obj instanceof Buffer) {
    return obj.toString();
  } else if (Array.isArray(obj)) {
    for (var i = 0, ln = obj.length; i < ln; i += 1) {
      obj[i] = prepare(obj[i]);
    }
    return obj;
  } else if (typeof obj === 'object') {
    var ret = {};
    Object.keys(obj).sort().forEach(key => ret[key] = prepare(obj[key]));
    return ret;
  }
  return obj;
}

// EventLogger
function EventLogger() {
  this._eventStats = {};
}

EventLogger.prototype.log = function(event) {
  // update stats
  if ('_event' in event && '_bits' in event) {
    var stat = this._eventStats[event._event] || [0, 0];
    stats[0] += 1; // count of events
    stats[1] += event._bits; // count of bits
    this._eventStats[event._event] = stat;
  }

  console.log(event);
};

EventLogger.prototype.logStats = function() {
  throw 'not yet ported';
  Object.keys(this._eventStats).sort().forEach(key => {

  });
};

// ReplayDecoder
const ReplayDecoder = exports.ReplayDecoder = function(file) {
  if (typeof file === 'string') {
    try {
      if (!path.isAbsolute(file))
        file = path.join(process.cwd(), file);
      this.archive = new MPQArchive(file);
      this.filename = file;
    } catch (err) {
      console.log('Error opening replay: ', file);
      console.log(err);
    }
  } else if (file instanceof MPQArchive) {
    archive = file;
    if (archive.filename) this.filename = archive.filename;
  } else {
    console.log('Unsupported setup parameter: ', file);
  }

  if (!this.archive) return null;

  this.logger = new EventLogger();

  // Read the protocol header, this can be read with any protocol
  var contents = this.archive.header.userDataHeader.content;
  this.header = prepare(protocol29406.decodeReplayHeader(contents));

  // The header's baseBuild determines which protocol to use
  this.baseBuild = this.header.m_version.m_baseBuild;
  try {
    this.protocol = require('./lib/protocol' + this.baseBuild);
  } catch (err) {
    // TODO - should return error instead of console.log to not pollute output
    console.log('Unsupported base build: ' + this.baseBuild);
  }
};

ReplayDecoder.prototype.parse = function(name) {
  if (name === 'details') {
    return this.details = prepare(this.protocol.decodeReplayDetails(this.archive.readFile('replay.details')));
  } else if (name === 'initdata') {
    return this.initdata = prepare(this.protocol.decodeReplayInitdata(this.archive.readFile('replay.initData')));
  } else if (name === 'gameevents') {
    this.gameevents = [];
    try {
      for (var event of this.protocol.decodeReplayGameEvents(this.archive.readFile('replay.game.events'))) {
        this.gameevents.push(prepare(event));
      }
    } catch (err) {
      console.trace(err);
    }
    return this.gameevents;
  } else if (name === 'messageevents') {
    this.messageevents = [];
    try {
      for (var event of this.protocol.decodeReplayMessageEvents(this.archive.readFile('replay.message.events'))) {
        this.messageevents.push(prepare(event));
      }
    } catch (err) {
      console.trace(err);
    }
    return this.messageevents;
  } else if (name === 'trackerevents') {
    this.trackerevents = [];
    try {
      for (var event of this.protocol.decodeReplayTrackerEvents(this.archive.readFile('replay.tracker.events'))) {
        this.trackerevents.push(prepare(event));
      }
    } catch (err) {
      console.trace(err);
    }
    return this.trackerevents;
  } else if (name === 'attributesevents') {
    return this.attributesevents = prepare(this.protocol.decodeReplayAttributesEvents(this.archive.readFile('replay.attributes.events')));
  } else {
    console.log('Unsupported file:', name);
  }
};

ReplayDecoder.prototype.log = function(name) {
  if (this[name]) {
    if (name.indexOf('events') > -1) {
      var events = this[name], event;
      for (event of events) this.log(event);
    } else {
      if (name === 'initdata') {
        this.logger.log(this.initdata.m_syncLobbyState.m_gameDescription.m_cacheHandles);
      }
      this.logger.log(this[name]);
    }
  }
};

ReplayDecoder.prototype.logStats = function () {
  this.logger.logStats();
};

ReplayDecoder.prototype.extractSync = function(name) {
  var attr = name.split('.').join('');
  if (this[attr]) {
    var dirname = path.join(process.cwd(), path.basename(this.filename, path.extname(this.filename)));

    try {
      fs.statSync(dirname);
    } catch (err) {
      fs.mkdirSync(dirname);
    }

    try {
      fs.writeFileSync(
        path.join(dirname, ['replay', name, 'json'].join('.')),
        JSON.stringify(this[attr], null, '  ')
      );
    } catch(err) {
      console.trace(err);
    }
  }
};

if (require.main === module) {
  (function () {
    const yargs = require('yargs')
                    .usage('usage: heroprotocol.js replayFile [--help] [--gameevents] [--messageevents] [--trackerevents] [--attributeevents] [--header] [--details] [--initdata] [--stats] [--print] [--extract]')
                    .demand(1)
                    .option('h', { alias: 'help', type: 'boolean', desc: 'show this help' })
                    .option('p', { alias: 'print', type: 'boolean', desc: 'print parsed information' })
                    .option('x', { alias: 'extract', type: 'boolean', desc: 'extract parsed information to disk in JSON format' })
                    .option('H', { alias: 'header', type: 'boolean', desc: 'parse protocol header' })
                    .option('d', { alias: 'details', type: 'boolean', desc: 'parse protocol details' })
                    .option('i', { alias: 'initdata', type: 'boolean', desc: 'parse protocol initdata' })
                    .option('g', { alias: 'gameevents', type: 'boolean', desc: 'parse protocol gameevents' })
                    .option('m', { alias: 'messageevents', type: 'boolean', desc: 'parse protocol messageevents' })
                    .option('t', { alias: 'trackerevents', type: 'boolean', desc: 'parse protocol trackerevents' })
                    .option('a', { alias: 'attributeevents', type: 'boolean', desc: 'parse protocol attributeevents' })
                    //.option('s', { alias: 'stats', type: 'boolean', desc: 'print stat' })
                    .option('players', { type: 'boolean', desc: 'print players name' });
    const args = yargs.argv;

    if (args.help) {
      yargs.showHelp();
      process.exit();
    }

    if (!args.print && !args.extract && !args.players) {
      console.log('At leas one command must be specified: -p --print, -x --extract, --players');
      process.exit(1);
    }

    if (args.players) args.details = args.d = true;

    var replayDecoder = new ReplayDecoder(process.cwd() + path.sep + args._[0]);

    if (args.header) {
      if (args.print) replayDecoder.log('header');
      if (args.extract) replayDecoder.extractSync('header');
    }

    if(!replayDecoder.protocol) process.exit(1);

    // Handle protocol details
    if (args.details) {
      replayDecoder.parse('details');
      if (args.print) replayDecoder.log('details');
      if (args.extract) replayDecoder.extractSync('details');
    }

    // Handle protocol init data
    if (args.initdata) {
      replayDecoder.parse('initdata');
      if (args.print) replayDecoder.log('initdata');
      if (args.extract) replayDecoder.extractSync('initdata');
    }

    // Handle game events and/or game events stats
    if (args.gameevents) {
      replayDecoder.parse('gameevents');
      if (args.print) replayDecoder.log('gameevents');
      if (args.extract) replayDecoder.extractSync('game.events');
    }

    // Handle message events
    if (args.messageevents) {
      replayDecoder.parse('messageevents');
      if (args.print) replayDecoder.log('messageevents');
      if (args.extract) replayDecoder.extractSync('message.events');
    }

    // Handle tracker events
    if (args.trackerevents) {
      replayDecoder.parse('trackerevents');
      if (args.print) replayDecoder.log('trackerevents');
      if (args.extract) replayDecoder.extractSync('tracker.events');
    }

    // Handle attributes events
    if (args.attributeevents) {
      replayDecoder.parse('attributesevents');
      if (args.print) replayDecoder.log('attributesevents');
      if (args.extract) replayDecoder.extractSync('attributes.events');
    }

    // Print stats
    if (args.stats) {
      replayDecoder.logStats();
    }

  })();
}
<|endoftext|>


<commit_before>/*
 * also includes methods for dealing with
 * keywords and search engines
 */
function Bookmarks()
{
    ////////////////////////////////////////////////////////////////////////////////
    ////////////////////// PRIVATE SECTION /////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////
    const search_service = Components.classes["@mozilla.org/browser/search-service;1"].
                           getService(Components.interfaces.nsIBrowserSearchService);
    const rdf_service    = Components.classes["@mozilla.org/rdf/rdf-service;1"].
                           getService( Components.interfaces.nsIRDFService );

    var bookmarks = null;
    var keywords = null;

    if(vimperator.options["preload"])
        setTimeout(function() { load(); } , 100);

    function load()
    {
        // update our bookmark cache
        var root = rdf_service.GetResource("NC:BookmarksRoot");
        bookmarks  = new Array(); // also clear our bookmark cache
        keywords   = new Array();

        var bmarks = [];   // here getAllChildren will store the bookmarks
        BookmarksUtils.getAllChildren(root, bmarks);
        for(var bm in bmarks)
        {
            if (bmarks[bm][0] && bmarks[bm][1])
                bookmarks.push([bmarks[bm][1].Value, bmarks[bm][0].Value ]);

            // keyword
            if(bmarks[bm][1] && bmarks[bm][2])
                keywords.push([bmarks[bm][2].Value, bmarks[bm][0].Value, bmarks[bm][1].Value]);
        }
    }

    ////////////////////////////////////////////////////////////////////////////////
    ////////////////////// PUBLIC SECTION //////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////
    /*
     * @return a new Array() of our bookmarks
     */
    this.get = function()
    {
        if (!bookmarks)
            load();

        return bookmarks;
    }

    /**
     * @TODO: keyword support
     */
    this.add = function (title, uri, keyword)
    {
        if(!bookmarks)
            load();

        folder = rdf_service.GetResource("NC:BookmarksRoot");
        var rSource = BookmarksUtils.createBookmark(title, uri, keyword, title);
        var selection = BookmarksUtils.getSelectionFromResource(rSource);
        var target = BookmarksUtils.getTargetFromFolder(folder);
        BookmarksUtils.insertAndCheckSelection("newbookmark", selection, target);

        //also update bookmark cache
        bookmarks.unshift([uri, title]);
        return true;
    }

    /* no idea what it does, it Just Works (TM)
     *
     * @returns number of deleted bookmarks
     */
    this.remove = function(url)
    {
        var deleted = 0;
        if(!url)
            return 0;

        // gNC_NS for trunk, NC_NS for 1.X
        //try { var pNC_NS; pNC_NS = gNC_NS;} catch (err) { pNC_NS = NC_NS;}
        if (!BMSVC || !BMDS || !RDF || !gNC_NS) // defined from firefox
            return 0;

        var curfolder = RDF.GetResource("NC:BookmarksRoot");
        var urlArc = RDF.GetResource(gNC_NS + "URL");
        var urlLiteral = RDF.GetLiteral(url);
        if (BMDS.hasArcIn(urlLiteral, urlArc))
        {
            var bmResources, bmResource, title, uri, type, ptype;
            bmResources = BMSVC.GetSources(urlArc, urlLiteral, true);
            while (bmResources.hasMoreElements())
            {
                bmResource = bmResources.getNext();
                type = BookmarksUtils.resolveType(bmResource);
                if (type != "ImmutableBookmark") {
                    ptype = BookmarksUtils.resolveType(BMSVC.getParent(bmResource));
                    //              alert(type);
                    //              if ( type == "Folder")  // store the current folder
                    //                  curfolder = bmResource;
                    if ( (type == "Bookmark" || type == "IEFavorite") && ptype != "Livemark")
                    {
                        title = BookmarksUtils.getProperty(bmResource, gNC_NS + "Name");
                        uri = BookmarksUtils.getProperty(bmResource, gNC_NS + "URL");

                        if (uri == url)
                        {
                            RDFC.Init(BMDS, BMSVC.getParent(bmResource));
                            RDFC.RemoveElement(bmResource, true);
                            deleted++;
                        }
                    }
                }
            }
        }

        // also update bookmark cache, if we removed at least one bookmark
        if(deleted > 0)
            load();

        return deleted;
    }

    /* also ensures that each search engine has a vimperator-friendly alias */
    this.getSearchEngines = function()
    {
        var search_engines = new Array();
        var firefox_engines = search_service.getVisibleEngines({ });
        for(var i in firefox_engines)
        {
            if (!firefox_engines[i].alias || !firefox_engines[i].alias.match(/^[a-z0-9_]+$/))
            {
                var alias = firefox_engines[i].name.replace(/^\W*(\w+).*/, "$1").toLowerCase();
                firefox_engines[i].alias = alias;
            }
            search_engines.push([firefox_engines[i].alias, firefox_engines[i].description]);
        }

        return search_engines;
    }

    // format of returned array:
    // [keyword, helptext, url]
    this.getKeywords = function()
    {
        if(!keywords)
            load();

        return keywords;
    }

    // if the engine name is null, it uses the default search engine
    // @returns the url for the search string
    this.getSearchURL = function(text, engine_name)
    {
        var url = null;
        if(!engine_name || engine_name == "")
            engine_name = vimperator.options["defsearch"];

        // first checks the search engines for a match
        var engine = search_service.getEngineByAlias(engine_name);
        if(engine)
        {
            if(text)
                url = engine.getSubmission(text, null).uri.spec;
            else
                url = engine.searchForm;
        }
        else // check for keyword urls
        {
            if(!keywords)
                load();

            for (var i in keywords)
            {
                if(keywords[i][0] == engine_name)
                {
                    if (text == null)
                        text = "";
                    url = keywords[i][2].replace(/%s/g, encodeURIComponent(text));
                    break;
                }
            }
        }

        // if we came here, the engine_name is neither a search engine or URL
        return url;
    }

    /*
       res = parseBookmarkString("-t tag1,tag2 -T title http://www.orf.at");
       res.tags is an array of tags
       res.title is the title or "" if no one was given
       res.url is the url as a string
       returns null, if parsing failed
    */
    Bookmarks.parseBookmarkString = function(str)
    {
        var res = {};
        res.tags = [];
        res.title = null;
        res.url = null;

        var re_title = /^\s*((-t|--title)\s+(\w+|\".*\"))(.*)/;
        var re_tags = /^\s*((-T|--tags)\s+((\w+)(,\w+)*))(.*)/;
        var re_url = /^\s*(\".+\"|\S+)(.*)/;

        var match_tags = null;
        var match_title = null;
        var match_url = null;

        while(!str.match(/^\s*$/))
        {
            /* first check for --tags */
            match_tags = str.match(re_tags);
            if(match_tags != null)
            {
                str = match_tags[match_tags.length-1]; // the last captured parenthesis is the rest of the string
                tags = match_tags[3].split(",");
                res.tags = res.tags.concat(tags);
            }
            else /* then for --titles */
            {

                match_title = str.match(re_title);
                if(match_title != null)
                {
                    // only one title allowed
                    if (res.title != null)
                        return null;

                    str = match_title[match_title.length-1]; // the last captured parenthesis is the rest of the string
                    var title = match_title[3];
                    if(title.charAt(0) == '"')
                        title = title.substring(1,title.length-1);
                    res.title = title;
                }
                else /* at last check for an url */
                {
                    match_url = str.match(re_url);
                    if (match_url != null)
                    {
                        // only one url allowed
                        if (res.url != null)
                            return null;

                        str = match_url[match_url.length-1]; // the last captured parenthesis is the rest of the string
                        url = match_url[1];
                        if(url.charAt(0) == '"')
                            url = url.substring(1,url.length-1);
                        res.url = url;
                    }
                    else
                        return null; // no url, tag or title found but still text left, abort
                }
            }
        }
        return res;
    }
}

function History()
{
    const rdf_service    = Components.classes["@mozilla.org/rdf/rdf-service;1"].
                           getService( Components.interfaces.nsIRDFService );
    const global_history_service = Components.classes["@mozilla.org/browser/global-history;2"].
                           getService(Components.interfaces.nsIRDFDataSource);

    var history = null;

    if(vimperator.options["preload"])
        setTimeout(function() { load(); } , 100);

    function load()
    {
        history = new Array();

        var historytree = document.getElementById("hiddenHistoryTree");
        if (!historytree)
            return;

        if (historytree.hidden)
        {
            historytree.hidden = false;
            historytree.database.AddDataSource(global_history_service);
        }

        if (!historytree.ref)
            historytree.ref = "NC:HistoryRoot";

        var nameResource = rdf_service.GetResource(gNC_NS + "Name");
        var builder = historytree.builder.QueryInterface(Components.interfaces.nsIXULTreeBuilder);

        var count = historytree.view.rowCount;
        for (var i = count-1; i >= 0; i--)
        {
            var res = builder.getResourceAtIndex(i);
            var url = res.Value;
            var title;
            var titleRes = historytree.database.GetTarget(res, nameResource, true);
            if (!titleRes)
                continue;

            var titleLiteral = titleRes.QueryInterface(Components.interfaces.nsIRDFLiteral);
            if(titleLiteral)
                title = titleLiteral.Value;
            else
                title = "";

            history.push([url, title]);
        }
    }
    ////////////////////////////////////////////////////////////////////////////////
    ////////////////////// PUBLIC SECTION //////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////
    /*
     * @return a new Array() of our bookmarks
     */
    this.get = function()
    {
        if (!history)
            load();

        return history;
    }

    this.add = function (url, title)
    {
        if(!history)
            load();

        history = history.filter(function(elem) {
                return elem[0] != url;
        });

        history.unshift([url, title]);
        return true;
    };
}

function Marks()
{
    var marks = {};
    var pendingMarks = [];
    var appcontent = document.getElementById("appcontent");
    if (appcontent)
        appcontent.addEventListener("load", onPageLoad, true);

    function onPageLoad(event)
    {
        var win = event.originalTarget.defaultView;
        for (var i = 0, length = pendingMarks.length; i < length; i++)
        {
            var mark = pendingMarks[i];
            if (win.location.href == mark.location)
            {
                win.scrollTo(mark.position.x * win.scrollMaxX, mark.position.y * win.scrollMaxY);
                pendingMarks.splice(i, 1);
                return;
            }
        }
    }

    function remove(mark)
    {
        var ok = false;
        if (mark.match(/^[A-Z0-9]$/))
        {
            if (mark in marks)
            {
                delete marks[mark];
                ok = true;
            }
        }
        else if (mark.match(/^[a-z]$/))
        {
            if (mark in marks)
            {
                var win = vimperator.getCurrentBuffer(), length = marks[mark].length;
                for (var i = 0; i < length; i++)
                {
                    if (marks[mark][i].location == win.location.href)
                    {
                        marks[mark].splice(i, 1);
                        ok = true;
                        break;
                    }
                }
            }
        }

        if (!ok)
        {
            vimperator.echoerr("E20: Mark not set");
            return false;
        }
        return ok;
    }

    this.add = function(mark)
    {
        var win = vimperator.getCurrentBuffer();
        var x = win.scrollMaxX ? win.pageXOffset / win.scrollMaxX : 0;
        var y = win.scrollMaxY ? win.pageYOffset / win.scrollMaxY : 0;
        var position = { x: x, y: y };
        if (mark.match(/^[A-Z0-9]$/))
            marks[mark] = { location: win.location.href, position: position, tab: vimperator.tabs.getTab() };
        else if (mark.match(/^[a-z]$/))
        {
            if (!marks[mark])
                marks[mark] = [];
            marks[mark].push({ location: win.location.href, position: position });
        }
        else
            return false;

        return this;
    }

    // TODO: add support for mark ranges (p-z)
    this.remove = function(marks_str, special)
    {
        if (special)
        {
            var win = vimperator.getCurrentBuffer();
            for (var i in marks)
            {
                if (i.match(/^[A-Z0-9]$/))
                    continue;

                var length = marks[i].length;
                for (var j = 0; j < length; j++)
                {
                    if (marks[i][j].location == win.location.href)
                    {
                        marks[i].splice(j, 1);
                        ok = true;
                        break;
                    }
                }
            }
            return this;
        }
        marks_str.split(/ /).forEach(function(mark)
        {
            if (mark.length == 1)
                remove(mark);
            else
                for (var i = 0; i < mark.length; i++)
                {
                    remove(mark.substr(i, 1));
                }
        });
        return this;
    }

    this.jumpTo = function(mark)
    {
        var ok = false;
        if (mark.match(/^[A-Z0-9]$/))
        {
            var slice = marks[mark];
            if (slice && slice.tab && slice.tab.linkedBrowser)
            {
                if (!slice.tab.parentNode)
                {
                    pendingMarks.push(slice);
                    openURLsInNewTab(slice.location, true);
                    return;
                }
                var index = vimperator.tabs.index(slice.tab);
                if (index)
                {
                    vimperator.tabs.select(index);
                    var win = slice.tab.linkedBrowser.contentWindow;
                    if (win.location.href != slice.location)
                    {
                        pendingMarks.push(slice);
                        win.location.href = slice.location;
                        return;
                    }
                    win.scrollTo(slice.position.x * win.scrollMaxX, slice.position.y * win.scrollMaxY);
                    ok = true;
                }
            }
        }
        else if (mark.match(/^[a-z]$/))
        {
            var win = vimperator.getCurrentBuffer();
            var slice = marks[mark] || [];
            for (var i = 0; i < slice.length; i++)
            {
                if (win.location.href == slice[i].location)
                {
                    win.scrollTo(slice[i].position.x * win.scrollMaxX, slice[i].position.y * win.scrollMaxY);
                    ok = true;
                }
            }
        }

        if (!ok)
        {
            vimperator.echoerr("E20: Mark not set");
            return false;
        }
        return this;
    }

    // TODO: show marks like vim does (when the multiline echo impl is done) or in the preview windwo right now
    this.list = function()
    {
        return this;
    }
}

// vim: set fdm=marker sw=4 ts=4 et:
<commit_msg>add fold markers<commit_after>/*
 * also includes methods for dealing with
 * keywords and search engines
 */
function Bookmarks()//{{{
{
    ////////////////////////////////////////////////////////////////////////////////
    ////////////////////// PRIVATE SECTION /////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////{{{
    const search_service = Components.classes["@mozilla.org/browser/search-service;1"].
                           getService(Components.interfaces.nsIBrowserSearchService);
    const rdf_service    = Components.classes["@mozilla.org/rdf/rdf-service;1"].
                           getService( Components.interfaces.nsIRDFService );

    var bookmarks = null;
    var keywords = null;

    if(vimperator.options["preload"])
        setTimeout(function() { load(); } , 100);

    function load()
    {
        // update our bookmark cache
        var root = rdf_service.GetResource("NC:BookmarksRoot");
        bookmarks  = new Array(); // also clear our bookmark cache
        keywords   = new Array();

        var bmarks = [];   // here getAllChildren will store the bookmarks
        BookmarksUtils.getAllChildren(root, bmarks);
        for(var bm in bmarks)
        {
            if (bmarks[bm][0] && bmarks[bm][1])
                bookmarks.push([bmarks[bm][1].Value, bmarks[bm][0].Value ]);

            // keyword
            if(bmarks[bm][1] && bmarks[bm][2])
                keywords.push([bmarks[bm][2].Value, bmarks[bm][0].Value, bmarks[bm][1].Value]);
        }
    }

    /////////////////////////////////////////////////////////////////////////////}}}
    ////////////////////// PUBLIC SECTION //////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////{{{
    /*
     * @return a new Array() of our bookmarks
     */
    this.get = function()
    {
        if (!bookmarks)
            load();

        return bookmarks;
    }

    /**
     * @TODO: keyword support
     */
    this.add = function (title, uri, keyword)
    {
        if(!bookmarks)
            load();

        folder = rdf_service.GetResource("NC:BookmarksRoot");
        var rSource = BookmarksUtils.createBookmark(title, uri, keyword, title);
        var selection = BookmarksUtils.getSelectionFromResource(rSource);
        var target = BookmarksUtils.getTargetFromFolder(folder);
        BookmarksUtils.insertAndCheckSelection("newbookmark", selection, target);

        //also update bookmark cache
        bookmarks.unshift([uri, title]);
        return true;
    }

    /* no idea what it does, it Just Works (TM)
     *
     * @returns number of deleted bookmarks
     */
    this.remove = function(url)
    {
        var deleted = 0;
        if(!url)
            return 0;

        // gNC_NS for trunk, NC_NS for 1.X
        //try { var pNC_NS; pNC_NS = gNC_NS;} catch (err) { pNC_NS = NC_NS;}
        if (!BMSVC || !BMDS || !RDF || !gNC_NS) // defined from firefox
            return 0;

        var curfolder = RDF.GetResource("NC:BookmarksRoot");
        var urlArc = RDF.GetResource(gNC_NS + "URL");
        var urlLiteral = RDF.GetLiteral(url);
        if (BMDS.hasArcIn(urlLiteral, urlArc))
        {
            var bmResources, bmResource, title, uri, type, ptype;
            bmResources = BMSVC.GetSources(urlArc, urlLiteral, true);
            while (bmResources.hasMoreElements())
            {
                bmResource = bmResources.getNext();
                type = BookmarksUtils.resolveType(bmResource);
                if (type != "ImmutableBookmark") {
                    ptype = BookmarksUtils.resolveType(BMSVC.getParent(bmResource));
                    //              alert(type);
                    //              if ( type == "Folder")  // store the current folder
                    //                  curfolder = bmResource;
                    if ( (type == "Bookmark" || type == "IEFavorite") && ptype != "Livemark")
                    {
                        title = BookmarksUtils.getProperty(bmResource, gNC_NS + "Name");
                        uri = BookmarksUtils.getProperty(bmResource, gNC_NS + "URL");

                        if (uri == url)
                        {
                            RDFC.Init(BMDS, BMSVC.getParent(bmResource));
                            RDFC.RemoveElement(bmResource, true);
                            deleted++;
                        }
                    }
                }
            }
        }

        // also update bookmark cache, if we removed at least one bookmark
        if(deleted > 0)
            load();

        return deleted;
    }

    /* also ensures that each search engine has a vimperator-friendly alias */
    this.getSearchEngines = function()
    {
        var search_engines = new Array();
        var firefox_engines = search_service.getVisibleEngines({ });
        for(var i in firefox_engines)
        {
            if (!firefox_engines[i].alias || !firefox_engines[i].alias.match(/^[a-z0-9_]+$/))
            {
                var alias = firefox_engines[i].name.replace(/^\W*(\w+).*/, "$1").toLowerCase();
                firefox_engines[i].alias = alias;
            }
            search_engines.push([firefox_engines[i].alias, firefox_engines[i].description]);
        }

        return search_engines;
    }

    // format of returned array:
    // [keyword, helptext, url]
    this.getKeywords = function()
    {
        if(!keywords)
            load();

        return keywords;
    }

    // if the engine name is null, it uses the default search engine
    // @returns the url for the search string
    this.getSearchURL = function(text, engine_name)
    {
        var url = null;
        if(!engine_name || engine_name == "")
            engine_name = vimperator.options["defsearch"];

        // first checks the search engines for a match
        var engine = search_service.getEngineByAlias(engine_name);
        if(engine)
        {
            if(text)
                url = engine.getSubmission(text, null).uri.spec;
            else
                url = engine.searchForm;
        }
        else // check for keyword urls
        {
            if(!keywords)
                load();

            for (var i in keywords)
            {
                if(keywords[i][0] == engine_name)
                {
                    if (text == null)
                        text = "";
                    url = keywords[i][2].replace(/%s/g, encodeURIComponent(text));
                    break;
                }
            }
        }

        // if we came here, the engine_name is neither a search engine or URL
        return url;
    }

    /*
       res = parseBookmarkString("-t tag1,tag2 -T title http://www.orf.at");
       res.tags is an array of tags
       res.title is the title or "" if no one was given
       res.url is the url as a string
       returns null, if parsing failed
    */
    Bookmarks.parseBookmarkString = function(str)
    {
        var res = {};
        res.tags = [];
        res.title = null;
        res.url = null;

        var re_title = /^\s*((-t|--title)\s+(\w+|\".*\"))(.*)/;
        var re_tags = /^\s*((-T|--tags)\s+((\w+)(,\w+)*))(.*)/;
        var re_url = /^\s*(\".+\"|\S+)(.*)/;

        var match_tags = null;
        var match_title = null;
        var match_url = null;

        while(!str.match(/^\s*$/))
        {
            /* first check for --tags */
            match_tags = str.match(re_tags);
            if(match_tags != null)
            {
                str = match_tags[match_tags.length-1]; // the last captured parenthesis is the rest of the string
                tags = match_tags[3].split(",");
                res.tags = res.tags.concat(tags);
            }
            else /* then for --titles */
            {

                match_title = str.match(re_title);
                if(match_title != null)
                {
                    // only one title allowed
                    if (res.title != null)
                        return null;

                    str = match_title[match_title.length-1]; // the last captured parenthesis is the rest of the string
                    var title = match_title[3];
                    if(title.charAt(0) == '"')
                        title = title.substring(1,title.length-1);
                    res.title = title;
                }
                else /* at last check for an url */
                {
                    match_url = str.match(re_url);
                    if (match_url != null)
                    {
                        // only one url allowed
                        if (res.url != null)
                            return null;

                        str = match_url[match_url.length-1]; // the last captured parenthesis is the rest of the string
                        url = match_url[1];
                        if(url.charAt(0) == '"')
                            url = url.substring(1,url.length-1);
                        res.url = url;
                    }
                    else
                        return null; // no url, tag or title found but still text left, abort
                }
            }
        }
        return res;
    }
    //}}}
}//}}}

function History()//{{{
{
    ////////////////////////////////////////////////////////////////////////////////
    ////////////////////// PRIVATE SECTION /////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////{{{
    const rdf_service    = Components.classes["@mozilla.org/rdf/rdf-service;1"].
                           getService( Components.interfaces.nsIRDFService );
    const global_history_service = Components.classes["@mozilla.org/browser/global-history;2"].
                           getService(Components.interfaces.nsIRDFDataSource);

    var history = null;

    if(vimperator.options["preload"])
        setTimeout(function() { load(); } , 100);

    function load()
    {
        history = new Array();

        var historytree = document.getElementById("hiddenHistoryTree");
        if (!historytree)
            return;

        if (historytree.hidden)
        {
            historytree.hidden = false;
            historytree.database.AddDataSource(global_history_service);
        }

        if (!historytree.ref)
            historytree.ref = "NC:HistoryRoot";

        var nameResource = rdf_service.GetResource(gNC_NS + "Name");
        var builder = historytree.builder.QueryInterface(Components.interfaces.nsIXULTreeBuilder);

        var count = historytree.view.rowCount;
        for (var i = count-1; i >= 0; i--)
        {
            var res = builder.getResourceAtIndex(i);
            var url = res.Value;
            var title;
            var titleRes = historytree.database.GetTarget(res, nameResource, true);
            if (!titleRes)
                continue;

            var titleLiteral = titleRes.QueryInterface(Components.interfaces.nsIRDFLiteral);
            if(titleLiteral)
                title = titleLiteral.Value;
            else
                title = "";

            history.push([url, title]);
        }
    }
    /////////////////////////////////////////////////////////////////////////////}}}
    ////////////////////// PUBLIC SECTION //////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////{{{
    /*
     * @return a new Array() of our bookmarks
     */
    this.get = function()
    {
        if (!history)
            load();

        return history;
    }

    this.add = function (url, title)
    {
        if(!history)
            load();

        history = history.filter(function(elem) {
                return elem[0] != url;
        });

        history.unshift([url, title]);
        return true;
    };
    //}}}
}//}}}

function Marks()//{{{
{
    ////////////////////////////////////////////////////////////////////////////////
    ////////////////////// PRIVATE SECTION /////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////{{{
    var marks = {};
    var pendingMarks = [];
    var appcontent = document.getElementById("appcontent");
    if (appcontent)
        appcontent.addEventListener("load", onPageLoad, true);

    function onPageLoad(event)
    {
        var win = event.originalTarget.defaultView;
        for (var i = 0, length = pendingMarks.length; i < length; i++)
        {
            var mark = pendingMarks[i];
            if (win.location.href == mark.location)
            {
                win.scrollTo(mark.position.x * win.scrollMaxX, mark.position.y * win.scrollMaxY);
                pendingMarks.splice(i, 1);
                return;
            }
        }
    }

    function remove(mark)
    {
        var ok = false;
        if (mark.match(/^[A-Z0-9]$/))
        {
            if (mark in marks)
            {
                delete marks[mark];
                ok = true;
            }
        }
        else if (mark.match(/^[a-z]$/))
        {
            if (mark in marks)
            {
                var win = vimperator.getCurrentBuffer(), length = marks[mark].length;
                for (var i = 0; i < length; i++)
                {
                    if (marks[mark][i].location == win.location.href)
                    {
                        marks[mark].splice(i, 1);
                        ok = true;
                        break;
                    }
                }
            }
        }

        if (!ok)
        {
            vimperator.echoerr("E20: Mark not set");
            return false;
        }
        return ok;
    }

    /////////////////////////////////////////////////////////////////////////////}}}
    ////////////////////// PUBLIC SECTION //////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////{{{
    this.add = function(mark)
    {
        var win = vimperator.getCurrentBuffer();
        var x = win.scrollMaxX ? win.pageXOffset / win.scrollMaxX : 0;
        var y = win.scrollMaxY ? win.pageYOffset / win.scrollMaxY : 0;
        var position = { x: x, y: y };
        if (mark.match(/^[A-Z0-9]$/))
            marks[mark] = { location: win.location.href, position: position, tab: vimperator.tabs.getTab() };
        else if (mark.match(/^[a-z]$/))
        {
            if (!marks[mark])
                marks[mark] = [];
            marks[mark].push({ location: win.location.href, position: position });
        }
        else
            return false;

        return this;
    }

    // TODO: add support for mark ranges (p-z)
    this.remove = function(marks_str, special)
    {
        if (special)
        {
            var win = vimperator.getCurrentBuffer();
            for (var i in marks)
            {
                if (i.match(/^[A-Z0-9]$/))
                    continue;

                var length = marks[i].length;
                for (var j = 0; j < length; j++)
                {
                    if (marks[i][j].location == win.location.href)
                    {
                        marks[i].splice(j, 1);
                        ok = true;
                        break;
                    }
                }
            }
            return this;
        }
        marks_str.split(/ /).forEach(function(mark)
        {
            if (mark.length == 1)
                remove(mark);
            else
                for (var i = 0; i < mark.length; i++)
                {
                    remove(mark.substr(i, 1));
                }
        });
        return this;
    }

    this.jumpTo = function(mark)
    {
        var ok = false;
        if (mark.match(/^[A-Z0-9]$/))
        {
            var slice = marks[mark];
            if (slice && slice.tab && slice.tab.linkedBrowser)
            {
                if (!slice.tab.parentNode)
                {
                    pendingMarks.push(slice);
                    openURLsInNewTab(slice.location, true);
                    return;
                }
                var index = vimperator.tabs.index(slice.tab);
                if (index)
                {
                    vimperator.tabs.select(index);
                    var win = slice.tab.linkedBrowser.contentWindow;
                    if (win.location.href != slice.location)
                    {
                        pendingMarks.push(slice);
                        win.location.href = slice.location;
                        return;
                    }
                    win.scrollTo(slice.position.x * win.scrollMaxX, slice.position.y * win.scrollMaxY);
                    ok = true;
                }
            }
        }
        else if (mark.match(/^[a-z]$/))
        {
            var win = vimperator.getCurrentBuffer();
            var slice = marks[mark] || [];
            for (var i = 0; i < slice.length; i++)
            {
                if (win.location.href == slice[i].location)
                {
                    win.scrollTo(slice[i].position.x * win.scrollMaxX, slice[i].position.y * win.scrollMaxY);
                    ok = true;
                }
            }
        }

        if (!ok)
        {
            vimperator.echoerr("E20: Mark not set");
            return false;
        }
        return this;
    }

    // TODO: show marks like vim does (when the multiline echo impl is done) or in the preview windwo right now
    this.list = function()
    {
        return this;
    }
    //}}}
}//}}}

// vim: set fdm=marker sw=4 ts=4 et:
<|endoftext|>


<commit_before>// Hex math defined here: http://blog.ruslans.com/2011/02/hexagonal-grid-math.html

//
// Utility
//

String.prototype.lpad = function(padString, length) {
    var str = this;
    while (str.length < length)
        str = padString + str;
    return str;
}

//
// Hexagon Grid
//

function HexagonGrid(canvasId, systems) {
    // Find the maximum extents of the system offset coordinates
    this.max_x = 0;
    this.max_y = 0;
    for (var system of systems) {
        var x = system[1][0];
        if (x > this.max_x) {
            this.max_x = x;
        }
        var y = system[1][1];
        if (y > this.max_y) {
            this.max_y = y;
        }
    }
    this.max_x++;
    this.max_y++;

    // Create model of blank hexes
    this.offset_model = new Array(this.max_x);
    for (var x = 0; x < this.max_x; x++) {
        this.offset_model[x] = new Array(this.max_y);
        for (var y = 0; y < this.max_y; y++) {
            this.offset_model[x][y] = null;
        }
    }

    // Populate model of hex grid with systems
    for (var system of systems) {
        var x = system[1][0];
        var y = system[1][1];
        var name = system[0];
        this.offset_model[x][y] = {
            "name": name,
        };
    }

    // Create model of path
    this.path_model = [];

    // Drawing setup
    this.canvas = document.getElementById(canvasId);
    this.context = this.canvas.getContext("2d");

    this.canvasOriginX = 0;
    this.canvasOriginY = 0;

    // Events
    //this.canvas.addEventListener("mousedown", this.clickEvent.bind(this), false);
};

//HexagonGrid.prototype.drawHexGrid = function (rows, cols, originX, originY, isDebug) {
HexagonGrid.prototype.redraw = function() {
    this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);

    // Fit the hex size to the smallest of the canvas dimensions
    var width_candidate = this.canvas.width / (this.max_x + 0.5);
    var height_candidate = this.canvas.height / (this.max_y + 0.5);
    var radius;
    if (width_candidate <= height_candidate) {
        radius = width_candidate / 2;
        this.width = width_candidate;
        this.height = Math.sqrt(3) * radius;
    } else {
        radius = height_candidate / Math.sqrt(3);
        this.width = 2 * radius;
        this.height = height_candidate;
    }
    this.side = (3 / 2) * radius;

    // Draw hexes using the model
    var offsetColumn = false;
    for (var x = 0; x < this.max_x; x++) {
        for (var y = 0; y < this.max_y; y++) {
            var coordtext = x.toString().lpad("0", 2) + y.toString().lpad("0", 2);

            var hex = this.offset_model[x][y];
            var color;
            var name;
            if (hex === null) {
                name = null;
                color = "#fff";
            } else {
                name = hex["name"];
                var path_index = this.path_model.indexOf(name)
                if (path_index === 0) {
                    color = "#B4D9EB";
                } else if (path_index !== -1) {
                    color = "#CCE7F4";
                } else {
                    color = "#E3F3FB";
                }
            }
            // TODO different colours for:
            // - nodes in path (including start and end)
            // - maybe special color for start of path
            // - reachable nodes from the head of the path

            var currentHexX;
            var currentHexY;
            if (offsetColumn) {
                currentHexX = x * this.side + this.canvasOriginX;
                currentHexY = (y * this.height) + this.canvasOriginY + (this.height * 0.5);
            } else {
                currentHexX = (x * this.side) + this.canvasOriginX;
                currentHexY = (y * this.height) + this.canvasOriginY;
            }

            this.drawHex(currentHexX, currentHexY, color, name, coordtext);
        }
        offsetColumn = !offsetColumn;
    }
    // TODO draw arrows between path nodes https://stackoverflow.com/questions/808826/draw-arrow-on-canvas-tag
};

HexagonGrid.prototype.drawHex = function(x0, y0, fillColor, name, coordtext) {
    this.context.strokeStyle = "#000";
    this.context.beginPath();
    this.context.moveTo(x0 + this.width - this.side, y0);
    this.context.lineTo(x0 + this.side, y0);
    this.context.lineTo(x0 + this.width, y0 + (this.height / 2));
    this.context.lineTo(x0 + this.side, y0 + this.height);
    this.context.lineTo(x0 + this.width - this.side, y0 + this.height);
    this.context.lineTo(x0, y0 + (this.height / 2));

    this.context.fillStyle = fillColor;
    this.context.fill();

    this.context.closePath();
    this.context.stroke();

    this.context.textAlign = "start";
    this.context.font = "normal normal " + (0.10 * this.width).toString() + "px sans";
    this.context.fillStyle = "#333";
    this.context.fillText(coordtext, x0 + (this.width / 2) - (this.width/4), y0 + (this.height - 5));

    if (name !== null) {
        this.context.textAlign = "center";
        this.context.font = "normal bold " + (0.11 * this.width).toString() + "px sans";
        this.context.fillStyle = "#000";
        this.context.fillText(name, x0 + (this.width/2), y0 + (this.height / 2));
    }
};

//Recusivly step up to the body to calculate canvas offset.
HexagonGrid.prototype.getRelativeCanvasOffset = function() {
	var x = 0, y = 0;
	var layoutElement = this.canvas;
    if (layoutElement.offsetParent) {
        do {
            x += layoutElement.offsetLeft;
            y += layoutElement.offsetTop;
        } while (layoutElement = layoutElement.offsetParent);

        return { x: x, y: y };
    }
}

//Uses a grid overlay algorithm to determine hexagon location
//Left edge of grid has a test to acuratly determin correct hex
HexagonGrid.prototype.getSelectedTile = function(mouseX, mouseY) {

	var offSet = this.getRelativeCanvasOffset();

    mouseX -= offSet.x;
    mouseY -= offSet.y;

    var column = Math.floor((mouseX) / this.side);
    var row = Math.floor(
        column % 2 == 0
            ? Math.floor((mouseY) / this.height)
            : Math.floor(((mouseY + (this.height * 0.5)) / this.height)) - 1);

    //Test if on left side of frame
    if (mouseX > (column * this.side) && mouseX < (column * this.side) + this.width - this.side) {

        //Now test which of the two triangles we are in
        //Top left triangle points
        var p1 = new Object();
        p1.x = column * this.side;
        p1.y = column % 2 == 0
            ? row * this.height
            : (row * this.height) + (this.height / 2);

        var p2 = new Object();
        p2.x = p1.x;
        p2.y = p1.y + (this.height / 2);

        var p3 = new Object();
        p3.x = p1.x + this.width - this.side;
        p3.y = p1.y;

        var mousePoint = new Object();
        mousePoint.x = mouseX;
        mousePoint.y = mouseY;

        if (this.isPointInTriangle(mousePoint, p1, p2, p3)) {
            column--;

            if (column % 2 != 0) {
                row--;
            }
        }

        //Bottom left triangle points
        var p4 = new Object();
        p4 = p2;

        var p5 = new Object();
        p5.x = p4.x;
        p5.y = p4.y + (this.height / 2);

        var p6 = new Object();
        p6.x = p5.x + (this.width - this.side);
        p6.y = p5.y;

        if (this.isPointInTriangle(mousePoint, p4, p5, p6)) {
            column--;

            if (column % 2 == 0) {
                row++;
            }
        }
    }

    return  { row: row, column: column };
};

HexagonGrid.prototype.sign = function(p1, p2, p3) {
    return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
};

//TODO: Replace with optimized barycentric coordinate method
HexagonGrid.prototype.isPointInTriangle = function isPointInTriangle(pt, v1, v2, v3) {
    var b1, b2, b3;

    b1 = this.sign(pt, v1, v2) < 0.0;
    b2 = this.sign(pt, v2, v3) < 0.0;
    b3 = this.sign(pt, v3, v1) < 0.0;

    return ((b1 == b2) && (b2 == b3));
};

HexagonGrid.prototype.clickEvent = function (e) {
    var mouseX = e.pageX;
    var mouseY = e.pageY;

    var localX = mouseX - this.canvasOriginX;
    var localY = mouseY - this.canvasOriginY;

    var tile = this.getSelectedTile(localX, localY);
    if (tile.column >= 0 && tile.row >= 0) {
        var drawy = tile.column % 2 == 0 ? (tile.row * this.height) + this.canvasOriginY + 6 : (tile.row * this.height) + this.canvasOriginY + 6 + (this.height / 2);
        var drawx = (tile.column * this.side) + this.canvasOriginX;

        this.drawHex(drawx, drawy - 6, "rgba(110,110,70,0.3)", "");
    }
};
<commit_msg>Add/remove to path<commit_after>// Hex math defined here: http://blog.ruslans.com/2011/02/hexagonal-grid-math.html

//
// Utility
//

String.prototype.lpad = function(padString, length) {
    var str = this;
    while (str.length < length)
        str = padString + str;
    return str;
}

//
// Hexagon Grid
//

function HexagonGrid(canvasId, systems) {
    // Find the maximum extents of the system offset coordinates
    this.max_x = 0;
    this.max_y = 0;
    for (var system of systems) {
        var x = system[1][0];
        if (x > this.max_x) {
            this.max_x = x;
        }
        var y = system[1][1];
        if (y > this.max_y) {
            this.max_y = y;
        }
    }
    this.max_x++;
    this.max_y++;

    // Create model of blank hexes
    this.offset_model = new Array(this.max_x);
    for (var x = 0; x < this.max_x; x++) {
        this.offset_model[x] = new Array(this.max_y);
        for (var y = 0; y < this.max_y; y++) {
            this.offset_model[x][y] = null;
        }
    }

    // Populate model of hex grid with systems
    for (var system of systems) {
        var x = system[1][0];
        var y = system[1][1];
        var name = system[0];
        this.offset_model[x][y] = {
            "name": name,
        };
    }

    // Create model of path
    this.path_model = [];

    // Drawing setup
    this.canvas = document.getElementById(canvasId);
    this.context = this.canvas.getContext("2d");

    this.canvasOriginX = 0;
    this.canvasOriginY = 0;

    // Events
    this.canvas.addEventListener("click", this.clickEvent.bind(this));
};

//HexagonGrid.prototype.drawHexGrid = function (rows, cols, originX, originY, isDebug) {
HexagonGrid.prototype.redraw = function() {
    this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);

    // Fit the hex size to the smallest of the canvas dimensions
    var width_candidate = this.canvas.width / (this.max_x + 0.5);
    var height_candidate = this.canvas.height / (this.max_y + 0.5);
    var radius;
    if (width_candidate <= height_candidate) {
        radius = width_candidate / 2;
        this.width = width_candidate;
        this.height = Math.sqrt(3) * radius;
    } else {
        radius = height_candidate / Math.sqrt(3);
        this.width = 2 * radius;
        this.height = height_candidate;
    }
    this.side = (3 / 2) * radius;

    // Draw hexes using the model
    var offsetColumn = false;
    for (var x = 0; x < this.max_x; x++) {
        for (var y = 0; y < this.max_y; y++) {
            var coordtext = x.toString().lpad("0", 2) + y.toString().lpad("0", 2);

            var hex = this.offset_model[x][y];
            var color;
            var name;
            if (hex === null) {
                name = null;
                color = "#fff";
            } else {
                name = hex["name"];
                var path_index = this.path_model.indexOf(name)
                if (path_index === 0) {
                    color = "#B4D9EB";
                } else if (path_index !== -1) {
                    color = "#CCE7F4";
                } else {
                    color = "#eee";
                }
            }
            // TODO different colours for:
            // - nodes in path (including start and end)
            // - maybe special color for start of path
            // - reachable nodes from the head of the path

            var currentHexX;
            var currentHexY;
            if (offsetColumn) {
                currentHexX = x * this.side + this.canvasOriginX;
                currentHexY = (y * this.height) + this.canvasOriginY + (this.height * 0.5);
            } else {
                currentHexX = (x * this.side) + this.canvasOriginX;
                currentHexY = (y * this.height) + this.canvasOriginY;
            }

            this.drawHex(currentHexX, currentHexY, color, name, coordtext);
        }
        offsetColumn = !offsetColumn;
    }
    // TODO draw arrows between path nodes https://stackoverflow.com/questions/808826/draw-arrow-on-canvas-tag
};

HexagonGrid.prototype.drawHex = function(x0, y0, fillColor, name, coordtext) {
    this.context.strokeStyle = "#000";
    this.context.beginPath();
    this.context.moveTo(x0 + this.width - this.side, y0);
    this.context.lineTo(x0 + this.side, y0);
    this.context.lineTo(x0 + this.width, y0 + (this.height / 2));
    this.context.lineTo(x0 + this.side, y0 + this.height);
    this.context.lineTo(x0 + this.width - this.side, y0 + this.height);
    this.context.lineTo(x0, y0 + (this.height / 2));

    this.context.fillStyle = fillColor;
    this.context.fill();

    this.context.closePath();
    this.context.stroke();

    this.context.textAlign = "start";
    this.context.font = "normal normal " + (0.10 * this.width).toString() + "px sans";
    this.context.fillStyle = "#333";
    this.context.fillText(coordtext, x0 + (this.width / 2) - (this.width/4), y0 + (this.height - 5));

    if (name !== null) {
        this.context.textAlign = "center";
        this.context.font = "normal bold " + (0.11 * this.width).toString() + "px sans";
        this.context.fillStyle = "#000";
        this.context.fillText(name, x0 + (this.width/2), y0 + (this.height / 2));
    }
};

//Recusivly step up to the body to calculate canvas offset.
HexagonGrid.prototype.getRelativeCanvasOffset = function() {
	var x = 0, y = 0;
	var layoutElement = this.canvas;
    if (layoutElement.offsetParent) {
        do {
            x += layoutElement.offsetLeft;
            y += layoutElement.offsetTop;
        } while (layoutElement = layoutElement.offsetParent);

        return { x: x, y: y };
    }
}

//Uses a grid overlay algorithm to determine hexagon location
//Left edge of grid has a test to acuratly determin correct hex
HexagonGrid.prototype.getSelectedTile = function(mouseX, mouseY) {

	var offSet = this.getRelativeCanvasOffset();

    mouseX -= offSet.x;
    mouseY -= offSet.y;

    var column = Math.floor((mouseX) / this.side);
    var row = Math.floor(
        column % 2 == 0
            ? Math.floor((mouseY) / this.height)
            : Math.floor(((mouseY + (this.height * 0.5)) / this.height)) - 1);

    //Test if on left side of frame
    if (mouseX > (column * this.side) && mouseX < (column * this.side) + this.width - this.side) {

        //Now test which of the two triangles we are in
        //Top left triangle points
        var p1 = new Object();
        p1.x = column * this.side;
        p1.y = column % 2 == 0
            ? row * this.height
            : (row * this.height) + (this.height / 2);

        var p2 = new Object();
        p2.x = p1.x;
        p2.y = p1.y + (this.height / 2);

        var p3 = new Object();
        p3.x = p1.x + this.width - this.side;
        p3.y = p1.y;

        var mousePoint = new Object();
        mousePoint.x = mouseX;
        mousePoint.y = mouseY;

        if (this.isPointInTriangle(mousePoint, p1, p2, p3)) {
            column--;

            if (column % 2 != 0) {
                row--;
            }
        }

        //Bottom left triangle points
        var p4 = new Object();
        p4 = p2;

        var p5 = new Object();
        p5.x = p4.x;
        p5.y = p4.y + (this.height / 2);

        var p6 = new Object();
        p6.x = p5.x + (this.width - this.side);
        p6.y = p5.y;

        if (this.isPointInTriangle(mousePoint, p4, p5, p6)) {
            column--;

            if (column % 2 == 0) {
                row++;
            }
        }
    }

    return  { y: row, x: column };
};

HexagonGrid.prototype.sign = function(p1, p2, p3) {
    return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
};

//TODO: Replace with optimized barycentric coordinate method
HexagonGrid.prototype.isPointInTriangle = function isPointInTriangle(pt, v1, v2, v3) {
    var b1, b2, b3;

    b1 = this.sign(pt, v1, v2) < 0.0;
    b2 = this.sign(pt, v2, v3) < 0.0;
    b3 = this.sign(pt, v3, v1) < 0.0;

    return ((b1 == b2) && (b2 == b3));
};

HexagonGrid.prototype.clickEvent = function (e) {
    if (e.button === 0) {
        var mouseX = e.pageX;
        var mouseY = e.pageY;

        var localX = mouseX - this.canvasOriginX;
        var localY = mouseY - this.canvasOriginY;

        var tile = this.getSelectedTile(localX, localY);
        if (tile.x >= 0 && tile.y >= 0 && tile.x < this.max_x && tile.y < this.max_y) {
            var hex = this.offset_model[tile.x][tile.y];
            if (hex !== null) {
                var name = hex["name"];
                var path_index = this.path_model.indexOf(name);
                if (path_index === -1) {
                    this.path_model.push(name);
                } else {
                    this.path_model.splice(path_index, 1);
                }
                this.redraw();
            }
        }
    }
};
<|endoftext|>